mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Merge branch 'master' into path_copy_paste
This commit is contained in:
commit
fee4e5ca0d
83 changed files with 3017 additions and 2408 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -2195,8 +2195,10 @@ dependencies = [
|
|||
"glam",
|
||||
"graph-craft",
|
||||
"graphene-application-io",
|
||||
"graphene-brush",
|
||||
"graphene-core",
|
||||
"graphene-path-bool",
|
||||
"graphene-raster-nodes",
|
||||
"graphene-svg-renderer",
|
||||
"iai-callgrind",
|
||||
"js-sys",
|
||||
|
@ -2228,6 +2230,19 @@ dependencies = [
|
|||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-brush"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"graphene-core",
|
||||
"graphene-raster-nodes",
|
||||
"node-macro",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-cli"
|
||||
version = "0.1.0"
|
||||
|
@ -2305,6 +2320,27 @@ dependencies = [
|
|||
"specta",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-raster-nodes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"bytemuck",
|
||||
"dyn-any",
|
||||
"fastnoise-lite",
|
||||
"futures",
|
||||
"glam",
|
||||
"graphene-core",
|
||||
"image",
|
||||
"ndarray",
|
||||
"node-macro",
|
||||
"rand 0.9.0",
|
||||
"rand_chacha 0.9.0",
|
||||
"serde",
|
||||
"specta",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-std"
|
||||
version = "0.1.0"
|
||||
|
@ -2317,9 +2353,11 @@ dependencies = [
|
|||
"glam",
|
||||
"graph-craft",
|
||||
"graphene-application-io",
|
||||
"graphene-brush",
|
||||
"graphene-core",
|
||||
"graphene-math-nodes",
|
||||
"graphene-path-bool",
|
||||
"graphene-raster-nodes",
|
||||
"graphene-svg-renderer",
|
||||
"image",
|
||||
"log",
|
||||
|
|
|
@ -5,12 +5,14 @@ members = [
|
|||
"frontend/wasm",
|
||||
"frontend/src-tauri",
|
||||
"node-graph/gapplication-io",
|
||||
"node-graph/gbrush",
|
||||
"node-graph/gcore",
|
||||
"node-graph/gstd",
|
||||
"node-graph/gmath-nodes",
|
||||
"node-graph/gpath-bool",
|
||||
"node-graph/graph-craft",
|
||||
"node-graph/graphene-cli",
|
||||
"node-graph/graster-nodes",
|
||||
"node-graph/gsvg-renderer",
|
||||
"node-graph/interpreted-executor",
|
||||
"node-graph/node-macro",
|
||||
|
@ -24,12 +26,14 @@ members = [
|
|||
default-members = [
|
||||
"editor",
|
||||
"frontend/wasm",
|
||||
"node-graph/gbrush",
|
||||
"node-graph/gcore",
|
||||
"node-graph/gstd",
|
||||
"node-graph/gmath-nodes",
|
||||
"node-graph/gpath-bool",
|
||||
"node-graph/graph-craft",
|
||||
"node-graph/graphene-cli",
|
||||
"node-graph/graster-nodes",
|
||||
"node-graph/gsvg-renderer",
|
||||
"node-graph/interpreted-executor",
|
||||
"node-graph/node-macro",
|
||||
|
@ -44,10 +48,12 @@ preprocessor = { path = "node-graph/preprocessor"}
|
|||
math-parser = { path = "libraries/math-parser" }
|
||||
path-bool = { path = "libraries/path-bool" }
|
||||
graphene-application-io = { path = "node-graph/gapplication-io" }
|
||||
graphene-brush = { path = "node-graph/gbrush" }
|
||||
graphene-core = { path = "node-graph/gcore" }
|
||||
graphene-math-nodes = { path = "node-graph/gmath-nodes" }
|
||||
graphene-path-bool = { path = "node-graph/gpath-bool" }
|
||||
graph-craft = { path = "node-graph/graph-craft" }
|
||||
graphene-raster-nodes = { path = "node-graph/graster-nodes" }
|
||||
graphene-std = { path = "node-graph/gstd" }
|
||||
graphene-svg-renderer = { path = "node-graph/gsvg-renderer" }
|
||||
interpreted-executor = { path = "node-graph/interpreted-executor" }
|
||||
|
@ -144,9 +150,10 @@ petgraph = { version = "0.7.1", default-features = false, features = [
|
|||
"graphmap",
|
||||
] }
|
||||
half = { version = "2.4.1", default-features = false, features = ["bytemuck", "serde"] }
|
||||
tinyvec = { version = "1" }
|
||||
tinyvec = { version = "1", features = ["std"] }
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
iai-callgrind = { version = "0.12.3" }
|
||||
ndarray = "0.16.1"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
|
2
demo-artwork/changing-seasons.graphite
generated
2
demo-artwork/changing-seasons.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/isometric-fountain.graphite
generated
2
demo-artwork/isometric-fountain.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/marbled-mandelbrot.graphite
generated
2
demo-artwork/marbled-mandelbrot.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/painted-dreams.graphite
generated
2
demo-artwork/painted-dreams.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/parametric-dunescape.graphite
generated
2
demo-artwork/parametric-dunescape.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/procedural-string-lights.graphite
generated
2
demo-artwork/procedural-string-lights.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/red-dress.graphite
generated
2
demo-artwork/red-dress.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/valley-of-spires.graphite
generated
2
demo-artwork/valley-of-spires.graphite
generated
File diff suppressed because one or more lines are too long
|
@ -40,7 +40,6 @@ impl DispatcherMessageHandlers {
|
|||
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
|
||||
/// In addition, these messages do not change any state in the backend (aside from caches).
|
||||
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
|
||||
PropertiesPanelMessageDiscriminant::Refresh,
|
||||
))),
|
||||
|
@ -319,12 +318,11 @@ impl Dispatcher {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Logs a message that is about to be executed,
|
||||
/// either as a tree with a discriminant or the entire payload (depending on settings)
|
||||
/// Logs a message that is about to be executed, either as a tree
|
||||
/// with a discriminant or the entire payload (depending on settings)
|
||||
fn log_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
|
||||
let discriminant = MessageDiscriminant::from(message);
|
||||
let is_blocked = DEBUG_MESSAGE_BLOCK_LIST.iter().any(|&blocked_discriminant| discriminant == blocked_discriminant)
|
||||
|| DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));
|
||||
let is_blocked = DEBUG_MESSAGE_BLOCK_LIST.contains(&discriminant) || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));
|
||||
|
||||
if !is_blocked {
|
||||
match message_logging_verbosity {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::{
|
||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, FrontendNodeWire, Transform, WirePath,
|
||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
||||
};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
|
||||
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::HintData;
|
||||
use graph_craft::document::NodeId;
|
||||
|
@ -250,12 +251,16 @@ pub enum FrontendMessage {
|
|||
UpdateMouseCursor {
|
||||
cursor: MouseCursorIcon,
|
||||
},
|
||||
UpdateNodeGraph {
|
||||
UpdateNodeGraphNodes {
|
||||
nodes: Vec<FrontendNode>,
|
||||
wires: Vec<FrontendNodeWire>,
|
||||
#[serde(rename = "wiresDirectNotGridAligned")]
|
||||
wires_direct_not_grid_aligned: bool,
|
||||
},
|
||||
UpdateVisibleNodes {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
UpdateNodeGraphWires {
|
||||
wires: Vec<WirePathUpdate>,
|
||||
},
|
||||
ClearAllNodeGraphWires,
|
||||
UpdateNodeGraphControlBarLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::utility_types::input_keyboard::KeysGroup;
|
||||
use super::utility_types::misc::Mapping;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{self, Key};
|
||||
use crate::messages::input_mapper::utility_types::misc::MappingEntry;
|
||||
use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
|
||||
use crate::messages::prelude::*;
|
||||
use std::fmt::Write;
|
||||
|
@ -47,12 +48,12 @@ impl InputMapperMessageHandler {
|
|||
ma.map(|a| ((i as u8).try_into().unwrap(), a))
|
||||
})
|
||||
.for_each(|(k, a): (Key, _)| {
|
||||
let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').last().unwrap());
|
||||
let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').next_back().unwrap());
|
||||
});
|
||||
output.replace("Key", "")
|
||||
}
|
||||
|
||||
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Vec<KeysGroup> {
|
||||
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Option<KeysGroup> {
|
||||
let all_key_mapping_entries = std::iter::empty()
|
||||
.chain(self.mapping.key_up.iter())
|
||||
.chain(self.mapping.key_down.iter())
|
||||
|
@ -66,55 +67,65 @@ impl InputMapperMessageHandler {
|
|||
// Filter for the desired message
|
||||
let found_actions = all_mapping_entries.filter(|entry| entry.action.to_discriminant() == *action_to_find);
|
||||
|
||||
// Get the `Key` for this platform's accelerator key
|
||||
let keyboard_layout = || GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();
|
||||
let platform_accel_key = match keyboard_layout() {
|
||||
KeyboardPlatformLayout::Standard => Key::Control,
|
||||
KeyboardPlatformLayout::Mac => Key::Command,
|
||||
};
|
||||
|
||||
let entry_to_key = |entry: &MappingEntry| {
|
||||
// Get the modifier keys for the entry (and convert them to Key)
|
||||
let mut keys = entry
|
||||
.modifiers
|
||||
.iter()
|
||||
.map(|i| {
|
||||
// TODO: Use a safe solution eventually
|
||||
assert!(
|
||||
i < input_keyboard::NUMBER_OF_KEYS,
|
||||
"Attempting to convert a Key with enum index {i}, which is larger than the number of Key enums",
|
||||
);
|
||||
(i as u8).try_into().unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Append the key button for the entry
|
||||
use InputMapperMessage as IMM;
|
||||
match entry.input {
|
||||
IMM::KeyDown(key) | IMM::KeyUp(key) | IMM::KeyDownNoRepeat(key) | IMM::KeyUpNoRepeat(key) => keys.push(key),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
keys.sort_by(|&a, &b| {
|
||||
// Order according to platform guidelines mentioned at https://ux.stackexchange.com/questions/58185/normative-ordering-for-modifier-key-combinations
|
||||
const ORDER: [Key; 4] = [Key::Control, Key::Alt, Key::Shift, Key::Command];
|
||||
|
||||
// Treat the `Accel` virtual key as the platform's accel key for sorting comparison purposes
|
||||
let a = if a == Key::Accel { platform_accel_key } else { a };
|
||||
let b = if b == Key::Accel { platform_accel_key } else { b };
|
||||
|
||||
// Find where the keys are in the order, or put them at the end if they're not found
|
||||
let a = ORDER.iter().position(|&key| key == a).unwrap_or(ORDER.len());
|
||||
let b = ORDER.iter().position(|&key| key == b).unwrap_or(ORDER.len());
|
||||
|
||||
// Compare the positions of both keys
|
||||
a.cmp(&b)
|
||||
});
|
||||
|
||||
KeysGroup(keys)
|
||||
};
|
||||
|
||||
// If a canonical key combination is found, return it
|
||||
if let Some(canonical) = found_actions.clone().find(|entry| entry.canonical).map(entry_to_key) {
|
||||
return Some(canonical);
|
||||
}
|
||||
|
||||
// Find the key combinations for all keymaps matching the desired action
|
||||
assert!(std::mem::size_of::<usize>() >= std::mem::size_of::<Key>());
|
||||
found_actions
|
||||
.map(|entry| {
|
||||
// Get the modifier keys for the entry (and convert them to Key)
|
||||
let mut keys = entry
|
||||
.modifiers
|
||||
.iter()
|
||||
.map(|i| {
|
||||
// TODO: Use a safe solution eventually
|
||||
assert!(
|
||||
i < input_keyboard::NUMBER_OF_KEYS,
|
||||
"Attempting to convert a Key with enum index {i}, which is larger than the number of Key enums",
|
||||
);
|
||||
(i as u8).try_into().unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut key_sequences = found_actions.map(entry_to_key).collect::<Vec<_>>();
|
||||
|
||||
// Append the key button for the entry
|
||||
use InputMapperMessage as IMM;
|
||||
match entry.input {
|
||||
IMM::KeyDown(key) | IMM::KeyUp(key) | IMM::KeyDownNoRepeat(key) | IMM::KeyUpNoRepeat(key) => keys.push(key),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
keys.sort_by(|&a, &b| {
|
||||
// Order according to platform guidelines mentioned at https://ux.stackexchange.com/questions/58185/normative-ordering-for-modifier-key-combinations
|
||||
const ORDER: [Key; 4] = [Key::Control, Key::Alt, Key::Shift, Key::Command];
|
||||
|
||||
// Treat the `Accel` virtual key as the platform's accel key for sorting comparison purposes
|
||||
let a = if a == Key::Accel { platform_accel_key } else { a };
|
||||
let b = if b == Key::Accel { platform_accel_key } else { b };
|
||||
|
||||
// Find where the keys are in the order, or put them at the end if they're not found
|
||||
let a = ORDER.iter().position(|&key| key == a).unwrap_or(ORDER.len());
|
||||
let b = ORDER.iter().position(|&key| key == b).unwrap_or(ORDER.len());
|
||||
|
||||
// Compare the positions of both keys
|
||||
a.cmp(&b)
|
||||
});
|
||||
|
||||
KeysGroup(keys)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
// Return the shortest key sequence, if any
|
||||
key_sequences.sort_by_key(|keys| keys.0.len());
|
||||
key_sequences.first().cloned()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ use crate::messages::input_mapper::utility_types::misc::{KeyMappingEntries, Mapp
|
|||
use crate::messages::portfolio::document::node_graph::utility_types::Direction;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
|
||||
use crate::messages::portfolio::document::utility_types::transformation::TransformType;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::brush_tool::BrushToolMessageOptionsUpdate;
|
||||
use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
|
||||
|
@ -224,7 +223,8 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt }),
|
||||
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }),
|
||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }),
|
||||
|
@ -316,9 +316,10 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolShapeEllipse),
|
||||
entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape),
|
||||
entry!(KeyDown(KeyB); action_dispatch=ToolMessage::ActivateToolBrush),
|
||||
entry!(KeyDown(KeyX); modifiers=[Accel, Shift], action_dispatch=ToolMessage::ResetColors),
|
||||
entry!(KeyDown(KeyD); action_dispatch=ToolMessage::ResetColors),
|
||||
entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors),
|
||||
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomPrimaryColor),
|
||||
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: true }),
|
||||
entry!(KeyDown(KeyC); modifiers=[Alt, Shift], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: false }),
|
||||
//
|
||||
// DocumentMessage
|
||||
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),
|
||||
|
@ -330,20 +331,21 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=DocumentMessage::ToggleSelectedVisibility),
|
||||
entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=DocumentMessage::ToggleSelectedLocked),
|
||||
entry!(KeyDown(KeyG); modifiers=[Alt], action_dispatch=DocumentMessage::ToggleGridVisibility),
|
||||
entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::Redo),
|
||||
entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::Redo),
|
||||
entry!(KeyDown(KeyY); modifiers=[Accel], action_dispatch=DocumentMessage::Redo),
|
||||
entry!(KeyDown(KeyZ); modifiers=[Accel], action_dispatch=DocumentMessage::Undo),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=DocumentMessage::SelectAllLayers),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], canonical, action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }),
|
||||
entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
||||
entry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder),
|
||||
entry!(KeyDown(Backslash); modifiers=[Alt], action_dispatch=DocumentMessage::SelectParentLayer),
|
||||
entry!(KeyDown(BracketLeft); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepBack),
|
||||
entry!(KeyDown(BracketRight); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepForward),
|
||||
entry!(KeyDown(Escape); modifiers=[Shift], action_dispatch=DocumentMessage::SelectParentLayer),
|
||||
entry!(KeyDown(BracketLeft); modifiers=[Alt], canonical, action_dispatch=DocumentMessage::SelectionStepBack),
|
||||
entry!(KeyDown(BracketRight); modifiers=[Alt], canonical, action_dispatch=DocumentMessage::SelectionStepForward),
|
||||
entry!(KeyDown(MouseBack); action_dispatch=DocumentMessage::SelectionStepBack),
|
||||
entry!(KeyDown(MouseForward); action_dispatch=DocumentMessage::SelectionStepForward),
|
||||
entry!(KeyDown(Digit0); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
|
||||
|
@ -379,9 +381,9 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(ArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),
|
||||
//
|
||||
// TransformLayerMessage
|
||||
entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGRS { transform_type: TransformType::Grab }),
|
||||
entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginGRS { transform_type: TransformType::Rotate }),
|
||||
entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginGRS { transform_type: TransformType::Scale }),
|
||||
entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab),
|
||||
entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginRotate),
|
||||
entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale),
|
||||
entry!(KeyDown(Digit0); action_dispatch=TransformLayerMessage::TypeDigit { digit: 0 }),
|
||||
entry!(KeyDown(Digit1); action_dispatch=TransformLayerMessage::TypeDigit { digit: 1 }),
|
||||
entry!(KeyDown(Digit2); action_dispatch=TransformLayerMessage::TypeDigit { digit: 2 }),
|
||||
|
|
|
@ -25,7 +25,7 @@ impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMapping
|
|||
}
|
||||
|
||||
impl KeyMappingMessageHandler {
|
||||
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Vec<KeysGroup> {
|
||||
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Option<KeysGroup> {
|
||||
self.mapping_handler.action_input_mapping(action_to_find)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,44 +24,54 @@ macro_rules! modifiers {
|
|||
/// Each handler adds or removes actions in the form of message discriminants. Here, we tie an input condition (such as a hotkey) to an action's full message.
|
||||
/// When an action is currently available, and the user enters that input, the action's message is dispatched on the message bus.
|
||||
macro_rules! entry {
|
||||
// Pattern with canonical parameter
|
||||
($input:expr_2021; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? canonical, action_dispatch=$action_dispatch:expr_2021$(,)?) => {
|
||||
entry!($input; $($($modifier),*)?; $($($refresh),*)?; $action_dispatch; true)
|
||||
};
|
||||
|
||||
// Pattern without canonical parameter
|
||||
($input:expr_2021; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? action_dispatch=$action_dispatch:expr_2021$(,)?) => {
|
||||
entry!($input; $($($modifier),*)?; $($($refresh),*)?; $action_dispatch; false)
|
||||
};
|
||||
|
||||
// Implementation macro to avoid code duplication
|
||||
($input:expr; $($modifier:ident),*; $($refresh:ident),*; $action_dispatch:expr; $canonical:expr) => {
|
||||
&[&[
|
||||
// Cause the `action_dispatch` message to be sent when the specified input occurs.
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: $input,
|
||||
modifiers: modifiers!($($($modifier),*)?),
|
||||
modifiers: modifiers!($($modifier),*),
|
||||
canonical: $canonical,
|
||||
},
|
||||
|
||||
// Also cause the `action_dispatch` message to be sent when any of the specified refresh keys change.
|
||||
//
|
||||
// For example, a snapping state bound to the Shift key may change if the user presses or releases that key.
|
||||
// In that case, we want to dispatch the action's message even though the pointer didn't necessarily move so
|
||||
// the input handler can update the snapping state without making the user move the mouse to see the change.
|
||||
$(
|
||||
$(
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: InputMapperMessage::KeyDown(Key::$refresh),
|
||||
modifiers: modifiers!(),
|
||||
canonical: $canonical,
|
||||
},
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: InputMapperMessage::KeyUp(Key::$refresh),
|
||||
modifiers: modifiers!(),
|
||||
canonical: $canonical,
|
||||
},
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: InputMapperMessage::KeyDownNoRepeat(Key::$refresh),
|
||||
modifiers: modifiers!(),
|
||||
canonical: $canonical,
|
||||
},
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: InputMapperMessage::KeyUpNoRepeat(Key::$refresh),
|
||||
modifiers: modifiers!(),
|
||||
canonical: $canonical,
|
||||
},
|
||||
)*
|
||||
)*
|
||||
]]
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, all_required_modifiers_pressed};
|
||||
use super::input_keyboard::{KeysGroup, LayoutKeysGroup, all_required_modifiers_pressed};
|
||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{KeyStates, NUMBER_OF_KEYS};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::NUMBER_OF_MOUSE_BUTTONS;
|
||||
|
@ -120,6 +120,8 @@ pub struct MappingEntry {
|
|||
pub input: InputMapperMessage,
|
||||
/// Any additional keys that must be also pressed for this input mapping to match
|
||||
pub modifiers: KeyStates,
|
||||
/// True indicates that this takes priority as the labeled hotkey shown in UI menus and tooltips instead of an alternate binding for the same action
|
||||
pub canonical: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
|
@ -130,36 +132,12 @@ pub enum ActionKeys {
|
|||
}
|
||||
|
||||
impl ActionKeys {
|
||||
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) -> String {
|
||||
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) -> String {
|
||||
match self {
|
||||
Self::Action(action) => {
|
||||
// Take the shortest sequence of keys
|
||||
let mut key_sequences = action_input_mapping(action);
|
||||
key_sequences.sort_by_key(|keys| keys.0.len());
|
||||
let mut secondary_key_sequence = key_sequences.get(1).cloned();
|
||||
let mut key_sequence = key_sequences.get_mut(0);
|
||||
|
||||
// TODO: Replace this exception with a per-action choice of canonical hotkey
|
||||
if let Some(key_sequence) = &mut key_sequence {
|
||||
if key_sequence.0.as_slice() == [Key::MouseBack] {
|
||||
if let Some(replacement) = &mut secondary_key_sequence {
|
||||
std::mem::swap(*key_sequence, replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(key_sequence) = &mut key_sequence {
|
||||
if key_sequence.0.as_slice() == [Key::MouseForward] {
|
||||
if let Some(replacement) = &mut secondary_key_sequence {
|
||||
std::mem::swap(*key_sequence, replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(keys) = key_sequence {
|
||||
let mut taken_keys = KeysGroup::default();
|
||||
std::mem::swap(keys, &mut taken_keys);
|
||||
let description = taken_keys.to_string();
|
||||
*self = Self::Keys(taken_keys.into());
|
||||
if let Some(keys) = action_input_mapping(action) {
|
||||
let description = keys.to_string();
|
||||
*self = Self::Keys(keys.into());
|
||||
description
|
||||
} else {
|
||||
*self = Self::Keys(KeysGroup::default().into());
|
||||
|
|
|
@ -342,7 +342,7 @@ impl LayoutMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
|
||||
impl<F: Fn(&MessageDiscriminant) -> Option<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
|
||||
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) {
|
||||
match message {
|
||||
LayoutMessage::ResendActiveWidget { layout_target, widget_id } => {
|
||||
|
@ -385,7 +385,7 @@ impl LayoutMessageHandler {
|
|||
layout_target: LayoutTarget,
|
||||
new_layout: Layout,
|
||||
responses: &mut VecDeque<Message>,
|
||||
action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>,
|
||||
action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>,
|
||||
) {
|
||||
match new_layout {
|
||||
Layout::WidgetLayout(_) => {
|
||||
|
@ -419,7 +419,7 @@ impl LayoutMessageHandler {
|
|||
}
|
||||
|
||||
/// Send a diff to the frontend based on the layout target.
|
||||
fn send_diff(&self, mut diff: Vec<WidgetDiff>, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
fn send_diff(&self, mut diff: Vec<WidgetDiff>, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {
|
||||
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
|
||||
|
||||
let message = match layout_target {
|
||||
|
|
|
@ -109,7 +109,7 @@ pub enum Layout {
|
|||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) -> MenuLayout {
|
||||
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) -> MenuLayout {
|
||||
if let Self::MenuLayout(mut menu) = self {
|
||||
menu.layout
|
||||
.iter_mut()
|
||||
|
@ -589,7 +589,7 @@ pub enum DiffUpdate {
|
|||
|
||||
impl DiffUpdate {
|
||||
/// Append the keyboard shortcut to the tooltip where applicable
|
||||
pub fn apply_keyboard_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
pub fn apply_keyboard_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {
|
||||
// Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip
|
||||
let apply_shortcut_to_tooltip = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| {
|
||||
let shortcut_text = tooltip_shortcut.to_keys(action_input_mapping);
|
||||
|
|
|
@ -12,7 +12,7 @@ impl MenuBarEntryChildren {
|
|||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {
|
||||
let entries = self.0.iter_mut().flatten();
|
||||
|
||||
for entry in entries {
|
||||
|
|
|
@ -444,6 +444,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
DocumentMessage::EnterNestedNetwork { node_id } => {
|
||||
self.breadcrumb_network_path.push(node_id);
|
||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(DocumentMessage::ZoomCanvasToFitAll);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
|
@ -473,9 +474,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
self.breadcrumb_network_path.pop();
|
||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||
}
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
DocumentMessage::FlipSelectedLayers { flip_axis } => {
|
||||
let scale = match flip_axis {
|
||||
|
@ -525,6 +527,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
}
|
||||
}
|
||||
DocumentMessage::GraphViewOverlay { open } => {
|
||||
let opened = !self.graph_view_overlay_open && open;
|
||||
self.graph_view_overlay_open = open;
|
||||
|
||||
responses.add(FrontendMessage::UpdateGraphViewOverlay { open });
|
||||
|
@ -537,6 +540,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
if opened {
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
}
|
||||
if open {
|
||||
responses.add(ToolMessage::DeactivateTools);
|
||||
responses.add(OverlaysMessage::Draw); // Clear the overlays
|
||||
|
@ -1878,6 +1884,7 @@ impl DocumentMessageHandler {
|
|||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
responses.add(Message::StartBuffer);
|
||||
Some(previous_network)
|
||||
|
@ -1909,7 +1916,8 @@ impl DocumentMessageHandler {
|
|||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
Some(previous_network)
|
||||
}
|
||||
|
||||
|
@ -2066,7 +2074,7 @@ impl DocumentMessageHandler {
|
|||
/// Loads all of the fonts in the document.
|
||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
|
||||
let mut fonts = HashSet::new();
|
||||
for (_node_id, node) in self.document_network().recursive_nodes() {
|
||||
for (_node_id, node, _) in self.document_network().recursive_nodes() {
|
||||
for input in &node.inputs {
|
||||
if let Some(TaggedValue::Font(font)) = input.as_value() {
|
||||
fonts.insert(font.clone());
|
||||
|
|
|
@ -6,12 +6,12 @@ use bezier_rs::Subpath;
|
|||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::VectorModificationType;
|
||||
use graphene_std::vector::brush_stroke::BrushStroke;
|
||||
use graphene_std::vector::style::{Fill, Stroke};
|
||||
|
||||
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
||||
|
|
|
@ -9,10 +9,10 @@ use graph_craft::concrete;
|
|||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::brush_stroke::BrushStroke;
|
||||
use graphene_std::vector::style::{Fill, Stroke};
|
||||
use graphene_std::vector::{PointId, VectorModificationType};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
|
|
|
@ -5,8 +5,8 @@ use super::node_properties::{self, ParameterWidgetsInfo};
|
|||
use super::utility_types::FrontendNodeType;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{
|
||||
DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, NumberInputSettings,
|
||||
PropertiesRow, Vec2InputSettings, WidgetOverride,
|
||||
DocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata,
|
||||
NumberInputSettings, Vec2InputSettings, WidgetOverride,
|
||||
};
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::Message;
|
||||
|
@ -16,8 +16,8 @@ use graph_craft::ProtoNodeIdentifier;
|
|||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::*;
|
||||
use graph_craft::document::*;
|
||||
use graphene_std::brush::brush_cache::BrushCache;
|
||||
use graphene_std::extract_xy::XY;
|
||||
use graphene_std::raster::brush_cache::BrushCache;
|
||||
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
|
@ -37,26 +37,14 @@ pub struct NodePropertiesContext<'a> {
|
|||
|
||||
impl NodePropertiesContext<'_> {
|
||||
pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option<Vec<LayoutGroup>> {
|
||||
let input_properties_row = self.network_interface.input_properties_row(node_id, index, self.selection_network_path)?;
|
||||
let input_properties_row = self.network_interface.persistent_input_metadata(node_id, index, self.selection_network_path)?;
|
||||
if let Some(widget_override) = &input_properties_row.widget_override {
|
||||
let Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else {
|
||||
log::error!("Could not get widget override '{widget_override}' lambda in call_widget_override");
|
||||
return None;
|
||||
};
|
||||
widget_override_lambda(*node_id, index, self)
|
||||
.map(|layout_group| {
|
||||
let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else {
|
||||
log::error!("Could not get input properties row in call_widget_override");
|
||||
return Vec::new();
|
||||
};
|
||||
match &input_properties_row.input_data.get("tooltip").and_then(|tooltip| tooltip.as_str()) {
|
||||
Some(tooltip) => layout_group.into_iter().map(|widget| widget.with_tooltip(*tooltip)).collect::<Vec<_>>(),
|
||||
_ => layout_group,
|
||||
}
|
||||
})
|
||||
.map_err(|error| {
|
||||
log::error!("Error in widget override lambda: {}", error);
|
||||
})
|
||||
.map_err(|error| log::error!("Error in widget override lambda: {}", error))
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
|
@ -105,7 +93,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -126,7 +114,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -223,7 +211,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![("Data", "TODO").into()],
|
||||
input_metadata: vec![("Data", "TODO").into()],
|
||||
output_names: vec!["Data".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -285,7 +273,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()],
|
||||
input_metadata: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)),
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
|
@ -397,10 +385,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Artboards", "TODO").into(),
|
||||
PropertiesRow::with_override("Contents", "TODO", WidgetOverride::Hidden),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override("Contents", "TODO", WidgetOverride::Hidden),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Location",
|
||||
"TODO",
|
||||
WidgetOverride::Vec2(Vec2InputSettings {
|
||||
|
@ -410,7 +398,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Dimensions",
|
||||
"TODO",
|
||||
WidgetOverride::Vec2(Vec2InputSettings {
|
||||
|
@ -420,7 +408,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override("Background", "TODO", WidgetOverride::Custom("artboard_background".to_string())),
|
||||
InputMetadata::with_name_description_override("Background", "TODO", WidgetOverride::Custom("artboard_background".to_string())),
|
||||
("Clip", "TODO").into(),
|
||||
],
|
||||
output_names: vec!["Out".to_string()],
|
||||
|
@ -498,7 +486,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Empty", "TODO").into(), ("URL", "TODO").into()],
|
||||
input_metadata: vec![("Empty", "TODO").into(), ("URL", "TODO").into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -640,7 +628,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Canvas".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -740,7 +728,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Artwork", "TODO").into(), ("Footprint", "TODO").into()],
|
||||
input_metadata: vec![("Artwork", "TODO").into(), ("Footprint", "TODO").into()],
|
||||
output_names: vec!["Canvas".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -812,23 +800,23 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Spacer", "TODO").into(),
|
||||
("Clip", "TODO").into(),
|
||||
("Seed", "TODO").into(),
|
||||
PropertiesRow::with_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())),
|
||||
PropertiesRow::with_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())),
|
||||
PropertiesRow::with_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())),
|
||||
PropertiesRow::with_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())),
|
||||
PropertiesRow::with_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())),
|
||||
PropertiesRow::with_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())),
|
||||
PropertiesRow::with_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())),
|
||||
PropertiesRow::with_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())),
|
||||
PropertiesRow::with_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())),
|
||||
PropertiesRow::with_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())),
|
||||
PropertiesRow::with_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())),
|
||||
PropertiesRow::with_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())),
|
||||
PropertiesRow::with_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())),
|
||||
InputMetadata::with_name_description_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())),
|
||||
InputMetadata::with_name_description_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())),
|
||||
InputMetadata::with_name_description_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())),
|
||||
InputMetadata::with_name_description_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())),
|
||||
InputMetadata::with_name_description_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())),
|
||||
InputMetadata::with_name_description_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())),
|
||||
InputMetadata::with_name_description_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())),
|
||||
],
|
||||
output_names: vec!["Image".to_string()],
|
||||
..Default::default()
|
||||
|
@ -897,7 +885,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Image", "TODO").into()],
|
||||
input_metadata: vec![("Image", "TODO").into()],
|
||||
output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
|
||||
has_primary_output: false,
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
|
@ -982,7 +970,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Coordinate", "TODO").into()],
|
||||
input_metadata: vec![("Coordinate", "TODO").into()],
|
||||
output_names: vec!["X".to_string(), "Y".to_string()],
|
||||
has_primary_output: false,
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
|
@ -1016,7 +1004,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("TODO"),
|
||||
description: Cow::Borrowed(
|
||||
"Decomposes the X and Y components of a 2D coordinate.\n\nThe inverse of this node is \"Coordinate Value\", which can have either or both its X and Y exposed as graph inputs.",
|
||||
),
|
||||
properties: None,
|
||||
},
|
||||
// TODO: Remove this and just use the proto node definition directly
|
||||
|
@ -1030,7 +1020,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
nodes: vec![DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Vec<graphene_std::vector::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(BrushCache), 2),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
|
@ -1051,7 +1041,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
|
||||
input_metadata: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1088,7 +1078,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Image", "TODO").into()],
|
||||
input_metadata: vec![("Image", "TODO").into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -1107,7 +1097,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Image", "TODO").into()],
|
||||
input_metadata: vec![("Image", "TODO").into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -1150,7 +1140,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Storage".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1229,7 +1219,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into(), ("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into(), ("In", "TODO").into()],
|
||||
output_names: vec!["Output Buffer".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1376,7 +1366,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Texture".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1430,7 +1420,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Node", "TODO").into()],
|
||||
input_metadata: vec![("Node", "TODO").into()],
|
||||
output_names: vec!["Document Node".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -1501,7 +1491,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()],
|
||||
input_metadata: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()],
|
||||
output_names: vec!["Vector Data".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1561,11 +1551,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Editor API", "TODO").into(),
|
||||
PropertiesRow::with_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())),
|
||||
PropertiesRow::with_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())),
|
||||
InputMetadata::with_name_description_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Size",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1574,7 +1564,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Line Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1584,7 +1574,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Character Spacing",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1594,7 +1584,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Max Width",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1604,7 +1594,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Max Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1614,7 +1604,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Tilt",
|
||||
"Faux italic",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1706,9 +1696,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Vector Data", "TODO").into(),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Translation",
|
||||
"TODO",
|
||||
WidgetOverride::Vec2(Vec2InputSettings {
|
||||
|
@ -1718,8 +1708,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Scale",
|
||||
"TODO",
|
||||
WidgetOverride::Vec2(Vec2InputSettings {
|
||||
|
@ -1729,8 +1719,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
|
||||
PropertiesRow::with_override("Pivot", "TODO", WidgetOverride::Hidden),
|
||||
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
|
||||
InputMetadata::with_name_description_override("Pivot", "TODO", WidgetOverride::Hidden),
|
||||
],
|
||||
output_names: vec!["Data".to_string()],
|
||||
..Default::default()
|
||||
|
@ -1829,7 +1819,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()],
|
||||
input_metadata: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -1856,7 +1846,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
||||
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
|
||||
NodeInput::network(concrete!(f64), 2),
|
||||
NodeInput::network(concrete!(f64), 3),
|
||||
NodeInput::network(concrete!(u32), 3),
|
||||
NodeInput::network(concrete!(f64), 4),
|
||||
NodeInput::network(concrete!(f64), 5),
|
||||
NodeInput::network(concrete!(bool), 6),
|
||||
|
@ -1895,7 +1885,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::U32(100), false),
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
|
@ -1955,10 +1945,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
|
||||
Into::<PropertiesRow>::into(("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING)),
|
||||
PropertiesRow::with_override(
|
||||
("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING).into(),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Separation",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1967,7 +1957,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Quantity",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1976,7 +1966,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Start Offset",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1985,7 +1975,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Stop Offset",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1994,7 +1984,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
Into::<PropertiesRow>::into(("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING)),
|
||||
("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING).into(),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
|
@ -2098,9 +2088,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Vector Data", "TODO").into(),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Separation Disk Diameter",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -2111,7 +2101,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Seed",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -2153,7 +2143,7 @@ fn static_node_properties() -> NodeProperties {
|
|||
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
|
||||
map.insert(
|
||||
"identity_properties".to_string(),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through.")),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("The identity node passes its data through.")),
|
||||
);
|
||||
map.insert(
|
||||
"monitor_properties".to_string(),
|
||||
|
@ -2173,7 +2163,7 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"string".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let Some(value) = context.network_interface.input_metadata(&node_id, index, "string_properties", context.selection_network_path) else {
|
||||
let Some(value) = context.network_interface.input_data(&node_id, index, "string_properties", context.selection_network_path) else {
|
||||
return Err(format!("Could not get string properties for node {}", node_id));
|
||||
};
|
||||
let Some(string) = value.as_str() else {
|
||||
|
@ -2185,37 +2175,36 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"number".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let mut number_input = NumberInput::default();
|
||||
if let Some(unit) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "unit", context.selection_network_path)
|
||||
.input_data(&node_id, index, "unit", context.selection_network_path)
|
||||
.and_then(|value| value.as_str())
|
||||
{
|
||||
number_input = number_input.unit(unit);
|
||||
}
|
||||
if let Some(min) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "min", context.selection_network_path)
|
||||
.input_data(&node_id, index, "min", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.min(min);
|
||||
}
|
||||
if let Some(max) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "max", context.selection_network_path)
|
||||
.input_data(&node_id, index, "max", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.max(max);
|
||||
}
|
||||
if let Some(step) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "step", context.selection_network_path)
|
||||
.input_data(&node_id, index, "step", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.step(step);
|
||||
}
|
||||
if let Some(mode) = context.network_interface.input_metadata(&node_id, index, "mode", context.selection_network_path).map(|value| {
|
||||
if let Some(mode) = context.network_interface.input_data(&node_id, index, "mode", context.selection_network_path).map(|value| {
|
||||
let mode: NumberInputMode = serde_json::from_value(value.clone()).unwrap();
|
||||
mode
|
||||
}) {
|
||||
|
@ -2223,87 +2212,83 @@ fn static_input_properties() -> InputProperties {
|
|||
}
|
||||
if let Some(range_min) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "range_min", context.selection_network_path)
|
||||
.input_data(&node_id, index, "range_min", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.range_min(Some(range_min));
|
||||
}
|
||||
if let Some(range_max) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "range_max", context.selection_network_path)
|
||||
.input_data(&node_id, index, "range_max", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.range_max(Some(range_max));
|
||||
}
|
||||
if let Some(is_integer) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "is_integer", context.selection_network_path)
|
||||
.input_data(&node_id, index, "is_integer", context.selection_network_path)
|
||||
.and_then(|value| value.as_bool())
|
||||
{
|
||||
number_input = number_input.is_integer(is_integer);
|
||||
}
|
||||
let blank_assist = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "blank_assist", context.selection_network_path)
|
||||
.input_data(&node_id, index, "blank_assist", context.selection_network_path)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get blank assist when displaying number input for node {node_id}, index {index}");
|
||||
true
|
||||
});
|
||||
|
||||
Ok(vec![LayoutGroup::Row {
|
||||
widgets: node_properties::number_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, blank_assist), number_input),
|
||||
widgets: node_properties::number_widget(ParameterWidgetsInfo::new(node_id, index, blank_assist, context), number_input),
|
||||
}])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"vec2".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let x = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "x", context.selection_network_path)
|
||||
.input_data(&node_id, index, "x", context.selection_network_path)
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get x for vec2 input");
|
||||
""
|
||||
});
|
||||
})
|
||||
.to_string();
|
||||
let y = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "y", context.selection_network_path)
|
||||
.input_data(&node_id, index, "y", context.selection_network_path)
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get y for vec2 input");
|
||||
""
|
||||
});
|
||||
})
|
||||
.to_string();
|
||||
let unit = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "unit", context.selection_network_path)
|
||||
.input_data(&node_id, index, "unit", context.selection_network_path)
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get unit for vec2 input");
|
||||
""
|
||||
});
|
||||
})
|
||||
.to_string();
|
||||
let min = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "min", context.selection_network_path)
|
||||
.input_data(&node_id, index, "min", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64());
|
||||
|
||||
Ok(vec![node_properties::coordinate_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
x,
|
||||
y,
|
||||
unit,
|
||||
min,
|
||||
)])
|
||||
Ok(vec![node_properties::coordinate_widget(ParameterWidgetsInfo::new(node_id, index, true, context), &x, &y, &unit, min)])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"noise_properties_scale".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let scale = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().min(0.).disabled(!coherent_noise_active),
|
||||
);
|
||||
Ok(vec![scale.into()])
|
||||
|
@ -2312,20 +2297,16 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_noise_type".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let noise_type_row = enum_choice::<NoiseType>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.property_row();
|
||||
let noise_type_row = enum_choice::<NoiseType>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
|
||||
Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"noise_properties_domain_warp_type".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let domain_warp_type = enum_choice::<DomainWarpType>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
|
||||
.disabled(!coherent_noise_active)
|
||||
.property_row();
|
||||
Ok(vec![domain_warp_type])
|
||||
|
@ -2334,10 +2315,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_domain_warp_amplitude".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, _, _, domain_warp_active, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let domain_warp_amplitude = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active),
|
||||
);
|
||||
Ok(vec![domain_warp_amplitude.into(), LayoutGroup::Row { widgets: Vec::new() }])
|
||||
|
@ -2346,10 +2326,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_type".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_type_row = enum_choice::<FractalType>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
|
||||
.disabled(!coherent_noise_active)
|
||||
.property_row();
|
||||
Ok(vec![fractal_type_row])
|
||||
|
@ -2358,10 +2337,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_octaves".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_octaves = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(1.)
|
||||
|
@ -2376,10 +2354,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_lacunarity".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_lacunarity = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(0.)
|
||||
|
@ -2392,10 +2369,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_gain".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_gain = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(0.)
|
||||
|
@ -2408,10 +2384,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_weighted_strength".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_weighted_strength = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(0.)
|
||||
|
@ -2424,10 +2399,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_ping_pong_strength".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, ping_pong_active, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_ping_pong_strength = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(0.)
|
||||
|
@ -2440,10 +2414,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_cellular_distance_function".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let cellular_distance_function_row = enum_choice::<CellularDistanceFunction>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
|
||||
.disabled(!coherent_noise_active || !cellular_noise_active)
|
||||
.property_row();
|
||||
Ok(vec![cellular_distance_function_row])
|
||||
|
@ -2452,10 +2425,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_cellular_return_type".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let cellular_return_type = enum_choice::<CellularReturnType>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
|
||||
.disabled(!coherent_noise_active || !cellular_noise_active)
|
||||
.property_row();
|
||||
Ok(vec![cellular_return_type])
|
||||
|
@ -2464,10 +2436,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_cellular_jitter".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let cellular_jitter = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.range_min(Some(0.))
|
||||
|
@ -2480,7 +2451,7 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"brightness".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let document_node = node_properties::get_document_node(node_id, context)?;
|
||||
let is_use_classic = document_node
|
||||
.inputs
|
||||
.iter()
|
||||
|
@ -2491,7 +2462,7 @@ fn static_input_properties() -> InputProperties {
|
|||
.unwrap_or(false);
|
||||
let (b_min, b_max) = if is_use_classic { (-100., 100.) } else { (-100., 150.) };
|
||||
let brightness = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().mode_range().range_min(Some(b_min)).range_max(Some(b_max)).unit("%").display_decimal_places(2),
|
||||
);
|
||||
Ok(vec![brightness.into()])
|
||||
|
@ -2500,7 +2471,8 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"contrast".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let document_node = node_properties::get_document_node(node_id, context)?;
|
||||
|
||||
let is_use_classic = document_node
|
||||
.inputs
|
||||
.iter()
|
||||
|
@ -2511,7 +2483,7 @@ fn static_input_properties() -> InputProperties {
|
|||
.unwrap_or(false);
|
||||
let (c_min, c_max) = if is_use_classic { (-100., 100.) } else { (-50., 100.) };
|
||||
let contrast = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().mode_range().range_min(Some(c_min)).range_max(Some(c_max)).unit("%").display_decimal_places(2),
|
||||
);
|
||||
Ok(vec![contrast.into()])
|
||||
|
@ -2520,21 +2492,16 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"assign_colors_gradient".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let gradient_row = node_properties::color_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ColorInput::default().allow_none(false),
|
||||
);
|
||||
let gradient_row = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default().allow_none(false));
|
||||
Ok(vec![gradient_row])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"assign_colors_seed".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?;
|
||||
let seed_row = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().min(0.).int().disabled(!randomize_enabled),
|
||||
);
|
||||
Ok(vec![seed_row.into()])
|
||||
|
@ -2543,10 +2510,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"assign_colors_repeat_every".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?;
|
||||
let repeat_every_row = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().min(0.).int().disabled(randomize_enabled),
|
||||
);
|
||||
Ok(vec![repeat_every_row.into()])
|
||||
|
@ -2555,33 +2521,24 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"mask_stencil".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let mask = node_properties::color_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ColorInput::default());
|
||||
let mask = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default());
|
||||
Ok(vec![mask])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"spline_input".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
Ok(vec![LayoutGroup::Row {
|
||||
widgets: node_properties::array_of_coordinates_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
TextInput::default().centered(true),
|
||||
),
|
||||
widgets: node_properties::array_of_coordinates_widget(ParameterWidgetsInfo::new(node_id, index, true, context), TextInput::default().centered(true)),
|
||||
}])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"transform_rotation".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
|
||||
let mut widgets = node_properties::start_widgets(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
super::utility_types::FrontendGraphDataType::Number,
|
||||
);
|
||||
let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context));
|
||||
|
||||
let document_node = node_properties::get_document_node(node_id, context)?;
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
return Err("Input not found in transform rotation input override".to_string());
|
||||
};
|
||||
|
@ -2610,13 +2567,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"transform_skew".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
|
||||
let mut widgets = node_properties::start_widgets(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
super::utility_types::FrontendGraphDataType::Number,
|
||||
);
|
||||
let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context));
|
||||
|
||||
let document_node = node_properties::get_document_node(node_id, context)?;
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
return Err("Input not found in transform skew input override".to_string());
|
||||
};
|
||||
|
@ -2658,17 +2611,15 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"text_area".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
Ok(vec![LayoutGroup::Row {
|
||||
widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)),
|
||||
widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)),
|
||||
}])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"text_font".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true));
|
||||
let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context));
|
||||
let mut result = vec![LayoutGroup::Row { widgets: font }];
|
||||
if let Some(style) = style {
|
||||
result.push(LayoutGroup::Row { widgets: style });
|
||||
|
@ -2679,9 +2630,8 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"artboard_background".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
Ok(vec![node_properties::color_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
ColorInput::default().allow_none(false),
|
||||
)])
|
||||
}),
|
||||
|
@ -2722,7 +2672,7 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
|
|||
// Extract input types (already creates owned Strings)
|
||||
let input_types = implementations
|
||||
.iter()
|
||||
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.clone().nested_type().to_string()))
|
||||
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_string()))
|
||||
.collect::<HashSet<String>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<String>>();
|
||||
|
@ -2828,7 +2778,7 @@ impl DocumentNodeDefinition {
|
|||
log::error!("Path is not valid for network");
|
||||
return;
|
||||
};
|
||||
nested_node_metadata.persistent_metadata.input_properties.resize_with(input_length, PropertiesRow::default);
|
||||
nested_node_metadata.persistent_metadata.input_metadata.resize_with(input_length, InputMetadata::default);
|
||||
|
||||
// Recurse over all sub-nodes if the current node is a network implementation
|
||||
let mut current_path = path.clone();
|
||||
|
@ -2847,7 +2797,7 @@ impl DocumentNodeDefinition {
|
|||
} else {
|
||||
// Base case
|
||||
let input_len = node_template.document_node.inputs.len();
|
||||
node_template.persistent_node_metadata.input_properties.resize_with(input_len, PropertiesRow::default);
|
||||
node_template.persistent_node_metadata.input_metadata.resize_with(input_len, InputMetadata::default);
|
||||
if let DocumentNodeImplementation::Network(node_template_network) = &node_template.document_node.implementation {
|
||||
for sub_node_id in node_template_network.nodes.keys().cloned().collect::<Vec<_>>() {
|
||||
populate_input_properties(node_template, vec![sub_node_id]);
|
||||
|
@ -2859,6 +2809,10 @@ impl DocumentNodeDefinition {
|
|||
|
||||
// Set the reference to the node definition
|
||||
template.persistent_node_metadata.reference = Some(self.identifier.to_string());
|
||||
// If the display name is empty and it is not a merge node, then set it to the reference
|
||||
if template.persistent_node_metadata.display_name.is_empty() && self.identifier != "Merge" {
|
||||
template.persistent_node_metadata.display_name = self.identifier.to_string();
|
||||
}
|
||||
template
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::DocumentNodeDefinition;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, NodeTemplate, WidgetOverride};
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
use graph_craft::document::*;
|
||||
use graphene_std::registry::*;
|
||||
|
@ -67,13 +67,13 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
|||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
// TODO: Store information for input overrides in the node macro
|
||||
input_properties: fields
|
||||
input_metadata: fields
|
||||
.iter()
|
||||
.map(|f| match f.widget_override {
|
||||
RegistryWidgetOverride::None => (f.name, f.description).into(),
|
||||
RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden),
|
||||
RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||
RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||
RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),
|
||||
RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||
RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||
})
|
||||
.collect(),
|
||||
output_names: vec![output_type.to_string()],
|
||||
|
|
|
@ -33,6 +33,7 @@ pub enum NodeGraphMessage {
|
|||
node_id: Option<NodeId>,
|
||||
node_type: String,
|
||||
xy: Option<(i32, i32)>,
|
||||
add_transaction: bool,
|
||||
},
|
||||
CreateWire {
|
||||
output_connector: OutputConnector,
|
||||
|
@ -123,6 +124,9 @@ pub enum NodeGraphMessage {
|
|||
},
|
||||
SendClickTargets,
|
||||
EndSendClickTargets,
|
||||
UnloadWires,
|
||||
SendWires,
|
||||
UpdateVisibleNodes,
|
||||
SendGraph,
|
||||
SetGridAlignedEdges,
|
||||
SetInputValue {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeWire, WirePath};
|
||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode};
|
||||
use super::{document_node_definitions, node_properties};
|
||||
use crate::consts::GRID_SIZE;
|
||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
|
@ -13,6 +13,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
|
|||
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
|
||||
};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
|
||||
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
|
||||
|
@ -67,6 +68,7 @@ pub struct NodeGraphMessageHandler {
|
|||
select_if_not_dragged: Option<NodeId>,
|
||||
/// The start of the dragged line (cannot be moved), stored in node graph coordinates
|
||||
pub wire_in_progress_from_connector: Option<DVec2>,
|
||||
wire_in_progress_type: FrontendGraphDataType,
|
||||
/// The end point of the dragged line (cannot be moved), stored in node graph coordinates
|
||||
pub wire_in_progress_to_connector: Option<DVec2>,
|
||||
/// State for the context menu popups.
|
||||
|
@ -77,12 +79,16 @@ pub struct NodeGraphMessageHandler {
|
|||
auto_panning: AutoPanning,
|
||||
/// The node to preview on mouse up if alt-clicked
|
||||
preview_on_mouse_up: Option<NodeId>,
|
||||
// The index of the import that is being moved
|
||||
/// The index of the import that is being moved
|
||||
reordering_import: Option<usize>,
|
||||
// The index of the export that is being moved
|
||||
/// The index of the export that is being moved
|
||||
reordering_export: Option<usize>,
|
||||
// The end index of the moved port
|
||||
/// The end index of the moved port
|
||||
end_index: Option<usize>,
|
||||
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
|
||||
frontend_nodes: Vec<NodeId>,
|
||||
/// Used to keep track of what wires are sent to the front end so the old ones can be removed
|
||||
frontend_wires: HashSet<(NodeId, usize)>,
|
||||
}
|
||||
|
||||
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
|
||||
|
@ -175,7 +181,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
NodeGraphMessage::CreateNodeFromContextMenu { node_id, node_type, xy } => {
|
||||
NodeGraphMessage::CreateNodeFromContextMenu {
|
||||
node_id,
|
||||
node_type,
|
||||
xy,
|
||||
add_transaction,
|
||||
} => {
|
||||
let (x, y) = if let Some((x, y)) = xy {
|
||||
(x, y)
|
||||
} else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) {
|
||||
|
@ -197,7 +208,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
let node_template = document_node_type.default_node_template();
|
||||
self.context_menu = None;
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
if add_transaction {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::InsertNode {
|
||||
node_id,
|
||||
node_template: node_template.clone(),
|
||||
|
@ -220,13 +234,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
};
|
||||
|
||||
// Ensure connection is to correct input of new node. If it does not have an input then do not connect
|
||||
if let Some((input_index, _)) = node_template
|
||||
.document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
{
|
||||
if let Some((input_index, _)) = node_template.document_node.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) {
|
||||
responses.add(NodeGraphMessage::CreateWire {
|
||||
output_connector: *output_connector,
|
||||
input_connector: InputConnector::node(node_id, input_index),
|
||||
|
@ -236,6 +244,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
}
|
||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
|
@ -367,9 +376,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(DocumentMessage::CommitTransaction);
|
||||
|
||||
// Update the graph UI and re-render
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
if graph_view_overlay_open {
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
} else {
|
||||
responses.add(DocumentMessage::GraphViewOverlay { open: true });
|
||||
responses.add(NavigationMessage::FitViewportToSelection);
|
||||
responses.add(DocumentMessage::ZoomCanvasTo100Percent);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::InsertNode { node_id, node_template } => {
|
||||
network_interface.insert_node(node_id, node_template, selection_network_path);
|
||||
|
@ -629,6 +643,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
// Abort dragging a wire
|
||||
if self.wire_in_progress_from_connector.is_some() {
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
|
@ -707,6 +722,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
if self.context_menu.is_some() {
|
||||
self.context_menu = None;
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
responses.add(FrontendMessage::UpdateContextMenuInformation {
|
||||
context_menu_information: self.context_menu.clone(),
|
||||
|
@ -740,6 +756,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
};
|
||||
let Some(output_connector) = output_connector else { return };
|
||||
self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path);
|
||||
self.wire_in_progress_type = FrontendGraphDataType::from_type(&network_interface.input_type(clicked_input, breadcrumb_network_path).0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -749,6 +766,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
self.initial_disconnecting = false;
|
||||
|
||||
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
|
||||
if let Some((output_type, source)) = clicked_output
|
||||
.node_id()
|
||||
.map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path))
|
||||
{
|
||||
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
|
||||
} else {
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
}
|
||||
|
||||
self.update_node_graph_hints(responses);
|
||||
return;
|
||||
}
|
||||
|
@ -895,9 +921,18 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
false
|
||||
}
|
||||
});
|
||||
let vector_wire = build_vector_wire(
|
||||
wire_in_progress_from_connector,
|
||||
wire_in_progress_to_connector,
|
||||
from_connector_is_layer,
|
||||
to_connector_is_layer,
|
||||
GraphWireStyle::Direct,
|
||||
);
|
||||
let mut path_string = String::new();
|
||||
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
|
||||
let wire_path = WirePath {
|
||||
path_string: Self::build_wire_path_string(wire_in_progress_from_connector, wire_in_progress_to_connector, from_connector_is_layer, to_connector_is_layer),
|
||||
data_type: FrontendGraphDataType::General,
|
||||
path_string,
|
||||
data_type: self.wire_in_progress_type,
|
||||
thick: false,
|
||||
dashed: false,
|
||||
};
|
||||
|
@ -941,7 +976,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
self.update_node_graph_hints(responses);
|
||||
} else if self.reordering_import.is_some() {
|
||||
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
||||
log::error!("Could not get modify import export in PointerUp");
|
||||
log::error!("Could not get modify import export in PointerMove");
|
||||
return;
|
||||
};
|
||||
// Find the first import that is below the mouse position
|
||||
|
@ -961,7 +996,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
|
||||
} else if self.reordering_export.is_some() {
|
||||
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
||||
log::error!("Could not get modify import export in PointerUp");
|
||||
log::error!("Could not get modify import export in PointerMove");
|
||||
return;
|
||||
};
|
||||
// Find the first export that is below the mouse position
|
||||
|
@ -1043,15 +1078,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
// Get the compatible type from the output connector
|
||||
let compatible_type = output_connector.and_then(|output_connector| {
|
||||
output_connector.node_id().and_then(|node_id| {
|
||||
let output_index = output_connector.index();
|
||||
// Get the output types from the network interface
|
||||
let output_types = network_interface.output_types(&node_id, selection_network_path);
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path);
|
||||
|
||||
// Extract the type if available
|
||||
output_types.get(output_index).and_then(|type_option| type_option.as_ref()).map(|(output_type, _)| {
|
||||
// Create a search term based on the type
|
||||
format!("type:{}", output_type.clone().nested_type())
|
||||
})
|
||||
match type_source {
|
||||
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
|
||||
_ => Some(format!("type:{}", output_type.nested_type())),
|
||||
}
|
||||
})
|
||||
});
|
||||
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
||||
|
@ -1117,107 +1150,56 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
let has_primary_output_connection = network_interface
|
||||
.outward_wires(selection_network_path)
|
||||
.is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty()));
|
||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||
return;
|
||||
};
|
||||
if let Some(selected_node) = network.nodes.get(&selected_node_id) {
|
||||
// Check if any downstream node has any input that feeds into the primary export of the selected node
|
||||
let primary_input_is_value = selected_node.inputs.first().is_some_and(|first_input| first_input.as_value().is_some());
|
||||
// Check that neither the primary input or output of the selected node are already connected.
|
||||
if !has_primary_output_connection && primary_input_is_value {
|
||||
if !has_primary_output_connection {
|
||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||
return;
|
||||
};
|
||||
let Some(selected_node) = network.nodes.get(&selected_node_id) else {
|
||||
return;
|
||||
};
|
||||
// Check that the first visible input is disconnected
|
||||
let selected_node_input_connect_index = selected_node
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|input| input.1.is_exposed())
|
||||
.filter(|input| input.1.as_value().is_some())
|
||||
.map(|input| input.0);
|
||||
if let Some(selected_node_input_connect_index) = selected_node_input_connect_index {
|
||||
let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
|
||||
log::error!("Could not get bounding box for node: {selected_node_id}");
|
||||
return;
|
||||
};
|
||||
// TODO: Cache all wire locations if this is a performance issue
|
||||
let overlapping_wires = Self::collect_wires(network_interface, selection_network_path)
|
||||
.into_iter()
|
||||
.filter(|frontend_wire| {
|
||||
// Prevent inserting on a link that is connected upstream to the selected node
|
||||
if network_interface
|
||||
.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow)
|
||||
.any(|upstream_id| {
|
||||
frontend_wire.wire_end.node_id().is_some_and(|wire_end_id| wire_end_id == upstream_id)
|
||||
|| frontend_wire.wire_start.node_id().is_some_and(|wire_start_id| wire_start_id == upstream_id)
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
let mut wires_to_check = network_interface.node_graph_input_connectors(selection_network_path).into_iter().collect::<HashSet<_>>();
|
||||
// Prevent inserting on a link that is connected upstream to the selected node
|
||||
for upstream_node in network_interface.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) {
|
||||
for input_index in 0..network_interface.number_of_inputs(&upstream_node, selection_network_path) {
|
||||
wires_to_check.remove(&InputConnector::node(upstream_node, input_index));
|
||||
}
|
||||
}
|
||||
|
||||
let overlapping_wires = wires_to_check
|
||||
.into_iter()
|
||||
.filter_map(|input| {
|
||||
// Prevent inserting a layer into a chain
|
||||
if network_interface.is_layer(&selected_node_id, selection_network_path)
|
||||
&& frontend_wire
|
||||
.wire_start
|
||||
.node_id()
|
||||
.is_some_and(|wire_start_id| network_interface.is_chain(&wire_start_id, selection_network_path))
|
||||
&& input.node_id().is_some_and(|input_node_id| network_interface.is_chain(&input_node_id, selection_network_path))
|
||||
{
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(input_position) = network_interface.input_position(&frontend_wire.wire_end, selection_network_path) else {
|
||||
log::error!("Could not get input port position for {:?}", frontend_wire.wire_end);
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(output_position) = network_interface.output_position(&frontend_wire.wire_start, selection_network_path) else {
|
||||
log::error!("Could not get output port position for {:?}", frontend_wire.wire_start);
|
||||
return false;
|
||||
};
|
||||
|
||||
let start_node_is_layer = frontend_wire
|
||||
.wire_end
|
||||
.node_id()
|
||||
.is_some_and(|wire_start_id| network_interface.is_layer(&wire_start_id, selection_network_path));
|
||||
let end_node_is_layer = frontend_wire
|
||||
.wire_end
|
||||
.node_id()
|
||||
.is_some_and(|wire_end_id| network_interface.is_layer(&wire_end_id, selection_network_path));
|
||||
|
||||
let locations = Self::build_wire_path_locations(output_position, input_position, start_node_is_layer, end_node_is_layer);
|
||||
let bezier = bezier_rs::Bezier::from_cubic_dvec2(
|
||||
(locations[0].x, locations[0].y).into(),
|
||||
(locations[1].x, locations[1].y).into(),
|
||||
(locations[2].x, locations[2].y).into(),
|
||||
(locations[3].x, locations[3].y).into(),
|
||||
);
|
||||
|
||||
!bezier.rectangle_intersections(bounding_box[0], bounding_box[1]).is_empty() || bezier.is_contained_within(bounding_box[0], bounding_box[1])
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.filter_map(|mut wire| {
|
||||
if let Some(end_node_id) = wire.wire_end.node_id() {
|
||||
let Some(actual_index_from_exposed) = (0..network_interface.number_of_inputs(&end_node_id, selection_network_path))
|
||||
.filter(|&input_index| {
|
||||
network_interface
|
||||
.input_from_connector(&InputConnector::Node { node_id: end_node_id, input_index }, selection_network_path)
|
||||
.is_some_and(|input| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
})
|
||||
.nth(wire.wire_end.input_index())
|
||||
else {
|
||||
log::error!("Could not get exposed input index for {:?}", wire.wire_end);
|
||||
return None;
|
||||
};
|
||||
wire.wire_end = InputConnector::Node {
|
||||
node_id: end_node_id,
|
||||
input_index: actual_index_from_exposed,
|
||||
};
|
||||
}
|
||||
Some(wire)
|
||||
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
||||
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let is_stack_wire = |wire: &FrontendNodeWire| match (wire.wire_start.node_id(), wire.wire_end.node_id(), wire.wire_end.input_index()) {
|
||||
(Some(start_id), Some(end_id), input_index) => {
|
||||
input_index == 0 && network_interface.is_layer(&start_id, selection_network_path) && network_interface.is_layer(&end_id, selection_network_path)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Prioritize vertical thick lines and cancel if there are multiple potential wires
|
||||
let mut node_wires = Vec::new();
|
||||
let mut stack_wires = Vec::new();
|
||||
for wire in overlapping_wires {
|
||||
if is_stack_wire(&wire) { stack_wires.push(wire) } else { node_wires.push(wire) }
|
||||
for (overlapping_wire_input, is_stack) in overlapping_wires {
|
||||
if is_stack {
|
||||
stack_wires.push(overlapping_wire_input)
|
||||
} else {
|
||||
node_wires.push(overlapping_wire_input)
|
||||
}
|
||||
}
|
||||
|
||||
let overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) {
|
||||
|
@ -1234,29 +1216,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
None
|
||||
};
|
||||
if let Some(overlapping_wire) = overlapping_wire {
|
||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||
return;
|
||||
};
|
||||
// Ensure connection is to first visible input of selected node. If it does not have an input then do not connect
|
||||
if let Some((selected_node_input_index, _)) = network
|
||||
.nodes
|
||||
.get(&selected_node_id)
|
||||
.unwrap()
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
{
|
||||
responses.add(NodeGraphMessage::InsertNodeBetween {
|
||||
node_id: selected_node_id,
|
||||
input_connector: overlapping_wire.wire_end,
|
||||
insert_node_input_index: selected_node_input_index,
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
responses.add(NodeGraphMessage::InsertNodeBetween {
|
||||
node_id: selected_node_id,
|
||||
input_connector: *overlapping_wire,
|
||||
insert_node_input_index: selected_node_input_connect_index,
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1283,6 +1249,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
self.begin_dragging = false;
|
||||
self.box_selection_start = None;
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
self.reordering_export = None;
|
||||
self.reordering_import = None;
|
||||
|
@ -1357,23 +1324,52 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
|
||||
}),
|
||||
NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }),
|
||||
NodeGraphMessage::UnloadWires => {
|
||||
for input in network_interface.node_graph_input_connectors(breadcrumb_network_path) {
|
||||
network_interface.unload_wire(&input, breadcrumb_network_path);
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::ClearAllNodeGraphWires);
|
||||
}
|
||||
NodeGraphMessage::SendWires => {
|
||||
let wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path);
|
||||
responses.add(FrontendMessage::UpdateNodeGraphWires { wires });
|
||||
}
|
||||
NodeGraphMessage::UpdateVisibleNodes => {
|
||||
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport_bbox = ipp.document_bounds();
|
||||
let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p));
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
for node_id in &self.frontend_nodes {
|
||||
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
|
||||
log::error!("Could not get bbox for node: {:?}", node_id);
|
||||
continue;
|
||||
};
|
||||
|
||||
if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {
|
||||
nodes.push(*node_id);
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
|
||||
}
|
||||
NodeGraphMessage::SendGraph => {
|
||||
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
if breadcrumb_network_path == selection_network_path && graph_view_overlay_open {
|
||||
// TODO: Implement culling of nodes and wires whose bounding boxes are outside of the viewport
|
||||
let wires = Self::collect_wires(network_interface, breadcrumb_network_path);
|
||||
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
|
||||
self.frontend_nodes = nodes.iter().map(|node| node.id).collect();
|
||||
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });
|
||||
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||
|
||||
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
|
||||
let wires_direct_not_grid_aligned = preferences.graph_wire_style.is_direct();
|
||||
|
||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||
responses.add(FrontendMessage::UpdateNodeGraph {
|
||||
nodes,
|
||||
wires,
|
||||
wires_direct_not_grid_aligned,
|
||||
});
|
||||
responses.add(FrontendMessage::UpdateLayerWidths {
|
||||
layer_widths,
|
||||
chain_widths,
|
||||
|
@ -1455,6 +1451,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
|
||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
||||
|
@ -1474,6 +1472,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
|
||||
network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path);
|
||||
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
|
||||
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
|
||||
|
@ -1487,6 +1487,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::SetDisplayName {
|
||||
node_id,
|
||||
|
@ -1623,7 +1624,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
// }
|
||||
|
||||
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
||||
log::error!("Could not get network metadata in PointerMove");
|
||||
log::error!("Could not get network metadata in UpdateBoxSelection");
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -1689,7 +1690,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
|
||||
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
responses.add(FrontendMessage::UpdateImportsExports {
|
||||
imports,
|
||||
exports,
|
||||
|
@ -1835,6 +1837,7 @@ impl NodeGraphMessageHandler {
|
|||
node_id: Some(node_id),
|
||||
node_type: node_type.clone(),
|
||||
xy: None,
|
||||
add_transaction: true,
|
||||
}
|
||||
.into(),
|
||||
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
|
||||
|
@ -2151,69 +2154,39 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn collect_wires(network_interface: &NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNodeWire> {
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
log::error!("Could not get network when collecting wires");
|
||||
return Vec::new();
|
||||
};
|
||||
let mut wires = network
|
||||
.nodes
|
||||
fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {
|
||||
let mut added_wires = network_interface
|
||||
.node_graph_input_connectors(breadcrumb_network_path)
|
||||
.iter()
|
||||
.flat_map(|(wire_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, wire_end, index)))
|
||||
.filter_map(|(input, &wire_end, wire_end_input_index)| {
|
||||
match *input {
|
||||
NodeInput::Node {
|
||||
node_id: wire_start,
|
||||
output_index: wire_start_output_index,
|
||||
// TODO: add ui for lambdas
|
||||
lambda: _,
|
||||
} => Some(FrontendNodeWire {
|
||||
wire_start: OutputConnector::node(wire_start, wire_start_output_index),
|
||||
wire_end: InputConnector::node(wire_end, wire_end_input_index),
|
||||
dashed: false,
|
||||
}),
|
||||
NodeInput::Network { import_index, .. } => Some(FrontendNodeWire {
|
||||
wire_start: OutputConnector::Import(import_index),
|
||||
wire_end: InputConnector::node(wire_end, wire_end_input_index),
|
||||
dashed: false,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.filter_map(|connector| network_interface.newly_loaded_input_wire(connector, graph_wire_style, breadcrumb_network_path))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Connect primary export to root node, since previewing a node will change the primary export
|
||||
if let Some(root_node) = network_interface.root_node(breadcrumb_network_path) {
|
||||
wires.push(FrontendNodeWire {
|
||||
wire_start: OutputConnector::node(root_node.node_id, root_node.output_index),
|
||||
wire_end: InputConnector::Export(0),
|
||||
dashed: false,
|
||||
});
|
||||
let changed_wire_inputs = added_wires.iter().map(|update| (update.id, update.input_index)).collect::<Vec<_>>();
|
||||
self.frontend_wires.extend(changed_wire_inputs);
|
||||
|
||||
let mut orphaned_wire_inputs = self.frontend_wires.clone();
|
||||
self.frontend_wires = network_interface
|
||||
.node_graph_wire_inputs(breadcrumb_network_path)
|
||||
.iter()
|
||||
.filter_map(|visible_wire_input| orphaned_wire_inputs.take(visible_wire_input))
|
||||
.collect::<HashSet<_>>();
|
||||
added_wires.extend(orphaned_wire_inputs.into_iter().map(|(id, input_index)| WirePathUpdate {
|
||||
id,
|
||||
input_index,
|
||||
wire_path_update: None,
|
||||
}));
|
||||
|
||||
if let Some(wire_to_root) = network_interface.wire_to_root(graph_wire_style, breadcrumb_network_path) {
|
||||
added_wires.push(wire_to_root);
|
||||
} else {
|
||||
added_wires.push(WirePathUpdate {
|
||||
id: NodeId(u64::MAX),
|
||||
input_index: usize::MAX,
|
||||
wire_path_update: None,
|
||||
})
|
||||
}
|
||||
|
||||
// Connect rest of exports to their actual export field since they are not affected by previewing. Only connect the primary export if it is dashed
|
||||
for (i, export) in network.exports.iter().enumerate() {
|
||||
let dashed = matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) && i == 0;
|
||||
if dashed || i != 0 {
|
||||
if let NodeInput::Node { node_id, output_index, .. } = export {
|
||||
wires.push(FrontendNodeWire {
|
||||
wire_start: OutputConnector::Node {
|
||||
node_id: *node_id,
|
||||
output_index: *output_index,
|
||||
},
|
||||
wire_end: InputConnector::Export(i),
|
||||
dashed,
|
||||
});
|
||||
} else if let NodeInput::Network { import_index, .. } = *export {
|
||||
wires.push(FrontendNodeWire {
|
||||
wire_start: OutputConnector::Import(import_index),
|
||||
wire_end: InputConnector::Export(i),
|
||||
dashed,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
wires
|
||||
added_wires
|
||||
}
|
||||
|
||||
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
|
||||
|
@ -2237,6 +2210,7 @@ impl NodeGraphMessageHandler {
|
|||
log::error!("Could not get position for node {node_id}");
|
||||
}
|
||||
}
|
||||
|
||||
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
log::error!("Could not get nested network when collecting nodes");
|
||||
|
@ -2252,13 +2226,14 @@ impl NodeGraphMessageHandler {
|
|||
let node_id_path = [breadcrumb_network_path, (&[node_id])].concat();
|
||||
|
||||
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
|
||||
|
||||
let mut inputs = inputs.into_iter().map(|input| {
|
||||
input.map(|input| FrontendGraphInput {
|
||||
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
|
||||
resolved_type: Some(format!("{:?}", &input.ty)),
|
||||
resolved_type: format!("{:?}", &input.ty),
|
||||
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
|
||||
name: input.input_name.unwrap_or_else(|| input.ty.nested_type().to_string()),
|
||||
description: input.input_description.unwrap_or_default(),
|
||||
name: input.input_name,
|
||||
description: input.input_description,
|
||||
connected_to: input.output_connector,
|
||||
})
|
||||
});
|
||||
|
@ -2266,20 +2241,16 @@ impl NodeGraphMessageHandler {
|
|||
let primary_input = inputs.next().flatten();
|
||||
let exposed_inputs = inputs.flatten().collect();
|
||||
|
||||
let output_types = network_interface.output_types(&node_id, breadcrumb_network_path);
|
||||
let primary_output_type = output_types.first().cloned().flatten();
|
||||
let frontend_data_type = if let Some((output_type, type_source)) = &primary_output_type {
|
||||
FrontendGraphDataType::displayed_type(output_type, type_source)
|
||||
} else {
|
||||
FrontendGraphDataType::General
|
||||
};
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||
let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
|
||||
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) && !output_types.is_empty() {
|
||||
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
Some(FrontendGraphOutput {
|
||||
data_type: frontend_data_type,
|
||||
name: "Output 1".to_string(),
|
||||
description: String::new(),
|
||||
resolved_type: primary_output_type.map(|(input, _)| format!("{input:?}")),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
connected_to,
|
||||
})
|
||||
} else {
|
||||
|
@ -2287,15 +2258,13 @@ impl NodeGraphMessageHandler {
|
|||
};
|
||||
|
||||
let mut exposed_outputs = Vec::new();
|
||||
for (index, exposed_output) in output_types.iter().enumerate() {
|
||||
if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {
|
||||
if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
continue;
|
||||
}
|
||||
let frontend_data_type = if let Some((output_type, type_source)) = &exposed_output {
|
||||
FrontendGraphDataType::displayed_type(output_type, type_source)
|
||||
} else {
|
||||
FrontendGraphDataType::General
|
||||
};
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||
let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||
|
||||
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
|
||||
log::error!("Could not get node_metadata when getting output for {node_id}");
|
||||
continue;
|
||||
|
@ -2303,17 +2272,17 @@ impl NodeGraphMessageHandler {
|
|||
let output_name = node_metadata
|
||||
.persistent_metadata
|
||||
.output_names
|
||||
.get(index)
|
||||
.map(|output_name| output_name.to_string())
|
||||
.get(output_index)
|
||||
.cloned()
|
||||
.filter(|output_name| !output_name.is_empty())
|
||||
.unwrap_or_else(|| exposed_output.clone().map(|(output_type, _)| output_type.nested_type().to_string()).unwrap_or_default());
|
||||
.unwrap_or_else(|| output_type.nested_type().to_string());
|
||||
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, index)).cloned().unwrap_or_default();
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, output_index)).cloned().unwrap_or_default();
|
||||
exposed_outputs.push(FrontendGraphOutput {
|
||||
data_type: frontend_data_type,
|
||||
data_type,
|
||||
name: output_name,
|
||||
description: String::new(),
|
||||
resolved_type: exposed_output.clone().map(|(input, _)| format!("{input:?}")),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
connected_to,
|
||||
});
|
||||
}
|
||||
|
@ -2416,9 +2385,9 @@ impl NodeGraphMessageHandler {
|
|||
network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id|
|
||||
network_interface.document_node(&node_id, &[]).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| {
|
||||
if network_interface.is_layer(&node_id, &[]) {
|
||||
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(1).is_some_and(|input| input.as_value().is_some())
|
||||
node.inputs.iter().filter(|input| input.is_exposed()).nth(1).is_some_and(|input| input.as_value().is_some())
|
||||
} else {
|
||||
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(0).is_some_and(|input| input.as_value().is_some())
|
||||
node.inputs.iter().filter(|input| input.is_exposed()).nth(0).is_some_and(|input| input.as_value().is_some())
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
@ -2467,66 +2436,6 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_wire_path_string(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> String {
|
||||
let locations = Self::build_wire_path_locations(output_position, input_position, vertical_out, vertical_in);
|
||||
let smoothing = 0.5;
|
||||
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
|
||||
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
|
||||
format!(
|
||||
"M{},{} L{},{} C{},{} {},{} {},{} L{},{}",
|
||||
locations[0].x,
|
||||
locations[0].y,
|
||||
locations[1].x,
|
||||
locations[1].y,
|
||||
locations[1].x + delta01.x,
|
||||
locations[1].y + delta01.y,
|
||||
locations[2].x - delta23.x,
|
||||
locations[2].y - delta23.y,
|
||||
locations[2].x,
|
||||
locations[2].y,
|
||||
locations[3].x,
|
||||
locations[3].y
|
||||
)
|
||||
}
|
||||
|
||||
fn build_wire_path_locations(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<DVec2> {
|
||||
let horizontal_gap = (output_position.x - input_position.x).abs();
|
||||
let vertical_gap = (output_position.y - input_position.y).abs();
|
||||
// TODO: Finish this commented out code replacement for the code below it based on this diagram: <https://files.keavon.com/-/SuperbWideFoxterrier/capture.png>
|
||||
// // Straight: stacking lines which are always straight, or a straight horizontal wire between two aligned nodes
|
||||
// if ((verticalOut && vertical_in) || (!verticalOut && !vertical_in && vertical_gap === 0)) {
|
||||
// return [
|
||||
// { x: output_position.x, y: output_position.y },
|
||||
// { x: input_position.x, y: input_position.y },
|
||||
// ];
|
||||
// }
|
||||
|
||||
// // L-shape bend
|
||||
// if (verticalOut !== vertical_in) {
|
||||
// }
|
||||
|
||||
let curve_length = 24.;
|
||||
let curve_falloff_rate = curve_length * std::f64::consts::PI * 2.;
|
||||
|
||||
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
|
||||
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
|
||||
let horizontal_curve = horizontal_curve_amount * curve_length;
|
||||
let vertical_curve = vertical_curve_amount * curve_length;
|
||||
|
||||
vec![
|
||||
output_position,
|
||||
DVec2::new(
|
||||
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
|
||||
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
|
||||
),
|
||||
DVec2::new(
|
||||
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
|
||||
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
|
||||
),
|
||||
DVec2::new(input_position.x, input_position.y),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
// A wire is in progress and its start and end connectors are set
|
||||
let wiring = self.wire_in_progress_from_connector.is_some();
|
||||
|
@ -2570,8 +2479,8 @@ impl NodeGraphMessageHandler {
|
|||
|
||||
#[derive(Default)]
|
||||
struct InputLookup {
|
||||
input_name: Option<String>,
|
||||
input_description: Option<String>,
|
||||
input_name: String,
|
||||
input_description: String,
|
||||
ty: Type,
|
||||
type_source: TypeSource,
|
||||
valid_types: Vec<Type>,
|
||||
|
@ -2586,34 +2495,31 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface:
|
|||
return Default::default();
|
||||
};
|
||||
let mut frontend_inputs_lookup = HashMap::new();
|
||||
for (&node_id, node) in network.nodes.iter() {
|
||||
let mut inputs = Vec::with_capacity(node.inputs.len());
|
||||
for (index, input) in node.inputs.iter().enumerate() {
|
||||
let is_exposed = input.is_exposed_to_frontend(breadcrumb_network_path.is_empty());
|
||||
|
||||
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
||||
if !is_exposed {
|
||||
inputs.push(None);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (node_id, index, output_connector, is_exposed) in network
|
||||
.nodes
|
||||
.iter()
|
||||
.flat_map(|(node_id, node)| {
|
||||
node.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed()))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
||||
let lookup = if !is_exposed {
|
||||
None
|
||||
} else {
|
||||
// Get the name from the metadata here (since it also requires a reference to the `network_interface`)
|
||||
let input_name = network_interface
|
||||
.input_name(node_id, index, breadcrumb_network_path)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|name| name.to_string());
|
||||
let input_description = network_interface.input_description(node_id, index, breadcrumb_network_path).map(|description| description.to_string());
|
||||
// Get the output connector that feeds into this input (done here as well for simplicity)
|
||||
let connector = OutputConnector::from_input(input);
|
||||
|
||||
inputs.push(Some(InputLookup {
|
||||
let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path);
|
||||
Some(InputLookup {
|
||||
input_name,
|
||||
input_description,
|
||||
output_connector: connector,
|
||||
output_connector,
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
frontend_inputs_lookup.insert(node_id, inputs);
|
||||
})
|
||||
};
|
||||
frontend_inputs_lookup.entry(node_id).or_insert_with(Vec::new).push(lookup);
|
||||
}
|
||||
|
||||
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
|
||||
|
@ -2656,6 +2562,7 @@ impl Default for NodeGraphMessageHandler {
|
|||
select_if_not_dragged: None,
|
||||
wire_in_progress_from_connector: None,
|
||||
wire_in_progress_to_connector: None,
|
||||
wire_in_progress_type: FrontendGraphDataType::General,
|
||||
context_menu: None,
|
||||
deselect_on_pointer_up: None,
|
||||
auto_panning: Default::default(),
|
||||
|
@ -2663,6 +2570,8 @@ impl Default for NodeGraphMessageHandler {
|
|||
reordering_export: None,
|
||||
reordering_import: None,
|
||||
end_index: None,
|
||||
frontend_nodes: Vec::new(),
|
||||
frontend_wires: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,17 +60,12 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData
|
|||
"Expose this parameter as a node input in the graph"
|
||||
})
|
||||
.on_update(move |_parameter| {
|
||||
Message::Batched(Box::new([
|
||||
NodeGraphMessage::ExposeInput {
|
||||
input_connector: InputConnector::node(node_id, index),
|
||||
set_to_exposed: !exposed,
|
||||
start_transaction: true,
|
||||
}
|
||||
.into(),
|
||||
DocumentMessage::GraphViewOverlay { open: true }.into(),
|
||||
NavigationMessage::FitViewportToSelection.into(),
|
||||
DocumentMessage::ZoomCanvasTo100Percent.into(),
|
||||
]))
|
||||
Message::Batched(Box::new([NodeGraphMessage::ExposeInput {
|
||||
input_connector: InputConnector::node(node_id, index),
|
||||
set_to_exposed: !exposed,
|
||||
start_transaction: true,
|
||||
}
|
||||
.into()]))
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
@ -85,28 +80,31 @@ pub fn add_blank_assist(widgets: &mut Vec<WidgetHolder>) {
|
|||
]);
|
||||
}
|
||||
|
||||
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType) -> Vec<WidgetHolder> {
|
||||
start_widgets_exposable(parameter_widgets_info, data_type, true)
|
||||
}
|
||||
|
||||
pub fn start_widgets_exposable(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType, exposable: bool) -> Vec<WidgetHolder> {
|
||||
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo {
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
input_type,
|
||||
blank_assist,
|
||||
exposeable,
|
||||
} = parameter_widgets_info;
|
||||
|
||||
let Some(document_node) = document_node else {
|
||||
log::warn!("A widget failed to be built because its document node is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
let description = if description != "TODO" { description } else { "" };
|
||||
let description = if description != "TODO" { description } else { String::new() };
|
||||
let mut widgets = Vec::with_capacity(6);
|
||||
if exposable {
|
||||
widgets.push(expose_widget(node_id, index, data_type, input.is_exposed()));
|
||||
if exposeable {
|
||||
widgets.push(expose_widget(node_id, index, input_type, input.is_exposed()));
|
||||
}
|
||||
widgets.push(TextLabel::new(name).tooltip(description).widget_holder());
|
||||
if blank_assist {
|
||||
|
@ -126,18 +124,6 @@ pub(crate) fn property_from_type(
|
|||
step: Option<f64>,
|
||||
context: &mut NodePropertiesContext,
|
||||
) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> {
|
||||
let Some(network) = context.network_interface.nested_network(context.selection_network_path) else {
|
||||
log::warn!("A widget failed to be built for node {node_id}, index {index} because the network could not be determined");
|
||||
return Err(vec![]);
|
||||
};
|
||||
let Some(document_node) = network.nodes.get(&node_id) else {
|
||||
log::warn!("A widget failed to be built for node {node_id}, index {index} because the document node does not exist");
|
||||
return Err(vec![]);
|
||||
};
|
||||
|
||||
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
|
||||
let (mut number_min, mut number_max, range) = number_options;
|
||||
let mut number_input = NumberInput::default();
|
||||
if let Some((range_start, range_end)) = range {
|
||||
|
@ -158,7 +144,7 @@ pub(crate) fn property_from_type(
|
|||
let min = |x: f64| number_min.unwrap_or(x);
|
||||
let max = |x: f64| number_max.unwrap_or(x);
|
||||
|
||||
let default_info = ParameterWidgetsInfo::new(document_node, node_id, index, name, description, true);
|
||||
let default_info = ParameterWidgetsInfo::new(node_id, index, true, context);
|
||||
|
||||
let mut extra_widgets = vec![];
|
||||
let widgets = match ty {
|
||||
|
@ -247,7 +233,7 @@ pub(crate) fn property_from_type(
|
|||
// OTHER
|
||||
// =====
|
||||
_ => {
|
||||
let mut widgets = start_widgets(default_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(default_info);
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextLabel::new("-")
|
||||
|
@ -277,8 +263,9 @@ pub(crate) fn property_from_type(
|
|||
pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -298,8 +285,9 @@ pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHo
|
|||
pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -319,8 +307,9 @@ pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Wid
|
|||
pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: CheckboxInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -341,8 +330,9 @@ pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input:
|
|||
pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -371,7 +361,7 @@ pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disa
|
|||
pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut location_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut location_widgets = start_widgets(parameter_widgets_info);
|
||||
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
|
||||
|
@ -382,10 +372,12 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||
add_blank_assist(&mut resolution_widgets);
|
||||
resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return Vec::new().into();
|
||||
};
|
||||
|
||||
if let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() {
|
||||
let top_left = footprint.transform.transform_point2(DVec2::ZERO);
|
||||
let bounds = footprint.scale();
|
||||
|
@ -517,8 +509,9 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
|
@ -629,7 +622,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
|
|||
pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let from_string = |string: &str| {
|
||||
string
|
||||
|
@ -641,6 +634,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
|||
.map(TaggedValue::VecF64)
|
||||
};
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -660,7 +654,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
|||
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let from_string = |string: &str| {
|
||||
string
|
||||
|
@ -672,6 +666,7 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
|
|||
.map(TaggedValue::VecDVec2)
|
||||
};
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -691,11 +686,12 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
|
|||
pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut first_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut first_widgets = start_widgets(parameter_widgets_info);
|
||||
let mut second_widgets = None;
|
||||
|
||||
let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone()));
|
||||
|
||||
let Some(document_node) = document_node else { return (Vec::new(), None) };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return (vec![], None);
|
||||
|
@ -725,7 +721,7 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
|
|||
}
|
||||
|
||||
pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::VectorData);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Vector data is supplied through the node graph").widget_holder());
|
||||
|
@ -734,7 +730,7 @@ pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<W
|
|||
}
|
||||
|
||||
pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Raster);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Raster data is supplied through the node graph").widget_holder());
|
||||
|
@ -743,7 +739,7 @@ pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Widget
|
|||
}
|
||||
|
||||
pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Group);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Group data is supplied through the node graph").widget_holder());
|
||||
|
@ -754,8 +750,9 @@ pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetH
|
|||
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -825,7 +822,8 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
|||
pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
|
@ -859,8 +857,9 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
|
|||
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
|
||||
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
|
||||
return LayoutGroup::Row { widgets };
|
||||
|
@ -913,8 +912,9 @@ pub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup
|
|||
pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
|
@ -939,14 +939,11 @@ pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext
|
|||
network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node"))
|
||||
}
|
||||
|
||||
pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, &'a str, &'a str), String> {
|
||||
pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a mut NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, String, String), String> {
|
||||
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, input_index, context.selection_network_path);
|
||||
let document_node = get_document_node(node_id, context)?;
|
||||
let input_name = context.network_interface.input_name(node_id, input_index, context.selection_network_path).unwrap_or_else(|| {
|
||||
log::warn!("input name not found in query_node_and_input_info");
|
||||
""
|
||||
});
|
||||
let input_description = context.network_interface.input_description(node_id, input_index, context.selection_network_path).unwrap_or_default();
|
||||
Ok((document_node, input_name, input_description))
|
||||
|
||||
Ok((document_node, name, description))
|
||||
}
|
||||
|
||||
pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> {
|
||||
|
@ -995,6 +992,9 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo
|
|||
pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::brightness_contrast::*;
|
||||
|
||||
// Use Classic
|
||||
let use_classic = bool_widget(ParameterWidgetsInfo::new(node_id, UseClassicInput::INDEX, true, context), CheckboxInput::default());
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1002,12 +1002,6 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Use Classic
|
||||
let use_classic = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, UseClassicInput::INDEX, true, context),
|
||||
CheckboxInput::default(),
|
||||
);
|
||||
let use_classic_value = match document_node.inputs[UseClassicInput::INDEX].as_value() {
|
||||
Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice,
|
||||
_ => false,
|
||||
|
@ -1015,7 +1009,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||
|
||||
// Brightness
|
||||
let brightness = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, BrightnessInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, BrightnessInput::INDEX, true, context),
|
||||
NumberInput::default()
|
||||
.unit("%")
|
||||
.mode_range()
|
||||
|
@ -1026,7 +1020,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||
|
||||
// Contrast
|
||||
let contrast = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ContrastInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, ContrastInput::INDEX, true, context),
|
||||
NumberInput::default()
|
||||
.unit("%")
|
||||
.mode_range()
|
||||
|
@ -1047,6 +1041,11 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||
pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::channel_mixer::*;
|
||||
|
||||
let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default());
|
||||
let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context);
|
||||
parameter_info.exposeable = false;
|
||||
let output_channel = enum_choice::<RedGreenBlue>().for_socket(parameter_info).property_row();
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1054,22 +1053,12 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Monochrome
|
||||
let is_monochrome = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, MonochromeInput::INDEX, true, context),
|
||||
CheckboxInput::default(),
|
||||
);
|
||||
let is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() {
|
||||
Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Output channel choice
|
||||
let output_channel = enum_choice::<RedGreenBlue>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, OutputChannelInput::INDEX, true, context))
|
||||
.exposable(false)
|
||||
.property_row();
|
||||
let output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() {
|
||||
Some(TaggedValue::RedGreenBlue(choice)) => choice,
|
||||
_ => {
|
||||
|
@ -1086,10 +1075,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||
(false, RedGreenBlue::Blue) => (BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX),
|
||||
};
|
||||
let number_input = NumberInput::default().mode_range().min(-200.).max(200.).unit("%");
|
||||
let red = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, red_output_index, true, context), number_input.clone());
|
||||
let green = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, green_output_index, true, context), number_input.clone());
|
||||
let blue = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, blue_output_index, true, context), number_input.clone());
|
||||
let constant = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, constant_output_index, true, context), number_input);
|
||||
let red = number_widget(ParameterWidgetsInfo::new(node_id, red_output_index, true, context), number_input.clone());
|
||||
let green = number_widget(ParameterWidgetsInfo::new(node_id, green_output_index, true, context), number_input.clone());
|
||||
let blue = number_widget(ParameterWidgetsInfo::new(node_id, blue_output_index, true, context), number_input.clone());
|
||||
let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);
|
||||
|
||||
// Monochrome
|
||||
let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }];
|
||||
|
@ -1110,6 +1099,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||
pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::selective_color::*;
|
||||
|
||||
let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context);
|
||||
default_info.exposeable = false;
|
||||
let colors = enum_choice::<SelectiveColorChoice>().for_socket(default_info).property_row();
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1117,13 +1110,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Colors choice
|
||||
let colors = enum_choice::<SelectiveColorChoice>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ColorsInput::INDEX, true, context))
|
||||
.exposable(false)
|
||||
.property_row();
|
||||
|
||||
let colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() {
|
||||
Some(TaggedValue::SelectiveColorChoice(choice)) => choice,
|
||||
_ => {
|
||||
|
@ -1131,7 +1118,6 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||
return vec![];
|
||||
}
|
||||
};
|
||||
|
||||
// CMYK
|
||||
let (c_index, m_index, y_index, k_index) = match colors_choice {
|
||||
SelectiveColorChoice::Reds => (RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX),
|
||||
|
@ -1145,14 +1131,14 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||
SelectiveColorChoice::Blacks => (KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX),
|
||||
};
|
||||
let number_input = NumberInput::default().mode_range().min(-100.).max(100.).unit("%");
|
||||
let cyan = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, c_index, true, context), number_input.clone());
|
||||
let magenta = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, m_index, true, context), number_input.clone());
|
||||
let yellow = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, y_index, true, context), number_input.clone());
|
||||
let black = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, k_index, true, context), number_input);
|
||||
let cyan = number_widget(ParameterWidgetsInfo::new(node_id, c_index, true, context), number_input.clone());
|
||||
let magenta = number_widget(ParameterWidgetsInfo::new(node_id, m_index, true, context), number_input.clone());
|
||||
let yellow = number_widget(ParameterWidgetsInfo::new(node_id, y_index, true, context), number_input.clone());
|
||||
let black = number_widget(ParameterWidgetsInfo::new(node_id, k_index, true, context), number_input);
|
||||
|
||||
// Mode
|
||||
let mode = enum_choice::<RelativeAbsolute>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ModeInput::INDEX, true, context))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, ModeInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
vec![
|
||||
|
@ -1171,19 +1157,19 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||
pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::generator_nodes::grid::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in exposure_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let grid_type = enum_choice::<GridType>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, GridTypeInput::INDEX, true, context))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, GridTypeInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let mut widgets = vec![grid_type];
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in grid_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -1191,36 +1177,24 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
||||
match grid_type {
|
||||
GridType::Rectangular => {
|
||||
let spacing = coordinate_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||
"W",
|
||||
"H",
|
||||
" px",
|
||||
Some(0.),
|
||||
);
|
||||
let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.));
|
||||
widgets.push(spacing);
|
||||
}
|
||||
GridType::Isometric => {
|
||||
let spacing = LayoutGroup::Row {
|
||||
widgets: number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||
NumberInput::default().label("H").min(0.).unit(" px"),
|
||||
),
|
||||
};
|
||||
let angles = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
|
||||
let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
|
||||
widgets.extend([spacing, angles]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let columns = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ColumnsInput::INDEX, true, context),
|
||||
NumberInput::default().min(1.),
|
||||
);
|
||||
let rows = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, RowsInput::INDEX, true, context),
|
||||
NumberInput::default().min(1.),
|
||||
);
|
||||
let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||
let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||
|
||||
widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]);
|
||||
|
||||
|
@ -1249,26 +1223,14 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
|
|||
let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));
|
||||
|
||||
let spacing = enum_choice::<PointSpacingType>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::INDEX, true, context))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, SpacingInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let separation = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let quantity = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, QuantityInput::INDEX, true, context),
|
||||
NumberInput::default().min(2.).int(),
|
||||
);
|
||||
let start_offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, StartOffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let stop_offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, StopOffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||
let quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput::INDEX, true, context), NumberInput::default().min(2.).int());
|
||||
let start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||
let stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||
let adaptive_spacing = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput::INDEX, true, context),
|
||||
CheckboxInput::default().disabled(is_quantity),
|
||||
);
|
||||
|
||||
|
@ -1288,23 +1250,10 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
|
|||
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::exposure::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in exposure_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let exposure = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ExposureInput::INDEX, true, context),
|
||||
NumberInput::default().min(-20.).max(20.),
|
||||
);
|
||||
let offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, OffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(-0.5).max(0.5),
|
||||
);
|
||||
let exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput::INDEX, true, context), NumberInput::default().min(-20.).max(20.));
|
||||
let offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput::INDEX, true, context), NumberInput::default().min(-0.5).max(0.5));
|
||||
let gamma_correction = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, GammaCorrectionInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, GammaCorrectionInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.01).max(9.99).increment_step(0.1),
|
||||
);
|
||||
|
||||
|
@ -1318,6 +1267,13 @@ pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesC
|
|||
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::generator_nodes::rectangle::*;
|
||||
|
||||
// Corner Radius
|
||||
let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::<f64>::INDEX, true, context));
|
||||
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1325,23 +1281,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
// Size X
|
||||
let size_x = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, WidthInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Size Y
|
||||
let size_y = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, HeightInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Corner Radius
|
||||
let mut corner_radius_row_1 = start_widgets(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, CornerRadiusInput::<f64>::INDEX, true, context),
|
||||
FrontendGraphDataType::Number,
|
||||
);
|
||||
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
||||
add_blank_assist(&mut corner_radius_row_2);
|
||||
|
||||
let Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -1427,6 +1366,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||
} else {
|
||||
NumberInput::default()
|
||||
.value(Some(uniform_val))
|
||||
.unit(" px")
|
||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, CornerRadiusInput::<f64>::INDEX))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder()
|
||||
|
@ -1434,8 +1374,16 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||
corner_radius_row_2.push(input_widget);
|
||||
}
|
||||
|
||||
// Size X
|
||||
let size_x = number_widget(ParameterWidgetsInfo::new(node_id, WidthInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Size Y
|
||||
let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
add_blank_assist(&mut corner_radius_row_2);
|
||||
|
||||
// Clamped
|
||||
let clamped = bool_widget(ParameterWidgetsInfo::from_index(document_node, node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
||||
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
||||
|
||||
vec![
|
||||
LayoutGroup::Row { widgets: size_x },
|
||||
|
@ -1564,6 +1512,8 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
|||
pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::fill::*;
|
||||
|
||||
let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::<Color>::INDEX, true, context));
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1572,11 +1522,6 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
}
|
||||
};
|
||||
|
||||
let mut widgets_first_row = start_widgets(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, FillInput::<Color>::INDEX, true, context),
|
||||
FrontendGraphDataType::General,
|
||||
);
|
||||
|
||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
||||
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
||||
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
||||
|
@ -1755,47 +1700,42 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||
_ => &StrokeJoin::Miter,
|
||||
};
|
||||
|
||||
let color = color_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||
);
|
||||
let weight = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, WeightInput::INDEX, true, context),
|
||||
NumberInput::default().unit(" px").min(0.),
|
||||
);
|
||||
let align = enum_choice::<StrokeAlign>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, AlignInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let cap = enum_choice::<StrokeCap>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, CapInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let miter_limit = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).disabled({
|
||||
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||
_ => &StrokeJoin::Miter,
|
||||
};
|
||||
join_value != &StrokeJoin::Miter
|
||||
}),
|
||||
);
|
||||
let paint_order = enum_choice::<PaintOrder>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, PaintOrderInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let dash_lengths_val = match &document_node.inputs[DashLengthsInput::INDEX].as_value() {
|
||||
Some(TaggedValue::VecF64(x)) => x,
|
||||
_ => &vec![],
|
||||
};
|
||||
let dash_lengths = array_of_number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, DashLengthsInput::INDEX, true, context),
|
||||
TextInput::default().centered(true),
|
||||
let has_dash_lengths = dash_lengths_val.is_empty();
|
||||
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
||||
|
||||
let color = color_widget(
|
||||
ParameterWidgetsInfo::new(node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||
);
|
||||
let number_input = NumberInput::default().unit(" px").disabled(dash_lengths_val.is_empty());
|
||||
let dash_offset = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DashOffsetInput::INDEX, true, context), number_input);
|
||||
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
|
||||
let align = enum_choice::<StrokeAlign>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, AlignInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let cap = enum_choice::<StrokeCap>().for_socket(ParameterWidgetsInfo::new(node_id, CapInput::INDEX, true, context)).property_row();
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let miter_limit = number_widget(
|
||||
ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).disabled(miter_limit_disabled),
|
||||
);
|
||||
let paint_order = enum_choice::<PaintOrder>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
|
||||
let dash_lengths = array_of_number_widget(ParameterWidgetsInfo::new(node_id, DashLengthsInput::INDEX, true, context), TextInput::default().centered(true));
|
||||
let number_input = disabled_number_input;
|
||||
let dash_offset = number_widget(ParameterWidgetsInfo::new(node_id, DashOffsetInput::INDEX, true, context), number_input);
|
||||
|
||||
vec![
|
||||
color,
|
||||
|
@ -1813,6 +1753,13 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||
pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::offset_path::*;
|
||||
|
||||
let number_input = NumberInput::default().unit(" px");
|
||||
let distance = number_widget(ParameterWidgetsInfo::new(node_id, DistanceInput::INDEX, true, context), number_input);
|
||||
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1820,13 +1767,6 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let number_input = NumberInput::default().unit(" px");
|
||||
let distance = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DistanceInput::INDEX, true, context), number_input);
|
||||
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let number_input = NumberInput::default().min(0.).disabled({
|
||||
let join_val = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||
|
@ -1834,7 +1774,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
};
|
||||
join_val != &StrokeJoin::Miter
|
||||
});
|
||||
let miter_limit = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context), number_input);
|
||||
let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }]
|
||||
}
|
||||
|
@ -1842,20 +1782,16 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::math_nodes::math::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in offset_path_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
let expression = (|| {
|
||||
let mut widgets = start_widgets(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ExpressionInput::INDEX, true, context),
|
||||
FrontendGraphDataType::General,
|
||||
);
|
||||
let mut widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ExpressionInput::INDEX, true, context));
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in offset_path_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -1888,10 +1824,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
|||
}
|
||||
widgets
|
||||
})();
|
||||
let operand_b = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, OperandBInput::<f64>::INDEX, true, context),
|
||||
NumberInput::default(),
|
||||
);
|
||||
let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::<f64>::INDEX, true, context), NumberInput::default());
|
||||
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()];
|
||||
|
||||
vec![
|
||||
|
@ -1902,44 +1835,37 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
|||
}
|
||||
|
||||
pub struct ParameterWidgetsInfo<'a> {
|
||||
document_node: &'a DocumentNode,
|
||||
document_node: Option<&'a DocumentNode>,
|
||||
node_id: NodeId,
|
||||
index: usize,
|
||||
name: &'a str,
|
||||
description: &'a str,
|
||||
name: String,
|
||||
description: String,
|
||||
input_type: FrontendGraphDataType,
|
||||
blank_assist: bool,
|
||||
exposeable: bool,
|
||||
}
|
||||
|
||||
impl<'a> ParameterWidgetsInfo<'a> {
|
||||
pub fn new(document_node: &'a DocumentNode, node_id: NodeId, index: usize, name: &'a str, description: &'a str, blank_assist: bool) -> ParameterWidgetsInfo<'a> {
|
||||
pub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
|
||||
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path);
|
||||
let input_type = FrontendGraphDataType::from_type(&context.network_interface.input_type(&InputConnector::node(node_id, index), context.selection_network_path).0);
|
||||
let document_node = context.network_interface.document_node(&node_id, context.selection_network_path);
|
||||
|
||||
ParameterWidgetsInfo {
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
input_type,
|
||||
blank_assist,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_index(document_node: &'a DocumentNode, node_id: NodeId, index: usize, blank_assist: bool, context: &'a NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
|
||||
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
|
||||
Self {
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
blank_assist,
|
||||
exposeable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod choice {
|
||||
use super::ParameterWidgetsInfo;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint};
|
||||
|
@ -1972,11 +1898,7 @@ pub mod choice {
|
|||
|
||||
impl<E: ChoiceTypeStatic + 'static> EnumChoice<E> {
|
||||
pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket<Self> {
|
||||
ForSocket {
|
||||
widget_factory: self,
|
||||
parameter_info,
|
||||
exposable: true,
|
||||
}
|
||||
ForSocket { widget_factory: self, parameter_info }
|
||||
}
|
||||
|
||||
/// Not yet implemented!
|
||||
|
@ -2067,7 +1989,6 @@ pub mod choice {
|
|||
pub struct ForSocket<'p, W> {
|
||||
widget_factory: W,
|
||||
parameter_info: ParameterWidgetsInfo<'p>,
|
||||
exposable: bool,
|
||||
}
|
||||
|
||||
impl<'p, W> ForSocket<'p, W>
|
||||
|
@ -2084,14 +2005,14 @@ pub mod choice {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn exposable(self, exposable: bool) -> Self {
|
||||
Self { exposable, ..self }
|
||||
}
|
||||
|
||||
pub fn property_row(self) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
|
||||
let Some(document_node) = document_node else {
|
||||
log::error!("Could not get document node when building property row for node {:?}", node_id);
|
||||
return LayoutGroup::Row { widgets: Vec::new() };
|
||||
};
|
||||
|
||||
let mut widgets = super::start_widgets_exposable(self.parameter_info, FrontendGraphDataType::General, self.exposable);
|
||||
let mut widgets = super::start_widgets(self.parameter_info);
|
||||
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
|
|
|
@ -15,7 +15,7 @@ pub enum FrontendGraphDataType {
|
|||
}
|
||||
|
||||
impl FrontendGraphDataType {
|
||||
fn with_type(input: &Type) -> Self {
|
||||
pub fn from_type(input: &Type) -> Self {
|
||||
match TaggedValue::from_type_or_none(input) {
|
||||
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster,
|
||||
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
||||
|
@ -38,7 +38,7 @@ impl FrontendGraphDataType {
|
|||
pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self {
|
||||
match type_source {
|
||||
TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General,
|
||||
_ => Self::with_type(input),
|
||||
_ => Self::from_type(input),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ pub struct FrontendGraphInput {
|
|||
pub name: String,
|
||||
pub description: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: Option<String>,
|
||||
pub resolved_type: String,
|
||||
#[serde(rename = "validTypes")]
|
||||
pub valid_types: Vec<String>,
|
||||
#[serde(rename = "connectedTo")]
|
||||
|
@ -64,7 +64,7 @@ pub struct FrontendGraphOutput {
|
|||
pub name: String,
|
||||
pub description: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: Option<String>,
|
||||
pub resolved_type: String,
|
||||
#[serde(rename = "connectedTo")]
|
||||
pub connected_to: Vec<InputConnector>,
|
||||
}
|
||||
|
@ -96,15 +96,6 @@ pub struct FrontendNode {
|
|||
pub ui_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendNodeWire {
|
||||
#[serde(rename = "wireStart")]
|
||||
pub wire_start: OutputConnector,
|
||||
#[serde(rename = "wireEnd")]
|
||||
pub wire_end: InputConnector,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendNodeType {
|
||||
pub name: String,
|
||||
|
@ -153,16 +144,6 @@ pub struct Transform {
|
|||
pub y: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePath {
|
||||
#[serde(rename = "pathString")]
|
||||
pub path_string: String,
|
||||
#[serde(rename = "dataType")]
|
||||
pub data_type: FrontendGraphDataType,
|
||||
pub thick: bool,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct BoxSelection {
|
||||
#[serde(rename = "startX")]
|
||||
|
@ -224,32 +205,3 @@ pub enum Direction {
|
|||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum GraphWireStyle {
|
||||
#[default]
|
||||
Direct = 0,
|
||||
GridAligned = 1,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GraphWireStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
|
||||
GraphWireStyle::Direct => write!(f, "Direct"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphWireStyle {
|
||||
pub fn tooltip_description(&self) -> &'static str {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
|
||||
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_direct(&self) -> bool {
|
||||
*self == GraphWireStyle::Direct
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,3 +5,4 @@ pub mod misc;
|
|||
pub mod network_interface;
|
||||
pub mod nodes;
|
||||
pub mod transformation;
|
||||
pub mod wires;
|
||||
|
|
File diff suppressed because it is too large
Load diff
589
editor/src/messages/portfolio/document/utility_types/wires.rs
Normal file
589
editor/src/messages/portfolio/document/utility_types/wires.rs
Normal file
|
@ -0,0 +1,589 @@
|
|||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use glam::{DVec2, IVec2};
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::PointId;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePath {
|
||||
#[serde(rename = "pathString")]
|
||||
pub path_string: String,
|
||||
#[serde(rename = "dataType")]
|
||||
pub data_type: FrontendGraphDataType,
|
||||
pub thick: bool,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePathUpdate {
|
||||
pub id: NodeId,
|
||||
#[serde(rename = "inputIndex")]
|
||||
pub input_index: usize,
|
||||
// If none, then remove the wire from the map
|
||||
#[serde(rename = "wirePathUpdate")]
|
||||
pub wire_path_update: Option<WirePath>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum GraphWireStyle {
|
||||
#[default]
|
||||
Direct = 0,
|
||||
GridAligned = 1,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GraphWireStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
|
||||
GraphWireStyle::Direct => write!(f, "Direct"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphWireStyle {
|
||||
pub fn tooltip_description(&self) -> &'static str {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
|
||||
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_direct(&self) -> bool {
|
||||
*self == GraphWireStyle::Direct
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath<PointId> {
|
||||
let grid_spacing = 24.;
|
||||
match graph_wire_style {
|
||||
GraphWireStyle::Direct => {
|
||||
let horizontal_gap = (output_position.x - input_position.x).abs();
|
||||
let vertical_gap = (output_position.y - input_position.y).abs();
|
||||
|
||||
let curve_length = grid_spacing;
|
||||
let curve_falloff_rate = curve_length * std::f64::consts::TAU;
|
||||
|
||||
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
|
||||
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
|
||||
let horizontal_curve = horizontal_curve_amount * curve_length;
|
||||
let vertical_curve = vertical_curve_amount * curve_length;
|
||||
|
||||
let locations = [
|
||||
output_position,
|
||||
DVec2::new(
|
||||
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
|
||||
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
|
||||
),
|
||||
DVec2::new(
|
||||
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
|
||||
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
|
||||
),
|
||||
DVec2::new(input_position.x, input_position.y),
|
||||
];
|
||||
|
||||
let smoothing = 0.5;
|
||||
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
|
||||
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
|
||||
|
||||
Subpath::new(
|
||||
vec![
|
||||
ManipulatorGroup {
|
||||
anchor: locations[0],
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[1],
|
||||
in_handle: None,
|
||||
out_handle: Some(locations[1] + delta01),
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[2],
|
||||
in_handle: Some(locations[2] - delta23),
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[3],
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
],
|
||||
false,
|
||||
)
|
||||
}
|
||||
GraphWireStyle::GridAligned => {
|
||||
let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in);
|
||||
straight_wire_subpath(locations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
|
||||
let grid_spacing = 24;
|
||||
let line_width = 2;
|
||||
|
||||
let in_x = input_position.x as i32;
|
||||
let in_y = input_position.y as i32;
|
||||
let out_x = output_position.x as i32;
|
||||
let out_y = output_position.y as i32;
|
||||
|
||||
let mid_x = (in_x + out_x) / 2 + (((in_x + out_x) / 2) % grid_spacing);
|
||||
let mid_y = (in_y + out_y) / 2 + (((in_y + out_y) / 2) % grid_spacing);
|
||||
let mid_y_alternate = (in_y + in_y) / 2 - (((in_y + in_y) / 2) % grid_spacing);
|
||||
|
||||
let x1 = out_x;
|
||||
let x2 = out_x + grid_spacing;
|
||||
let x3 = in_x - 2 * grid_spacing;
|
||||
let x4 = in_x;
|
||||
let x5 = in_x - 2 * grid_spacing + line_width;
|
||||
let x6 = out_x + grid_spacing + line_width;
|
||||
let x7 = out_x + 2 * grid_spacing + line_width;
|
||||
let x8 = in_x + line_width;
|
||||
let x9 = out_x + 2 * grid_spacing;
|
||||
let x10 = mid_x + line_width;
|
||||
let x11 = out_x - grid_spacing;
|
||||
let x12 = out_x - 4 * grid_spacing;
|
||||
let x13 = mid_x;
|
||||
let x14 = in_x + grid_spacing;
|
||||
let x15 = in_x - 4 * grid_spacing;
|
||||
let x16 = in_x + 8 * grid_spacing;
|
||||
let x17 = mid_x - 2 * line_width;
|
||||
let x18 = out_x + grid_spacing - 2 * line_width;
|
||||
let x19 = out_x - 2 * line_width;
|
||||
let x20 = mid_x - line_width;
|
||||
|
||||
let y1 = out_y;
|
||||
let y2 = out_y - grid_spacing;
|
||||
let y3 = in_y;
|
||||
let y4 = out_y - grid_spacing + 5 * line_width + 1;
|
||||
let y5 = in_y - 2 * grid_spacing;
|
||||
let y6 = out_y + 4 * line_width;
|
||||
let y7 = out_y + 5 * line_width;
|
||||
let y8 = out_y - 2 * grid_spacing + 5 * line_width + 1;
|
||||
let y9 = out_y + 6 * line_width;
|
||||
let y10 = in_y + 2 * grid_spacing;
|
||||
let y111 = in_y + grid_spacing + 6 * line_width + 1;
|
||||
let y12 = in_y + grid_spacing - 5 * line_width + 1;
|
||||
let y13 = in_y - grid_spacing;
|
||||
let y14 = in_y + grid_spacing;
|
||||
let y15 = mid_y;
|
||||
let y16 = mid_y_alternate;
|
||||
|
||||
let wire1 = vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x5, y4), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||
|
||||
let wire2 = vec![IVec2::new(x1, y1), IVec2::new(x1, y16), IVec2::new(x3, y16), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
|
||||
let wire3 = vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x12, y4),
|
||||
IVec2::new(x12, y10),
|
||||
IVec2::new(x3, y10),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
|
||||
let wire4 = vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x13, y4),
|
||||
IVec2::new(x13, y10),
|
||||
IVec2::new(x3, y10),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
|
||||
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point
|
||||
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// Handle straight lines
|
||||
if out_y == in_y || (out_x == in_x && vertical_out) {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// Handle standard right-angle paths
|
||||
// Start vertical, then horizontal
|
||||
|
||||
// `outConnector` point lies to the left of `inConnector` point
|
||||
if vertical_out && in_x > out_x {
|
||||
// `outConnector` point lies above `inConnector` point
|
||||
if out_y < in_y {
|
||||
// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point
|
||||
if -4 * grid_spacing <= out_x - in_x && out_x - in_x < -3 * grid_spacing {
|
||||
return wire1;
|
||||
};
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point
|
||||
if -3 * grid_spacing <= out_x - in_x && out_x - in_x <= -grid_spacing {
|
||||
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y2), IVec2::new(x2, y2), IVec2::new(x2, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x6, y4), IVec2::new(x6, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x7, y4),
|
||||
IVec2::new(x7, y5),
|
||||
IVec2::new(x3, y5),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point
|
||||
if -grid_spacing < out_x - in_x && out_x - in_x <= 0 {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||
return vec![IVec2::new(x1, y6), IVec2::new(x2, y6), IVec2::new(x8, y3)];
|
||||
};
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
|
||||
return vec![IVec2::new(x1, y7), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y2),
|
||||
IVec2::new(x9, y2),
|
||||
IVec2::new(x9, y5),
|
||||
IVec2::new(x3, y5),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x10, y4), IVec2::new(x10, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point
|
||||
if -grid_spacing <= out_x - in_x && out_x - in_x <= 0 {
|
||||
// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point
|
||||
if 0 <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y6), IVec2::new(x11, y6), IVec2::new(x11, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
return wire2;
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point lies to the right of `inConnector` point
|
||||
if vertical_out && in_x <= out_x {
|
||||
// `outConnector` point lying on any horizontal grid line above `inConnector` point
|
||||
if out_y < in_y {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if -2 * grid_spacing < out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||
return wire1;
|
||||
};
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if -grid_spacing < out_y - in_y && out_y - in_y <= 0 {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y8), IVec2::new(x5, y8), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point
|
||||
if grid_spacing <= out_x - in_x && out_x - in_x <= 3 * grid_spacing {
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x9, y4),
|
||||
IVec2::new(x9, y5),
|
||||
IVec2::new(x3, y5),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x10, y4),
|
||||
IVec2::new(x10, y5),
|
||||
IVec2::new(x5, y5),
|
||||
IVec2::new(x5, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
if out_y - in_y <= grid_spacing {
|
||||
// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point
|
||||
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y9), IVec2::new(x3, y9), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
|
||||
return wire3;
|
||||
};
|
||||
|
||||
return wire4;
|
||||
}
|
||||
|
||||
// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point
|
||||
if grid_spacing <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
|
||||
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y7), IVec2::new(x5, y7), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
|
||||
return wire3;
|
||||
};
|
||||
|
||||
return wire4;
|
||||
}
|
||||
|
||||
// 0 to 4 units below the `outConnector` Point
|
||||
if out_y - in_y <= 4 * grid_spacing {
|
||||
return wire1;
|
||||
};
|
||||
|
||||
return wire2;
|
||||
}
|
||||
|
||||
// Start horizontal, then vertical
|
||||
if vertical_in {
|
||||
// when `outConnector` lies below `inConnector`
|
||||
if out_y > in_y {
|
||||
// `out_x` lies to the left of `in_x`
|
||||
if out_x < in_x {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x4, y1), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// `out_x` lies to the right of `in_x`
|
||||
if out_y - in_y <= grid_spacing {
|
||||
// `outConnector` point directly below `inConnector` point
|
||||
if 0 <= out_x - in_x && out_x - in_x <= grid_spacing {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x14, y1), IVec2::new(x14, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y111), IVec2::new(x4, y111), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `out_y` lies on or above the `in_y` point
|
||||
if -6 * grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
|
||||
// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point
|
||||
if -grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x2, y1),
|
||||
IVec2::new(x2, y2),
|
||||
IVec2::new(x15, y2),
|
||||
IVec2::new(x15, y12),
|
||||
IVec2::new(x4, y12),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x16, y1), IVec2::new(x16, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point
|
||||
if 4 * grid_spacing < in_x - out_x {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x17, y1), IVec2::new(x17, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point
|
||||
if 6 * grid_spacing > in_x - out_x {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||
};
|
||||
}
|
||||
|
||||
// Both horizontal - use horizontal middle point
|
||||
// When `inConnector` point is one of the two closest diagonally opposite points
|
||||
if 0 <= in_x - out_x && in_x - out_x <= grid_spacing && in_y - out_y >= -grid_spacing && in_y - out_y <= grid_spacing {
|
||||
return vec![IVec2::new(x19, y1), IVec2::new(x19, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point
|
||||
if -grid_spacing <= out_y - in_y && out_y - in_y <= grid_spacing && out_x > in_x {
|
||||
// Horizontal line above `out_y`
|
||||
if in_y < out_y {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y13), IVec2::new(x3, y13), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// Horizontal line below `out_y`
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y14), IVec2::new(x3, y14), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point to the right of `inConnector` point
|
||||
if out_x > in_x - grid_spacing {
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x18, y1),
|
||||
IVec2::new(x18, y15),
|
||||
IVec2::new(x5, y15),
|
||||
IVec2::new(x5, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
};
|
||||
|
||||
// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point
|
||||
if grid_spacing <= in_x - out_x && in_x - out_x <= 2 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)]
|
||||
}
|
||||
|
||||
fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
|
||||
if locations.is_empty() {
|
||||
return Subpath::new(Vec::new(), false);
|
||||
}
|
||||
|
||||
if locations.len() == 2 {
|
||||
return Subpath::new(
|
||||
vec![
|
||||
ManipulatorGroup {
|
||||
anchor: locations[0].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[1].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
],
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
let corner_radius = 10;
|
||||
|
||||
// Create path with rounded corners
|
||||
let mut path = vec![ManipulatorGroup {
|
||||
anchor: locations[0].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
}];
|
||||
|
||||
for i in 1..(locations.len() - 1) {
|
||||
let prev = locations[i - 1];
|
||||
let curr = locations[i];
|
||||
let next = locations[i + 1];
|
||||
|
||||
let corner_start = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == prev.x {
|
||||
0
|
||||
} else if prev.x > curr.x {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == prev.y {
|
||||
0
|
||||
} else if prev.y > curr.y {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
);
|
||||
|
||||
let corner_start_mid = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == prev.x {
|
||||
0
|
||||
} else if prev.x > curr.x {
|
||||
corner_radius / 2
|
||||
} else {
|
||||
-corner_radius / 2
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == prev.y {
|
||||
0
|
||||
} else {
|
||||
match prev.y > curr.y {
|
||||
true => corner_radius / 2,
|
||||
false => -corner_radius / 2,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let corner_end = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == next.x {
|
||||
0
|
||||
} else if next.x > curr.x {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == next.y {
|
||||
0
|
||||
} else if next.y > curr.y {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
);
|
||||
|
||||
let corner_end_mid = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == next.x {
|
||||
0
|
||||
} else if next.x > curr.x {
|
||||
corner_radius / 2
|
||||
} else {
|
||||
-corner_radius / 2
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == next.y {
|
||||
0
|
||||
} else if next.y > curr.y {
|
||||
10 / 2
|
||||
} else {
|
||||
-corner_radius / 2
|
||||
},
|
||||
);
|
||||
|
||||
path.extend(vec![
|
||||
ManipulatorGroup {
|
||||
anchor: corner_start.into(),
|
||||
in_handle: None,
|
||||
out_handle: Some(corner_start_mid.into()),
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: corner_end.into(),
|
||||
in_handle: Some(corner_end_mid.into()),
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
path.push(ManipulatorGroup {
|
||||
anchor: (*locations.last().unwrap()).into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
});
|
||||
Subpath::new(path, false)
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
// TODO: Eventually remove this document upgrade code
|
||||
// This file contains lots of hacky code for upgrading old documents to the new format
|
||||
|
||||
use super::document::utility_types::network_interface::{NumberInputSettings, PropertiesRow, WidgetOverride};
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::IVec2;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
||||
use graphene_std::text::TypesettingConfig;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::style::{Fill, FillType, Gradient, PaintOrder, StrokeAlign};
|
||||
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -25,6 +24,7 @@ const REPLACEMENTS: &[(&str, &str)] = &[
|
|||
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
|
||||
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
|
||||
("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"),
|
||||
// math_nodes
|
||||
("graphene_core::ops::MathNode", "graphene_math_nodes::MathNode"),
|
||||
("graphene_core::ops::AddNode", "graphene_math_nodes::AddNode"),
|
||||
("graphene_core::ops::SubtractNode", "graphene_math_nodes::SubtractNode"),
|
||||
|
@ -61,47 +61,92 @@ const REPLACEMENTS: &[(&str, &str)] = &[
|
|||
("graphene_core::ops::NumberValueNode", "graphene_math_nodes::NumberValueNode"),
|
||||
("graphene_core::ops::PercentageValueNode", "graphene_math_nodes::PercentageValueNode"),
|
||||
("graphene_core::ops::CoordinateValueNode", "graphene_math_nodes::CoordinateValueNode"),
|
||||
("graphene_core::ops::ConstructVector2", "graphene_math_nodes::CoordinateValueNode"),
|
||||
("graphene_core::ops::Vector2ValueNode", "graphene_math_nodes::CoordinateValueNode"),
|
||||
("graphene_core::ops::ColorValueNode", "graphene_math_nodes::ColorValueNode"),
|
||||
("graphene_core::ops::GradientValueNode", "graphene_math_nodes::GradientValueNode"),
|
||||
("graphene_core::ops::StringValueNode", "graphene_math_nodes::StringValueNode"),
|
||||
("graphene_core::ops::DotProductNode", "graphene_math_nodes::DotProductNode"),
|
||||
// debug
|
||||
("graphene_core::ops::SizeOfNode", "graphene_core::debug::SizeOfNode"),
|
||||
("graphene_core::ops::SomeNode", "graphene_core::debug::SomeNode"),
|
||||
("graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"),
|
||||
("graphene_core::ops::CloneNode", "graphene_core::debug::CloneNode"),
|
||||
// ???
|
||||
("graphene_core::ops::ExtractXyNode", "graphene_core::extract_xy::ExtractXyNode"),
|
||||
("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
|
||||
("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
|
||||
("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
|
||||
("graphene_core::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"),
|
||||
("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"),
|
||||
("graphene_core::raster::BlendModeNode", "graphene_core::blending_nodes::BlendModeNode"),
|
||||
("graphene_core::raster::OpacityNode", "graphene_core::blending_nodes::OpacityNode"),
|
||||
("graphene_core::raster::BlendingNode", "graphene_core::blending_nodes::BlendingNode"),
|
||||
("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"),
|
||||
("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_core::raster::adjustments::ColorOverlayNode"),
|
||||
("graphene_core::raster::ExposureNode", "graphene_core::raster::adjustments::ExposureNode"),
|
||||
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
|
||||
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
|
||||
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
|
||||
("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
|
||||
("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"),
|
||||
("graphene_core::raster::ExtractOpaqueNode", "graphene_core::raster::adjustments::MakeOpaqueNode"),
|
||||
("graphene_core::raster::PosterizeNode", "graphene_core::raster::adjustments::PosterizeNode"),
|
||||
("graphene_core::raster::ThresholdNode", "graphene_core::raster::adjustments::ThresholdNode"),
|
||||
("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"),
|
||||
// raster::adjustments
|
||||
("graphene_core::raster::adjustments::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"),
|
||||
("graphene_core::raster::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"),
|
||||
("graphene_core::raster::adjustments::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"),
|
||||
("graphene_core::raster::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"),
|
||||
("graphene_core::raster::adjustments::MakeOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"),
|
||||
("graphene_core::raster::ExtractOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"),
|
||||
(
|
||||
"graphene_core::raster::adjustments::BrightnessContrastNode",
|
||||
"graphene_raster_nodes::adjustments::BrightnessContrastNode",
|
||||
),
|
||||
("graphene_core::raster::adjustments::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"),
|
||||
("graphene_core::raster::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"),
|
||||
("graphene_core::raster::adjustments::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"),
|
||||
("graphene_core::raster::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"),
|
||||
("graphene_core::raster::adjustments::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"),
|
||||
("graphene_core::raster::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"),
|
||||
("graphene_core::raster::adjustments::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"),
|
||||
("graphene_core::raster::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"),
|
||||
("graphene_core::raster::InvertRGBNode", "graphene_raster_nodes::adjustments::InvertNode"),
|
||||
("graphene_core::raster::adjustments::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"),
|
||||
("graphene_core::raster::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"),
|
||||
("graphene_core::raster::adjustments::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"),
|
||||
("graphene_core::raster::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"),
|
||||
("graphene_core::raster::BlendColorPairNode", "graphene_raster_nodes::adjustments::BlendColorPairNode"),
|
||||
("graphene_core::raster::adjustments::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"),
|
||||
("graphene_core::raster::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"),
|
||||
("graphene_core::raster::adjustments::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"),
|
||||
("graphene_core::raster::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"),
|
||||
("graphene_core::raster::adjustments::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"),
|
||||
("graphene_core::raster::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"),
|
||||
("graphene_core::raster::adjustments::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"),
|
||||
("graphene_core::raster::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"),
|
||||
("graphene_core::raster::adjustments::SelectiveColorNode", "graphene_raster_nodes::adjustments::SelectiveColorNode"),
|
||||
("graphene_core::raster::adjustments::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"),
|
||||
("graphene_core::raster::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"),
|
||||
("graphene_core::raster::adjustments::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"),
|
||||
("graphene_core::raster::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"),
|
||||
("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"),
|
||||
("graphene_raster_nodes::generate_curves::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"),
|
||||
// raster
|
||||
("graphene_core::raster::adjustments::GenerateCurvesNode", "graphene_raster_nodes::generate_curves::GenerateCurvesNode"),
|
||||
("graphene_std::dehaze::DehazeNode", "graphene_raster_nodes::dehaze::DehazeNode"),
|
||||
("graphene_std::filter::BlurNode", "graphene_raster_nodes::filter::BlurNode"),
|
||||
(
|
||||
"graphene_std::image_color_palette::ImageColorPaletteNode",
|
||||
"graphene_raster_nodes::image_color_palette::ImageColorPaletteNode",
|
||||
),
|
||||
("graphene_std::raster::SampleImageNode", "graphene_raster_nodes::std_nodes::SampleImageNode"),
|
||||
("graphene_std::raster::CombineChannelsNode", "graphene_raster_nodes::std_nodes::CombineChannelsNode"),
|
||||
("graphene_std::raster::MaskNode", "graphene_raster_nodes::std_nodes::MaskNode"),
|
||||
("graphene_std::raster::ExtendImageToBoundsNode", "graphene_raster_nodes::std_nodes::ExtendImageToBoundsNode"),
|
||||
("graphene_std::raster::EmptyImageNode", "graphene_raster_nodes::std_nodes::EmptyImageNode"),
|
||||
("graphene_std::raster::ImageValueNode", "graphene_raster_nodes::std_nodes::ImageValueNode"),
|
||||
("graphene_std::raster::NoisePatternNode", "graphene_raster_nodes::std_nodes::NoisePatternNode"),
|
||||
("graphene_std::raster::MandelbrotNode", "graphene_raster_nodes::std_nodes::MandelbrotNode"),
|
||||
// text
|
||||
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
|
||||
// transform
|
||||
("graphene_core::transform::SetTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
|
||||
("graphene_core::transform::ReplaceTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
|
||||
("graphene_core::transform::TransformNode", "graphene_core::transform_nodes::TransformNode"),
|
||||
("graphene_core::transform::BoundlessFootprintNode", "graphene_core::transform_nodes::BoundlessFootprintNode"),
|
||||
("graphene_core::transform::FreezeRealTimeNode", "graphene_core::transform_nodes::FreezeRealTimeNode"),
|
||||
// ???
|
||||
("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"),
|
||||
("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
|
||||
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
|
||||
|
@ -117,6 +162,10 @@ const REPLACEMENTS: &[(&str, &str)] = &[
|
|||
("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"),
|
||||
("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"),
|
||||
("graphene_std::vector::BooleanOperationNode", "graphene_path_bool::BooleanOperationNode"),
|
||||
// brush
|
||||
("graphene_std::brush::BrushStampGeneratorNode", "graphene_brush::brush::BrushStampGeneratorNode"),
|
||||
("graphene_std::brush::BlitNode", "graphene_brush::brush::BlitNode"),
|
||||
("graphene_std::brush::BrushNode", "graphene_brush::brush::BrushNode"),
|
||||
];
|
||||
|
||||
pub fn document_migration_string_preprocessing(document_serialized_content: String) -> String {
|
||||
|
@ -140,90 +189,37 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
|
|||
}
|
||||
|
||||
pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {
|
||||
let mut network = document.network_interface.document_network().clone();
|
||||
network.generate_node_paths(&[]);
|
||||
let network = document.network_interface.document_network().clone();
|
||||
|
||||
// Apply string replacements to each node
|
||||
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect();
|
||||
for (node_id, path) in &node_ids {
|
||||
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
|
||||
|
||||
if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document
|
||||
.network_interface
|
||||
.nested_network(&network_path)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get(node_id)
|
||||
.map(|node| node.implementation.clone())
|
||||
{
|
||||
for (node_id, node, network_path) in network.recursive_nodes() {
|
||||
if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation {
|
||||
for (old, new) in REPLACEMENTS {
|
||||
let node_path_without_type_args = protonode_id.name.split('<').next();
|
||||
let mut default_template = NodeTemplate::default();
|
||||
default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.to_string().into());
|
||||
if node_path_without_type_args == Some(old) {
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation(node_id, &network_path, DocumentNodeImplementation::ProtoNode(new.to_string().into()));
|
||||
document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
|
||||
document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if reset_node_definitions_on_open {
|
||||
// This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed.
|
||||
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file
|
||||
for node_id in &document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<NodeId>>()
|
||||
{
|
||||
if let Some(reference) = document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.get(node_id)
|
||||
.and_then(|node| node.persistent_metadata.reference.as_ref())
|
||||
{
|
||||
// Apply upgrades to each unmodified node.
|
||||
let nodes = document
|
||||
.network_interface
|
||||
.document_network()
|
||||
.recursive_nodes()
|
||||
.map(|(node_id, node, path)| (*node_id, node.clone(), path))
|
||||
.collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>();
|
||||
for (node_id, node, network_path) in &nodes {
|
||||
if reset_node_definitions_on_open {
|
||||
if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) {
|
||||
let Some(node_definition) = resolve_document_node_type(reference) else { continue };
|
||||
let default_definition_node = node_definition.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation);
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata);
|
||||
document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition);
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.iter()
|
||||
.any(|(node_id, node)| node.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Output") && *node_id == NodeId(0))
|
||||
{
|
||||
document.network_interface.delete_nodes(vec![NodeId(0)], true, &[]);
|
||||
}
|
||||
|
||||
let mut network = document.network_interface.document_network().clone();
|
||||
network.generate_node_paths(&[]);
|
||||
|
||||
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect();
|
||||
|
||||
// Apply upgrades to each node
|
||||
for (node_id, path) in &node_ids {
|
||||
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
|
||||
let network_path = &network_path;
|
||||
|
||||
let Some(node) = document.network_interface.nested_network(network_path).unwrap().nodes.get(node_id).cloned() else {
|
||||
log::error!("could not get node in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition
|
||||
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) {
|
||||
|
@ -232,87 +228,19 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
.set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
|
||||
}
|
||||
|
||||
let Some(node_metadata) = document.network_interface.network_metadata(network_path).unwrap().persistent_metadata.node_metadata.get(node_id) else {
|
||||
log::error!("could not get node metadata for node {node_id} in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
|
||||
// TODO: Investigate if this should be an expected case, because currently it runs hundreds of times normally.
|
||||
// TODO: Either delete the commented out error below if this is normal, or fix the underlying issue if this is not expected.
|
||||
// log::error!("could not get reference in deserialize_document");
|
||||
let Some(Some(reference)) = document.network_interface.reference(node_id, network_path).cloned() else {
|
||||
// Only nodes that have not been modified and still refer to a definition can be updated
|
||||
continue;
|
||||
};
|
||||
let reference = &reference;
|
||||
|
||||
let inputs_count = node.inputs.len();
|
||||
|
||||
// Upgrade Fill nodes to the format change in #1778
|
||||
if reference == "Fill" && inputs_count == 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
let Some(fill_type) = old_inputs[1].as_value().cloned() else { continue };
|
||||
let TaggedValue::FillType(fill_type) = fill_type else { continue };
|
||||
let Some(solid_color) = old_inputs[2].as_value().cloned() else { continue };
|
||||
let TaggedValue::OptionalColor(solid_color) = solid_color else { continue };
|
||||
let Some(gradient_type) = old_inputs[3].as_value().cloned() else { continue };
|
||||
let TaggedValue::GradientType(gradient_type) = gradient_type else { continue };
|
||||
let Some(start) = old_inputs[4].as_value().cloned() else { continue };
|
||||
let TaggedValue::DVec2(start) = start else { continue };
|
||||
let Some(end) = old_inputs[5].as_value().cloned() else { continue };
|
||||
let TaggedValue::DVec2(end) = end else { continue };
|
||||
let Some(transform) = old_inputs[6].as_value().cloned() else { continue };
|
||||
let TaggedValue::DAffine2(transform) = transform else { continue };
|
||||
let Some(positions) = old_inputs[7].as_value().cloned() else { continue };
|
||||
let TaggedValue::GradientStops(positions) = positions else { continue };
|
||||
|
||||
let fill = match (fill_type, solid_color) {
|
||||
(FillType::Solid, None) => Fill::None,
|
||||
(FillType::Solid, Some(color)) => Fill::Solid(color),
|
||||
(FillType::Gradient, _) => Fill::Gradient(Gradient {
|
||||
stops: positions,
|
||||
gradient_type,
|
||||
start,
|
||||
end,
|
||||
transform,
|
||||
}),
|
||||
};
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Fill(fill.clone()), false), network_path);
|
||||
match fill {
|
||||
Fill::None => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(None), false), network_path);
|
||||
}
|
||||
Fill::Solid(color) => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), network_path);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(gradient), false), network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
|
||||
if reference == "Stroke" && inputs_count == 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document.network_interface.insert_input_properties_row(node_id, 8, network_path, ("", "TODO").into());
|
||||
document.network_interface.insert_input_properties_row(node_id, 9, network_path, ("", "TODO").into());
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
|
||||
let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false);
|
||||
|
||||
|
@ -404,11 +332,9 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
|
||||
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
|
||||
if reference == "Text" && inputs_count != 9 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let mut template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut template);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -450,21 +376,6 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.insert_input_properties_row(
|
||||
node_id,
|
||||
9,
|
||||
network_path,
|
||||
PropertiesRow::with_override(
|
||||
"Tilt",
|
||||
"Faux italic",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(-85.),
|
||||
max: Some(85.),
|
||||
unit: Some("°".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 8),
|
||||
if inputs_count >= 9 {
|
||||
|
@ -478,11 +389,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
|
||||
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
|
||||
if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document
|
||||
|
@ -492,11 +402,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
|
||||
// Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option
|
||||
if reference == "Modulo" && inputs_count == 2 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -507,11 +416,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
|
||||
// Upgrade the Mirror node to add the `keep_original` boolean input
|
||||
if reference == "Mirror" && inputs_count == 3 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -523,15 +431,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
|
||||
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
|
||||
if reference == "Mirror" && inputs_count == 4 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return };
|
||||
let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y };
|
||||
|
@ -558,9 +461,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Image" && inputs_count == 1 {
|
||||
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
|
||||
let new_image_node = node_definition.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, new_image_node.document_node.implementation);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
// Insert a new empty input for the image
|
||||
document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]);
|
||||
|
@ -568,13 +470,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Noise Pattern" && inputs_count == 15 {
|
||||
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
|
||||
let new_noise_pattern_node = node_definition.default_node_template();
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation(node_id, network_path, new_noise_pattern_node.document_node.implementation);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, new_noise_pattern_node.document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document
|
||||
.network_interface
|
||||
|
@ -585,30 +484,20 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Instance on Points" && inputs_count == 2 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
}
|
||||
|
||||
if reference == "Morph" && inputs_count == 4 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -617,15 +506,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Brush" && inputs_count == 4 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
|
||||
|
@ -634,15 +518,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Flatten Vector Elements" {
|
||||
let node_definition = resolve_document_node_type("Flatten Path").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
|
@ -650,15 +529,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Remove Handles" {
|
||||
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document
|
||||
|
@ -672,15 +546,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Generate Handles" {
|
||||
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type("Auto-Tangents").unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -692,15 +561,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Merge by Distance" && inputs_count == 2 {
|
||||
let node_definition = resolve_document_node_type("Merge by Distance").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -712,15 +576,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Spatial Merge by Distance" {
|
||||
let node_definition = resolve_document_node_type("Merge by Distance").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type("Merge by Distance").unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -734,51 +593,89 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Sample Points" && inputs_count == 5 {
|
||||
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type("Sample Polyline").unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
|
||||
document.network_interface.load_structure();
|
||||
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
|
||||
for layer in all_layers {
|
||||
let Some((downstream_node, input_index)) = document
|
||||
.network_interface
|
||||
.outward_wires(&[])
|
||||
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||
.and_then(|outward_wires| outward_wires.first())
|
||||
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
|
||||
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||
// Make the "Quantity" parameter a u32 instead of f64
|
||||
if reference == "Sample Polyline" {
|
||||
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
|
||||
let mut new_node_template = node_definition.default_node_template();
|
||||
|
||||
// Get the inputs, obtain the quantity value, and put the inputs back
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap();
|
||||
let quantity_value = old_inputs.get(3).cloned();
|
||||
|
||||
if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_value {
|
||||
if let TaggedValue::F64(value) = *tagged_value {
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), exposed);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
|
||||
if reference == "Grid" && inputs_count == 6 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let mut new_node_template = node_definition.default_node_template();
|
||||
|
||||
let mut current_node_template = document.network_interface.create_node_template(node_id, network_path).unwrap();
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap();
|
||||
let index_3_value = old_inputs.get(3).cloned();
|
||||
|
||||
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
|
||||
if matches!(*tagged_value, TaggedValue::DVec2(_)) {
|
||||
// Move index 3 to the end
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
} else {
|
||||
// Swap it back if we're not changing anything
|
||||
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
|
||||
document.network_interface.load_structure();
|
||||
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
|
||||
for layer in all_layers {
|
||||
let Some((downstream_node, input_index)) = document
|
||||
.network_interface
|
||||
.outward_wires(&[])
|
||||
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||
.and_then(|outward_wires| outward_wires.first())
|
||||
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if layer_position.x == downstream_position.x {
|
||||
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
|
||||
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||
continue;
|
||||
};
|
||||
if layer_position.x == downstream_position.x {
|
||||
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use crate::messages::portfolio::document::DocumentMessageData;
|
|||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
|
||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
use crate::messages::portfolio::document_migration::*;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
|
@ -431,6 +432,42 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
// Upgrade the document's nodes to be compatible with the latest version
|
||||
document_migration_upgrades(&mut document, reset_node_definitions_on_open);
|
||||
|
||||
// Ensure each node has the metadata for its inputs
|
||||
for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() {
|
||||
document.network_interface.validate_input_metadata(node_id, node, &path);
|
||||
document.network_interface.validate_display_name_metadata(node_id, &path);
|
||||
}
|
||||
|
||||
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
|
||||
document.network_interface.load_structure();
|
||||
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
|
||||
for layer in all_layers {
|
||||
let Some((downstream_node, input_index)) = document
|
||||
.network_interface
|
||||
.outward_wires(&[])
|
||||
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||
.and_then(|outward_wires| outward_wires.first())
|
||||
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) =
|
||||
(document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[]))
|
||||
else {
|
||||
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||
continue;
|
||||
};
|
||||
|
||||
if layer_position.x == downstream_position.x {
|
||||
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the save state of the document based on what's given to us by the caller to this message
|
||||
document.set_auto_save_state(document_is_auto_saved);
|
||||
document.set_save_state(document_is_saved);
|
||||
|
@ -798,7 +835,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
}
|
||||
|
||||
let Some(document) = self.documents.get_mut(&document_id) else {
|
||||
warn!("Tried to read non existant document");
|
||||
warn!("Tried to read non existent document");
|
||||
return;
|
||||
};
|
||||
if !document.is_loaded {
|
||||
|
@ -1013,11 +1050,11 @@ impl PortfolioMessageHandler {
|
|||
let result = self.executor.poll_node_graph_evaluation(active_document, responses);
|
||||
if result.is_err() {
|
||||
let error = r#"
|
||||
<rect x="50%" y="50%" width="480" height="100" transform="translate(-240 -50)" rx="4" fill="var(--color-error-red)" />
|
||||
<rect x="50%" y="50%" width="460" height="100" transform="translate(-230 -50)" rx="4" fill="var(--color-warning-yellow)" />
|
||||
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
|
||||
<tspan x="50%" dy="-24" font-weight="bold">The document cannot be rendered in its current state.</tspan>
|
||||
<tspan x="50%" dy="24">Check for error details in the node graph, which can be</tspan>
|
||||
<tspan x="50%" dy="24">opened with the viewport's top right <tspan font-style="italic">Node Graph</tspan> button.</tspan>
|
||||
<tspan x="50%" dy="-24" font-weight="bold">The document cannot render in its current state.</tspan>
|
||||
<tspan x="50%" dy="24">Undo to go back, if available, or check for error details</tspan>
|
||||
<tspan x="50%" dy="24">by clicking the <tspan font-style="italic">Node Graph</tspan> button up at the top right.</tspan>
|
||||
/text>"#
|
||||
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
|
||||
.to_string();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE;
|
||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::wasm_application_io::EditorPreferences;
|
||||
|
@ -86,7 +86,8 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
|||
}
|
||||
PreferencesMessage::GraphWireStyle { style } => {
|
||||
self.graph_wire_style = style;
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
PreferencesMessage::ViewportZoomWheelRate { rate } => {
|
||||
self.viewport_zoom_wheel_rate = rate;
|
||||
|
|
|
@ -80,12 +80,12 @@ pub enum ToolMessage {
|
|||
Redo,
|
||||
RefreshToolOptions,
|
||||
ResetColors,
|
||||
SelectPrimaryColor {
|
||||
SelectWorkingColor {
|
||||
color: Color,
|
||||
primary: bool,
|
||||
},
|
||||
SelectRandomPrimaryColor,
|
||||
SelectSecondaryColor {
|
||||
color: Color,
|
||||
SelectRandomWorkingColor {
|
||||
primary: bool,
|
||||
},
|
||||
SwapColors,
|
||||
Undo,
|
||||
|
|
|
@ -240,14 +240,8 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
|||
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
ToolMessage::SelectPrimaryColor { color } => {
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
document_data.primary_color = color;
|
||||
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
ToolMessage::SelectRandomPrimaryColor => {
|
||||
// Select a random primary color (rgba) based on an UUID
|
||||
ToolMessage::SelectRandomWorkingColor { primary } => {
|
||||
// Select a random working color (RGBA) based on an UUID
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
|
||||
let random_number = generate_uuid();
|
||||
|
@ -255,13 +249,23 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
|||
let g = (random_number >> 8) as u8;
|
||||
let b = random_number as u8;
|
||||
let random_color = Color::from_rgba8_srgb(r, g, b, 255);
|
||||
document_data.primary_color = random_color;
|
||||
|
||||
if primary {
|
||||
document_data.primary_color = random_color;
|
||||
} else {
|
||||
document_data.secondary_color = random_color;
|
||||
}
|
||||
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
ToolMessage::SelectSecondaryColor { color } => {
|
||||
ToolMessage::SelectWorkingColor { color, primary } => {
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
document_data.secondary_color = color;
|
||||
|
||||
if primary {
|
||||
document_data.primary_color = color;
|
||||
} else {
|
||||
document_data.secondary_color = color;
|
||||
}
|
||||
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
|
@ -340,7 +344,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
|||
|
||||
ActivateToolBrush,
|
||||
|
||||
SelectRandomPrimaryColor,
|
||||
SelectRandomWorkingColor,
|
||||
ResetColors,
|
||||
SwapColors,
|
||||
Undo,
|
||||
|
|
|
@ -8,8 +8,8 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio
|
|||
use graph_craft::document::NodeId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::brush::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
|
||||
const BRUSH_MAX_SIZE: f64 = 5000.;
|
||||
|
||||
|
|
|
@ -12,10 +12,11 @@ pub enum TransformLayerMessage {
|
|||
|
||||
// Messages
|
||||
ApplyTransformOperation { final_transform: bool },
|
||||
BeginTransformOperation { operation: TransformType },
|
||||
BeginGrab,
|
||||
BeginRotate,
|
||||
BeginScale,
|
||||
BeginGRS { transform_type: TransformType },
|
||||
BeginGRS { operation: TransformType },
|
||||
BeginGrabPen { last_point: DVec2, handle: DVec2 },
|
||||
BeginRotatePen { last_point: DVec2, handle: DVec2 },
|
||||
BeginScalePen { last_point: DVec2, handle: DVec2 },
|
||||
|
|
|
@ -368,6 +368,15 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||
}
|
||||
}
|
||||
TransformLayerMessage::BeginTransformOperation { operation } => {
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform);
|
||||
self.transform_operation = match operation {
|
||||
TransformType::Grab => TransformOperation::Grabbing(Default::default()),
|
||||
TransformType::Rotate => TransformOperation::Rotating(Default::default()),
|
||||
TransformType::Scale => TransformOperation::Scaling(Default::default()),
|
||||
};
|
||||
self.layer_bounding_box = selected.bounding_box();
|
||||
}
|
||||
TransformLayerMessage::BeginGrabPen { last_point, handle } | TransformLayerMessage::BeginRotatePen { last_point, handle } | TransformLayerMessage::BeginScalePen { last_point, handle } => {
|
||||
self.typing.clear();
|
||||
|
||||
|
@ -402,9 +411,10 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
increments_key: INCREMENTS_KEY,
|
||||
});
|
||||
}
|
||||
TransformLayerMessage::BeginGRS { transform_type } => {
|
||||
TransformLayerMessage::BeginGRS { operation: transform_type } => {
|
||||
let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect();
|
||||
let selected_segments = shape_editor.selected_segments().collect::<Vec<_>>();
|
||||
|
||||
if (using_path_tool && selected_points.is_empty() && selected_segments.is_empty())
|
||||
|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)
|
||||
|| selected_layers.is_empty()
|
||||
|
@ -439,42 +449,24 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
}
|
||||
}
|
||||
|
||||
self.local = false;
|
||||
self.operation_count += 1;
|
||||
|
||||
let chain_operation = self.transform_operation != TransformOperation::None;
|
||||
if chain_operation {
|
||||
responses.add(TransformLayerMessage::ApplyTransformOperation { final_transform: false });
|
||||
} else {
|
||||
responses.add(OverlaysMessage::AddProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||
}
|
||||
|
||||
let response = match transform_type {
|
||||
TransformType::Grab => TransformLayerMessage::BeginGrab,
|
||||
TransformType::Rotate => TransformLayerMessage::BeginRotate,
|
||||
TransformType::Scale => TransformLayerMessage::BeginScale,
|
||||
};
|
||||
|
||||
self.local = false;
|
||||
self.operation_count += 1;
|
||||
responses.add(response);
|
||||
responses.add(TransformLayerMessage::BeginTransformOperation { operation: transform_type });
|
||||
responses.add(TransformLayerMessage::PointerMove {
|
||||
slow_key: SLOW_KEY,
|
||||
increments_key: INCREMENTS_KEY,
|
||||
});
|
||||
}
|
||||
TransformLayerMessage::BeginGrab => {
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform);
|
||||
self.transform_operation = TransformOperation::Grabbing(Default::default());
|
||||
self.layer_bounding_box = selected.bounding_box();
|
||||
}
|
||||
TransformLayerMessage::BeginRotate => {
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform);
|
||||
self.transform_operation = TransformOperation::Rotating(Default::default());
|
||||
self.layer_bounding_box = selected.bounding_box();
|
||||
}
|
||||
TransformLayerMessage::BeginScale => {
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform);
|
||||
self.transform_operation = TransformOperation::Scaling(Default::default());
|
||||
self.layer_bounding_box = selected.bounding_box();
|
||||
}
|
||||
TransformLayerMessage::BeginGrab => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Grab }),
|
||||
TransformLayerMessage::BeginRotate => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Rotate }),
|
||||
TransformLayerMessage::BeginScale => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Scale }),
|
||||
TransformLayerMessage::CancelTransformOperation => {
|
||||
if using_pen_tool {
|
||||
self.typing.clear();
|
||||
|
@ -707,7 +699,9 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(TransformLayerMessageDiscriminant;
|
||||
BeginGRS,
|
||||
BeginGrab,
|
||||
BeginRotate,
|
||||
BeginScale,
|
||||
);
|
||||
|
||||
if self.transform_operation != TransformOperation::None {
|
||||
|
|
|
@ -172,9 +172,10 @@ impl EditorTestUtils {
|
|||
pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator<Item = T> + 'a {
|
||||
self.active_document()
|
||||
.network_interface
|
||||
.iter_recursive()
|
||||
.inspect(|node| println!("{:#?}", node.1.implementation))
|
||||
.filter_map(move |(_, document)| T::new_with_source(document))
|
||||
.document_network()
|
||||
.recursive_nodes()
|
||||
.inspect(|(_, node, _)| println!("{:#?}", node.implementation))
|
||||
.filter_map(move |(_, document, _)| T::new_with_source(document))
|
||||
}
|
||||
|
||||
pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) {
|
||||
|
@ -227,11 +228,11 @@ impl EditorTestUtils {
|
|||
}
|
||||
|
||||
pub async fn select_primary_color(&mut self, color: Color) {
|
||||
self.handle_message(Message::Tool(ToolMessage::SelectPrimaryColor { color })).await;
|
||||
self.handle_message(Message::Tool(ToolMessage::SelectWorkingColor { color, primary: true })).await;
|
||||
}
|
||||
|
||||
pub async fn select_secondary_color(&mut self, color: Color) {
|
||||
self.handle_message(Message::Tool(ToolMessage::SelectSecondaryColor { color })).await;
|
||||
self.handle_message(Message::Tool(ToolMessage::SelectWorkingColor { color, primary: false })).await;
|
||||
}
|
||||
|
||||
pub async fn create_raster_image(&mut self, image: graphene_std::raster::Image<Color>, mouse: Option<(f64, f64)>) {
|
||||
|
@ -300,7 +301,7 @@ pub trait FrontendMessageTestUtils {
|
|||
|
||||
impl FrontendMessageTestUtils for FrontendMessage {
|
||||
fn check_node_graph_error(&self) {
|
||||
let FrontendMessage::UpdateNodeGraph { nodes, .. } = self else { return };
|
||||
let FrontendMessage::UpdateNodeGraphNodes { nodes, .. } = self else { return };
|
||||
|
||||
for node in nodes {
|
||||
if let Some(error) = &node.errors {
|
||||
|
|
|
@ -104,6 +104,8 @@
|
|||
--color-f-white-rgb: 255, 255, 255;
|
||||
--color-error-red: #d6536e;
|
||||
--color-error-red-rgb: 214, 83, 110;
|
||||
--color-warning-yellow: #d5aa43;
|
||||
--color-warning-yellow-rgb: 213, 170, 67;
|
||||
|
||||
--color-data-general: #cfcfcf;
|
||||
--color-data-general-dim: #8a8a8a;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { getContext, onMount, tick } from "svelte";
|
||||
import { getContext } from "svelte";
|
||||
import { cubicInOut } from "svelte/easing";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import type { Node } from "@graphite/messages";
|
||||
import type { FrontendNodeWire, FrontendNode, FrontendGraphInput, FrontendGraphOutput, FrontendGraphDataType, WirePath } from "@graphite/messages";
|
||||
import type { FrontendNode, FrontendGraphInput, FrontendGraphOutput } from "@graphite/messages";
|
||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||
import type { IconName } from "@graphite/utility-functions/icons";
|
||||
|
||||
|
@ -27,26 +27,13 @@
|
|||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||
|
||||
let graph: HTMLDivElement | undefined;
|
||||
let nodesContainer: HTMLDivElement | undefined;
|
||||
|
||||
// TODO: Using this not-complete code, or another better approach, make it so the dragged in-progress connector correctly handles showing/hiding the SVG shape of the connector caps
|
||||
// let wireInProgressFromLayerTop: bigint | undefined = undefined;
|
||||
// let wireInProgressFromLayerBottom: bigint | undefined = undefined;
|
||||
|
||||
let nodeWirePaths: WirePath[] = [];
|
||||
|
||||
// TODO: Convert these arrays-of-arrays to a Map?
|
||||
let inputs: SVGSVGElement[][] = [];
|
||||
let outputs: SVGSVGElement[][] = [];
|
||||
let nodeElements: HTMLDivElement[] = [];
|
||||
|
||||
$: watchNodes($nodeGraph.nodes);
|
||||
// Key value is node id + input/output index
|
||||
// Imports/Export are stored at a key value of 0
|
||||
|
||||
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
|
||||
$: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
|
||||
|
||||
$: wirePaths = createWirePaths($nodeGraph.wirePathInProgress, nodeWirePaths);
|
||||
|
||||
let inputElement: HTMLInputElement;
|
||||
let hoveringImportIndex: number | undefined = undefined;
|
||||
let hoveringExportIndex: number | undefined = undefined;
|
||||
|
@ -121,80 +108,6 @@
|
|||
return sparse;
|
||||
}
|
||||
|
||||
function createWirePaths(wirePathInProgress: WirePath | undefined, nodeWirePaths: WirePath[]): WirePath[] {
|
||||
const maybeWirePathInProgress = wirePathInProgress ? [wirePathInProgress] : [];
|
||||
return [...maybeWirePathInProgress, ...nodeWirePaths];
|
||||
}
|
||||
|
||||
async function watchNodes(nodes: Map<bigint, FrontendNode>) {
|
||||
Array.from(nodes.keys()).forEach((_, index) => {
|
||||
if (!inputs[index + 1]) inputs[index + 1] = [];
|
||||
if (!outputs[index + 1]) outputs[index + 1] = [];
|
||||
});
|
||||
if (!inputs[0]) inputs[0] = [];
|
||||
if (!outputs[0]) outputs[0] = [];
|
||||
|
||||
await refreshWires();
|
||||
}
|
||||
|
||||
function resolveWire(wire: FrontendNodeWire): { nodeOutput: SVGSVGElement; nodeInput: SVGSVGElement } | undefined {
|
||||
// TODO: Avoid the linear search
|
||||
const wireStartNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireStart as Node).nodeId);
|
||||
let nodeOutputConnectors = outputs[wireStartNodeIdIndex + 1];
|
||||
if (nodeOutputConnectors === undefined && (wire.wireStart as Node).nodeId === undefined) {
|
||||
nodeOutputConnectors = outputs[0];
|
||||
}
|
||||
const indexOutput = Number(wire.wireStart.index);
|
||||
const nodeOutput = nodeOutputConnectors?.[indexOutput] as SVGSVGElement | undefined;
|
||||
if (nodeOutput === undefined) return undefined;
|
||||
|
||||
// TODO: Avoid the linear search
|
||||
const wireEndNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireEnd as Node).nodeId);
|
||||
let nodeInputConnectors = inputs[wireEndNodeIdIndex + 1] || undefined;
|
||||
if (nodeInputConnectors === undefined && (wire.wireEnd as Node).nodeId === undefined) {
|
||||
nodeInputConnectors = inputs[0];
|
||||
}
|
||||
const indexInput = Number(wire.wireEnd.index);
|
||||
const nodeInput = nodeInputConnectors?.[indexInput] as SVGSVGElement | undefined;
|
||||
if (nodeInput === undefined) return undefined;
|
||||
|
||||
return { nodeOutput, nodeInput };
|
||||
}
|
||||
|
||||
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement, verticalOut: boolean, verticalIn: boolean, dashed: boolean, directNotGridAligned: boolean): WirePath {
|
||||
const inputPortRect = inputPort.getBoundingClientRect();
|
||||
const outputPortRect = outputPort.getBoundingClientRect();
|
||||
|
||||
const pathString = directNotGridAligned
|
||||
? buildCurvedWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn)
|
||||
: buildStraightWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
|
||||
const dataType = (outputPort.getAttribute("data-datatype") as FrontendGraphDataType) || "General";
|
||||
const thick = verticalIn && verticalOut;
|
||||
|
||||
return { pathString, dataType, thick, dashed };
|
||||
}
|
||||
|
||||
async function refreshWires() {
|
||||
await tick();
|
||||
|
||||
nodeWirePaths = $nodeGraph.wires.flatMap((wire) => {
|
||||
// TODO: This call contains linear searches, which combined with the loop we're in, causes O(n^2) complexity as the graph grows
|
||||
const resolvedWires = resolveWire(wire);
|
||||
if (!resolvedWires) return [];
|
||||
const { nodeOutput, nodeInput } = resolvedWires;
|
||||
|
||||
const wireStartNode = wire.wireStart.nodeId !== undefined ? $nodeGraph.nodes.get(wire.wireStart.nodeId) : undefined;
|
||||
const wireStart = wireStartNode?.isLayer || false;
|
||||
|
||||
const wireEndNode = wire.wireEnd.nodeId !== undefined ? $nodeGraph.nodes.get(wire.wireEnd.nodeId) : undefined;
|
||||
const wireEnd = (wireEndNode?.isLayer && Number(wire.wireEnd.index) === 0) || false;
|
||||
|
||||
return [createWirePath(nodeOutput, nodeInput, wireStart, wireEnd, wire.dashed, $nodeGraph.wiresDirectNotGridAligned)];
|
||||
});
|
||||
}
|
||||
|
||||
onMount(refreshWires);
|
||||
|
||||
function nodeIcon(icon?: string): IconName {
|
||||
if (!icon) return "NodeNodes";
|
||||
const iconMap: Record<string, IconName> = {
|
||||
|
@ -203,325 +116,6 @@
|
|||
return iconMap[icon] || "NodeNodes";
|
||||
}
|
||||
|
||||
function buildStraightWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
|
||||
if (!nodesContainer) return [];
|
||||
|
||||
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
|
||||
const LINE_WIDTH = 2;
|
||||
|
||||
// Calculate coordinates for input and output connectors
|
||||
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x;
|
||||
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
|
||||
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
|
||||
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
|
||||
|
||||
// Adjust for scale
|
||||
const containerBounds = nodesContainer.getBoundingClientRect();
|
||||
const scale = $nodeGraph.transform.scale;
|
||||
const inConnectorX = Math.round((inX - containerBounds.x) / scale);
|
||||
const inConnectorY = Math.round((inY - containerBounds.y) / scale);
|
||||
const outConnectorX = Math.round((outX - containerBounds.x) / scale);
|
||||
const outConnectorY = Math.round((outY - containerBounds.y) / scale);
|
||||
|
||||
// Helper functions for calculating coordinates
|
||||
const calculateMidX = () => (inConnectorX + outConnectorX) / 2 + (((inConnectorX + outConnectorX) / 2) % gridSpacing);
|
||||
const calculateMidY = () => (inConnectorY + outConnectorY) / 2 + (((inConnectorY + outConnectorY) / 2) % gridSpacing);
|
||||
const calculateMidYAlternate = () => (inConnectorY + outConnectorY) / 2 - (((inConnectorY + outConnectorY) / 2) % gridSpacing);
|
||||
|
||||
// Define X coordinate calculations
|
||||
const x1 = () => outConnectorX;
|
||||
const x2 = () => outConnectorX + gridSpacing;
|
||||
const x3 = () => inConnectorX - 2 * gridSpacing;
|
||||
const x4 = () => inConnectorX;
|
||||
const x5 = () => inConnectorX - 2 * gridSpacing + LINE_WIDTH;
|
||||
const x6 = () => outConnectorX + gridSpacing + LINE_WIDTH;
|
||||
const x7 = () => outConnectorX + 2 * gridSpacing + LINE_WIDTH;
|
||||
const x8 = () => inConnectorX + LINE_WIDTH;
|
||||
const x9 = () => outConnectorX + 2 * gridSpacing;
|
||||
const x10 = () => calculateMidX() + LINE_WIDTH;
|
||||
const x11 = () => outConnectorX - gridSpacing;
|
||||
const x12 = () => outConnectorX - 4 * gridSpacing;
|
||||
const x13 = () => calculateMidX();
|
||||
const x14 = () => inConnectorX + gridSpacing;
|
||||
const x15 = () => inConnectorX - 4 * gridSpacing;
|
||||
const x16 = () => inConnectorX + 8 * gridSpacing;
|
||||
const x17 = () => calculateMidX() - 2 * LINE_WIDTH;
|
||||
const x18 = () => outConnectorX + gridSpacing - 2 * LINE_WIDTH;
|
||||
const x19 = () => outConnectorX - 2 * LINE_WIDTH;
|
||||
const x20 = () => calculateMidX() - LINE_WIDTH;
|
||||
|
||||
// Define Y coordinate calculations
|
||||
const y1 = () => outConnectorY;
|
||||
const y2 = () => outConnectorY - gridSpacing;
|
||||
const y3 = () => inConnectorY;
|
||||
const y4 = () => outConnectorY - gridSpacing + 5.5 * LINE_WIDTH;
|
||||
const y5 = () => inConnectorY - 2 * gridSpacing;
|
||||
const y6 = () => outConnectorY + 4 * LINE_WIDTH;
|
||||
const y7 = () => outConnectorY + 5 * LINE_WIDTH;
|
||||
const y8 = () => outConnectorY - 2 * gridSpacing + 5.5 * LINE_WIDTH;
|
||||
const y9 = () => outConnectorY + 6 * LINE_WIDTH;
|
||||
const y10 = () => inConnectorY + 2 * gridSpacing;
|
||||
const y111 = () => inConnectorY + gridSpacing + 6.5 * LINE_WIDTH;
|
||||
const y12 = () => inConnectorY + gridSpacing - 5.5 * LINE_WIDTH;
|
||||
const y13 = () => inConnectorY - gridSpacing;
|
||||
const y14 = () => inConnectorY + gridSpacing;
|
||||
const y15 = () => calculateMidY();
|
||||
const y16 = () => calculateMidYAlternate();
|
||||
|
||||
// Helper function for constructing coordinate pairs
|
||||
const construct = (...coords: [() => number, () => number][]) => coords.map(([x, y]) => ({ x: x(), y: y() }));
|
||||
|
||||
// Define wire path shapes that get used more than once
|
||||
const wire1 = () => construct([x1, y1], [x1, y4], [x5, y4], [x5, y3], [x4, y3]);
|
||||
const wire2 = () => construct([x1, y1], [x1, y16], [x3, y16], [x3, y3], [x4, y3]);
|
||||
const wire3 = () => construct([x1, y1], [x1, y4], [x12, y4], [x12, y10], [x3, y10], [x3, y3], [x4, y3]);
|
||||
const wire4 = () => construct([x1, y1], [x1, y4], [x13, y4], [x13, y10], [x3, y10], [x3, y3], [x4, y3]);
|
||||
|
||||
// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point
|
||||
if (outConnectorY === inConnectorY && outConnectorX > inConnectorX && (verticalOut || !verticalIn)) return construct([x1, y1], [x2, y1], [x2, y2], [x3, y2], [x3, y3], [x4, y3]);
|
||||
|
||||
// Handle straight lines
|
||||
if (outConnectorY === inConnectorY || (outConnectorX === inConnectorX && verticalOut)) return construct([x1, y1], [x4, y3]);
|
||||
|
||||
// Handle standard right-angle paths
|
||||
// Start vertical, then horizontal
|
||||
|
||||
// `outConnector` point lies to the left of `inConnector` point
|
||||
if (verticalOut && inConnectorX > outConnectorX) {
|
||||
// `outConnector` point lies above `inConnector` point
|
||||
if (outConnectorY < inConnectorY) {
|
||||
// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point
|
||||
if (-4 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX < -3 * gridSpacing) return wire1();
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point
|
||||
if (-3 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= -1 * gridSpacing) {
|
||||
if (-2 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return construct([x1, y1], [x1, y2], [x2, y2], [x2, y3], [x4, y3]);
|
||||
|
||||
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y1], [x1, y4], [x6, y4], [x6, y3], [x4, y3]);
|
||||
|
||||
return construct([x1, y1], [x1, y4], [x7, y4], [x7, y5], [x3, y5], [x3, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point
|
||||
if (-1 * gridSpacing < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 0 * gridSpacing) {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if (-2 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return construct([x1, y6], [x2, y6], [x8, y3]);
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y7], [x4, y3]);
|
||||
|
||||
return construct([x1, y1], [x1, y2], [x9, y2], [x9, y5], [x3, y5], [x3, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x1, y4], [x10, y4], [x10, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point
|
||||
if (-1 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 0 * gridSpacing) {
|
||||
// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point
|
||||
if (0 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 2 * gridSpacing) construct([x1, y6], [x11, y6], [x11, y3], [x4, y3]);
|
||||
|
||||
return wire2();
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x1, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point lies to the right of `inConnector` point
|
||||
if (verticalOut && inConnectorX <= outConnectorX) {
|
||||
// `outConnector` point lying on any horizontal grid line above `inConnector` point
|
||||
if (outConnectorY < inConnectorY) {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if (-2 * gridSpacing < outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return wire1();
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if (-1 * gridSpacing < outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y1], [x1, y8], [x5, y8], [x5, y3], [x4, y3]);
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point
|
||||
if (gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 3 * gridSpacing) {
|
||||
return construct([x1, y1], [x1, y4], [x9, y4], [x9, y5], [x3, y5], [x3, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x1, y4], [x10, y4], [x10, y5], [x5, y5], [x5, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
if (outConnectorY - inConnectorY <= gridSpacing) {
|
||||
// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point
|
||||
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 13 * gridSpacing) return construct([x1, y9], [x3, y9], [x3, y3], [x4, y3]);
|
||||
|
||||
if (13 < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 18 * gridSpacing) return wire3();
|
||||
|
||||
return wire4();
|
||||
}
|
||||
|
||||
// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point
|
||||
if (gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 2 * gridSpacing) {
|
||||
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 13 * gridSpacing) return construct([x1, y7], [x5, y7], [x5, y3], [x4, y3]);
|
||||
|
||||
if (13 < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 18 * gridSpacing) return wire3();
|
||||
|
||||
return wire4();
|
||||
}
|
||||
|
||||
// 0 to 4 units below the `outConnector` Point
|
||||
if (outConnectorY - inConnectorY <= 4 * gridSpacing) return wire1();
|
||||
|
||||
return wire2();
|
||||
}
|
||||
|
||||
// Start horizontal, then vertical
|
||||
if (verticalIn) {
|
||||
// when `outConnector` lies below `inConnector`
|
||||
if (outConnectorY > inConnectorY) {
|
||||
// `outConnectorX` lies to the left of `inConnectorX`
|
||||
if (outConnectorX < inConnectorX) return construct([x1, y1], [x4, y1], [x4, y3]);
|
||||
|
||||
// `outConnectorX` lies to the right of `inConnectorX`
|
||||
if (outConnectorY - inConnectorY <= gridSpacing) {
|
||||
// `outConnector` point directly below `inConnector` point
|
||||
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= gridSpacing) return construct([x1, y1], [x14, y1], [x14, y2], [x4, y2], [x4, y3]);
|
||||
|
||||
// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point
|
||||
return construct([x1, y1], [x2, y1], [x2, y111], [x4, y111], [x4, y3]);
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x2, y1], [x2, y2], [x4, y2], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnectorY` lies on or above the `inConnectorY` point
|
||||
if (-6 * gridSpacing < inConnectorX - outConnectorX && inConnectorX - outConnectorX < 4 * gridSpacing) {
|
||||
// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point
|
||||
if (-1 * gridSpacing < inConnectorX - outConnectorX && inConnectorX - outConnectorX < 4 * gridSpacing) {
|
||||
return construct([x1, y1], [x2, y1], [x2, y2], [x15, y2], [x15, y12], [x4, y12], [x4, y3]);
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x16, y1], [x16, y12], [x4, y12], [x4, y3]);
|
||||
}
|
||||
|
||||
// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point
|
||||
if (4 * gridSpacing < inConnectorX - outConnectorX) return construct([x1, y1], [x17, y1], [x17, y12], [x4, y12], [x4, y3]);
|
||||
|
||||
// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point
|
||||
if (6 * gridSpacing > inConnectorX - outConnectorX) return construct([x1, y1], [x18, y1], [x18, y12], [x4, y12], [x4, y3]);
|
||||
}
|
||||
|
||||
// Both horizontal - use horizontal middle point
|
||||
// When `inConnector` point is one of the two closest diagonally opposite points
|
||||
if (0 <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= gridSpacing && inConnectorY - outConnectorY >= -1 * gridSpacing && inConnectorY - outConnectorY <= gridSpacing) {
|
||||
return construct([x19, y1], [x19, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point
|
||||
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= gridSpacing && outConnectorX > inConnectorX) {
|
||||
// Horizontal line above `outConnectorY`
|
||||
if (inConnectorY < outConnectorY) return construct([x1, y1], [x2, y1], [x2, y13], [x3, y13], [x3, y3], [x4, y3]);
|
||||
|
||||
// Horizontal line below `outConnectorY`
|
||||
return construct([x1, y1], [x2, y1], [x2, y14], [x3, y14], [x3, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point to the right of `inConnector` point
|
||||
if (outConnectorX > inConnectorX - gridSpacing) return construct([x1, y1], [x18, y1], [x18, y15], [x5, y15], [x5, y3], [x4, y3]);
|
||||
|
||||
// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point
|
||||
if (gridSpacing <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= 2 * gridSpacing) return construct([x1, y1], [x18, y1], [x18, y3], [x4, y3]);
|
||||
|
||||
return construct([x1, y1], [x20, y1], [x20, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
function buildStraightWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
|
||||
const locations = buildStraightWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
|
||||
if (locations.length === 0) return "[error]";
|
||||
if (locations.length === 2) return `M${locations[0].x},${locations[0].y} L${locations[1].x},${locations[1].y}`;
|
||||
|
||||
const CORNER_RADIUS = 10;
|
||||
|
||||
// Create path with rounded corners
|
||||
let path = `M${locations[0].x},${locations[0].y}`;
|
||||
|
||||
for (let i = 1; i < locations.length - 1; i++) {
|
||||
const prev = locations[i - 1];
|
||||
const curr = locations[i];
|
||||
const next = locations[i + 1];
|
||||
|
||||
// Calculate corner points
|
||||
const isVertical = curr.x === prev.x;
|
||||
const cornerStart = {
|
||||
x: curr.x + (isVertical ? 0 : prev.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS),
|
||||
y: curr.y + (isVertical ? (prev.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
|
||||
};
|
||||
const cornerEnd = {
|
||||
x: curr.x + (isVertical ? (next.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
|
||||
y: curr.y + (isVertical ? 0 : next.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS),
|
||||
};
|
||||
|
||||
// Add line to corner start, quadratic curve for corner, then continue to next point
|
||||
path += ` L${cornerStart.x},${cornerStart.y}`;
|
||||
path += ` Q${curr.x},${curr.y} ${cornerEnd.x},${cornerEnd.y}`;
|
||||
}
|
||||
|
||||
path += ` L${locations[locations.length - 1].x},${locations[locations.length - 1].y}`;
|
||||
return path;
|
||||
}
|
||||
|
||||
function buildCurvedWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
|
||||
if (!nodesContainer) return [];
|
||||
|
||||
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
|
||||
|
||||
const containerBounds = nodesContainer.getBoundingClientRect();
|
||||
|
||||
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
|
||||
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
|
||||
const outConnectorX = (outX - containerBounds.x) / $nodeGraph.transform.scale;
|
||||
const outConnectorY = (outY - containerBounds.y) / $nodeGraph.transform.scale;
|
||||
|
||||
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x;
|
||||
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
|
||||
const inConnectorX = (inX - containerBounds.x) / $nodeGraph.transform.scale;
|
||||
const inConnectorY = (inY - containerBounds.y) / $nodeGraph.transform.scale;
|
||||
const horizontalGap = Math.abs(outConnectorX - inConnectorX);
|
||||
const verticalGap = Math.abs(outConnectorY - inConnectorY);
|
||||
|
||||
const curveLength = 24;
|
||||
const curveFalloffRate = curveLength * Math.PI * 2;
|
||||
|
||||
const horizontalCurveAmount = -(2 ** ((-10 * horizontalGap) / curveFalloffRate)) + 1;
|
||||
const verticalCurveAmount = -(2 ** ((-10 * verticalGap) / curveFalloffRate)) + 1;
|
||||
const horizontalCurve = horizontalCurveAmount * curveLength;
|
||||
const verticalCurve = verticalCurveAmount * curveLength;
|
||||
|
||||
return [
|
||||
{ x: outConnectorX, y: outConnectorY },
|
||||
{ x: verticalOut ? outConnectorX : outConnectorX + horizontalCurve, y: verticalOut ? outConnectorY - verticalCurve : outConnectorY },
|
||||
{ x: verticalIn ? inConnectorX : inConnectorX - horizontalCurve, y: verticalIn ? inConnectorY + verticalCurve : inConnectorY },
|
||||
{ x: inConnectorX, y: inConnectorY },
|
||||
];
|
||||
}
|
||||
|
||||
function buildCurvedWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
|
||||
const locations = buildCurvedWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
|
||||
if (locations.length === 0) return "[error]";
|
||||
|
||||
const SMOOTHING = 0.5;
|
||||
const delta01 = { x: (locations[1].x - locations[0].x) * SMOOTHING, y: (locations[1].y - locations[0].y) * SMOOTHING };
|
||||
const delta23 = { x: (locations[3].x - locations[2].x) * SMOOTHING, y: (locations[3].y - locations[2].y) * SMOOTHING };
|
||||
|
||||
return `
|
||||
M${locations[0].x},${locations[0].y}
|
||||
L${locations[1].x},${locations[1].y}
|
||||
C${locations[1].x + delta01.x},${locations[1].y + delta01.y}
|
||||
${locations[2].x - delta23.x},${locations[2].y - delta23.y}
|
||||
${locations[2].x},${locations[2].y}
|
||||
L${locations[3].x},${locations[3].y}
|
||||
`
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) {
|
||||
let node = $nodeGraph.nodes.get(toggleId);
|
||||
if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer);
|
||||
|
@ -719,19 +313,21 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Node connection wires -->
|
||||
<!-- Thick vertical layer connection wires -->
|
||||
<div class="wires" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
||||
<svg>
|
||||
{#each wirePaths as { pathString, dataType, thick, dashed }}
|
||||
{#if thick}
|
||||
<path
|
||||
d={pathString}
|
||||
style:--data-line-width={`${thick ? 8 : 2}px`}
|
||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
|
||||
/>
|
||||
{/if}
|
||||
{#each $nodeGraph.wires.values() as map}
|
||||
{#each map.values() as { pathString, dataType, thick, dashed }}
|
||||
{#if thick}
|
||||
<path
|
||||
d={pathString}
|
||||
style:--data-line-width={"8px"}
|
||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</svg>
|
||||
</div>
|
||||
|
@ -749,7 +345,6 @@
|
|||
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
|
||||
style:--offset-left={position.x / 24}
|
||||
style:--offset-top={position.y / 24}
|
||||
bind:this={outputs[0][index]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`}</title>
|
||||
{#if outputMetadata.connectedTo !== undefined}
|
||||
|
@ -823,7 +418,6 @@
|
|||
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
|
||||
style:--offset-left={position.x / 24}
|
||||
style:--offset-top={position.y / 24}
|
||||
bind:this={inputs[0][index]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`}</title>
|
||||
{#if inputMetadata.connectedTo !== undefined}
|
||||
|
@ -887,14 +481,11 @@
|
|||
</div>
|
||||
|
||||
<!-- Layers and nodes -->
|
||||
<div
|
||||
class="layers-and-nodes"
|
||||
style:transform-origin={`0 0`}
|
||||
style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}
|
||||
bind:this={nodesContainer}
|
||||
>
|
||||
<div class="layers-and-nodes" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
||||
<!-- Layers -->
|
||||
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
|
||||
{#each Array.from($nodeGraph.nodes)
|
||||
.filter(([nodeId, node]) => node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
|
||||
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
|
||||
{@const clipPathId = String(Math.random()).substring(2)}
|
||||
{@const stackDataInput = node.exposedInputs[0]}
|
||||
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
|
||||
|
@ -916,7 +507,6 @@
|
|||
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
||||
data-node={node.id}
|
||||
bind:this={nodeElements[nodeIndex]}
|
||||
>
|
||||
{#if node.errors}
|
||||
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||
|
@ -936,7 +526,6 @@
|
|||
data-datatype={node.primaryOutput.dataType}
|
||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={outputs[nodeIndex + 1][0]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
||||
{#if node.primaryOutput.connectedTo.length > 0}
|
||||
|
@ -958,7 +547,6 @@
|
|||
data-datatype={node.primaryInput?.dataType}
|
||||
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][0]}
|
||||
>
|
||||
{#if node.primaryInput}
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||
|
@ -984,7 +572,6 @@
|
|||
data-datatype={stackDataInput.dataType}
|
||||
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][1]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title>
|
||||
{#if stackDataInput.connectedTo !== undefined}
|
||||
|
@ -1025,22 +612,35 @@
|
|||
<!-- Node connection wires -->
|
||||
<div class="wires">
|
||||
<svg>
|
||||
{#each wirePaths as { pathString, dataType, thick, dashed }}\
|
||||
{#if !thick}
|
||||
<path
|
||||
d={pathString}
|
||||
style:--data-line-width={`${thick ? 8 : 2}px`}
|
||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={dashed ? "4" : undefined}
|
||||
/>
|
||||
{/if}
|
||||
{#each $nodeGraph.wires.values() as map}
|
||||
{#each map.values() as { pathString, dataType, thick, dashed }}
|
||||
{#if !thick}
|
||||
<path
|
||||
d={pathString}
|
||||
style:--data-line-width={"2px"}
|
||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
{#if $nodeGraph.wirePathInProgress}
|
||||
<path
|
||||
d={$nodeGraph.wirePathInProgress?.pathString}
|
||||
style:--data-line-width={`${$nodeGraph.wirePathInProgress.thick ? 8 : 2}px`}
|
||||
style:--data-color={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={`3,${$nodeGraph.wirePathInProgress.dashed ? 2 : 0}`}
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Nodes -->
|
||||
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [] : [{ node, nodeIndex }])) as { node, nodeIndex } (nodeIndex)}
|
||||
{#each Array.from($nodeGraph.nodes)
|
||||
.filter(([nodeId, node]) => !node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
|
||||
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
|
||||
{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}
|
||||
{@const clipPathId = String(Math.random()).substring(2)}
|
||||
{@const description = (node.reference && $nodeGraph.nodeDescriptions.get(node.reference)) || undefined}
|
||||
|
@ -1056,7 +656,6 @@
|
|||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
||||
data-node={node.id}
|
||||
bind:this={nodeElements[nodeIndex]}
|
||||
>
|
||||
{#if node.errors}
|
||||
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||
|
@ -1091,7 +690,6 @@
|
|||
data-datatype={node.primaryInput?.dataType}
|
||||
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][0]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||
{#if node.primaryInput.connectedTo !== undefined}
|
||||
|
@ -1111,7 +709,6 @@
|
|||
data-datatype={secondary.dataType}
|
||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][index + (node.primaryInput ? 1 : 0)]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title>
|
||||
{#if secondary.connectedTo !== undefined}
|
||||
|
@ -1134,7 +731,6 @@
|
|||
data-datatype={node.primaryOutput.dataType}
|
||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={outputs[nodeIndex + 1][0]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
||||
{#if node.primaryOutput.connectedTo !== undefined}
|
||||
|
@ -1144,7 +740,7 @@
|
|||
{/if}
|
||||
</svg>
|
||||
{/if}
|
||||
{#each node.exposedOutputs as secondary, outputIndex}
|
||||
{#each node.exposedOutputs as secondary}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
|
@ -1153,7 +749,6 @@
|
|||
data-datatype={secondary.dataType}
|
||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={outputs[nodeIndex + 1][outputIndex + (node.primaryOutput ? 1 : 0)]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title>
|
||||
{#if secondary.connectedTo !== undefined}
|
||||
|
|
|
@ -96,16 +96,21 @@ export class UpdateLayerWidths extends JsMessage {
|
|||
readonly hasLeftInputWire!: Map<bigint, boolean>;
|
||||
}
|
||||
|
||||
export class UpdateNodeGraph extends JsMessage {
|
||||
export class UpdateNodeGraphNodes extends JsMessage {
|
||||
@Type(() => FrontendNode)
|
||||
readonly nodes!: FrontendNode[];
|
||||
|
||||
@Type(() => FrontendNodeWire)
|
||||
readonly wires!: FrontendNodeWire[];
|
||||
|
||||
readonly wiresDirectNotGridAligned!: boolean;
|
||||
}
|
||||
|
||||
export class UpdateVisibleNodes extends JsMessage {
|
||||
readonly nodes!: bigint[];
|
||||
}
|
||||
|
||||
export class UpdateNodeGraphWires extends JsMessage {
|
||||
readonly wires!: WireUpdate[];
|
||||
}
|
||||
|
||||
export class ClearAllNodeGraphWires extends JsMessage {}
|
||||
|
||||
export class UpdateNodeGraphTransform extends JsMessage {
|
||||
readonly transform!: NodeGraphTransform;
|
||||
}
|
||||
|
@ -219,7 +224,7 @@ export class FrontendGraphInput {
|
|||
|
||||
readonly description!: string;
|
||||
|
||||
readonly resolvedType!: string | undefined;
|
||||
readonly resolvedType!: string;
|
||||
|
||||
readonly validTypes!: string[];
|
||||
|
||||
|
@ -252,7 +257,7 @@ export class FrontendGraphOutput {
|
|||
|
||||
readonly description!: string;
|
||||
|
||||
readonly resolvedType!: string | undefined;
|
||||
readonly resolvedType!: string;
|
||||
|
||||
@CreateInputConnectorArray
|
||||
connectedTo!: Node[];
|
||||
|
@ -297,44 +302,6 @@ export class FrontendNode {
|
|||
readonly uiOnly!: boolean;
|
||||
}
|
||||
|
||||
const CreateOutputConnector = Transform(({ obj }) => {
|
||||
if (obj.wireStart.export !== undefined) {
|
||||
return { index: obj.wireStart.export };
|
||||
} else if (obj.wireStart.import !== undefined) {
|
||||
return { index: obj.wireStart.import };
|
||||
} else {
|
||||
if (obj.wireStart.node.inputIndex !== undefined) {
|
||||
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.inputIndex };
|
||||
} else {
|
||||
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.outputIndex };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const CreateInputConnector = Transform(({ obj }) => {
|
||||
if (obj.wireEnd.export !== undefined) {
|
||||
return { index: obj.wireEnd.export };
|
||||
} else if (obj.wireEnd.import !== undefined) {
|
||||
return { index: obj.wireEnd.import };
|
||||
} else {
|
||||
if (obj.wireEnd.node.inputIndex !== undefined) {
|
||||
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.inputIndex };
|
||||
} else {
|
||||
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.outputIndex };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export class FrontendNodeWire {
|
||||
@CreateOutputConnector
|
||||
readonly wireStart!: Node;
|
||||
|
||||
@CreateInputConnector
|
||||
readonly wireEnd!: Node;
|
||||
|
||||
readonly dashed!: boolean;
|
||||
}
|
||||
|
||||
export class FrontendNodeType {
|
||||
readonly name!: string;
|
||||
|
||||
|
@ -356,6 +323,12 @@ export class WirePath {
|
|||
readonly dashed!: boolean;
|
||||
}
|
||||
|
||||
export class WireUpdate {
|
||||
readonly id!: bigint;
|
||||
readonly inputIndex!: number;
|
||||
readonly wirePathUpdate!: WirePath | undefined;
|
||||
}
|
||||
|
||||
export class IndexedDbDocumentDetails extends DocumentDetails {
|
||||
@Transform(({ value }: { value: bigint }) => value.toString())
|
||||
id!: string;
|
||||
|
@ -1645,6 +1618,7 @@ type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHand
|
|||
type MessageMaker = typeof JsMessage | JSMessageFactory;
|
||||
|
||||
export const messageMakers: Record<string, MessageMaker> = {
|
||||
ClearAllNodeGraphWires,
|
||||
DisplayDialog,
|
||||
DisplayDialogDismiss,
|
||||
DisplayDialogPanic,
|
||||
|
@ -1700,10 +1674,12 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
UpdateLayerWidths,
|
||||
UpdateMenuBarLayout,
|
||||
UpdateMouseCursor,
|
||||
UpdateNodeGraph,
|
||||
UpdateNodeGraphNodes,
|
||||
UpdateVisibleNodes,
|
||||
UpdateNodeGraphWires,
|
||||
UpdateNodeGraphTransform,
|
||||
UpdateNodeGraphControlBarLayout,
|
||||
UpdateNodeGraphSelection,
|
||||
UpdateNodeGraphTransform,
|
||||
UpdateNodeThumbnail,
|
||||
UpdateOpenDocumentsList,
|
||||
UpdatePropertyPanelSectionsLayout,
|
||||
|
|
|
@ -7,9 +7,9 @@ import {
|
|||
type FrontendClickTargets,
|
||||
type ContextMenuInformation,
|
||||
type FrontendNode,
|
||||
type FrontendNodeWire as FrontendNodeWire,
|
||||
type FrontendNodeType,
|
||||
type WirePath,
|
||||
ClearAllNodeGraphWires,
|
||||
SendUIMetadata,
|
||||
UpdateBox,
|
||||
UpdateClickTargets,
|
||||
|
@ -19,7 +19,9 @@ import {
|
|||
UpdateExportReorderIndex,
|
||||
UpdateImportsExports,
|
||||
UpdateLayerWidths,
|
||||
UpdateNodeGraph,
|
||||
UpdateNodeGraphNodes,
|
||||
UpdateVisibleNodes,
|
||||
UpdateNodeGraphWires,
|
||||
UpdateNodeGraphSelection,
|
||||
UpdateNodeGraphTransform,
|
||||
UpdateNodeThumbnail,
|
||||
|
@ -40,8 +42,9 @@ export function createNodeGraphState(editor: Editor) {
|
|||
addImport: undefined as { x: number; y: number } | undefined,
|
||||
addExport: undefined as { x: number; y: number } | undefined,
|
||||
nodes: new Map<bigint, FrontendNode>(),
|
||||
wires: [] as FrontendNodeWire[],
|
||||
wiresDirectNotGridAligned: false,
|
||||
visibleNodes: new Set<bigint>(),
|
||||
/// The index is the exposed input index. The exports have a first key value of u32::MAX.
|
||||
wires: new Map<bigint, Map<number, WirePath>>(),
|
||||
wirePathInProgress: undefined as WirePath | undefined,
|
||||
nodeDescriptions: new Map<string, string>(),
|
||||
nodeTypes: [] as FrontendNodeType[],
|
||||
|
@ -114,15 +117,42 @@ export function createNodeGraphState(editor: Editor) {
|
|||
return state;
|
||||
});
|
||||
});
|
||||
// TODO: Add a way to only update the nodes that have changed
|
||||
editor.subscriptions.subscribeJsMessage(UpdateNodeGraph, (updateNodeGraph) => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => {
|
||||
update((state) => {
|
||||
state.nodes.clear();
|
||||
updateNodeGraph.nodes.forEach((node) => {
|
||||
updateNodeGraphNodes.nodes.forEach((node) => {
|
||||
state.nodes.set(node.id, node);
|
||||
});
|
||||
state.wires = updateNodeGraph.wires;
|
||||
state.wiresDirectNotGridAligned = updateNodeGraph.wiresDirectNotGridAligned;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => {
|
||||
update((state) => {
|
||||
state.visibleNodes = new Set<bigint>(updateVisibleNodes.nodes);
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphWires, (updateNodeWires) => {
|
||||
update((state) => {
|
||||
updateNodeWires.wires.forEach((wireUpdate) => {
|
||||
let inputMap = state.wires.get(wireUpdate.id);
|
||||
// If it doesn't exist, create it and set it in the outer map
|
||||
if (!inputMap) {
|
||||
inputMap = new Map();
|
||||
state.wires.set(wireUpdate.id, inputMap);
|
||||
}
|
||||
if (wireUpdate.wirePathUpdate !== undefined) {
|
||||
inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate);
|
||||
} else {
|
||||
inputMap.delete(wireUpdate.inputIndex);
|
||||
}
|
||||
});
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWires, (_) => {
|
||||
update((state) => {
|
||||
state.wires.clear();
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -467,8 +467,9 @@ impl EditorHandle {
|
|||
return Err(Error::new("Invalid color").into());
|
||||
};
|
||||
|
||||
let message = ToolMessage::SelectPrimaryColor {
|
||||
let message = ToolMessage::SelectWorkingColor {
|
||||
color: primary_color.to_linear_srgb(),
|
||||
primary: true,
|
||||
};
|
||||
self.dispatch(message);
|
||||
|
||||
|
@ -482,8 +483,9 @@ impl EditorHandle {
|
|||
return Err(Error::new("Invalid color").into());
|
||||
};
|
||||
|
||||
let message = ToolMessage::SelectSecondaryColor {
|
||||
let message = ToolMessage::SelectWorkingColor {
|
||||
color: secondary_color.to_linear_srgb(),
|
||||
primary: false,
|
||||
};
|
||||
self.dispatch(message);
|
||||
|
||||
|
@ -609,6 +611,7 @@ impl EditorHandle {
|
|||
node_id: Some(id),
|
||||
node_type,
|
||||
xy: Some((x / 24, y / 24)),
|
||||
add_transaction: true,
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
|
28
node-graph/gbrush/Cargo.toml
Normal file
28
node-graph/gbrush/Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "graphene-brush"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "graphene brush"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
graphene-raster-nodes = { workspace = true }
|
||||
node-macro = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
serde = { workspace = true, optional = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
# Workspace dependencies
|
||||
tokio = { workspace = true }
|
|
@ -1,19 +1,21 @@
|
|||
use crate::raster::{empty_image, extend_image_to_bounds};
|
||||
use crate::brush_cache::BrushCache;
|
||||
use crate::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::generic::FnNode;
|
||||
use graph_craft::proto::FutureWrapperNode;
|
||||
use graphene_core::blending::BlendMode;
|
||||
use graphene_core::bounds::BoundingBox;
|
||||
use graphene_core::color::{Alpha, Color, Pixel, Sample};
|
||||
use graphene_core::generic::FnNode;
|
||||
use graphene_core::instances::Instance;
|
||||
use graphene_core::math::bbox::{AxisAlignedBbox, Bbox};
|
||||
use graphene_core::raster::adjustments::blend_colors;
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::BitmapMut;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster::{Alpha, BitmapMut, BlendMode, Color, Pixel, Sample};
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::registry::FutureWrapperNode;
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::value::ClonedNode;
|
||||
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use graphene_core::{Ctx, GraphicElement, Node};
|
||||
use graphene_core::{Ctx, Node};
|
||||
use graphene_raster_nodes::adjustments::blend_colors;
|
||||
use graphene_raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct BrushStampGenerator<P: Pixel + Alpha> {
|
||||
|
@ -50,13 +52,13 @@ impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
|
|||
return None;
|
||||
};
|
||||
|
||||
use graphene_core::raster::Channel;
|
||||
use graphene_core::color::Channel;
|
||||
Some(self.color.multiplied_alpha(P::AlphaChannel::from_linear(result)))
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(skip_impl)]
|
||||
fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
|
||||
fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
|
||||
// Diameter
|
||||
let radius = diameter / 2.;
|
||||
|
||||
|
@ -78,7 +80,6 @@ fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64)
|
|||
fn blit<BlendFn>(mut target: RasterDataTable<CPU>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> RasterDataTable<CPU>
|
||||
where
|
||||
BlendFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color>,
|
||||
GraphicElement: From<Raster<CPU>>,
|
||||
{
|
||||
if positions.is_empty() {
|
||||
return target;
|
||||
|
@ -239,7 +240,6 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
|
|||
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
|
||||
extend_image_to_bounds((), target.to_table(), stroke_to_layer)
|
||||
} else {
|
||||
use crate::raster::empty_image;
|
||||
empty_image((), stroke_to_layer, Color::TRANSPARENT)
|
||||
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
|
||||
};
|
||||
|
@ -393,7 +393,7 @@ mod test {
|
|||
(),
|
||||
RasterDataTable::<CPU>::new(Raster::new_cpu(Image::<Color>::default())),
|
||||
vec![BrushStroke {
|
||||
trace: vec![crate::vector::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
|
||||
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
|
||||
style: BrushStyle {
|
||||
color: Color::BLACK,
|
||||
diameter: 20.,
|
|
@ -1,9 +1,9 @@
|
|||
use crate::instances::Instance;
|
||||
use crate::raster_types::CPU;
|
||||
use crate::raster_types::Raster;
|
||||
use crate::vector::brush_stroke::BrushStroke;
|
||||
use crate::vector::brush_stroke::BrushStyle;
|
||||
use crate::brush_stroke::BrushStroke;
|
||||
use crate::brush_stroke::BrushStyle;
|
||||
use dyn_any::DynAny;
|
||||
use graphene_core::instances::Instance;
|
||||
use graphene_core::raster_types::CPU;
|
||||
use graphene_core::raster_types::Raster;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
@ -15,11 +15,11 @@ struct BrushCacheImpl {
|
|||
prev_input: Vec<BrushStroke>,
|
||||
|
||||
// The strokes that have been fully processed and blended into the background.
|
||||
#[serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
background: Instance<Raster<CPU>>,
|
||||
#[serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
blended_image: Instance<Raster<CPU>>,
|
||||
#[serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
last_stroke_texture: Instance<Raster<CPU>>,
|
||||
|
||||
// A cache for brush textures.
|
|
@ -1,8 +1,8 @@
|
|||
use crate::Color;
|
||||
use crate::math::bbox::AxisAlignedBbox;
|
||||
use crate::raster::BlendMode;
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use graphene_core::blending::BlendMode;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::math::bbox::AxisAlignedBbox;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// The style of a brush.
|
3
node-graph/gbrush/src/lib.rs
Normal file
3
node-graph/gbrush/src/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod brush;
|
||||
pub mod brush_cache;
|
||||
pub mod brush_stroke;
|
|
@ -1,5 +1,14 @@
|
|||
use crate::raster_types::{CPU, RasterDataTable};
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{Color, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Debug"), name("Log to Console"))]
|
||||
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
log::debug!("{:#?}", value);
|
||||
value
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
|
|
|
@ -2,7 +2,9 @@ use crate::Ctx;
|
|||
use dyn_any::DynAny;
|
||||
use glam::{DVec2, IVec2, UVec2};
|
||||
|
||||
/// Obtain the X or Y component of a coordinate.
|
||||
/// Obtains the X or Y component of a coordinate point.
|
||||
///
|
||||
/// The inverse of this node is "Coordinate Value", which can have either or both its X and Y exposed as graph inputs.
|
||||
#[node_macro::node(name("Extract XY"), category("Math: Vector"))]
|
||||
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {
|
||||
match axis {
|
||||
|
|
|
@ -551,3 +551,53 @@ impl From<GraphicGroupTable> for GraphicElement {
|
|||
pub trait ToGraphicElement {
|
||||
fn to_graphic_element(&self) -> GraphicElement;
|
||||
}
|
||||
|
||||
/// Returns the value at the specified index in the collection.
|
||||
/// If that index has no value, the type's default value is returned.
|
||||
#[node_macro::node(category("General"))]
|
||||
fn index<T: AtIndex + Clone + Default>(
|
||||
_: impl Ctx,
|
||||
/// The collection of data, such as a list or table.
|
||||
#[implementations(
|
||||
Vec<Color>,
|
||||
Vec<Option<Color>>,
|
||||
Vec<f64>, Vec<u64>,
|
||||
Vec<DVec2>,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
GraphicGroupTable,
|
||||
)]
|
||||
collection: T,
|
||||
/// The index of the item to retrieve, starting from 0 for the first item.
|
||||
index: u32,
|
||||
) -> T::Output
|
||||
where
|
||||
T::Output: Clone + Default,
|
||||
{
|
||||
collection.at_index(index as usize).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub trait AtIndex {
|
||||
type Output;
|
||||
fn at_index(&self, index: usize) -> Option<Self::Output>;
|
||||
}
|
||||
impl<T: Clone> AtIndex for Vec<T> {
|
||||
type Output = T;
|
||||
|
||||
fn at_index(&self, index: usize) -> Option<Self::Output> {
|
||||
self.get(index).cloned()
|
||||
}
|
||||
}
|
||||
impl<T: Clone> AtIndex for Instances<T> {
|
||||
type Output = Instances<T>;
|
||||
|
||||
fn at_index(&self, index: usize) -> Option<Self::Output> {
|
||||
let mut result_table = Self::default();
|
||||
if let Some(row) = self.instance_ref_iter().nth(index) {
|
||||
result_table.push(row.to_instance_cloned());
|
||||
Some(result_table)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use crate::ArtboardGroupTable;
|
||||
use crate::Color;
|
||||
use crate::GraphicElement;
|
||||
use crate::GraphicGroupTable;
|
||||
use crate::gradient::GradientStops;
|
||||
use crate::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{Color, Context, Ctx};
|
||||
use crate::{Context, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Debug"), name("Log to Console"))]
|
||||
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
log::debug!("{:#?}", value);
|
||||
value
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
|
||||
format!("{:?}", value)
|
||||
|
@ -45,24 +44,42 @@ async fn switch<T, C: Send + 'n + Clone>(
|
|||
#[implementations(
|
||||
Context -> String,
|
||||
Context -> bool,
|
||||
Context -> f32,
|
||||
Context -> f64,
|
||||
Context -> u32,
|
||||
Context -> u64,
|
||||
Context -> DVec2,
|
||||
Context -> VectorDataTable,
|
||||
Context -> DAffine2,
|
||||
Context -> ArtboardGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> GraphicElement,
|
||||
Context -> Color,
|
||||
Context -> Option<Color>,
|
||||
Context -> GradientStops,
|
||||
)]
|
||||
if_true: impl Node<C, Output = T>,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
Context -> String,
|
||||
Context -> bool,
|
||||
Context -> f32,
|
||||
Context -> f64,
|
||||
Context -> u32,
|
||||
Context -> u64,
|
||||
Context -> DVec2,
|
||||
Context -> VectorDataTable,
|
||||
Context -> DAffine2,
|
||||
Context -> ArtboardGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> GraphicElement,
|
||||
Context -> Color,
|
||||
Context -> Option<Color>,
|
||||
Context -> GradientStops,
|
||||
)]
|
||||
if_false: impl Node<C, Output = T>,
|
||||
) -> T {
|
||||
|
|
|
@ -73,9 +73,17 @@ pub trait Convert<T>: Sized {
|
|||
fn convert(self) -> T;
|
||||
}
|
||||
|
||||
impl<T: ToString> Convert<String> for T {
|
||||
/// Converts this type into a `String` using its `ToString` implementation.
|
||||
#[inline]
|
||||
fn convert(self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.
|
||||
macro_rules! impl_convert {
|
||||
($from:ty,$to:ty) => {
|
||||
($from:ty, $to:ty) => {
|
||||
impl Convert<$to> for $from {
|
||||
fn convert(self) -> $to {
|
||||
self as $to
|
||||
|
|
|
@ -12,13 +12,9 @@ pub mod color {
|
|||
pub use super::*;
|
||||
}
|
||||
|
||||
pub mod adjustments;
|
||||
pub mod brush_cache;
|
||||
pub mod curve;
|
||||
pub mod image;
|
||||
|
||||
pub use self::image::Image;
|
||||
pub use adjustments::*;
|
||||
|
||||
pub trait Bitmap {
|
||||
type Pixel: Pixel;
|
||||
|
|
|
@ -97,11 +97,20 @@ impl Raster<GPU> {
|
|||
let RasterStorage::Gpu(gpu) = &self.data else { unreachable!() };
|
||||
gpu.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Raster<GPU> {
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let data = self.data();
|
||||
data.width() == 0 || data.height() == 0
|
||||
}
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
impl Deref for Raster<GPU> {
|
||||
type Target = wgpu::Texture;
|
||||
|
@ -110,6 +119,7 @@ impl Deref for Raster<GPU> {
|
|||
self.data()
|
||||
}
|
||||
}
|
||||
|
||||
pub type RasterDataTable<Storage> = Instances<Raster<Storage>>;
|
||||
|
||||
// TODO: Make this not dupliated
|
||||
|
|
|
@ -70,6 +70,7 @@ async fn boundless_footprint<T: 'n + 'static>(
|
|||
|
||||
transform_target.eval(ctx.into_context()).await
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn freeze_real_time<T: 'n + 'static>(
|
||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||
|
|
|
@ -86,6 +86,7 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
// TODO: Make this return a u32 instead of an f64, but we ned to improve math-related compatibility with integer types first.
|
||||
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 {
|
||||
match ctx.try_index() {
|
||||
|
|
|
@ -67,6 +67,10 @@ impl ClickTarget {
|
|||
self.bounding_box
|
||||
}
|
||||
|
||||
pub fn bounding_box_center(&self) -> Option<DVec2> {
|
||||
self.bounding_box.map(|bbox| bbox[0] + (bbox[1] - bbox[0]) / 2.)
|
||||
}
|
||||
|
||||
pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)])
|
||||
}
|
||||
|
|
|
@ -37,7 +37,13 @@ impl CornerRadius for [f64; 4] {
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
|
||||
fn circle(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[unit(" px")]
|
||||
#[default(50.)]
|
||||
radius: f64,
|
||||
) -> VectorDataTable {
|
||||
let radius = radius.abs();
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||
}
|
||||
|
@ -46,7 +52,9 @@ fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataT
|
|||
fn arc(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(50.)] radius: f64,
|
||||
#[unit(" px")]
|
||||
#[default(50.)]
|
||||
radius: f64,
|
||||
start_angle: Angle,
|
||||
#[default(270.)]
|
||||
#[range((0., 360.))]
|
||||
|
@ -66,7 +74,16 @@ fn arc(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
|
||||
fn ellipse(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[unit(" px")]
|
||||
#[default(50)]
|
||||
radius_x: f64,
|
||||
#[unit(" px")]
|
||||
#[default(25)]
|
||||
radius_y: f64,
|
||||
) -> VectorDataTable {
|
||||
let radius = DVec2::new(radius_x, radius_y);
|
||||
let corner1 = -radius;
|
||||
let corner2 = radius;
|
||||
|
@ -87,8 +104,12 @@ fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25
|
|||
fn rectangle<T: CornerRadius>(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(100)] width: f64,
|
||||
#[default(100)] height: f64,
|
||||
#[unit(" px")]
|
||||
#[default(100)]
|
||||
width: f64,
|
||||
#[unit(" px")]
|
||||
#[default(100)]
|
||||
height: f64,
|
||||
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
|
||||
#[implementations(f64, [f64; 4])] corner_radius: T,
|
||||
#[default(true)] clamped: bool,
|
||||
|
@ -104,7 +125,9 @@ fn regular_polygon<T: AsU64>(
|
|||
#[hard_min(3.)]
|
||||
#[implementations(u32, u64, f64)]
|
||||
sides: T,
|
||||
#[default(50)] radius: f64,
|
||||
#[unit(" px")]
|
||||
#[default(50)]
|
||||
radius: f64,
|
||||
) -> VectorDataTable {
|
||||
let points = sides.as_u64();
|
||||
let radius: f64 = radius * 2.;
|
||||
|
@ -119,8 +142,12 @@ fn star<T: AsU64>(
|
|||
#[hard_min(2.)]
|
||||
#[implementations(u32, u64, f64)]
|
||||
sides: T,
|
||||
#[default(50)] radius_1: f64,
|
||||
#[default(25)] radius_2: f64,
|
||||
#[unit(" px")]
|
||||
#[default(50)]
|
||||
radius_1: f64,
|
||||
#[unit(" px")]
|
||||
#[default(25)]
|
||||
radius_2: f64,
|
||||
) -> VectorDataTable {
|
||||
let points = sides.as_u64();
|
||||
let diameter: f64 = radius_1 * 2.;
|
||||
|
@ -130,7 +157,7 @@ fn star<T: AsU64>(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: PixelSize, #[default((0., 50.))] end: PixelSize) -> VectorDataTable {
|
||||
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> VectorDataTable {
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
|
||||
}
|
||||
|
||||
|
@ -153,13 +180,14 @@ fn grid<T: GridSpacing>(
|
|||
_: impl Ctx,
|
||||
_primary: (),
|
||||
grid_type: GridType,
|
||||
#[unit(" px")]
|
||||
#[hard_min(0.)]
|
||||
#[default(10)]
|
||||
#[implementations(f64, DVec2)]
|
||||
spacing: T,
|
||||
#[default(30., 30.)] angles: DVec2,
|
||||
#[default(10)] columns: u32,
|
||||
#[default(10)] rows: u32,
|
||||
#[default(30., 30.)] angles: DVec2,
|
||||
) -> VectorDataTable {
|
||||
let (x_spacing, y_spacing) = spacing.as_dvec2().into();
|
||||
let (angle_a, angle_b) = angles.into();
|
||||
|
@ -251,11 +279,11 @@ mod tests {
|
|||
#[test]
|
||||
fn isometric_grid_test() {
|
||||
// Doesn't crash with weird angles
|
||||
grid((), (), GridType::Isometric, 0., (0., 0.).into(), 5, 5);
|
||||
grid((), (), GridType::Isometric, 90., (90., 90.).into(), 5, 5);
|
||||
grid((), (), GridType::Isometric, 0., 5, 5, (0., 0.).into());
|
||||
grid((), (), GridType::Isometric, 90., 5, 5, (90., 90.).into());
|
||||
|
||||
// Works properly
|
||||
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5);
|
||||
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
|
||||
|
@ -270,7 +298,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn skew_isometric_grid_test() {
|
||||
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5);
|
||||
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
pub mod algorithms;
|
||||
pub mod brush_stroke;
|
||||
pub mod click_target;
|
||||
pub mod generator_nodes;
|
||||
pub mod misc;
|
||||
|
|
|
@ -65,6 +65,7 @@ async fn assign_colors<T>(
|
|||
randomize: bool,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")]
|
||||
/// The seed used for randomization.
|
||||
/// Seed to determine unique variations on the randomized color selection.
|
||||
seed: SeedValue,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")]
|
||||
/// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.
|
||||
|
@ -165,6 +166,7 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
|
|||
#[default(Color::BLACK)]
|
||||
/// The stroke color.
|
||||
color: C,
|
||||
#[unit(" px")]
|
||||
#[default(2.)]
|
||||
/// The stroke weight.
|
||||
weight: f64,
|
||||
|
@ -183,6 +185,7 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
|
|||
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
|
||||
dash_lengths: Vec<f64>,
|
||||
/// The phase offset distance from the starting point of the dash pattern.
|
||||
#[unit(" px")]
|
||||
dash_offset: f64,
|
||||
) -> Instances<V>
|
||||
where
|
||||
|
@ -253,7 +256,9 @@ async fn circular_repeat<I: 'n + Send + Clone>(
|
|||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
|
||||
angle_offset: Angle,
|
||||
#[default(5)] radius: f64,
|
||||
#[unit(" px")]
|
||||
#[default(5)]
|
||||
radius: f64,
|
||||
#[default(5)] instances: IntegerCount,
|
||||
) -> Instances<I> {
|
||||
let count = instances.max(1);
|
||||
|
@ -363,7 +368,7 @@ async fn mirror<I: 'n + Send + Clone>(
|
|||
_: impl Ctx,
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
|
||||
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
|
||||
offset: f64,
|
||||
#[unit(" px")] offset: f64,
|
||||
#[range((-90., 90.))] angle: Angle,
|
||||
#[default(true)] keep_original: bool,
|
||||
) -> Instances<I>
|
||||
|
@ -1139,10 +1144,10 @@ async fn sample_polyline(
|
|||
_: impl Ctx,
|
||||
vector_data: VectorDataTable,
|
||||
spacing: PointSpacingType,
|
||||
separation: f64,
|
||||
quantity: f64,
|
||||
start_offset: f64,
|
||||
stop_offset: f64,
|
||||
#[unit(" px")] separation: f64,
|
||||
quantity: u32,
|
||||
#[unit(" px")] start_offset: f64,
|
||||
#[unit(" px")] stop_offset: f64,
|
||||
adaptive_spacing: bool,
|
||||
subpath_segment_lengths: Vec<f64>,
|
||||
) -> VectorDataTable {
|
||||
|
@ -1182,7 +1187,7 @@ async fn sample_polyline(
|
|||
|
||||
let amount = match spacing {
|
||||
PointSpacingType::Separation => separation,
|
||||
PointSpacingType::Quantity => quantity,
|
||||
PointSpacingType::Quantity => quantity as f64,
|
||||
};
|
||||
let Some(mut sample_bezpath) = sample_polyline_on_bezpath(bezpath, spacing, amount, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else {
|
||||
continue;
|
||||
|
@ -1388,6 +1393,7 @@ async fn tangent_on_path(
|
|||
async fn poisson_disk_points(
|
||||
_: impl Ctx,
|
||||
vector_data: VectorDataTable,
|
||||
#[unit(" px")]
|
||||
#[default(10.)]
|
||||
#[hard_min(0.01)]
|
||||
separation_disk_diameter: f64,
|
||||
|
@ -1498,7 +1504,14 @@ async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))]
|
||||
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
|
||||
async fn jitter_points(
|
||||
_: impl Ctx,
|
||||
vector_data: VectorDataTable,
|
||||
#[unit(" px")]
|
||||
#[default(5.)]
|
||||
amount: f64,
|
||||
seed: SeedValue,
|
||||
) -> VectorDataTable {
|
||||
let mut result_table = VectorDataTable::default();
|
||||
|
||||
for mut vector_data_instance in vector_data.instance_iter() {
|
||||
|
@ -2080,7 +2093,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn sample_polyline() {
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0., 0., 0., false, vec![100.]).await;
|
||||
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0, 0., 0., false, vec![100.]).await;
|
||||
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
||||
|
@ -2090,7 +2103,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn sample_polyline_adaptive_spacing() {
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 18., 0., 45., 10., true, vec![100.]).await;
|
||||
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 18., 0, 45., 10., true, vec![100.]).await;
|
||||
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
||||
|
|
|
@ -78,8 +78,12 @@ fn math<U: num_traits::float::Float>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn add<U: Add<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U,
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T,
|
||||
/// The left-hand side of the addition operation.
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
augend: U,
|
||||
/// The right-hand side of the addition operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
addend: T,
|
||||
) -> <U as Add<T>>::Output {
|
||||
augend + addend
|
||||
}
|
||||
|
@ -88,8 +92,12 @@ fn add<U: Add<T>, T>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn subtract<U: Sub<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U,
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T,
|
||||
/// The left-hand side of the subtraction operation.
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
minuend: U,
|
||||
/// The right-hand side of the subtraction operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
subtrahend: T,
|
||||
) -> <U as Sub<T>>::Output {
|
||||
minuend - subtrahend
|
||||
}
|
||||
|
@ -98,9 +106,12 @@ fn subtract<U: Sub<T>, T>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn multiply<U: Mul<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U,
|
||||
/// The left-hand side of the multiplication operation.
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
multiplier: U,
|
||||
/// The right-hand side of the multiplication operation.
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)]
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
multiplicand: T,
|
||||
) -> <U as Mul<T>>::Output {
|
||||
multiplier * multiplicand
|
||||
|
@ -112,7 +123,10 @@ fn multiply<U: Mul<T>, T>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn divide<U: Div<T> + Default + PartialEq, T: Default + PartialEq>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, DVec2, f64)] numerator: U,
|
||||
/// The left-hand side of the division operation.
|
||||
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, DVec2, f64)]
|
||||
numerator: U,
|
||||
/// The right-hand side of the division operation.
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, f64, DVec2)]
|
||||
denominator: T,
|
||||
|
@ -130,10 +144,15 @@ where
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U,
|
||||
/// The left-hand side of the modulo operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
numerator: U,
|
||||
/// The right-hand side of the modulo operation.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)]
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
modulus: T,
|
||||
/// Ensures the result will always be positive, even if the numerator is negative.
|
||||
#[default(true)]
|
||||
always_positive: bool,
|
||||
) -> <U as Rem<T>>::Output {
|
||||
if always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus }
|
||||
|
@ -143,9 +162,12 @@ fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn exponent<U: Pow<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U,
|
||||
/// The base number that will be raised to the power.
|
||||
#[implementations(f64, f32, u32)]
|
||||
base: U,
|
||||
/// The power to which the base number will be raised.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
|
||||
#[implementations(f64, f32, u32)]
|
||||
power: T,
|
||||
) -> <U as num_traits::Pow<T>>::Output {
|
||||
base.pow(power)
|
||||
|
@ -155,9 +177,11 @@ fn exponent<U: Pow<T>, T>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn root<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number for which the nth root will be calculated.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
radicand: U,
|
||||
/// The degree of the root to be calculated. Square root is 2, cube root is 3, and so on.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
degree: U,
|
||||
|
@ -175,7 +199,10 @@ fn root<U: num_traits::float::Float>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn logarithm<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, f32)] value: U,
|
||||
/// The number for which the logarithm will be calculated.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
/// The base of the logarithm, such as 2 (binary), 10 (decimal), and e (natural logarithm).
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
base: U,
|
||||
|
@ -193,39 +220,83 @@ fn logarithm<U: num_traits::float::Float>(
|
|||
|
||||
/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
fn sine<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given angle.
|
||||
#[implementations(f64, f32)]
|
||||
theta: U,
|
||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { theta.sin() } else { theta.to_radians().sin() }
|
||||
}
|
||||
|
||||
/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
fn cosine<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given angle.
|
||||
#[implementations(f64, f32)]
|
||||
theta: U,
|
||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { theta.cos() } else { theta.to_radians().cos() }
|
||||
}
|
||||
|
||||
/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
fn tangent<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given angle.
|
||||
#[implementations(f64, f32)]
|
||||
theta: U,
|
||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { theta.tan() } else { theta.to_radians().tan() }
|
||||
}
|
||||
|
||||
/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
fn sine_inverse<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
/// Whether the resulting angle should be given in as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { value.asin() } else { value.asin().to_degrees() }
|
||||
}
|
||||
|
||||
/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
fn cosine_inverse<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
/// Whether the resulting angle should be given in as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { value.acos() } else { value.acos().to_degrees() }
|
||||
}
|
||||
|
||||
/// The inverse tangent trigonometric function (atan or atan2, depending on input type) calculates:
|
||||
/// atan: the angle whose tangent is the specified scalar number.
|
||||
/// atan2: the angle of a ray from the origin to the specified coordinate.
|
||||
///
|
||||
/// The resulting angle is always in the range [0°, 180°] or, in radians, [-π/2, π/2].
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent_inverse<U: TangentInverse>(_: impl Ctx, #[implementations(f64, f32, DVec2)] value: U, radians: bool) -> U::Output {
|
||||
fn tangent_inverse<U: TangentInverse>(
|
||||
_: impl Ctx,
|
||||
/// The given value for which the angle will be calculated.
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
value: U,
|
||||
/// Whether the resulting angle should be given in as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U::Output {
|
||||
value.atan(radians)
|
||||
}
|
||||
|
||||
|
@ -257,10 +328,13 @@ impl TangentInverse for DVec2 {
|
|||
fn random<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
/// Seed to determine the unique variation of which number will be generated.
|
||||
seed: u64,
|
||||
/// The smaller end of the range within which the random number will be generated.
|
||||
#[implementations(f64, f32)]
|
||||
#[default(0.)]
|
||||
min: U,
|
||||
/// The larger end of the range within which the random number will be generated.
|
||||
#[implementations(f64, f32)]
|
||||
#[default(1.)]
|
||||
max: U,
|
||||
|
@ -294,37 +368,73 @@ fn to_f64<U: num_traits::int::PrimInt>(_: impl Ctx, #[implementations(u32, u64)]
|
|||
|
||||
/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn round<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
fn round<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be rounded.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
) -> U {
|
||||
value.round()
|
||||
}
|
||||
|
||||
/// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole.
|
||||
/// The floor function (floor) rounds down an input value to the nearest whole number, unless the input number is already whole.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn floor<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
fn floor<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be rounded down.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
) -> U {
|
||||
value.floor()
|
||||
}
|
||||
|
||||
/// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole.
|
||||
/// The ceiling function (ceil) rounds up an input value to the nearest whole number, unless the input number is already whole.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn ceiling<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
fn ceiling<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be rounded up.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
) -> U {
|
||||
value.ceil()
|
||||
}
|
||||
|
||||
/// The absolute value function (abs) removes the negative sign from an input value, if present.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn absolute_value<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
fn absolute_value<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be made positive.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
) -> U {
|
||||
value.abs()
|
||||
}
|
||||
|
||||
/// The minimum function (min) picks the smaller of two numbers.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn min<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
fn min<T: std::cmp::PartialOrd>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers, of which the lesser will be returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers, of which the lesser will be returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
if value < other_value { value } else { other_value }
|
||||
}
|
||||
|
||||
/// The maximum function (max) picks the larger of two numbers.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn max<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
fn max<T: std::cmp::PartialOrd>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers, of which the greater will be returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers, of which the greater will be returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
if value > other_value { value } else { other_value }
|
||||
}
|
||||
|
||||
|
@ -332,9 +442,15 @@ fn max<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &
|
|||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn clamp<T: std::cmp::PartialOrd>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T,
|
||||
/// The number to be clamped, which will be restricted to the range between the minimum and maximum values.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
value: T,
|
||||
/// The left (smaller) side of the range. The output will never be less than this number.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
min: T,
|
||||
/// The right (greater) side of the range. The output will never be greater than this number.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
max: T,
|
||||
) -> T {
|
||||
let (min, max) = if min < max { (min, max) } else { (max, min) };
|
||||
if value < min {
|
||||
|
@ -350,8 +466,12 @@ fn clamp<T: std::cmp::PartialOrd>(
|
|||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn equals<U: std::cmp::PartialEq<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U,
|
||||
/// One of the two numbers to compare for equality.
|
||||
#[implementations(f64, f32, u32, DVec2, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers to compare for equality.
|
||||
#[implementations(f64, f32, u32, DVec2, &str)]
|
||||
other_value: U,
|
||||
) -> bool {
|
||||
other_value == value
|
||||
}
|
||||
|
@ -360,8 +480,12 @@ fn equals<U: std::cmp::PartialEq<T>, T>(
|
|||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn not_equals<U: std::cmp::PartialEq<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U,
|
||||
/// One of the two numbers to compare for inequality.
|
||||
#[implementations(f64, f32, u32, DVec2, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers to compare for inequality.
|
||||
#[implementations(f64, f32, u32, DVec2, &str)]
|
||||
other_value: U,
|
||||
) -> bool {
|
||||
other_value != value
|
||||
}
|
||||
|
@ -371,8 +495,13 @@ fn not_equals<U: std::cmp::PartialEq<T>, T>(
|
|||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn less_than<T: std::cmp::PartialOrd<T>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
|
||||
/// The number on the left-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
value: T,
|
||||
/// The number on the right-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
other_value: T,
|
||||
/// Uses the less-than-or-equal operation (<=) instead of the less-than operation (<).
|
||||
or_equal: bool,
|
||||
) -> bool {
|
||||
if or_equal { value <= other_value } else { value < other_value }
|
||||
|
@ -383,8 +512,13 @@ fn less_than<T: std::cmp::PartialOrd<T>>(
|
|||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn greater_than<T: std::cmp::PartialOrd<T>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
|
||||
/// The number on the left-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
value: T,
|
||||
/// The number on the right-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
other_value: T,
|
||||
/// Uses the greater-than-or-equal operation (>=) instead of the greater-than operation (>).
|
||||
or_equal: bool,
|
||||
) -> bool {
|
||||
if or_equal { value >= other_value } else { value > other_value }
|
||||
|
@ -392,19 +526,35 @@ fn greater_than<T: std::cmp::PartialOrd<T>>(
|
|||
|
||||
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||
fn logical_or(
|
||||
_: impl Ctx,
|
||||
/// One of the two boolean values, either of which may be true for the node to output true.
|
||||
value: bool,
|
||||
/// The other of the two boolean values, either of which may be true for the node to output true.
|
||||
other_value: bool,
|
||||
) -> bool {
|
||||
value || other_value
|
||||
}
|
||||
|
||||
/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||
fn logical_and(
|
||||
_: impl Ctx,
|
||||
/// One of the two boolean values, both of which must be true for the node to output true.
|
||||
value: bool,
|
||||
/// The other of the two boolean values, both of which must be true for the node to output true.
|
||||
other_value: bool,
|
||||
) -> bool {
|
||||
value && other_value
|
||||
}
|
||||
|
||||
/// The logical not operation (!) reverses true and false value of the input.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_not(_: impl Ctx, input: bool) -> bool {
|
||||
fn logical_not(
|
||||
_: impl Ctx,
|
||||
/// The boolean value to be reversed.
|
||||
input: bool,
|
||||
) -> bool {
|
||||
!input
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,10 @@ loading = ["serde_json"]
|
|||
dyn-any = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
graphene-path-bool = { workspace = true }
|
||||
graphene-brush = { workspace = true }
|
||||
graphene-application-io = { workspace = true }
|
||||
graphene-svg-renderer = { workspace = true }
|
||||
graphene-raster-nodes = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
log = { workspace = true }
|
||||
|
|
|
@ -363,17 +363,6 @@ impl NodeInput {
|
|||
NodeInput::Reflection(_) => false,
|
||||
}
|
||||
}
|
||||
/// Network node inputs in the document network are not displayed, but still exist in the compiled network
|
||||
pub fn is_exposed_to_frontend(&self, is_document_network: bool) -> bool {
|
||||
match self {
|
||||
NodeInput::Node { .. } => true,
|
||||
NodeInput::Value { exposed, .. } => *exposed,
|
||||
NodeInput::Network { .. } => !is_document_network,
|
||||
NodeInput::Inline(_) => false,
|
||||
NodeInput::Scope(_) => false,
|
||||
NodeInput::Reflection(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> Type {
|
||||
match self {
|
||||
|
@ -1250,24 +1239,28 @@ impl NodeNetwork {
|
|||
|
||||
/// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested.
|
||||
pub fn recursive_nodes(&self) -> RecursiveNodeIter<'_> {
|
||||
let nodes = self.nodes.iter().collect();
|
||||
let nodes = self.nodes.iter().map(|(id, node)| (id, node, Vec::new())).collect();
|
||||
RecursiveNodeIter { nodes }
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over all [`DocumentNode`]s, including ones that are deeply nested.
|
||||
pub struct RecursiveNodeIter<'a> {
|
||||
nodes: Vec<(&'a NodeId, &'a DocumentNode)>,
|
||||
nodes: Vec<(&'a NodeId, &'a DocumentNode, Vec<NodeId>)>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RecursiveNodeIter<'a> {
|
||||
type Item = (&'a NodeId, &'a DocumentNode);
|
||||
type Item = (&'a NodeId, &'a DocumentNode, Vec<NodeId>);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let node = self.nodes.pop()?;
|
||||
if let DocumentNodeImplementation::Network(network) = &node.1.implementation {
|
||||
self.nodes.extend(network.nodes.iter());
|
||||
let (current_id, node, path) = self.nodes.pop()?;
|
||||
if let DocumentNodeImplementation::Network(network) = &node.implementation {
|
||||
self.nodes.extend(network.nodes.iter().map(|(id, node)| {
|
||||
let mut nested_path = path.clone();
|
||||
nested_path.push(*current_id);
|
||||
(id, node, nested_path)
|
||||
}));
|
||||
}
|
||||
Some(node)
|
||||
Some((current_id, node, path))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ use dyn_any::DynAny;
|
|||
pub use dyn_any::StaticType;
|
||||
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||
use graphene_application_io::SurfaceFrame;
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::{BlendMode, LuminanceCalculation};
|
||||
use graphene_brush::brush_cache::BrushCache;
|
||||
use graphene_brush::brush_stroke::BrushStroke;
|
||||
use graphene_core::raster_types::CPU;
|
||||
use graphene_core::transform::ReferencePoint;
|
||||
use graphene_core::uuid::NodeId;
|
||||
|
@ -209,29 +209,29 @@ tagged_value! {
|
|||
#[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias document upgrade code
|
||||
GradientStops(graphene_core::vector::style::GradientStops),
|
||||
Font(graphene_core::text::Font),
|
||||
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
||||
BrushStrokes(Vec<BrushStroke>),
|
||||
BrushCache(BrushCache),
|
||||
DocumentNode(DocumentNode),
|
||||
Curve(graphene_core::raster::curve::Curve),
|
||||
Curve(graphene_raster_nodes::curve::Curve),
|
||||
Footprint(graphene_core::transform::Footprint),
|
||||
VectorModification(Box<graphene_core::vector::VectorModification>),
|
||||
FontCache(Arc<graphene_core::text::FontCache>),
|
||||
// ==========
|
||||
// ENUM TYPES
|
||||
// ==========
|
||||
BlendMode(BlendMode),
|
||||
LuminanceCalculation(LuminanceCalculation),
|
||||
BlendMode(graphene_core::blending::BlendMode),
|
||||
LuminanceCalculation(graphene_raster_nodes::adjustments::LuminanceCalculation),
|
||||
XY(graphene_core::extract_xy::XY),
|
||||
RedGreenBlue(graphene_core::raster::RedGreenBlue),
|
||||
RedGreenBlueAlpha(graphene_core::raster::RedGreenBlueAlpha),
|
||||
RedGreenBlue(graphene_raster_nodes::adjustments::RedGreenBlue),
|
||||
RedGreenBlueAlpha(graphene_raster_nodes::adjustments::RedGreenBlueAlpha),
|
||||
RealTimeMode(graphene_core::animation::RealTimeMode),
|
||||
NoiseType(graphene_core::raster::NoiseType),
|
||||
FractalType(graphene_core::raster::FractalType),
|
||||
CellularDistanceFunction(graphene_core::raster::CellularDistanceFunction),
|
||||
CellularReturnType(graphene_core::raster::CellularReturnType),
|
||||
DomainWarpType(graphene_core::raster::DomainWarpType),
|
||||
RelativeAbsolute(graphene_core::raster::RelativeAbsolute),
|
||||
SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice),
|
||||
NoiseType(graphene_raster_nodes::adjustments::NoiseType),
|
||||
FractalType(graphene_raster_nodes::adjustments::FractalType),
|
||||
CellularDistanceFunction(graphene_raster_nodes::adjustments::CellularDistanceFunction),
|
||||
CellularReturnType(graphene_raster_nodes::adjustments::CellularReturnType),
|
||||
DomainWarpType(graphene_raster_nodes::adjustments::DomainWarpType),
|
||||
RelativeAbsolute(graphene_raster_nodes::adjustments::RelativeAbsolute),
|
||||
SelectiveColorChoice(graphene_raster_nodes::adjustments::SelectiveColorChoice),
|
||||
GridType(graphene_core::vector::misc::GridType),
|
||||
ArcType(graphene_core::vector::misc::ArcType),
|
||||
MergeByDistanceAlgorithm(graphene_core::vector::misc::MergeByDistanceAlgorithm),
|
||||
|
|
35
node-graph/graster-nodes/Cargo.toml
Normal file
35
node-graph/graster-nodes/Cargo.toml
Normal file
|
@ -0,0 +1,35 @@
|
|||
[package]
|
||||
name = "graphene-raster-nodes"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "graphene raster data format"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
node-macro = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
specta = { workspace = true }
|
||||
image = { workspace = true }
|
||||
bytemuck = { workspace = true }
|
||||
ndarray = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
fastnoise-lite = { workspace = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
serde = { workspace = true, optional = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true }
|
||||
futures = { workspace = true }
|
|
@ -1,16 +1,16 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use crate::GraphicElement;
|
||||
use crate::blending::BlendMode;
|
||||
use crate::raster::curve::{CubicSplines, CurveManipulatorGroup};
|
||||
use crate::raster::curve::{Curve, ValueMapperNode};
|
||||
use crate::raster::image::Image;
|
||||
use crate::raster::{Channel, Color, Pixel};
|
||||
use crate::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use crate::registry::types::{Angle, Percentage, SignedPercentage};
|
||||
use crate::vector::style::GradientStops;
|
||||
use crate::{Ctx, Node};
|
||||
use crate::curve::CubicSplines;
|
||||
use dyn_any::DynAny;
|
||||
use graphene_core::Node;
|
||||
use graphene_core::blending::BlendMode;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::color::Pixel;
|
||||
use graphene_core::context::Ctx;
|
||||
use graphene_core::gradient::GradientStops;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::registry::types::{Angle, Percentage, SignedPercentage};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
@ -576,10 +576,7 @@ impl Adjust<Color> for GradientStops {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl Adjust<Color> for RasterDataTable<CPU>
|
||||
where
|
||||
GraphicElement: From<Image<Color>>,
|
||||
{
|
||||
impl Adjust<Color> for RasterDataTable<CPU> {
|
||||
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
for c in instance.instance.data_mut().data.iter_mut() {
|
||||
|
@ -1130,48 +1127,6 @@ async fn exposure<T: Adjust<Color>>(
|
|||
input
|
||||
}
|
||||
|
||||
const WINDOW_SIZE: usize = 1024;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn generate_curves<C: Channel + crate::raster::Linear>(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
|
||||
let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];
|
||||
let mut lut = vec![C::from_f64(0.); WINDOW_SIZE];
|
||||
let end = CurveManipulatorGroup {
|
||||
anchor: [1.; 2],
|
||||
handles: [curve.last_handle, [0.; 2]],
|
||||
};
|
||||
for sample in curve.manipulator_groups.iter().chain(std::iter::once(&end)) {
|
||||
let [x0, y0, x1, y1, x2, y2, x3, y3] = [pos[0], pos[1], param[0], param[1], sample.handles[0][0], sample.handles[0][1], sample.anchor[0], sample.anchor[1]].map(f64::from);
|
||||
|
||||
let bezier = Bezier::from_cubic_coordinates(x0, y0, x1, y1, x2, y2, x3, y3);
|
||||
|
||||
let [left, right] = [pos[0], sample.anchor[0]].map(|c| c.clamp(0., 1.));
|
||||
let lut_index_left: usize = (left * (lut.len() - 1) as f32).floor() as _;
|
||||
let lut_index_right: usize = (right * (lut.len() - 1) as f32).ceil() as _;
|
||||
for index in lut_index_left..=lut_index_right {
|
||||
let x = index as f64 / (lut.len() - 1) as f64;
|
||||
let y = if x <= x0 {
|
||||
y0
|
||||
} else if x >= x3 {
|
||||
y3
|
||||
} else {
|
||||
bezier.find_tvalues_for_x(x)
|
||||
.next()
|
||||
.map(|t| bezier.evaluate(TValue::Parametric(t.clamp(0., 1.))).y)
|
||||
// Fall back to a very bad approximation if Bezier-rs fails
|
||||
.unwrap_or_else(|| (x - x0) / (x3 - x0) * (y3 - y0) + y0)
|
||||
};
|
||||
lut[index] = C::from_f64(y);
|
||||
}
|
||||
|
||||
pos = sample.anchor;
|
||||
param = sample.handles[1];
|
||||
}
|
||||
ValueMapperNode::new(lut)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
fn color_overlay<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
|
@ -1224,10 +1179,10 @@ fn color_overlay<T: Adjust<Color>>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Color;
|
||||
use crate::blending::BlendMode;
|
||||
use crate::raster::image::Image;
|
||||
use crate::raster_types::{Raster, RasterDataTable};
|
||||
use graphene_core::blending::BlendMode;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster_types::{Raster, RasterDataTable};
|
||||
|
||||
#[tokio::test]
|
||||
async fn color_overlay_multiply() {
|
|
@ -1,6 +1,7 @@
|
|||
use super::{Channel, Linear, LuminanceMut};
|
||||
use crate::Node;
|
||||
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||
use graphene_core::Node;
|
||||
use graphene_core::color::{Channel, Linear, LuminanceMut};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Add, Mul, Sub};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DynAny, specta::Type, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -23,8 +24,8 @@ impl Default for Curve {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Curve {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
impl Hash for Curve {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.manipulator_groups.hash(state);
|
||||
[self.first_handle, self.last_handle].iter().flatten().for_each(|f| f.to_bits().hash(state));
|
||||
}
|
||||
|
@ -36,8 +37,8 @@ pub struct CurveManipulatorGroup {
|
|||
pub handles: [[f32; 2]; 2],
|
||||
}
|
||||
|
||||
impl std::hash::Hash for CurveManipulatorGroup {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
impl Hash for CurveManipulatorGroup {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
for c in self.handles.iter().chain([&self.anchor]).flatten() {
|
||||
c.to_bits().hash(state);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use graph_craft::proto::types::Percentage;
|
||||
use graphene_core::Ctx;
|
||||
use graphene_core::context::Ctx;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::registry::types::Percentage;
|
||||
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
|
||||
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
|
||||
use std::cmp::{max, min};
|
||||
|
@ -15,7 +15,7 @@ async fn dehaze(_: impl Ctx, image_frame: RasterDataTable<CPU>, strength: Percen
|
|||
// Prepare the image data for processing
|
||||
let image_data = bytemuck::cast_vec(image.data.clone());
|
||||
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type.");
|
||||
let dynamic_image: image::DynamicImage = image_buffer.into();
|
||||
let dynamic_image: DynamicImage = image_buffer.into();
|
||||
|
||||
// Run the dehaze algorithm
|
||||
let dehazed_dynamic_image = dehaze_image(dynamic_image, strength / 100.);
|
|
@ -1,8 +1,9 @@
|
|||
use graph_craft::proto::types::PixelLength;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::context::Ctx;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster::{Bitmap, BitmapMut};
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::{Color, Ctx};
|
||||
use graphene_core::registry::types::PixelLength;
|
||||
|
||||
/// Blurs the image with a Gaussian or blur kernel filter.
|
||||
#[node_macro::node(category("Raster: Filter"))]
|
46
node-graph/graster-nodes/src/generate_curves.rs
Normal file
46
node-graph/graster-nodes/src/generate_curves.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
//! requires bezier-rs
|
||||
|
||||
use crate::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
use graphene_core::color::{Channel, Linear};
|
||||
use graphene_core::context::Ctx;
|
||||
|
||||
const WINDOW_SIZE: usize = 1024;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn generate_curves<C: Channel + Linear>(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
|
||||
let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];
|
||||
let mut lut = vec![C::from_f64(0.); WINDOW_SIZE];
|
||||
let end = CurveManipulatorGroup {
|
||||
anchor: [1.; 2],
|
||||
handles: [curve.last_handle, [0.; 2]],
|
||||
};
|
||||
for sample in curve.manipulator_groups.iter().chain(std::iter::once(&end)) {
|
||||
let [x0, y0, x1, y1, x2, y2, x3, y3] = [pos[0], pos[1], param[0], param[1], sample.handles[0][0], sample.handles[0][1], sample.anchor[0], sample.anchor[1]].map(f64::from);
|
||||
|
||||
let bezier = Bezier::from_cubic_coordinates(x0, y0, x1, y1, x2, y2, x3, y3);
|
||||
|
||||
let [left, right] = [pos[0], sample.anchor[0]].map(|c| c.clamp(0., 1.));
|
||||
let lut_index_left: usize = (left * (lut.len() - 1) as f32).floor() as _;
|
||||
let lut_index_right: usize = (right * (lut.len() - 1) as f32).ceil() as _;
|
||||
for index in lut_index_left..=lut_index_right {
|
||||
let x = index as f64 / (lut.len() - 1) as f64;
|
||||
let y = if x <= x0 {
|
||||
y0
|
||||
} else if x >= x3 {
|
||||
y3
|
||||
} else {
|
||||
bezier.find_tvalues_for_x(x)
|
||||
.next()
|
||||
.map(|t| bezier.evaluate(TValue::Parametric(t.clamp(0., 1.))).y)
|
||||
// Fall back to a very bad approximation if Bezier-rs fails
|
||||
.unwrap_or_else(|| (x - x0) / (x3 - x0) * (y3 - y0) + y0)
|
||||
};
|
||||
lut[index] = C::from_f64(y);
|
||||
}
|
||||
|
||||
pos = sample.anchor;
|
||||
param = sample.handles[1];
|
||||
}
|
||||
ValueMapperNode::new(lut)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use graphene_core::color::Color;
|
||||
use graphene_core::context::Ctx;
|
||||
use graphene_core::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_core::{Color, Ctx};
|
||||
|
||||
#[node_macro::node(category("Color"))]
|
||||
async fn image_color_palette(
|
7
node-graph/graster-nodes/src/lib.rs
Normal file
7
node-graph/graster-nodes/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
pub mod adjustments;
|
||||
pub mod curve;
|
||||
pub mod dehaze;
|
||||
pub mod filter;
|
||||
pub mod generate_curves;
|
||||
pub mod image_color_palette;
|
||||
pub mod std_nodes;
|
|
@ -1,12 +1,17 @@
|
|||
use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, NoiseType};
|
||||
use dyn_any::DynAny;
|
||||
use fastnoise_lite;
|
||||
use glam::{DAffine2, DVec2, Vec2};
|
||||
use graphene_core::blending::AlphaBlending;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
|
||||
use graphene_core::context::{Ctx, ExtractFootprint};
|
||||
use graphene_core::instances::Instance;
|
||||
use graphene_core::math::bbox::Bbox;
|
||||
pub use graphene_core::raster::*;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster::{Bitmap, BitmapMut};
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::{Ctx, ExtractFootprint};
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use std::fmt::Debug;
|
||||
|
@ -25,7 +30,7 @@ impl From<std::io::Error> for Error {
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Debug: Raster"))]
|
||||
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
let mut result_table = RasterDataTable::default();
|
||||
|
||||
for mut image_frame_instance in image_frame.instance_iter() {
|
||||
|
@ -92,7 +97,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDa
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Channels"))]
|
||||
fn combine_channels(
|
||||
pub fn combine_channels(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[expose] red: RasterDataTable<CPU>,
|
||||
|
@ -181,7 +186,7 @@ fn combine_channels(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn mask(
|
||||
pub fn mask(
|
||||
_: impl Ctx,
|
||||
/// The image to be masked.
|
||||
image: RasterDataTable<CPU>,
|
||||
|
@ -231,7 +236,7 @@ fn mask(
|
|||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAffine2) -> RasterDataTable<CPU> {
|
||||
pub fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAffine2) -> RasterDataTable<CPU> {
|
||||
let mut result_table = RasterDataTable::default();
|
||||
|
||||
for mut image_instance in image.instance_iter() {
|
||||
|
@ -284,7 +289,7 @@ fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAff
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Debug: Raster"))]
|
||||
fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTable<CPU> {
|
||||
pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTable<CPU> {
|
||||
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
|
||||
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
|
||||
|
||||
|
@ -301,13 +306,13 @@ fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTabl
|
|||
|
||||
/// Constructs a raster image.
|
||||
#[node_macro::node(category(""))]
|
||||
fn image_value(_: impl Ctx, _primary: (), image: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
pub fn image_value(_: impl Ctx, _primary: (), image: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
image
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Pattern"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn noise_pattern(
|
||||
pub fn noise_pattern(
|
||||
ctx: impl ExtractFootprint + Ctx,
|
||||
_primary: (),
|
||||
clip: bool,
|
||||
|
@ -463,7 +468,7 @@ fn noise_pattern(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Pattern"))]
|
||||
fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> {
|
||||
pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> {
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
|
|
@ -32,6 +32,8 @@ graphene-path-bool = { workspace = true }
|
|||
graphene-math-nodes = { workspace = true }
|
||||
graphene-svg-renderer = { workspace = true }
|
||||
graphene-application-io = { workspace = true }
|
||||
graphene-raster-nodes = { workspace = true }
|
||||
graphene-brush = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
fastnoise-lite = { workspace = true }
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
pub mod any;
|
||||
pub mod brush;
|
||||
pub mod dehaze;
|
||||
pub mod filter;
|
||||
pub mod http;
|
||||
pub mod image_color_palette;
|
||||
pub mod raster;
|
||||
pub mod text;
|
||||
#[cfg(feature = "wasm")]
|
||||
pub mod wasm_application_io;
|
||||
|
||||
pub use graphene_application_io as application_io;
|
||||
pub use graphene_brush as brush;
|
||||
pub use graphene_core::vector;
|
||||
pub use graphene_core::*;
|
||||
pub use graphene_math_nodes as math_nodes;
|
||||
pub use graphene_path_bool as path_bool;
|
||||
pub use graphene_raster_nodes as raster_nodes;
|
||||
|
||||
/// stop gap solution until all `Quad` and `Rect` paths have been replaced with their absolute ones
|
||||
/// stop gap solutions until all paths have been replaced with their absolute ones
|
||||
pub mod renderer {
|
||||
pub use graphene_core::math::quad::Quad;
|
||||
pub use graphene_core::math::rect::Rect;
|
||||
pub use graphene_svg_renderer::*;
|
||||
}
|
||||
|
||||
pub mod raster {
|
||||
pub use graphene_core::raster::*;
|
||||
pub use graphene_raster_nodes::adjustments::*;
|
||||
pub use graphene_raster_nodes::*;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ static NODE_ID: AtomicU64 = AtomicU64::new(0);
|
|||
|
||||
pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStream2> {
|
||||
let ParsedNodeFn {
|
||||
vis,
|
||||
attributes,
|
||||
fn_name,
|
||||
struct_name,
|
||||
|
@ -345,7 +346,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
/// Underlying implementation for [#struct_name]
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
|
||||
#vis #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
|
||||
|
||||
#[automatically_derived]
|
||||
impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*>
|
||||
|
|
|
@ -7,8 +7,8 @@ use syn::punctuated::Punctuated;
|
|||
use syn::spanned::Spanned;
|
||||
use syn::token::{Comma, RArrow};
|
||||
use syn::{
|
||||
AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitInt, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, WhereClause,
|
||||
parse_quote,
|
||||
AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitInt, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, Visibility,
|
||||
WhereClause, parse_quote,
|
||||
};
|
||||
|
||||
use crate::codegen::generate_node_code;
|
||||
|
@ -22,6 +22,7 @@ pub(crate) struct Implementation {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParsedNodeFn {
|
||||
pub(crate) vis: Visibility,
|
||||
pub(crate) attributes: NodeFnAttributes,
|
||||
pub(crate) fn_name: Ident,
|
||||
pub(crate) struct_name: Ident,
|
||||
|
@ -263,6 +264,7 @@ fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<ParsedNo
|
|||
let attributes = syn::parse2::<NodeFnAttributes>(attr.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse node_fn attributes: {}", e)))?;
|
||||
let input_fn = syn::parse2::<ItemFn>(item.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse function: {}. Make sure it's a valid Rust function.", e)))?;
|
||||
|
||||
let vis = input_fn.vis;
|
||||
let fn_name = input_fn.sig.ident.clone();
|
||||
let struct_name = format_ident!("{}", fn_name.to_string().to_case(Case::Pascal));
|
||||
let mod_name = fn_name.clone();
|
||||
|
@ -297,6 +299,7 @@ fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<ParsedNo
|
|||
.fold(String::new(), |acc, b| acc + &b + "\n");
|
||||
|
||||
Ok(ParsedNodeFn {
|
||||
vis,
|
||||
attributes,
|
||||
fn_name,
|
||||
struct_name,
|
||||
|
@ -748,6 +751,7 @@ mod tests {
|
|||
|
||||
let parsed = parse_node_fn(attr, input).unwrap();
|
||||
let expected = ParsedNodeFn {
|
||||
vis: Visibility::Inherited,
|
||||
attributes: NodeFnAttributes {
|
||||
category: Some(parse_quote!("Math: Arithmetic")),
|
||||
display_name: None,
|
||||
|
@ -808,6 +812,7 @@ mod tests {
|
|||
|
||||
let parsed = parse_node_fn(attr, input).unwrap();
|
||||
let expected = ParsedNodeFn {
|
||||
vis: Visibility::Inherited,
|
||||
attributes: NodeFnAttributes {
|
||||
category: Some(parse_quote!("General")),
|
||||
display_name: None,
|
||||
|
@ -879,6 +884,7 @@ mod tests {
|
|||
|
||||
let parsed = parse_node_fn(attr, input).unwrap();
|
||||
let expected = ParsedNodeFn {
|
||||
vis: Visibility::Inherited,
|
||||
attributes: NodeFnAttributes {
|
||||
category: Some(parse_quote!("Vector: Shape")),
|
||||
display_name: None,
|
||||
|
@ -935,6 +941,7 @@ mod tests {
|
|||
|
||||
let parsed = parse_node_fn(attr, input).unwrap();
|
||||
let expected = ParsedNodeFn {
|
||||
vis: Visibility::Inherited,
|
||||
attributes: NodeFnAttributes {
|
||||
category: Some(parse_quote!("Raster: Adjustment")),
|
||||
display_name: None,
|
||||
|
@ -1003,6 +1010,7 @@ mod tests {
|
|||
|
||||
let parsed = parse_node_fn(attr, input).unwrap();
|
||||
let expected = ParsedNodeFn {
|
||||
vis: Visibility::Inherited,
|
||||
attributes: NodeFnAttributes {
|
||||
category: Some(parse_quote!("Math: Arithmetic")),
|
||||
display_name: None,
|
||||
|
@ -1059,6 +1067,7 @@ mod tests {
|
|||
|
||||
let parsed = parse_node_fn(attr, input).unwrap();
|
||||
let expected = ParsedNodeFn {
|
||||
vis: Visibility::Inherited,
|
||||
attributes: NodeFnAttributes {
|
||||
category: Some(parse_quote!("IO")),
|
||||
display_name: None,
|
||||
|
@ -1115,6 +1124,7 @@ mod tests {
|
|||
|
||||
let parsed = parse_node_fn(attr, input).unwrap();
|
||||
let expected = ParsedNodeFn {
|
||||
vis: Visibility::Inherited,
|
||||
attributes: NodeFnAttributes {
|
||||
category: Some(parse_quote!("Custom")),
|
||||
display_name: Some(parse_quote!("CustomNode2")),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue