mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
General purpose value node
This commit is contained in:
parent
ab346245f5
commit
4075003505
9 changed files with 339 additions and 92 deletions
|
@ -101,6 +101,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."),
|
description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."),
|
||||||
properties: Some("identity_properties"),
|
properties: Some("identity_properties"),
|
||||||
},
|
},
|
||||||
|
DocumentNodeDefinition {
|
||||||
|
identifier: "Value",
|
||||||
|
category: "General",
|
||||||
|
node_template: NodeTemplate {
|
||||||
|
document_node: DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::proto("graphene_core::any::ValueNode"),
|
||||||
|
manual_composition: Some(generic!(T)),
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::None, false)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
input_metadata: vec![("", "Value").into()],
|
||||||
|
output_names: vec!["Out".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: Cow::Borrowed("Returns the value stored in its input"),
|
||||||
|
properties: Some("value_properties"),
|
||||||
|
},
|
||||||
// TODO: Auto-generate this from its proto node macro
|
// TODO: Auto-generate this from its proto node macro
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Monitor",
|
identifier: "Monitor",
|
||||||
|
@ -2149,6 +2168,7 @@ fn static_node_properties() -> NodeProperties {
|
||||||
"monitor_properties".to_string(),
|
"monitor_properties".to_string(),
|
||||||
Box::new(|_node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it.")),
|
Box::new(|_node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it.")),
|
||||||
);
|
);
|
||||||
|
map.insert("value_properties".to_string(), Box::new(node_properties::value_properties));
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,10 @@ pub enum NodeGraphMessage {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
alias: String,
|
alias: String,
|
||||||
},
|
},
|
||||||
|
SetReference {
|
||||||
|
node_id: NodeId,
|
||||||
|
reference: Option<String>,
|
||||||
|
},
|
||||||
SetToNodeOrLayer {
|
SetToNodeOrLayer {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
is_layer: bool,
|
is_layer: bool,
|
||||||
|
|
|
@ -498,7 +498,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
log::error!("Could not get center of selected_nodes");
|
log::error!("Could not get center of selected_nodes");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let center_of_selected_nodes_grid_space = IVec2::new((center_of_selected_nodes.x / 24. + 0.5).floor() as i32, (center_of_selected_nodes.y / 24. + 0.5).floor() as i32);
|
let center_of_selected_nodes_grid_space = IVec2::new(
|
||||||
|
(center_of_selected_nodes.x / GRID_SIZE as f64 + 0.5).floor() as i32,
|
||||||
|
(center_of_selected_nodes.y / GRID_SIZE as f64 + 0.5).floor() as i32,
|
||||||
|
);
|
||||||
default_node_template.persistent_node_metadata.node_type_metadata = NodeTypePersistentMetadata::node(center_of_selected_nodes_grid_space - IVec2::new(3, 1));
|
default_node_template.persistent_node_metadata.node_type_metadata = NodeTypePersistentMetadata::node(center_of_selected_nodes_grid_space - IVec2::new(3, 1));
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::InsertNode {
|
responses.add(NodeGraphMessage::InsertNode {
|
||||||
|
@ -603,7 +606,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
log::error!("Could not get network metadata in PointerDown");
|
log::error!("Could not get network metadata in PointerDown");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
self.disconnecting = None;
|
||||||
let click = ipp.mouse.position;
|
let click = ipp.mouse.position;
|
||||||
|
|
||||||
let node_graph_point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);
|
let node_graph_point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);
|
||||||
|
@ -938,8 +941,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
};
|
};
|
||||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) });
|
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) });
|
||||||
}
|
}
|
||||||
} else if self.disconnecting.is_some() {
|
}
|
||||||
// Disconnecting with no upstream node, create new value node.
|
// Dragging from an exposed value input
|
||||||
|
else if self.disconnecting.is_some() {
|
||||||
let to_connector = network_interface.input_connector_from_click(ipp.mouse.position, selection_network_path);
|
let to_connector = network_interface.input_connector_from_click(ipp.mouse.position, selection_network_path);
|
||||||
if let Some(to_connector) = &to_connector {
|
if let Some(to_connector) = &to_connector {
|
||||||
let Some(input_position) = network_interface.input_position(to_connector, selection_network_path) else {
|
let Some(input_position) = network_interface.input_position(to_connector, selection_network_path) else {
|
||||||
|
@ -948,58 +952,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
};
|
};
|
||||||
self.wire_in_progress_to_connector = Some(input_position);
|
self.wire_in_progress_to_connector = Some(input_position);
|
||||||
}
|
}
|
||||||
// Not hovering over a node input or node output, insert the node
|
// Not hovering over a node input or node output, create the value node if alt is pressed
|
||||||
else {
|
else if ipp.keyboard.get(Key::Alt as usize) {
|
||||||
// Disconnect if the wire was previously connected to an input
|
self.preview_on_mouse_up = None;
|
||||||
if let Some(disconnecting) = self.disconnecting.take() {
|
self.create_value_node(network_interface, point, breadcrumb_network_path, responses);
|
||||||
let mut position = if let Some(to_connector) = self.wire_in_progress_to_connector { to_connector } else { point };
|
} else {
|
||||||
// Offset to drag from center of node
|
//TODO: Start creating wire
|
||||||
position = position - DVec2::new(24. * 3., 24.);
|
|
||||||
|
|
||||||
// Offset to account for division rounding error
|
|
||||||
if position.x < 0. {
|
|
||||||
position.x = position.x - 1.;
|
|
||||||
}
|
|
||||||
if position.y < 0. {
|
|
||||||
position.y = position.y - 1.;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(input) = network_interface.take_input(&disconnecting, breadcrumb_network_path) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let drag_start = DragStart {
|
|
||||||
start_x: point.x,
|
|
||||||
start_y: point.y,
|
|
||||||
round_x: 0,
|
|
||||||
round_y: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.drag_start = Some((drag_start, false));
|
|
||||||
self.node_has_moved_in_drag = false;
|
|
||||||
self.update_node_graph_hints(responses);
|
|
||||||
|
|
||||||
let node_id = NodeId::new();
|
|
||||||
responses.add(NodeGraphMessage::CreateNodeFromContextMenu {
|
|
||||||
node_id: Some(node_id),
|
|
||||||
node_type: "Identity".to_string(),
|
|
||||||
xy: Some(((position.x / 24.) as i32, (position.y / 24.) as i32)),
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SetInput {
|
|
||||||
input_connector: InputConnector::node(node_id, 0),
|
|
||||||
input,
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::CreateWire {
|
|
||||||
output_connector: OutputConnector::Node { node_id, output_index: 0 },
|
|
||||||
input_connector: disconnecting,
|
|
||||||
});
|
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] });
|
|
||||||
// Update the frontend that the node is disconnected
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if let Some((drag_start, dragged)) = &mut self.drag_start {
|
} else if let Some((drag_start, dragged)) = &mut self.drag_start {
|
||||||
if drag_start.start_x != point.x || drag_start.start_y != point.y {
|
if drag_start.start_x != point.x || drag_start.start_y != point.y {
|
||||||
|
@ -1020,7 +978,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut graph_delta = IVec2::new(((point.x - drag_start.start_x) / 24.).round() as i32, ((point.y - drag_start.start_y) / 24.).round() as i32);
|
let mut graph_delta = IVec2::new(
|
||||||
|
((point.x - drag_start.start_x) / GRID_SIZE as f64).round() as i32,
|
||||||
|
((point.y - drag_start.start_y) / GRID_SIZE as f64).round() as i32,
|
||||||
|
);
|
||||||
let previous_round_x = drag_start.round_x;
|
let previous_round_x = drag_start.round_x;
|
||||||
let previous_round_y = drag_start.round_y;
|
let previous_round_y = drag_start.round_y;
|
||||||
|
|
||||||
|
@ -1407,6 +1368,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p));
|
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();
|
let mut nodes = Vec::new();
|
||||||
|
|
||||||
for node_id in &self.frontend_nodes {
|
for node_id in &self.frontend_nodes {
|
||||||
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
|
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);
|
log::error!("Could not get bbox for node: {:?}", node_id);
|
||||||
|
@ -1418,6 +1380,17 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always send nodes with errors
|
||||||
|
for error in &self.node_graph_errors {
|
||||||
|
let Some((id, path)) = error.node_path.split_last() else {
|
||||||
|
log::error!("Could not get node path in error: {:?}", error);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if breadcrumb_network_path == path {
|
||||||
|
nodes.push(*id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
|
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SendGraph => {
|
NodeGraphMessage::SendGraph => {
|
||||||
|
@ -1456,7 +1429,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
input,
|
input,
|
||||||
});
|
});
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
if !(network_interface.reference(&node_id, selection_network_path).is_none() || input_index == 0) && network_interface.connected_to_output(&node_id, selection_network_path) {
|
if network_interface.connected_to_output(&node_id, selection_network_path) {
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1538,6 +1511,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SendWires);
|
responses.add(NodeGraphMessage::SendWires);
|
||||||
}
|
}
|
||||||
|
NodeGraphMessage::SetReference { node_id, reference } => {
|
||||||
|
network_interface.set_reference(&node_id, breadcrumb_network_path, reference);
|
||||||
|
}
|
||||||
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
|
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
|
||||||
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
|
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
|
||||||
return;
|
return;
|
||||||
|
@ -2217,6 +2193,69 @@ impl NodeGraphMessageHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_value_node(&mut self, network_interface: &mut NodeNetworkInterface, point: DVec2, breadcrumb_network_path: &[NodeId], responses: &mut VecDeque<Message>) {
|
||||||
|
let Some(disconnecting) = self.disconnecting.take() else {
|
||||||
|
log::error!("To connector must be initialized to create a value node");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(mut position) = self.wire_in_progress_to_connector.take() else {
|
||||||
|
log::error!("To connector must be initialized to create a value node");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Offset node insertion 3 grid spaces left and 1 grid space up so the center of the node is dragged
|
||||||
|
position = position - DVec2::new(GRID_SIZE as f64 * 3., GRID_SIZE as f64);
|
||||||
|
|
||||||
|
// Offset to account for division rounding error and place the selected node to the top left of the input
|
||||||
|
if position.x < 0. {
|
||||||
|
position.x = position.x - 1.;
|
||||||
|
}
|
||||||
|
if position.y < 0. {
|
||||||
|
position.y = position.y - 1.;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(mut input) = network_interface.take_input(&disconnecting, breadcrumb_network_path) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match &mut input {
|
||||||
|
NodeInput::Value { exposed, .. } => *exposed = false,
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
|
||||||
|
let drag_start = DragStart {
|
||||||
|
start_x: point.x,
|
||||||
|
start_y: point.y,
|
||||||
|
round_x: 0,
|
||||||
|
round_y: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.drag_start = Some((drag_start, false));
|
||||||
|
self.node_has_moved_in_drag = false;
|
||||||
|
self.update_node_graph_hints(responses);
|
||||||
|
|
||||||
|
let node_id = NodeId::new();
|
||||||
|
responses.add(NodeGraphMessage::CreateNodeFromContextMenu {
|
||||||
|
node_id: Some(node_id),
|
||||||
|
node_type: "Value".to_string(),
|
||||||
|
xy: Some(((position.x / GRID_SIZE as f64) as i32, (position.y / GRID_SIZE as f64) as i32)),
|
||||||
|
add_transaction: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::SetInput {
|
||||||
|
input_connector: InputConnector::node(node_id, 0),
|
||||||
|
input,
|
||||||
|
});
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::CreateWire {
|
||||||
|
output_connector: OutputConnector::Node { node_id, output_index: 0 },
|
||||||
|
input_connector: disconnecting,
|
||||||
|
});
|
||||||
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] });
|
||||||
|
// Update the frontend that the node is disconnected
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
|
}
|
||||||
|
|
||||||
fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {
|
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
|
let mut added_wires = network_interface
|
||||||
.node_graph_input_connectors(breadcrumb_network_path)
|
.node_graph_input_connectors(breadcrumb_network_path)
|
||||||
|
|
|
@ -9,7 +9,7 @@ use choice::enum_choice;
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||||
use graph_craft::Type;
|
use graph_craft::Type;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::{TaggedValue, TaggedValueChoice};
|
||||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||||
use graphene_std::animation::RealTimeMode;
|
use graphene_std::animation::RealTimeMode;
|
||||||
use graphene_std::extract_xy::XY;
|
use graphene_std::extract_xy::XY;
|
||||||
|
@ -89,7 +89,7 @@ pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Widget
|
||||||
description,
|
description,
|
||||||
input_type,
|
input_type,
|
||||||
blank_assist,
|
blank_assist,
|
||||||
exposeable,
|
exposable: exposeable,
|
||||||
} = parameter_widgets_info;
|
} = parameter_widgets_info;
|
||||||
|
|
||||||
let Some(document_node) = document_node else {
|
let Some(document_node) = document_node else {
|
||||||
|
@ -122,6 +122,7 @@ pub(crate) fn property_from_type(
|
||||||
unit: Option<&str>,
|
unit: Option<&str>,
|
||||||
display_decimal_places: Option<u32>,
|
display_decimal_places: Option<u32>,
|
||||||
step: Option<f64>,
|
step: Option<f64>,
|
||||||
|
exposable: bool,
|
||||||
context: &mut NodePropertiesContext,
|
context: &mut NodePropertiesContext,
|
||||||
) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> {
|
) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> {
|
||||||
let (mut number_min, mut number_max, range) = number_options;
|
let (mut number_min, mut number_max, range) = number_options;
|
||||||
|
@ -144,7 +145,8 @@ pub(crate) fn property_from_type(
|
||||||
let min = |x: f64| number_min.unwrap_or(x);
|
let min = |x: f64| number_min.unwrap_or(x);
|
||||||
let max = |x: f64| number_max.unwrap_or(x);
|
let max = |x: f64| number_max.unwrap_or(x);
|
||||||
|
|
||||||
let default_info = ParameterWidgetsInfo::new(node_id, index, true, context);
|
let mut default_info = ParameterWidgetsInfo::new(node_id, index, true, context);
|
||||||
|
default_info.exposable = exposable;
|
||||||
|
|
||||||
let mut extra_widgets = vec![];
|
let mut extra_widgets = vec![];
|
||||||
let widgets = match ty {
|
let widgets = match ty {
|
||||||
|
@ -252,8 +254,8 @@ pub(crate) fn property_from_type(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(),
|
Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(),
|
||||||
Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, context),
|
Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, exposable, context),
|
||||||
Type::Future(out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, context),
|
Type::Future(out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, exposable, context),
|
||||||
};
|
};
|
||||||
|
|
||||||
extra_widgets.push(widgets);
|
extra_widgets.push(widgets);
|
||||||
|
@ -1044,7 +1046,7 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
||||||
|
|
||||||
let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default());
|
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);
|
let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context);
|
||||||
parameter_info.exposeable = false;
|
parameter_info.exposable = false;
|
||||||
let output_channel = enum_choice::<RedGreenBlue>().for_socket(parameter_info).property_row();
|
let output_channel = enum_choice::<RedGreenBlue>().for_socket(parameter_info).property_row();
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let document_node = match get_document_node(node_id, context) {
|
||||||
|
@ -1101,7 +1103,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
||||||
use graphene_std::raster::selective_color::*;
|
use graphene_std::raster::selective_color::*;
|
||||||
|
|
||||||
let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context);
|
let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context);
|
||||||
default_info.exposeable = false;
|
default_info.exposable = false;
|
||||||
let colors = enum_choice::<SelectiveColorChoice>().for_socket(default_info).property_row();
|
let colors = enum_choice::<SelectiveColorChoice>().for_socket(default_info).property_row();
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let document_node = match get_document_node(node_id, context) {
|
||||||
|
@ -1456,7 +1458,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
||||||
let mut input_types = implementations
|
let mut input_types = implementations
|
||||||
.keys()
|
.keys()
|
||||||
.filter_map(|item| item.inputs.get(input_index))
|
.filter_map(|item| item.inputs.get(input_index))
|
||||||
.filter(|ty| property_from_type(node_id, input_index, ty, number_options, unit_suffix, display_decimal_places, step, context).is_ok())
|
.filter(|ty| property_from_type(node_id, input_index, ty, number_options, unit_suffix, display_decimal_places, step, true, context).is_ok())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
input_types.sort_by_key(|ty| ty.type_name());
|
input_types.sort_by_key(|ty| ty.type_name());
|
||||||
let input_type = input_types.first().cloned();
|
let input_type = input_types.first().cloned();
|
||||||
|
@ -1470,7 +1472,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
||||||
_ => context.network_interface.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path).0,
|
_ => context.network_interface.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path).0,
|
||||||
};
|
};
|
||||||
|
|
||||||
property_from_type(node_id, input_index, &input_type, number_options, unit_suffix, display_decimal_places, step, context).unwrap_or_else(|value| value)
|
property_from_type(node_id, input_index, &input_type, number_options, unit_suffix, display_decimal_places, step, true, context).unwrap_or_else(|value| value)
|
||||||
});
|
});
|
||||||
|
|
||||||
layout.extend(row);
|
layout.extend(row);
|
||||||
|
@ -1835,6 +1837,77 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn value_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
|
let Some(document_node) = context.network_interface.document_node(&node_id, context.selection_network_path) else {
|
||||||
|
log::warn!("Value properties failed to be built because its document node is invalid.");
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
let Some(input) = document_node.inputs.get(0) else {
|
||||||
|
log::warn!("Secondary value input could not be found on value properties");
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
let mut select_value_widgets = Vec::new();
|
||||||
|
|
||||||
|
select_value_widgets.push(TextLabel::new("Value type: ").tooltip("Select the type the value node should output").widget_holder());
|
||||||
|
|
||||||
|
let Some(input_value) = input.as_non_exposed_value() else {
|
||||||
|
log::error!("Primary value node input should be a hidden value input");
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
let input_type = input_value.ty();
|
||||||
|
|
||||||
|
let Some(choice) = TaggedValueChoice::from_tagged_value(input_value) else {
|
||||||
|
log::error!("Tagged value in value node should always have a choice. input: {:?}", input);
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
// let committer = || {|_| {
|
||||||
|
// let messages = vec![
|
||||||
|
// DocumentMessage::AddTransaction.into(),
|
||||||
|
// NodeGraphMessage::RunDocumentGraph.into(),
|
||||||
|
// ];
|
||||||
|
// Message::Batched(messages.into_boxed_slice()).into()
|
||||||
|
// };
|
||||||
|
let updater = || {
|
||||||
|
move |v: &TaggedValueChoice| {
|
||||||
|
let value = v.to_tagged_value();
|
||||||
|
|
||||||
|
let messages = vec![NodeGraphMessage::SetInputValue { node_id, input_index: 0, value }.into(), NodeGraphMessage::SendGraph.into()];
|
||||||
|
|
||||||
|
Message::Batched(messages.into_boxed_slice()).into()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let value_dropdown = enum_choice::<TaggedValueChoice>().dropdown_menu(choice, updater, || commit_value);
|
||||||
|
select_value_widgets.extend_from_slice(&[Separator::new(SeparatorType::Unrelated).widget_holder(), value_dropdown]);
|
||||||
|
|
||||||
|
let mut type_widgets = match property_from_type(node_id, 0, &input_type, (None, None, None), None, None, None, false, context) {
|
||||||
|
Ok(type_widgets) => type_widgets,
|
||||||
|
Err(type_widgets) => type_widgets,
|
||||||
|
};
|
||||||
|
|
||||||
|
if type_widgets.len() <= 0 {
|
||||||
|
log::error!("Could not generate type widgets for value node");
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let LayoutGroup::Row { widgets: mut type_widgets } = type_widgets.remove(0) else {
|
||||||
|
log::error!("Could not get autogenerated widgets for value node");
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
if type_widgets.len() <= 2 {
|
||||||
|
log::error!("Could not generate type widgets for value node");
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove the name and blank assist
|
||||||
|
type_widgets.remove(0);
|
||||||
|
type_widgets.remove(0);
|
||||||
|
|
||||||
|
vec![LayoutGroup::Row { widgets: select_value_widgets }, LayoutGroup::Row { widgets: type_widgets }]
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ParameterWidgetsInfo<'a> {
|
pub struct ParameterWidgetsInfo<'a> {
|
||||||
document_node: Option<&'a DocumentNode>,
|
document_node: Option<&'a DocumentNode>,
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
|
@ -1843,7 +1916,7 @@ pub struct ParameterWidgetsInfo<'a> {
|
||||||
description: String,
|
description: String,
|
||||||
input_type: FrontendGraphDataType,
|
input_type: FrontendGraphDataType,
|
||||||
blank_assist: bool,
|
blank_assist: bool,
|
||||||
exposeable: bool,
|
exposable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ParameterWidgetsInfo<'a> {
|
impl<'a> ParameterWidgetsInfo<'a> {
|
||||||
|
@ -1860,7 +1933,7 @@ impl<'a> ParameterWidgetsInfo<'a> {
|
||||||
description,
|
description,
|
||||||
input_type,
|
input_type,
|
||||||
blank_assist,
|
blank_assist,
|
||||||
exposeable: true,
|
exposable: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1916,7 +1989,7 @@ pub mod choice {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dropdown_menu<U, C>(&self, current: E, updater_factory: impl Fn() -> U, committer_factory: impl Fn() -> C) -> WidgetHolder
|
pub fn dropdown_menu<U, C>(&self, current: E, updater_factory: impl Fn() -> U, committer_factory: impl Fn() -> C) -> WidgetHolder
|
||||||
where
|
where
|
||||||
U: Fn(&E) -> Message + 'static + Send + Sync,
|
U: Fn(&E) -> Message + 'static + Send + Sync,
|
||||||
C: Fn(&()) -> Message + 'static + Send + Sync,
|
C: Fn(&()) -> Message + 'static + Send + Sync,
|
||||||
|
|
|
@ -4217,13 +4217,23 @@ impl NodeNetworkInterface {
|
||||||
// Side effects
|
// Side effects
|
||||||
match (&old_input, &new_input) {
|
match (&old_input, &new_input) {
|
||||||
// If a node input is exposed or hidden reload the click targets and update the bounding box for all nodes
|
// If a node input is exposed or hidden reload the click targets and update the bounding box for all nodes
|
||||||
(NodeInput::Value { exposed: new_exposed, .. }, NodeInput::Value { exposed: old_exposed, .. }) => {
|
(NodeInput::Value { exposed: new_exposed, .. }, NodeInput::Value { exposed: old_exposed, tagged_value }) => {
|
||||||
if let InputConnector::Node { node_id, .. } = input_connector {
|
if let InputConnector::Node { node_id, .. } = input_connector {
|
||||||
if new_exposed != old_exposed {
|
if new_exposed != old_exposed {
|
||||||
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
|
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
|
||||||
self.unload_all_nodes_bounding_box(network_path);
|
self.unload_all_nodes_bounding_box(network_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Update the name of the value node
|
||||||
|
if let InputConnector::Node { node_id, .. } = input_connector {
|
||||||
|
let Some(reference) = self.reference(node_id, network_path) else {
|
||||||
|
log::error!("Could not get reference for {:?}", node_id);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if reference.as_deref() == Some("Value") {
|
||||||
|
self.set_display_name(node_id, format!("{:?} Value", tagged_value.ty().nested_type()), network_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(_, NodeInput::Node { node_id: upstream_node_id, .. }) => {
|
(_, NodeInput::Node { node_id: upstream_node_id, .. }) => {
|
||||||
// Load structure if the change is to the document network and to the first or second
|
// Load structure if the change is to the document network and to the first or second
|
||||||
|
|
|
@ -13,28 +13,28 @@ impl<'i, const N: u32, I> Node<'i, I> for IntNode<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
// #[derive(Default, Debug, Clone, Copy)]
|
||||||
pub struct ValueNode<T>(pub T);
|
// pub struct ValueNode<T>(pub T);
|
||||||
|
|
||||||
impl<'i, T: 'i, I> Node<'i, I> for ValueNode<T> {
|
// impl<'i, T: 'i, I> Node<'i, I> for ValueNode<T> {
|
||||||
type Output = &'i T;
|
// type Output = &'i T;
|
||||||
#[inline(always)]
|
// #[inline(always)]
|
||||||
fn eval(&'i self, _input: I) -> Self::Output {
|
// fn eval(&'i self, _input: I) -> Self::Output {
|
||||||
&self.0
|
// &self.0
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl<T> ValueNode<T> {
|
// impl<T> ValueNode<T> {
|
||||||
pub const fn new(value: T) -> ValueNode<T> {
|
// pub const fn new(value: T) -> ValueNode<T> {
|
||||||
ValueNode(value)
|
// ValueNode(value)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl<T> From<T> for ValueNode<T> {
|
// impl<T> From<T> for ValueNode<T> {
|
||||||
fn from(value: T) -> Self {
|
// fn from(value: T) -> Self {
|
||||||
ValueNode::new(value)
|
// ValueNode::new(value)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy)]
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
pub struct AsRefNode<T: AsRef<U>, U>(pub T, PhantomData<U>);
|
pub struct AsRefNode<T: AsRef<U>, U>(pub T, PhantomData<U>);
|
||||||
|
|
|
@ -8,11 +8,13 @@ use graphene_application_io::SurfaceFrame;
|
||||||
use graphene_brush::brush_cache::BrushCache;
|
use graphene_brush::brush_cache::BrushCache;
|
||||||
use graphene_brush::brush_stroke::BrushStroke;
|
use graphene_brush::brush_stroke::BrushStroke;
|
||||||
use graphene_core::raster_types::CPU;
|
use graphene_core::raster_types::CPU;
|
||||||
|
use graphene_core::registry::{ChoiceTypeStatic, ChoiceWidgetHint, VariantMetadata};
|
||||||
use graphene_core::transform::ReferencePoint;
|
use graphene_core::transform::ReferencePoint;
|
||||||
use graphene_core::uuid::NodeId;
|
use graphene_core::uuid::NodeId;
|
||||||
use graphene_core::vector::style::Fill;
|
use graphene_core::vector::style::Fill;
|
||||||
use graphene_core::{Color, MemoHash, Node, Type};
|
use graphene_core::{AsU32, Color, MemoHash, Node, Type};
|
||||||
use graphene_svg_renderer::RenderMetadata;
|
use graphene_svg_renderer::RenderMetadata;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
@ -36,6 +38,65 @@ macro_rules! tagged_value {
|
||||||
EditorApi(Arc<WasmEditorApi>)
|
EditorApi(Arc<WasmEditorApi>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum TaggedValueChoice {
|
||||||
|
None,
|
||||||
|
$($identifier,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaggedValueChoice {
|
||||||
|
pub fn to_tagged_value(&self) -> TaggedValue {
|
||||||
|
match self {
|
||||||
|
TaggedValueChoice::None => TaggedValue::None,
|
||||||
|
$(TaggedValueChoice::$identifier => TaggedValue::$identifier(Default::default()),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_tagged_value(value: &TaggedValue) -> Option<Self> {
|
||||||
|
match value {
|
||||||
|
TaggedValue::None => Some(TaggedValueChoice::None),
|
||||||
|
$( TaggedValue::$identifier(_) => Some(TaggedValueChoice::$identifier), )*
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChoiceTypeStatic for TaggedValueChoice {
|
||||||
|
const WIDGET_HINT: ChoiceWidgetHint = ChoiceWidgetHint::Dropdown; // or your preferred hint
|
||||||
|
|
||||||
|
const DESCRIPTION: Option<&'static str> = Some("Select a value");
|
||||||
|
|
||||||
|
fn list() -> &'static [&'static [(Self, VariantMetadata)]] {
|
||||||
|
|
||||||
|
const COUNT: usize = 0 $( + one!($identifier) )*;
|
||||||
|
// Define static array of (choice, metadata) tuples
|
||||||
|
static VALUES: [(TaggedValueChoice, VariantMetadata); 1 + COUNT] = [
|
||||||
|
|
||||||
|
(TaggedValueChoice::None, VariantMetadata {
|
||||||
|
name: Cow::Borrowed(stringify!(None)),
|
||||||
|
label: Cow::Borrowed(stringify!(None)),
|
||||||
|
docstring: None,
|
||||||
|
icon: None,
|
||||||
|
}),
|
||||||
|
$(
|
||||||
|
(TaggedValueChoice::$identifier, VariantMetadata {
|
||||||
|
name: Cow::Borrowed(stringify!($identifier)),
|
||||||
|
label: Cow::Borrowed(stringify!($identifier)),
|
||||||
|
docstring: None,
|
||||||
|
icon: None,
|
||||||
|
}),
|
||||||
|
)*
|
||||||
|
];
|
||||||
|
|
||||||
|
// Static reference to the slice of VALUES
|
||||||
|
static LIST: [&'static [(TaggedValueChoice, VariantMetadata)]; 1] = [
|
||||||
|
&VALUES,
|
||||||
|
];
|
||||||
|
|
||||||
|
&LIST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
|
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
|
||||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||||
impl Hash for TaggedValue {
|
impl Hash for TaggedValue {
|
||||||
|
@ -155,6 +216,12 @@ macro_rules! tagged_value {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! one {
|
||||||
|
($anything:tt) => {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
tagged_value! {
|
tagged_value! {
|
||||||
// ===============
|
// ===============
|
||||||
// PRIMITIVE TYPES
|
// PRIMITIVE TYPES
|
||||||
|
@ -387,6 +454,12 @@ impl Display for TaggedValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsU32 for TaggedValueChoice {
|
||||||
|
fn as_u32(&self) -> u32 {
|
||||||
|
*self as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct UpcastNode {
|
pub struct UpcastNode {
|
||||||
value: MemoHash<TaggedValue>,
|
value: MemoHash<TaggedValue>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,3 +46,21 @@ pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(),
|
||||||
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
|
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
|
||||||
DowncastBothNode::new(n)
|
DowncastBothNode::new(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same idea as the identity node, but with a hidden primary input in order to have an auto generated properties widget for the value
|
||||||
|
pub struct Value {
|
||||||
|
value: SharedNodeContainer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'i> Node<'i, Any<'i>> for Value {
|
||||||
|
type Output = DynFuture<'i, Any<'i>>;
|
||||||
|
fn eval(&'i self, input: Any<'i>) -> Self::Output {
|
||||||
|
Box::pin(async move { self.value.eval(input).await })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub const fn new(value: SharedNodeContainer) -> Self {
|
||||||
|
Value { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use graphene_std::Context;
|
||||||
use graphene_std::GraphicElement;
|
use graphene_std::GraphicElement;
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
use graphene_std::any::DowncastBothNode;
|
use graphene_std::any::DowncastBothNode;
|
||||||
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode};
|
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode, Value};
|
||||||
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
|
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
|
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
|
||||||
|
@ -113,6 +113,16 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
|
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
ProtoNodeIdentifier::new("graphene_core::any::ValueNode"),
|
||||||
|
|args| {
|
||||||
|
Box::pin(async move {
|
||||||
|
let node = Value::new(args[0].clone());
|
||||||
|
node.into_type_erased()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
NodeIOTypes::new(generic!(T), generic!(U), vec![Type::Fn(Box::new(concrete!(Context)), Box::new(generic!(U)))]),
|
||||||
|
),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue