mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 13:02:20 +00:00
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:
parent
0a78ebda25
commit
3dd9e88655
8 changed files with 174 additions and 109 deletions
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -82,6 +82,8 @@ export class FrontendNode {
|
|||
|
||||
readonly displayName!: string;
|
||||
|
||||
readonly primaryInput!: FrontendGraphDataType | undefined;
|
||||
|
||||
readonly exposedInputs!: NodeGraphInput[];
|
||||
|
||||
readonly outputs!: FrontendGraphDataType[];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue