Add support for exposing node parameter inputs (#866)

* Add exposing inputs to graph

* Use uuids and better node positioning

* Fix accidentally refering to the wrong grid spacing

* Secondary input without primary input

* Cleanup document node types

* Rename to input and addend
This commit is contained in:
0HyperCube 2022-11-22 19:57:08 +00:00 committed by Keavon Chambers
parent 0a78ebda25
commit 3dd9e88655
8 changed files with 174 additions and 109 deletions

View file

@ -14,7 +14,7 @@ pub enum NodeGraphMessage {
},
CreateNode {
// Having the caller generate the id means that we don't have to return it. This can be a random u64.
node_id: NodeId,
node_id: Option<NodeId>,
// I don't really know what this is for (perhaps a user identifiable name).
node_type: String,
},

View file

@ -35,6 +35,8 @@ pub struct FrontendNode {
pub id: graph_craft::document::NodeId,
#[serde(rename = "displayName")]
pub display_name: String,
#[serde(rename = "primaryInput")]
pub primary_input: Option<FrontendGraphDataType>,
#[serde(rename = "exposedInputs")]
pub exposed_inputs: Vec<NodeGraphInput>,
pub outputs: Vec<FrontendGraphDataType>,
@ -122,10 +124,17 @@ impl NodeGraphMessageHandler {
nodes.push(FrontendNode {
id: *id,
display_name: node.name.clone(),
primary_input: node
.inputs
.first()
.filter(|input| input.is_exposed())
.and_then(|_| node_type.inputs.get(0))
.map(|input_type| input_type.data_type),
exposed_inputs: node
.inputs
.iter()
.zip(node_type.inputs)
.skip(1)
.filter(|(input, _)| input.is_exposed())
.map(|(_, input_type)| NodeGraphInput {
data_type: input_type.data_type,
@ -180,6 +189,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
}
NodeGraphMessage::CreateNode { node_id, node_type } => {
let node_id = node_id.unwrap_or_else(crate::application::generate_uuid);
let Some(network) = self.get_active_network_mut(document) else{
warn!("No network");
return;
@ -208,6 +218,8 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
.into_iter()
.collect(),
};
let far_right_node = network.nodes.iter().map(|node| node.1.metadata.position).max_by_key(|pos| pos.0).unwrap_or_default();
network.nodes.insert(
node_id,
DocumentNode {
@ -217,7 +229,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
implementation: DocumentNodeImplementation::Network(inner_network),
metadata: graph_craft::document::DocumentNodeMetadata {
// TODO: Better position default
position: (node_id as i32 * 7 - 41, node_id as i32 * 2 - 10),
position: (far_right_node.0 + 7, far_right_node.1 + 2),
},
},
);
@ -243,8 +255,16 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
if let NodeInput::Value { exposed, .. } = &mut node.inputs[input_index] {
*exposed = new_exposed;
} else if let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) {
if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default {
node.inputs[input_index] = NodeInput::Value {
tagged_value: tagged_value.clone(),
exposed: new_exposed,
};
}
}
Self::send_graph(network, responses);
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
}
NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y } => {
let Some(network) = self.get_active_network_mut(document) else{

View file

@ -37,7 +37,7 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
properties: |_document_node, _node_id| {
vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: format!("The identity node simply returns the input"),
value: "The identity node simply returns the input".to_string(),
..Default::default()
}))],
}]
@ -46,12 +46,16 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
DocumentNodeType {
name: "Input",
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
inputs: &[],
inputs: &[DocumentInputType {
name: "In",
data_type: FrontendGraphDataType::Raster,
default: NodeInput::Network,
}],
outputs: &[FrontendGraphDataType::Raster],
properties: |_document_node, _node_id| {
vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: format!("The input to the graph is the bitmap under the frame"),
value: "The input to the graph is the bitmap under the frame".to_string(),
..Default::default()
}))],
}]
@ -72,7 +76,7 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
properties: |_document_node, _node_id| {
vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: format!("The output to the graph is rendered in the frame"),
value: "The output to the graph is rendered in the frame".to_string(),
..Default::default()
}))],
}]
@ -93,7 +97,7 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
properties: |_document_node, _node_id| {
vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: format!("The output to the graph is rendered in the frame"),
value: "The output to the graph is rendered in the frame".to_string(),
..Default::default()
}))],
}]
@ -152,7 +156,7 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
inputs: &[
DocumentInputType {
name: "Left",
name: "Input",
data_type: FrontendGraphDataType::Number,
default: NodeInput::Value {
tagged_value: TaggedValue::F32(0.),
@ -160,7 +164,7 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
},
},
DocumentInputType {
name: "Right",
name: "Addend",
data_type: FrontendGraphDataType::Number,
default: NodeInput::Value {
tagged_value: TaggedValue::F32(0.),
@ -178,5 +182,9 @@ pub fn resolve_document_node_type(name: &str) -> Option<&DocumentNodeType> {
}
pub fn collect_node_types() -> Vec<FrontendNodeType> {
DOCUMENT_NODE_TYPES.iter().map(|node_type| FrontendNodeType { name: node_type.name.to_string() }).collect()
DOCUMENT_NODE_TYPES
.iter()
.filter(|node_type| !matches!(node_type.name, "Input" | "Output"))
.map(|node_type| FrontendNodeType { name: node_type.name.to_string() })
.collect()
}

View file

@ -10,12 +10,23 @@ use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use super::FrontendGraphDataType;
pub fn hue_shift_image_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
vec![LayoutGroup::Row {
widgets: vec![
let index = 1;
let input: &NodeInput = document_node.inputs.get(index).unwrap();
let exposed = input.is_exposed();
let mut widgets = vec![
WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton {
exposed: true,
exposed,
data_type: FrontendGraphDataType::Number,
tooltip: "Expose input parameter in node graph".into(),
on_update: WidgetCallback::new(move |_parameter| {
NodeGraphMessage::ExposeInput {
node_id,
input_index: index,
new_exposed: !exposed,
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
@ -26,17 +37,19 @@ pub fn hue_shift_image_properties(document_node: &DocumentNode, node_id: NodeId)
value: "Shift Degrees".into(),
..Default::default()
})),
];
if let NodeInput::Value {
tagged_value: TaggedValue::F32(x),
exposed: false,
} = document_node.inputs[index]
{
widgets.extend_from_slice(&[
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: Some({
let NodeInput::Value {tagged_value: TaggedValue::F32(x), ..} = document_node.inputs[1] else {
panic!("Hue rotate should be f32")
};
x as f64
}),
value: Some(x as f64),
unit: "°".into(),
mode: NumberInputMode::Range,
range_min: Some(-180.),
@ -51,17 +64,30 @@ pub fn hue_shift_image_properties(document_node: &DocumentNode, node_id: NodeId)
}),
..NumberInput::default()
})),
],
}]
])
}
vec![LayoutGroup::Row { widgets }]
}
pub fn brighten_image_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
vec![LayoutGroup::Row {
widgets: vec![
let index = 1;
let input: &NodeInput = document_node.inputs.get(index).unwrap();
let exposed = input.is_exposed();
let mut widgets = vec![
WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton {
exposed: true,
exposed,
data_type: FrontendGraphDataType::Number,
tooltip: "Expose input parameter in node graph".into(),
on_update: WidgetCallback::new(move |_parameter| {
NodeGraphMessage::ExposeInput {
node_id,
input_index: index,
new_exposed: !exposed,
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
@ -72,17 +98,20 @@ pub fn brighten_image_properties(document_node: &DocumentNode, node_id: NodeId)
value: "Brighten Amount".into(),
..Default::default()
})),
];
if let NodeInput::Value {
tagged_value: TaggedValue::F32(x),
exposed: false,
} = document_node.inputs[index]
{
widgets.extend_from_slice(&[
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: Some({
let NodeInput::Value {tagged_value: TaggedValue::F32(x), ..} = document_node.inputs[1] else {
panic!("Brighten amount should be f32")
};
x as f64
}),
value: Some(x as f64),
mode: NumberInputMode::Range,
range_min: Some(-255.),
range_max: Some(255.),
@ -96,17 +125,29 @@ pub fn brighten_image_properties(document_node: &DocumentNode, node_id: NodeId)
}),
..NumberInput::default()
})),
],
}]
])
}
vec![LayoutGroup::Row { widgets }]
}
pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
let operand = |name: &str, index| LayoutGroup::Row {
widgets: vec![
let operand = |name: &str, index| {
let input: &NodeInput = document_node.inputs.get(index).unwrap();
let exposed = input.is_exposed();
let mut widgets = vec![
WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton {
exposed: true,
exposed,
data_type: FrontendGraphDataType::Number,
tooltip: "Expose input parameter in node graph".into(),
on_update: WidgetCallback::new(move |_parameter| {
NodeGraphMessage::ExposeInput {
node_id,
input_index: index,
new_exposed: !exposed,
}
.into()
}),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
@ -117,18 +158,20 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<Layo
value: name.into(),
..Default::default()
})),
];
if let NodeInput::Value {
tagged_value: TaggedValue::F32(x),
exposed: false,
} = document_node.inputs[index]
{
widgets.extend_from_slice(&[
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: Some({
let NodeInput::Value {tagged_value: TaggedValue::F32(x), ..} = document_node.inputs[index] else {
panic!("Add input should be f32")
};
x as f64
}),
value: Some(x as f64),
mode: NumberInputMode::Increment,
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
NodeGraphMessage::SetInputValue {
@ -140,9 +183,12 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<Layo
}),
..NumberInput::default()
})),
],
]);
}
LayoutGroup::Row { widgets }
};
vec![operand("Left", 0), operand("Right", 1)]
vec![operand("Input", 0), operand("Addend", 1)]
}
fn unknown_node_properties(document_node: &DocumentNode) -> Vec<LayoutGroup> {

View file

@ -45,11 +45,11 @@
<div class="primary">
<div class="ports">
<div
v-if="node.exposedInputs.length > 0"
v-if="node.primaryInput"
class="input port"
data-port="input"
:data-datatype="node.exposedInputs[0].dataType"
:style="{ '--data-color': `var(--color-data-${node.exposedInputs[0].dataType})`, '--data-color-dim': `var(--color-data-${node.exposedInputs[0].dataType}-dim)` }"
:data-datatype="node.primaryInput"
:style="{ '--data-color': `var(--color-data-${node.primaryInput})`, '--data-color-dim': `var(--color-data-${node.primaryInput}-dim)` }"
>
<div></div>
</div>
@ -66,8 +66,8 @@
<IconLabel :icon="nodeIcon(node.displayName)" />
<TextLabel>{{ node.displayName }}</TextLabel>
</div>
<div v-if="node.exposedInputs.length > 1" class="arguments">
<div v-for="(argument, index) in node.exposedInputs.slice(1)" :key="index" class="argument">
<div v-if="node.exposedInputs.length > 0" class="arguments">
<div v-for="(argument, index) in node.exposedInputs" :key="index" class="argument">
<div class="ports">
<div
class="input port"
@ -366,7 +366,7 @@ export default defineComponent({
const links = this.nodeGraph.state.links;
this.nodeLinkPaths = links.flatMap((link) => {
const connectorIndex = 0;
const connectorIndex = Number(link.linkEndInputIndex);
const nodePrimaryOutput = (containerBounds.querySelector(`[data-node="${String(link.linkStart)}"] [data-port="output"]`) || undefined) as HTMLDivElement | undefined;
@ -511,8 +511,8 @@ export default defineComponent({
this.linkInProgressToConnector = new DOMRect(e.x, e.y);
}
} else if (this.draggingNodes) {
const deltaX = Math.round((e.x - this.draggingNodes.startX) / this.transform.scale / this.gridSpacing);
const deltaY = Math.round((e.y - this.draggingNodes.startY) / this.transform.scale / this.gridSpacing);
const deltaX = Math.round((e.x - this.draggingNodes.startX) / this.transform.scale / GRID_SIZE);
const deltaY = Math.round((e.y - this.draggingNodes.startY) / this.transform.scale / GRID_SIZE);
if (this.draggingNodes.roundX !== deltaX || this.draggingNodes.roundY !== deltaY) {
this.draggingNodes.roundX = deltaX;
this.draggingNodes.roundY = deltaY;

View file

@ -82,6 +82,8 @@ export class FrontendNode {
readonly displayName!: string;
readonly primaryInput!: FrontendGraphDataType | undefined;
readonly exposedInputs!: NodeGraphInput[];
readonly outputs!: FrontendGraphDataType[];

View file

@ -553,18 +553,7 @@ impl JsEditorHandle {
/// Creates a new document node in the node graph
#[wasm_bindgen(js_name = createNode)]
pub fn create_node(&self, node_type: String) {
fn generate_node_id() -> u64 {
static mut NODE_ID: u64 = 10;
unsafe {
NODE_ID += 1;
NODE_ID
}
}
let message = NodeGraphMessage::CreateNode {
node_id: generate_node_id(),
node_type,
};
let message = NodeGraphMessage::CreateNode { node_id: None, node_type };
self.dispatch(message);
}

View file

@ -108,10 +108,10 @@ impl NodeInput {
}
}
pub fn is_exposed(&self) -> bool {
if let NodeInput::Value { exposed, .. } = self {
*exposed
} else {
true
match self {
NodeInput::Node(_) => true,
NodeInput::Value { exposed, .. } => *exposed,
NodeInput::Network => false,
}
}
}