Add some additional image effect nodes (#869)

* Move the Subpath type to graphene-std

* Add the transform subpath node

* Delete selected nodes

* Inserting node list on right click

* Add several bitmap manipulator nodes

* Convert add node to use f64

* Add posterize node

* Rename names randomly

* Fix naming

* Exposure node

* Fix typo

* Adjust exposure node range

* Comment out vector nodes

* Adjust exposure range again

* Posterise as ints

* Rename input

* Use >= in the to hsl function
This commit is contained in:
0HyperCube
2022-12-03 14:29:45 -08:00
committed by GitHub
parent 0fb12bf39b
commit f9d772070b
50 changed files with 1310 additions and 481 deletions
@@ -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),
@@ -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<String>) -> Self {
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: text.into(),
..Default::default()
}))
}
}
#[derive(Clone)]
@@ -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<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
}
common.extend(self.navigation_handler.actions());
common.extend(self.transform_layer_handler.actions());
common.extend(self.node_graph_handler.actions());
common
}
}
@@ -15,12 +15,14 @@ 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: Option<NodeId>,
// 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,
@@ -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<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageHandler)> for NodeGraphMessageHandler {
@@ -188,7 +244,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
Self::send_graph(network, responses);
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
}
NodeGraphMessage::CreateNode { node_id, node_type } => {
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<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,
@@ -227,19 +282,29 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
inputs: document_node_type.inputs.iter().map(|input| input.default.clone()).collect(),
// TODO: Allow inserting nodes that contain other nodes.
implementation: DocumentNodeImplementation::Network(inner_network),
metadata: graph_craft::document::DocumentNodeMetadata {
// TODO: Better position default
position: (far_right_node.0 + 7, far_right_node.1 + 2),
},
metadata: graph_craft::document::DocumentNodeMetadata { position: (x, y) },
},
);
Self::send_graph(network, responses);
}
NodeGraphMessage::DeleteNode { node_id } => {
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<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
}
}
advertise_actions!(NodeGraphMessageDiscriminant; DeleteNode,);
fn actions(&self) -> ActionList {
if self.layer_path.is_some() && !self.selected_nodes.is_empty() {
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes,)
} else {
actions!(NodeGraphMessageDiscriminant;)
}
}
}
@@ -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<FrontendNodeType> {
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()
}
@@ -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<LayoutGroup> {
let index = 1;
pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
let widget = WidgetHolder::text_widget(text);
vec![LayoutGroup::Row { widgets: vec![widget] }]
}
fn update_value<T, F: Fn(&T) -> TaggedValue + 'static>(value: F, node_id: NodeId, input_index: usize) -> WidgetCallback<T> {
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<f64>, range_max: Option<f64>, unit: String, is_integer: bool) -> Vec<WidgetHolder> {
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<LayoutGroup> {
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<LayoutGroup> {
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<LayoutGroup> {
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<LayoutGroup> {
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<LayoutGroup> {
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<LayoutGroup> {
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<LayoutGroup> {
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<Layo
} = 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),
mode: NumberInputMode::Increment,
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
NodeGraphMessage::SetInputValue {
node: node_id,
input_index: index,
value: TaggedValue::F32(number_input.value.unwrap() as f32),
}
.into()
}),
on_update: update_value(|number_input: &NumberInput| TaggedValue::F32(number_input.value.unwrap() as f32), node_id, index),
..NumberInput::default()
})),
]);
@@ -191,13 +146,123 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<Layo
vec![operand("Input", 0), operand("Addend", 1)]
}
pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
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<LayoutGroup> {
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<LayoutGroup> {
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 {
@@ -136,6 +136,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
}
.into(),
);
responses.push_back(NodeGraphMessage::CloseNodeGraph.into());
}
}
ResendActiveProperties => {
@@ -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};
@@ -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};
@@ -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;
@@ -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;
@@ -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};
@@ -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};