diff --git a/Cargo.lock b/Cargo.lock index e67bbc99..0b57a1a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,7 @@ name = "dyn-any" version = "0.2.1" dependencies = [ "dyn-any-derive", + "glam", "log", ] @@ -357,6 +358,7 @@ dependencies = [ "borrow_stack", "dyn-any", "dyn-clone", + "glam", "graphene-core", "graphene-std", "log", @@ -390,12 +392,16 @@ dependencies = [ name = "graphene-std" version = "0.1.0" dependencies = [ + "bezier-rs", "borrow_stack", "dyn-any", "dyn-clone", + "glam", "graph-proc-macros", "graphene-core", "image", + "kurbo", + "log", "once_cell", "proc-macro2", "quote", @@ -440,6 +446,7 @@ dependencies = [ "bezier-rs", "glam", "graph-craft", + "graphene-std", "image", "kurbo", "log", diff --git a/editor/src/messages/input_mapper/default_mapping.rs b/editor/src/messages/input_mapper/default_mapping.rs index 4e4f73d1..cce31b02 100644 --- a/editor/src/messages/input_mapper/default_mapping.rs +++ b/editor/src/messages/input_mapper/default_mapping.rs @@ -27,6 +27,10 @@ pub fn default_mapping() -> Mapping { ), // NORMAL PRIORITY: // + // NodeGraphMessage + entry!(KeyDown(Delete); action_dispatch=NodeGraphMessage::DeleteSelectedNodes), + entry!(KeyDown(Backspace); action_dispatch=NodeGraphMessage::DeleteSelectedNodes), + // // TransformLayerMessage entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation), entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation), diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index 22dbfe80..9846586d 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -263,6 +263,18 @@ impl WidgetHolder { pub fn new(widget: Widget) -> Self { Self { widget_id: generate_uuid(), widget } } + pub fn unrelated_seperator() -> Self { + WidgetHolder::new(Widget::Separator(Separator { + separator_type: SeparatorType::Unrelated, + direction: SeparatorDirection::Horizontal, + })) + } + pub fn text_widget(text: impl Into) -> Self { + WidgetHolder::new(Widget::TextLabel(TextLabel { + value: text.into(), + ..Default::default() + })) + } } #[derive(Clone)] diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 5ae06797..7b859b7f 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -29,8 +29,8 @@ use graphene::layers::imaginate_layer::{ImaginateBaseImage, ImaginateGenerationP use graphene::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant}; use graphene::layers::style::{Fill, RenderData, ViewMode}; use graphene::layers::text_layer::{Font, FontCache}; -use graphene::layers::vector::subpath::Subpath; use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation}; +use graphene_std::vector::subpath::Subpath; use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; @@ -928,6 +928,7 @@ impl MessageHandler, - // I don't really know what this is for (perhaps a user identifiable name). node_type: String, + x: i32, + y: i32, }, DeleteNode { node_id: NodeId, }, + DeleteSelectedNodes, ExposeInput { node_id: NodeId, input_index: usize, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 796f0763..1f07ca0d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1,6 +1,6 @@ use crate::messages::layout::utility_types::layout_widget::LayoutGroup; use crate::messages::prelude::*; -use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeInput, NodeNetwork}; +use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork}; use graphene::document::Document; use graphene::layers::layer_info::LayerDataType; use graphene::layers::nodegraph_layer::NodeGraphFrameLayer; @@ -18,9 +18,13 @@ pub enum FrontendGraphDataType { #[serde(rename = "color")] Color, #[serde(rename = "vector")] - Vector, + Subpath, #[serde(rename = "number")] Number, + #[serde(rename = "boolean")] + Boolean, + #[serde(rename = "vec2")] + Vector, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] @@ -57,10 +61,14 @@ pub struct FrontendNodeLink { #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub struct FrontendNodeType { pub name: String, + pub category: String, } impl FrontendNodeType { - pub fn new(name: &'static str) -> Self { - Self { name: name.to_string() } + pub fn new(name: &'static str, category: &'static str) -> Self { + Self { + name: name.to_string(), + category: category.to_string(), + } } } @@ -148,6 +156,54 @@ impl NodeGraphMessageHandler { log::debug!("Nodes:\n{:#?}\n\nFrontend Nodes:\n{:#?}\n\nLinks:\n{:#?}", network.nodes, nodes, links); responses.push_back(FrontendMessage::UpdateNodeGraph { nodes, links }.into()); } + + fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId) -> bool { + fn remove_from_network(network: &mut NodeNetwork, node_id: NodeId) -> bool { + if network.inputs.iter().any(|&id| id == node_id) { + warn!("Deleting input node"); + return false; + } + if network.output == node_id { + warn!("Deleting the output node!"); + return false; + } + for (id, node) in network.nodes.iter_mut() { + if *id == node_id { + continue; + } + for (input_index, input) in node.inputs.iter_mut().enumerate() { + let NodeInput::Node(id) = input else { + continue; + }; + if *id != node_id { + continue; + } + + let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) else{ + warn!("Removing input of invalid node type '{}'", node.name); + return false; + }; + if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default { + *input = NodeInput::Value { + tagged_value: tagged_value.clone(), + exposed: true, + }; + } + } + if let DocumentNodeImplementation::Network(network) = &mut node.implementation { + remove_from_network(network, node_id); + } + } + true + } + if remove_from_network(network, node_id) { + network.nodes.remove(&node_id); + self.selected_nodes.retain(|&id| id != node_id); + true + } else { + false + } + } } impl MessageHandler for NodeGraphMessageHandler { @@ -188,7 +244,7 @@ impl MessageHandler { + NodeGraphMessage::CreateNode { node_id, node_type, x, y } => { 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"); @@ -218,7 +274,6 @@ impl MessageHandler { if let Some(network) = self.get_active_network_mut(document) { - network.nodes.remove(&node_id); - Self::send_graph(network, responses); - responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into()); + if self.remove_node(network, node_id) { + Self::send_graph(network, responses); + responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into()); + } + } + } + NodeGraphMessage::DeleteSelectedNodes => { + if let Some(network) = self.get_active_network_mut(document) { + let mut modified = false; + for node_id in self.selected_nodes.clone() { + modified = modified || self.remove_node(network, node_id); + } + if modified { + Self::send_graph(network, responses); + responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into()); + } } } NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => { @@ -314,5 +379,11 @@ impl MessageHandler ActionList { + if self.layer_path.is_some() && !self.selected_nodes.is_empty() { + actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes,) + } else { + actions!(NodeGraphMessageDiscriminant;) + } + } } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index f5ea3d14..66e15609 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -1,11 +1,13 @@ -use super::{FrontendGraphDataType, FrontendNodeType}; +use super::{node_properties, FrontendGraphDataType, FrontendNodeType}; use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetHolder}; use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel; +use glam::DVec2; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; use graph_craft::proto::{NodeIdentifier, Type}; use graphene_std::raster::Image; +use graphene_std::vector::subpath::Subpath; use std::borrow::Cow; @@ -15,8 +17,19 @@ pub struct DocumentInputType { pub default: NodeInput, } +impl DocumentInputType { + pub const fn none() -> Self { + Self { + name: "None", + data_type: FrontendGraphDataType::General, + default: NodeInput::value(TaggedValue::None, false), + } + } +} + pub struct DocumentNodeType { pub name: &'static str, + pub category: &'static str, pub identifier: NodeIdentifier, pub inputs: &'static [DocumentInputType], pub outputs: &'static [FrontendGraphDataType], @@ -24,9 +37,10 @@ pub struct DocumentNodeType { } // TODO: Dynamic node library -static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [ +static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[ DocumentNodeType { name: "Identity", + category: "Meta", identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), inputs: &[DocumentInputType { name: "In", @@ -45,6 +59,7 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [ }, DocumentNodeType { name: "Input", + category: "Meta", identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), inputs: &[DocumentInputType { name: "In", @@ -52,129 +67,273 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [ default: NodeInput::Network, }], outputs: &[FrontendGraphDataType::Raster], - properties: |_document_node, _node_id| { - vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: "The input to the graph is the bitmap under the frame".to_string(), - ..Default::default() - }))], - }] - }, + properties: |_document_node, _node_id| node_properties::string_properties("The input to the graph is the bitmap under the frame".to_string()), }, DocumentNodeType { name: "Output", + category: "Meta", identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), inputs: &[DocumentInputType { name: "In", data_type: FrontendGraphDataType::Raster, - default: NodeInput::Value { - tagged_value: TaggedValue::Image(Image::empty()), - exposed: true, - }, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), }], outputs: &[], - properties: |_document_node, _node_id| { - vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: "The output to the graph is rendered in the frame".to_string(), - ..Default::default() - }))], - }] - }, + properties: |_document_node, _node_id| node_properties::string_properties("The output to the graph is rendered in the frame".to_string()), }, DocumentNodeType { name: "Grayscale Image", + category: "Image Color Correction", identifier: NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]), inputs: &[DocumentInputType { name: "Image", data_type: FrontendGraphDataType::Raster, - default: NodeInput::Value { - tagged_value: TaggedValue::Image(Image::empty()), - exposed: true, - }, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), }], outputs: &[FrontendGraphDataType::Raster], - properties: |_document_node, _node_id| { - vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: "The output to the graph is rendered in the frame".to_string(), - ..Default::default() - }))], - }] - }, + properties: node_properties::no_properties, }, DocumentNodeType { - name: "Brighten Image", - identifier: NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + name: "Invert Image Color", + category: "Image Color Correction", + identifier: NodeIdentifier::new("graphene_std::raster::InvertImageColorNode", &[]), + inputs: &[DocumentInputType { + name: "Image", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + }], + outputs: &[FrontendGraphDataType::Raster], + properties: node_properties::no_properties, + }, + DocumentNodeType { + name: "Shift Image HSL", + category: "Image Color Correction", + identifier: NodeIdentifier::new("graphene_std::raster::ShiftImageHSLNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), inputs: &[ DocumentInputType { name: "Image", data_type: FrontendGraphDataType::Raster, - default: NodeInput::Value { - tagged_value: TaggedValue::Image(Image::empty()), - exposed: true, - }, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), }, DocumentInputType { - name: "Amount", + name: "Hue Shift", data_type: FrontendGraphDataType::Number, - default: NodeInput::Value { - tagged_value: TaggedValue::F32(10.), - exposed: false, - }, + default: NodeInput::value(TaggedValue::F64(0.), false), + }, + DocumentInputType { + name: "Saturation Shift", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(0.), false), + }, + DocumentInputType { + name: "Luminance Shift", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(0.), false), }, ], outputs: &[FrontendGraphDataType::Raster], - properties: super::node_properties::brighten_image_properties, + properties: node_properties::adjust_hsl_properties, }, DocumentNodeType { - name: "Hue Shift Image", - identifier: NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + name: "Image Contrast and Brightness", + category: "Image Color Correction", + identifier: NodeIdentifier::new("graphene_std::raster::ImageBrightnessAndContrast", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), inputs: &[ DocumentInputType { name: "Image", data_type: FrontendGraphDataType::Raster, - default: NodeInput::Value { - tagged_value: TaggedValue::Image(Image::empty()), - exposed: true, - }, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), }, DocumentInputType { - name: "Amount", + name: "Brightness", data_type: FrontendGraphDataType::Number, - default: NodeInput::Value { - tagged_value: TaggedValue::F32(10.), - exposed: false, - }, + default: NodeInput::value(TaggedValue::F64(0.), false), + }, + DocumentInputType { + name: "Contrast", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(0.), false), }, ], outputs: &[FrontendGraphDataType::Raster], - properties: super::node_properties::hue_shift_image_properties, + properties: node_properties::brighten_image_properties, + }, + DocumentNodeType { + name: "Adjust Image Gamma", + category: "Image Color Correction", + identifier: NodeIdentifier::new("graphene_std::raster::ImageGammaNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + inputs: &[ + DocumentInputType { + name: "Image", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + }, + DocumentInputType { + name: "Gamma", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(1.), false), + }, + ], + outputs: &[FrontendGraphDataType::Raster], + properties: node_properties::adjust_gamma_properties, + }, + DocumentNodeType { + name: "Multiply Image Opactiy", + category: "Image Color Correction", + identifier: NodeIdentifier::new("graphene_std::raster::ImageOpacityNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + inputs: &[ + DocumentInputType { + name: "Image", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + }, + DocumentInputType { + name: "Factor", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(1.), false), + }, + ], + outputs: &[FrontendGraphDataType::Raster], + properties: node_properties::multiply_opacity, + }, + DocumentNodeType { + name: "Posterize", + category: "Image Filters", + identifier: NodeIdentifier::new("graphene_std::raster::Posterize", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + inputs: &[ + DocumentInputType { + name: "Image", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + }, + DocumentInputType { + name: "Value", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(5.), false), + }, + ], + outputs: &[FrontendGraphDataType::Raster], + properties: node_properties::posterize_properties, + }, + DocumentNodeType { + name: "Exposure", + category: "Image Color Correction", + identifier: NodeIdentifier::new("graphene_std::raster::ExposureNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + inputs: &[ + DocumentInputType { + name: "Image", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + }, + DocumentInputType { + name: "Value", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(1.), false), + }, + ], + outputs: &[FrontendGraphDataType::Raster], + properties: node_properties::exposure_properties, }, DocumentNodeType { name: "Add", + category: "Mathmatics", identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), inputs: &[ DocumentInputType { name: "Input", data_type: FrontendGraphDataType::Number, - default: NodeInput::Value { - tagged_value: TaggedValue::F32(0.), - exposed: true, - }, + default: NodeInput::value(TaggedValue::F64(0.), true), }, DocumentInputType { name: "Addend", data_type: FrontendGraphDataType::Number, - default: NodeInput::Value { - tagged_value: TaggedValue::F32(0.), - exposed: true, - }, + default: NodeInput::value(TaggedValue::F64(0.), true), }, ], outputs: &[FrontendGraphDataType::Number], - properties: super::node_properties::add_properties, + properties: node_properties::add_properties, }, + /*DocumentNodeType { + name: "Unit Circle Generator", + category: "Vector", + identifier: NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]), + inputs: &[DocumentInputType::none()], + outputs: &[FrontendGraphDataType::Subpath], + properties: node_properties::no_properties, + }, + DocumentNodeType { + name: "Unit Square Generator", + category: "Vector", + identifier: NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]), + inputs: &[DocumentInputType::none()], + outputs: &[FrontendGraphDataType::Subpath], + properties: node_properties::no_properties, + }, + DocumentNodeType { + name: "Path Generator", + category: "Vector", + identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), + inputs: &[DocumentInputType { + name: "Path Data", + data_type: FrontendGraphDataType::Subpath, + default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), false), + }], + outputs: &[FrontendGraphDataType::Subpath], + properties: node_properties::no_properties, + }, + DocumentNodeType { + name: "Transform Subpath", + category: "Vector", + identifier: NodeIdentifier::new("graphene_std::vector::generator_nodes::TransformSubpathNode", &[]), + inputs: &[ + DocumentInputType { + name: "Subpath", + data_type: FrontendGraphDataType::Subpath, + default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), true), + }, + DocumentInputType { + name: "Translation", + data_type: FrontendGraphDataType::Vector, + default: NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), + }, + DocumentInputType { + name: "Rotation", + data_type: FrontendGraphDataType::Number, + default: NodeInput::value(TaggedValue::F64(0.), false), + }, + DocumentInputType { + name: "Scale", + data_type: FrontendGraphDataType::Vector, + default: NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false), + }, + DocumentInputType { + name: "Skew", + data_type: FrontendGraphDataType::Vector, + default: NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), + }, + ], + outputs: &[FrontendGraphDataType::Subpath], + properties: node_properties::transform_properties, + }, + DocumentNodeType { + name: "Blit Subpath", + category: "Vector", + identifier: NodeIdentifier::new("graphene_std::vector::generator_nodes::BlitSubpath", &[]), + inputs: &[ + DocumentInputType { + name: "Image", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::Image(Image::empty()), true), + }, + DocumentInputType { + name: "Subpath", + data_type: FrontendGraphDataType::Subpath, + default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), true), + }, + ], + outputs: &[FrontendGraphDataType::Raster], + properties: node_properties::no_properties, + },*/ ]; pub fn resolve_document_node_type(name: &str) -> Option<&DocumentNodeType> { @@ -185,6 +344,6 @@ pub fn collect_node_types() -> Vec { DOCUMENT_NODE_TYPES .iter() .filter(|node_type| !matches!(node_type.name, "Input" | "Output")) - .map(|node_type| FrontendNodeType { name: node_type.name.to_string() }) + .map(|node_type| FrontendNodeType::new(node_type.name, node_type.category)) .collect() } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index 60caa8ef..85b53060 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -1,163 +1,128 @@ use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder}; use crate::messages::layout::utility_types::widgets::button_widgets::ParameterExposeButton; use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode}; -use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel}; use crate::messages::prelude::NodeGraphMessage; +use glam::DVec2; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; use super::FrontendGraphDataType; -pub fn hue_shift_image_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec { - let index = 1; +pub fn string_properties(text: impl Into) -> Vec { + let widget = WidgetHolder::text_widget(text); + vec![LayoutGroup::Row { widgets: vec![widget] }] +} + +fn update_value TaggedValue + 'static>(value: F, node_id: NodeId, input_index: usize) -> WidgetCallback { + WidgetCallback::new(move |number_input: &T| { + NodeGraphMessage::SetInputValue { + node: node_id, + input_index, + value: value(number_input), + } + .into() + }) +} + +fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType, exposed: bool) -> WidgetHolder { + WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton { + exposed, + data_type, + 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() + })) +} + +fn number_range_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, range_min: Option, range_max: Option, unit: String, is_integer: bool) -> Vec { let input: &NodeInput = document_node.inputs.get(index).unwrap(); - let exposed = input.is_exposed(); let mut widgets = vec![ - WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton { - 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 { - separator_type: SeparatorType::Unrelated, - direction: SeparatorDirection::Horizontal, - })), - WidgetHolder::new(Widget::TextLabel(TextLabel { - value: "Shift Degrees".into(), - ..Default::default() - })), + expose_widget(node_id, index, FrontendGraphDataType::Number, input.is_exposed()), + WidgetHolder::unrelated_seperator(), + WidgetHolder::text_widget(name), ]; + if let NodeInput::Value { - tagged_value: TaggedValue::F32(x), + tagged_value: TaggedValue::F64(x), exposed: false, } = document_node.inputs[index] { widgets.extend_from_slice(&[ - WidgetHolder::new(Widget::Separator(Separator { - separator_type: SeparatorType::Unrelated, - direction: SeparatorDirection::Horizontal, - })), + WidgetHolder::unrelated_seperator(), WidgetHolder::new(Widget::NumberInput(NumberInput { - value: Some(x as f64), - unit: "°".into(), - mode: NumberInputMode::Range, - range_min: Some(-180.), - range_max: Some(180.), - on_update: WidgetCallback::new(move |number_input: &NumberInput| { - NodeGraphMessage::SetInputValue { - node: node_id, - input_index: 1, - value: TaggedValue::F32(number_input.value.unwrap() as f32), - } - .into() - }), + value: Some(x), + mode: if range_max.is_some() { NumberInputMode::Range } else { NumberInputMode::Increment }, + range_min, + range_max, + unit, + is_integer, + on_update: update_value(|x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index), ..NumberInput::default() })), ]) } + widgets +} - vec![LayoutGroup::Row { widgets }] +pub fn adjust_hsl_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec { + let hue_shift = number_range_widget(document_node, node_id, 1, "Hue Shift", Some(-180.), Some(180.), "°".into(), false); + let saturation_shift = number_range_widget(document_node, node_id, 2, "Saturation Shift", Some(-100.), Some(100.), "%".into(), false); + let luminance_shift = number_range_widget(document_node, node_id, 3, "Luminance Shift", Some(-100.), Some(100.), "%".into(), false); + + vec![ + LayoutGroup::Row { widgets: hue_shift }, + LayoutGroup::Row { widgets: saturation_shift }, + LayoutGroup::Row { widgets: luminance_shift }, + ] } pub fn brighten_image_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec { - let index = 1; - let input: &NodeInput = document_node.inputs.get(index).unwrap(); - let exposed = input.is_exposed(); + let brightness = number_range_widget(document_node, node_id, 1, "Brightness", Some(-255.), Some(255.), "".into(), false); + let contrast = number_range_widget(document_node, node_id, 2, "Contrast", Some(-255.), Some(255.), "".into(), false); - let mut widgets = vec![ - WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton { - 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 { - separator_type: SeparatorType::Unrelated, - direction: SeparatorDirection::Horizontal, - })), - WidgetHolder::new(Widget::TextLabel(TextLabel { - value: "Brighten Amount".into(), - ..Default::default() - })), - ]; + vec![LayoutGroup::Row { widgets: brightness }, LayoutGroup::Row { widgets: contrast }] +} - 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(x as f64), - mode: NumberInputMode::Range, - range_min: Some(-255.), - range_max: Some(255.), - on_update: WidgetCallback::new(move |number_input: &NumberInput| { - NodeGraphMessage::SetInputValue { - node: node_id, - input_index: 1, - value: TaggedValue::F32(number_input.value.unwrap() as f32), - } - .into() - }), - ..NumberInput::default() - })), - ]) - } +pub fn adjust_gamma_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec { + let gamma = number_range_widget(document_node, node_id, 1, "Gamma", Some(0.01), None, "".into(), false); - vec![LayoutGroup::Row { widgets }] + vec![LayoutGroup::Row { widgets: gamma }] +} + +pub fn multiply_opacity(document_node: &DocumentNode, node_id: NodeId) -> Vec { + let gamma = number_range_widget(document_node, node_id, 1, "Factor", Some(0.), Some(1.), "".into(), false); + + vec![LayoutGroup::Row { widgets: gamma }] +} + +pub fn posterize_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec { + let value = number_range_widget(document_node, node_id, 1, "Levels", Some(2.), Some(255.), "".into(), true); + + vec![LayoutGroup::Row { widgets: value }] +} + +pub fn exposure_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec { + let value = number_range_widget(document_node, node_id, 1, "Value", Some(-3.), Some(3.), "".into(), false); + + vec![LayoutGroup::Row { widgets: value }] } pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> 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, - 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 { - separator_type: SeparatorType::Unrelated, - direction: SeparatorDirection::Horizontal, - })), - WidgetHolder::new(Widget::TextLabel(TextLabel { - value: name.into(), - ..Default::default() - })), + expose_widget(node_id, index, FrontendGraphDataType::Number, input.is_exposed()), + WidgetHolder::unrelated_seperator(), + WidgetHolder::text_widget(name), ]; if let NodeInput::Value { @@ -166,21 +131,11 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec Vec Vec { + let translation = { + let index = 1; + let input: &NodeInput = document_node.inputs.get(index).unwrap(); + + let mut widgets = vec![ + expose_widget(node_id, index, FrontendGraphDataType::Vector, input.is_exposed()), + WidgetHolder::unrelated_seperator(), + WidgetHolder::text_widget("Translation"), + ]; + + if let NodeInput::Value { + tagged_value: TaggedValue::DVec2(vec2), + exposed: false, + } = document_node.inputs[index] + { + widgets.extend_from_slice(&[ + WidgetHolder::unrelated_seperator(), + WidgetHolder::new(Widget::NumberInput(NumberInput { + value: Some(vec2.x), + label: "X".into(), + unit: " px".into(), + on_update: update_value(move |number_input: &NumberInput| TaggedValue::DVec2(DVec2::new(number_input.value.unwrap(), vec2.y)), node_id, index), + ..NumberInput::default() + })), + WidgetHolder::unrelated_seperator(), + WidgetHolder::new(Widget::NumberInput(NumberInput { + value: Some(vec2.y), + label: "Y".into(), + unit: " px".into(), + on_update: update_value(move |number_input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, number_input.value.unwrap())), node_id, index), + ..NumberInput::default() + })), + ]); + } + + LayoutGroup::Row { widgets } + }; + + let rotation = { + let index = 2; + let input: &NodeInput = document_node.inputs.get(index).unwrap(); + + let mut widgets = vec![ + expose_widget(node_id, index, FrontendGraphDataType::Number, input.is_exposed()), + WidgetHolder::unrelated_seperator(), + WidgetHolder::text_widget("Rotation"), + ]; + + if let NodeInput::Value { + tagged_value: TaggedValue::F64(val), + exposed: false, + } = document_node.inputs[index] + { + widgets.extend_from_slice(&[ + WidgetHolder::unrelated_seperator(), + WidgetHolder::new(Widget::NumberInput(NumberInput { + value: Some(val.to_degrees()), + unit: "°".into(), + mode: NumberInputMode::Range, + range_min: Some(-180.), + range_max: Some(180.), + on_update: update_value(|number_input: &NumberInput| TaggedValue::F64(number_input.value.unwrap().to_radians()), node_id, index), + ..NumberInput::default() + })), + ]); + } + + LayoutGroup::Row { widgets } + }; + + let scale = { + let index = 3; + let input: &NodeInput = document_node.inputs.get(index).unwrap(); + + let mut widgets = vec![ + expose_widget(node_id, index, FrontendGraphDataType::Vector, input.is_exposed()), + WidgetHolder::unrelated_seperator(), + WidgetHolder::text_widget("Scale"), + ]; + + if let NodeInput::Value { + tagged_value: TaggedValue::DVec2(vec2), + exposed: false, + } = document_node.inputs[index] + { + widgets.extend_from_slice(&[ + WidgetHolder::unrelated_seperator(), + WidgetHolder::new(Widget::NumberInput(NumberInput { + value: Some(vec2.x), + label: "X".into(), + unit: "".into(), + on_update: update_value(move |number_input: &NumberInput| TaggedValue::DVec2(DVec2::new(number_input.value.unwrap(), vec2.y)), node_id, index), + ..NumberInput::default() + })), + WidgetHolder::unrelated_seperator(), + WidgetHolder::new(Widget::NumberInput(NumberInput { + value: Some(vec2.y), + label: "Y".into(), + unit: "".into(), + on_update: update_value(move |number_input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, number_input.value.unwrap())), node_id, index), + ..NumberInput::default() + })), + ]); + } + + LayoutGroup::Row { widgets } + }; + vec![translation, rotation, scale] +} + fn unknown_node_properties(document_node: &DocumentNode) -> Vec { - vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: format!("Node '{}' cannot be found in library", document_node.name), - ..Default::default() - }))], - }] + string_properties(format!("Node '{}' cannot be found in library", document_node.name)) +} + +pub fn no_properties(document_node: &DocumentNode, _node_id: NodeId) -> Vec { + string_properties(format!("The {} node requires no properties.", document_node.name.to_lowercase())) } pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId) -> LayoutGroup { diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index ca03c638..a2aaf068 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -136,6 +136,7 @@ impl<'a> MessageHandler { diff --git a/editor/src/messages/tool/common_functionality/overlay_renderer.rs b/editor/src/messages/tool/common_functionality/overlay_renderer.rs index f0790225..1454b89a 100644 --- a/editor/src/messages/tool/common_functionality/overlay_renderer.rs +++ b/editor/src/messages/tool/common_functionality/overlay_renderer.rs @@ -6,11 +6,11 @@ use crate::messages::prelude::*; use graphene::color::Color; use graphene::document::Document; use graphene::layers::style::{self, Fill, Stroke}; -use graphene::layers::vector::consts::ManipulatorType; -use graphene::layers::vector::manipulator_group::ManipulatorGroup; -use graphene::layers::vector::manipulator_point::ManipulatorPoint; -use graphene::layers::vector::subpath::Subpath; use graphene::{LayerId, Operation}; +use graphene_std::vector::consts::ManipulatorType; +use graphene_std::vector::manipulator_group::ManipulatorGroup; +use graphene_std::vector::manipulator_point::ManipulatorPoint; +use graphene_std::vector::subpath::Subpath; use glam::{DAffine2, DVec2}; diff --git a/editor/src/messages/tool/common_functionality/path_outline.rs b/editor/src/messages/tool/common_functionality/path_outline.rs index 72e25526..ecf0e645 100644 --- a/editor/src/messages/tool/common_functionality/path_outline.rs +++ b/editor/src/messages/tool/common_functionality/path_outline.rs @@ -6,8 +6,8 @@ use graphene::intersection::Quad; use graphene::layers::layer_info::LayerDataType; use graphene::layers::style::{self, Fill, Stroke}; use graphene::layers::text_layer::FontCache; -use graphene::layers::vector::subpath::Subpath; use graphene::{LayerId, Operation}; +use graphene_std::vector::subpath::Subpath; use glam::{DAffine2, DVec2}; diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index c98e7a15..35bdb097 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -1,11 +1,11 @@ use crate::messages::prelude::*; use bezier_rs::ComputeType; -use graphene::layers::vector::consts::ManipulatorType; -use graphene::layers::vector::manipulator_group::ManipulatorGroup; -use graphene::layers::vector::manipulator_point::ManipulatorPoint; -use graphene::layers::vector::subpath::{BezierId, Subpath}; use graphene::{LayerId, Operation}; +use graphene_std::vector::consts::ManipulatorType; +use graphene_std::vector::manipulator_group::ManipulatorGroup; +use graphene_std::vector::manipulator_point::ManipulatorPoint; +use graphene_std::vector::subpath::{BezierId, Subpath}; use glam::DVec2; use graphene::document::Document; diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 2e990e9d..de5b5619 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -7,8 +7,8 @@ use crate::messages::prelude::*; use graphene::layers::layer_info::{Layer, LayerDataType}; use graphene::layers::style::{self, Stroke}; -use graphene::layers::vector::consts::ManipulatorType; use graphene::{LayerId, Operation}; +use graphene_std::vector::consts::ManipulatorType; use glam::{DAffine2, DVec2}; use std::f64::consts::PI; diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 7f0c06b3..d973d768 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -10,7 +10,7 @@ use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHan use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::intersection::Quad; -use graphene::layers::vector::consts::ManipulatorType; +use graphene_std::vector::consts::ManipulatorType; use glam::DVec2; use serde::{Deserialize, Serialize}; diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index ac10bd59..622e098a 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -10,10 +10,10 @@ use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHan use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::layers::style; -use graphene::layers::vector::consts::ManipulatorType; -use graphene::layers::vector::manipulator_group::ManipulatorGroup; use graphene::LayerId; use graphene::Operation; +use graphene_std::vector::consts::ManipulatorType; +use graphene_std::vector::manipulator_group::ManipulatorGroup; use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; diff --git a/frontend/src/App.vue b/frontend/src/App.vue index e630be2c..5b5d9271 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -40,27 +40,18 @@ --color-f-white-rgb: 255, 255, 255; --color-data-general: #c5c5c5; - --color-data-general-rgb: 197, 197, 197; --color-data-general-dim: #767676; - --color-data-general-dim-rgb: 118, 118, 118; --color-data-vector: #65bbe5; - --color-data-vector-rgb: 101, 187, 229; --color-data-vector-dim: #4b778c; - --color-data-vector-dim-rgb: 75, 119, 140; --color-data-raster: #e4bb72; - --color-data-raster-rgb: 228, 187, 114; --color-data-raster-dim: #8b7752; - --color-data-raster-dim-rgb: 139, 119, 82; --color-data-mask: #8d85c7; - --color-data-mask-rgb: 141, 133, 199; --color-data-number: #d6536e; - --color-data-number-rgb: 214, 83, 110; --color-data-number-dim: #803242; - --color-data-number-dim-rgb: 128, 50, 66; + --color-data-vec2: #cc00ff; + --color-data-vec2-dim: #71008d; --color-data-color: #70a898; - --color-data-color-rgb: 112, 168, 152; --color-data-color-dim: #43645b; - --color-data-color-dim-rgb: 67, 100, 91; --color-none: white; --color-none-repeat: no-repeat; diff --git a/frontend/src/components/panels/NodeGraph.vue b/frontend/src/components/panels/NodeGraph.vue index 228ca2fa..f892a759 100644 --- a/frontend/src/components/panels/NodeGraph.vue +++ b/frontend/src/components/panels/NodeGraph.vue @@ -1,12 +1,6 @@