mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-02 04:22:15 +00:00
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:
parent
59b638e4e4
commit
eb9848365f
50 changed files with 1310 additions and 481 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -223,6 +223,7 @@ name = "dyn-any"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dyn-any-derive",
|
"dyn-any-derive",
|
||||||
|
"glam",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -357,6 +358,7 @@ dependencies = [
|
||||||
"borrow_stack",
|
"borrow_stack",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
|
"glam",
|
||||||
"graphene-core",
|
"graphene-core",
|
||||||
"graphene-std",
|
"graphene-std",
|
||||||
"log",
|
"log",
|
||||||
|
@ -390,12 +392,16 @@ dependencies = [
|
||||||
name = "graphene-std"
|
name = "graphene-std"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bezier-rs",
|
||||||
"borrow_stack",
|
"borrow_stack",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
|
"glam",
|
||||||
"graph-proc-macros",
|
"graph-proc-macros",
|
||||||
"graphene-core",
|
"graphene-core",
|
||||||
"image",
|
"image",
|
||||||
|
"kurbo",
|
||||||
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -440,6 +446,7 @@ dependencies = [
|
||||||
"bezier-rs",
|
"bezier-rs",
|
||||||
"glam",
|
"glam",
|
||||||
"graph-craft",
|
"graph-craft",
|
||||||
|
"graphene-std",
|
||||||
"image",
|
"image",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -27,6 +27,10 @@ pub fn default_mapping() -> Mapping {
|
||||||
),
|
),
|
||||||
// NORMAL PRIORITY:
|
// NORMAL PRIORITY:
|
||||||
//
|
//
|
||||||
|
// NodeGraphMessage
|
||||||
|
entry!(KeyDown(Delete); action_dispatch=NodeGraphMessage::DeleteSelectedNodes),
|
||||||
|
entry!(KeyDown(Backspace); action_dispatch=NodeGraphMessage::DeleteSelectedNodes),
|
||||||
|
//
|
||||||
// TransformLayerMessage
|
// TransformLayerMessage
|
||||||
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||||
entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||||
|
|
|
@ -263,6 +263,18 @@ impl WidgetHolder {
|
||||||
pub fn new(widget: Widget) -> Self {
|
pub fn new(widget: Widget) -> Self {
|
||||||
Self { widget_id: generate_uuid(), widget }
|
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)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -29,8 +29,8 @@ use graphene::layers::imaginate_layer::{ImaginateBaseImage, ImaginateGenerationP
|
||||||
use graphene::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
|
use graphene::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
|
||||||
use graphene::layers::style::{Fill, RenderData, ViewMode};
|
use graphene::layers::style::{Fill, RenderData, ViewMode};
|
||||||
use graphene::layers::text_layer::{Font, FontCache};
|
use graphene::layers::text_layer::{Font, FontCache};
|
||||||
use graphene::layers::vector::subpath::Subpath;
|
|
||||||
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -928,6 +928,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||||
}
|
}
|
||||||
common.extend(self.navigation_handler.actions());
|
common.extend(self.navigation_handler.actions());
|
||||||
common.extend(self.transform_layer_handler.actions());
|
common.extend(self.transform_layer_handler.actions());
|
||||||
|
common.extend(self.node_graph_handler.actions());
|
||||||
common
|
common
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,14 @@ pub enum NodeGraphMessage {
|
||||||
CreateNode {
|
CreateNode {
|
||||||
// Having the caller generate the id means that we don't have to return it. This can be a random u64.
|
// 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>,
|
node_id: Option<NodeId>,
|
||||||
// I don't really know what this is for (perhaps a user identifiable name).
|
|
||||||
node_type: String,
|
node_type: String,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
},
|
},
|
||||||
DeleteNode {
|
DeleteNode {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
},
|
},
|
||||||
|
DeleteSelectedNodes,
|
||||||
ExposeInput {
|
ExposeInput {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
||||||
use crate::messages::prelude::*;
|
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::document::Document;
|
||||||
use graphene::layers::layer_info::LayerDataType;
|
use graphene::layers::layer_info::LayerDataType;
|
||||||
use graphene::layers::nodegraph_layer::NodeGraphFrameLayer;
|
use graphene::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||||
|
@ -18,9 +18,13 @@ pub enum FrontendGraphDataType {
|
||||||
#[serde(rename = "color")]
|
#[serde(rename = "color")]
|
||||||
Color,
|
Color,
|
||||||
#[serde(rename = "vector")]
|
#[serde(rename = "vector")]
|
||||||
Vector,
|
Subpath,
|
||||||
#[serde(rename = "number")]
|
#[serde(rename = "number")]
|
||||||
Number,
|
Number,
|
||||||
|
#[serde(rename = "boolean")]
|
||||||
|
Boolean,
|
||||||
|
#[serde(rename = "vec2")]
|
||||||
|
Vector,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[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)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct FrontendNodeType {
|
pub struct FrontendNodeType {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub category: String,
|
||||||
}
|
}
|
||||||
impl FrontendNodeType {
|
impl FrontendNodeType {
|
||||||
pub fn new(name: &'static str) -> Self {
|
pub fn new(name: &'static str, category: &'static str) -> Self {
|
||||||
Self { name: name.to_string() }
|
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);
|
log::debug!("Nodes:\n{:#?}\n\nFrontend Nodes:\n{:#?}\n\nLinks:\n{:#?}", network.nodes, nodes, links);
|
||||||
responses.push_back(FrontendMessage::UpdateNodeGraph { nodes, links }.into());
|
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 {
|
impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageHandler)> for NodeGraphMessageHandler {
|
||||||
|
@ -188,7 +244,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, responses);
|
||||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
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 node_id = node_id.unwrap_or_else(crate::application::generate_uuid);
|
||||||
let Some(network) = self.get_active_network_mut(document) else{
|
let Some(network) = self.get_active_network_mut(document) else{
|
||||||
warn!("No network");
|
warn!("No network");
|
||||||
|
@ -218,7 +274,6 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.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(
|
network.nodes.insert(
|
||||||
node_id,
|
node_id,
|
||||||
|
@ -227,19 +282,29 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||||
inputs: document_node_type.inputs.iter().map(|input| input.default.clone()).collect(),
|
inputs: document_node_type.inputs.iter().map(|input| input.default.clone()).collect(),
|
||||||
// TODO: Allow inserting nodes that contain other nodes.
|
// TODO: Allow inserting nodes that contain other nodes.
|
||||||
implementation: DocumentNodeImplementation::Network(inner_network),
|
implementation: DocumentNodeImplementation::Network(inner_network),
|
||||||
metadata: graph_craft::document::DocumentNodeMetadata {
|
metadata: graph_craft::document::DocumentNodeMetadata { position: (x, y) },
|
||||||
// TODO: Better position default
|
|
||||||
position: (far_right_node.0 + 7, far_right_node.1 + 2),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, responses);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::DeleteNode { node_id } => {
|
NodeGraphMessage::DeleteNode { node_id } => {
|
||||||
if let Some(network) = self.get_active_network_mut(document) {
|
if let Some(network) = self.get_active_network_mut(document) {
|
||||||
network.nodes.remove(&node_id);
|
if self.remove_node(network, node_id) {
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, responses);
|
||||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
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 } => {
|
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::layout_widget::{LayoutGroup, Widget, WidgetHolder};
|
||||||
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
|
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
|
||||||
|
|
||||||
|
use glam::DVec2;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||||
use graph_craft::proto::{NodeIdentifier, Type};
|
use graph_craft::proto::{NodeIdentifier, Type};
|
||||||
use graphene_std::raster::Image;
|
use graphene_std::raster::Image;
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
@ -15,8 +17,19 @@ pub struct DocumentInputType {
|
||||||
pub default: NodeInput,
|
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 struct DocumentNodeType {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
pub category: &'static str,
|
||||||
pub identifier: NodeIdentifier,
|
pub identifier: NodeIdentifier,
|
||||||
pub inputs: &'static [DocumentInputType],
|
pub inputs: &'static [DocumentInputType],
|
||||||
pub outputs: &'static [FrontendGraphDataType],
|
pub outputs: &'static [FrontendGraphDataType],
|
||||||
|
@ -24,9 +37,10 @@ pub struct DocumentNodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Dynamic node library
|
// TODO: Dynamic node library
|
||||||
static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
|
static DOCUMENT_NODE_TYPES: &[DocumentNodeType] = &[
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Identity",
|
name: "Identity",
|
||||||
|
category: "Meta",
|
||||||
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
||||||
inputs: &[DocumentInputType {
|
inputs: &[DocumentInputType {
|
||||||
name: "In",
|
name: "In",
|
||||||
|
@ -45,6 +59,7 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
|
||||||
},
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Input",
|
name: "Input",
|
||||||
|
category: "Meta",
|
||||||
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
||||||
inputs: &[DocumentInputType {
|
inputs: &[DocumentInputType {
|
||||||
name: "In",
|
name: "In",
|
||||||
|
@ -52,129 +67,273 @@ static DOCUMENT_NODE_TYPES: [DocumentNodeType; 7] = [
|
||||||
default: NodeInput::Network,
|
default: NodeInput::Network,
|
||||||
}],
|
}],
|
||||||
outputs: &[FrontendGraphDataType::Raster],
|
outputs: &[FrontendGraphDataType::Raster],
|
||||||
properties: |_document_node, _node_id| {
|
properties: |_document_node, _node_id| node_properties::string_properties("The input to the graph is the bitmap under the frame".to_string()),
|
||||||
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()
|
|
||||||
}))],
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Output",
|
name: "Output",
|
||||||
|
category: "Meta",
|
||||||
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]),
|
||||||
inputs: &[DocumentInputType {
|
inputs: &[DocumentInputType {
|
||||||
name: "In",
|
name: "In",
|
||||||
data_type: FrontendGraphDataType::Raster,
|
data_type: FrontendGraphDataType::Raster,
|
||||||
default: NodeInput::Value {
|
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||||
tagged_value: TaggedValue::Image(Image::empty()),
|
|
||||||
exposed: true,
|
|
||||||
},
|
|
||||||
}],
|
}],
|
||||||
outputs: &[],
|
outputs: &[],
|
||||||
properties: |_document_node, _node_id| {
|
properties: |_document_node, _node_id| node_properties::string_properties("The output to the graph is rendered in the frame".to_string()),
|
||||||
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()
|
|
||||||
}))],
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Grayscale Image",
|
name: "Grayscale Image",
|
||||||
|
category: "Image Color Correction",
|
||||||
identifier: NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]),
|
identifier: NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]),
|
||||||
inputs: &[DocumentInputType {
|
inputs: &[DocumentInputType {
|
||||||
name: "Image",
|
name: "Image",
|
||||||
data_type: FrontendGraphDataType::Raster,
|
data_type: FrontendGraphDataType::Raster,
|
||||||
default: NodeInput::Value {
|
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||||
tagged_value: TaggedValue::Image(Image::empty()),
|
|
||||||
exposed: true,
|
|
||||||
},
|
|
||||||
}],
|
}],
|
||||||
outputs: &[FrontendGraphDataType::Raster],
|
outputs: &[FrontendGraphDataType::Raster],
|
||||||
properties: |_document_node, _node_id| {
|
properties: node_properties::no_properties,
|
||||||
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()
|
|
||||||
}))],
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Brighten Image",
|
name: "Invert Image Color",
|
||||||
identifier: NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
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: &[
|
inputs: &[
|
||||||
DocumentInputType {
|
DocumentInputType {
|
||||||
name: "Image",
|
name: "Image",
|
||||||
data_type: FrontendGraphDataType::Raster,
|
data_type: FrontendGraphDataType::Raster,
|
||||||
default: NodeInput::Value {
|
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||||
tagged_value: TaggedValue::Image(Image::empty()),
|
|
||||||
exposed: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
DocumentInputType {
|
DocumentInputType {
|
||||||
name: "Amount",
|
name: "Hue Shift",
|
||||||
data_type: FrontendGraphDataType::Number,
|
data_type: FrontendGraphDataType::Number,
|
||||||
default: NodeInput::Value {
|
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||||
tagged_value: TaggedValue::F32(10.),
|
},
|
||||||
exposed: 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],
|
outputs: &[FrontendGraphDataType::Raster],
|
||||||
properties: super::node_properties::brighten_image_properties,
|
properties: node_properties::adjust_hsl_properties,
|
||||||
},
|
},
|
||||||
DocumentNodeType {
|
DocumentNodeType {
|
||||||
name: "Hue Shift Image",
|
name: "Image Contrast and Brightness",
|
||||||
identifier: NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
category: "Image Color Correction",
|
||||||
|
identifier: NodeIdentifier::new("graphene_std::raster::ImageBrightnessAndContrast", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||||
inputs: &[
|
inputs: &[
|
||||||
DocumentInputType {
|
DocumentInputType {
|
||||||
name: "Image",
|
name: "Image",
|
||||||
data_type: FrontendGraphDataType::Raster,
|
data_type: FrontendGraphDataType::Raster,
|
||||||
default: NodeInput::Value {
|
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||||
tagged_value: TaggedValue::Image(Image::empty()),
|
|
||||||
exposed: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
DocumentInputType {
|
DocumentInputType {
|
||||||
name: "Amount",
|
name: "Brightness",
|
||||||
data_type: FrontendGraphDataType::Number,
|
data_type: FrontendGraphDataType::Number,
|
||||||
default: NodeInput::Value {
|
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||||
tagged_value: TaggedValue::F32(10.),
|
},
|
||||||
exposed: false,
|
DocumentInputType {
|
||||||
},
|
name: "Contrast",
|
||||||
|
data_type: FrontendGraphDataType::Number,
|
||||||
|
default: NodeInput::value(TaggedValue::F64(0.), false),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
outputs: &[FrontendGraphDataType::Raster],
|
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 {
|
DocumentNodeType {
|
||||||
name: "Add",
|
name: "Add",
|
||||||
|
category: "Mathmatics",
|
||||||
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||||
inputs: &[
|
inputs: &[
|
||||||
DocumentInputType {
|
DocumentInputType {
|
||||||
name: "Input",
|
name: "Input",
|
||||||
data_type: FrontendGraphDataType::Number,
|
data_type: FrontendGraphDataType::Number,
|
||||||
default: NodeInput::Value {
|
default: NodeInput::value(TaggedValue::F64(0.), true),
|
||||||
tagged_value: TaggedValue::F32(0.),
|
|
||||||
exposed: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
DocumentInputType {
|
DocumentInputType {
|
||||||
name: "Addend",
|
name: "Addend",
|
||||||
data_type: FrontendGraphDataType::Number,
|
data_type: FrontendGraphDataType::Number,
|
||||||
default: NodeInput::Value {
|
default: NodeInput::value(TaggedValue::F64(0.), true),
|
||||||
tagged_value: TaggedValue::F32(0.),
|
|
||||||
exposed: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
outputs: &[FrontendGraphDataType::Number],
|
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> {
|
pub fn resolve_document_node_type(name: &str) -> Option<&DocumentNodeType> {
|
||||||
|
@ -185,6 +344,6 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
|
||||||
DOCUMENT_NODE_TYPES
|
DOCUMENT_NODE_TYPES
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|node_type| !matches!(node_type.name, "Input" | "Output"))
|
.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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,163 +1,128 @@
|
||||||
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder};
|
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::button_widgets::ParameterExposeButton;
|
||||||
use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode};
|
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 crate::messages::prelude::NodeGraphMessage;
|
||||||
|
|
||||||
|
use glam::DVec2;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||||
|
|
||||||
use super::FrontendGraphDataType;
|
use super::FrontendGraphDataType;
|
||||||
|
|
||||||
pub fn hue_shift_image_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
pub fn string_properties(text: impl Into<String>) -> Vec<LayoutGroup> {
|
||||||
let index = 1;
|
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 input: &NodeInput = document_node.inputs.get(index).unwrap();
|
||||||
let exposed = input.is_exposed();
|
|
||||||
|
|
||||||
let mut widgets = vec![
|
let mut widgets = vec![
|
||||||
WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton {
|
expose_widget(node_id, index, FrontendGraphDataType::Number, input.is_exposed()),
|
||||||
exposed,
|
WidgetHolder::unrelated_seperator(),
|
||||||
data_type: FrontendGraphDataType::Number,
|
WidgetHolder::text_widget(name),
|
||||||
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()
|
|
||||||
})),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if let NodeInput::Value {
|
if let NodeInput::Value {
|
||||||
tagged_value: TaggedValue::F32(x),
|
tagged_value: TaggedValue::F64(x),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
} = document_node.inputs[index]
|
} = document_node.inputs[index]
|
||||||
{
|
{
|
||||||
widgets.extend_from_slice(&[
|
widgets.extend_from_slice(&[
|
||||||
WidgetHolder::new(Widget::Separator(Separator {
|
WidgetHolder::unrelated_seperator(),
|
||||||
separator_type: SeparatorType::Unrelated,
|
|
||||||
direction: SeparatorDirection::Horizontal,
|
|
||||||
})),
|
|
||||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
value: Some(x as f64),
|
value: Some(x),
|
||||||
unit: "°".into(),
|
mode: if range_max.is_some() { NumberInputMode::Range } else { NumberInputMode::Increment },
|
||||||
mode: NumberInputMode::Range,
|
range_min,
|
||||||
range_min: Some(-180.),
|
range_max,
|
||||||
range_max: Some(180.),
|
unit,
|
||||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
is_integer,
|
||||||
NodeGraphMessage::SetInputValue {
|
on_update: update_value(|x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index),
|
||||||
node: node_id,
|
|
||||||
input_index: 1,
|
|
||||||
value: TaggedValue::F32(number_input.value.unwrap() as f32),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}),
|
|
||||||
..NumberInput::default()
|
..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> {
|
pub fn brighten_image_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||||
let index = 1;
|
let brightness = number_range_widget(document_node, node_id, 1, "Brightness", Some(-255.), Some(255.), "".into(), false);
|
||||||
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
let contrast = number_range_widget(document_node, node_id, 2, "Contrast", Some(-255.), Some(255.), "".into(), false);
|
||||||
let exposed = input.is_exposed();
|
|
||||||
|
|
||||||
let mut widgets = vec![
|
vec![LayoutGroup::Row { widgets: brightness }, LayoutGroup::Row { widgets: contrast }]
|
||||||
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()
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
|
|
||||||
if let NodeInput::Value {
|
pub fn adjust_gamma_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||||
tagged_value: TaggedValue::F32(x),
|
let gamma = number_range_widget(document_node, node_id, 1, "Gamma", Some(0.01), None, "".into(), false);
|
||||||
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()
|
|
||||||
})),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||||
let operand = |name: &str, index| {
|
let operand = |name: &str, index| {
|
||||||
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
||||||
let exposed = input.is_exposed();
|
|
||||||
let mut widgets = vec![
|
let mut widgets = vec![
|
||||||
WidgetHolder::new(Widget::ParameterExposeButton(ParameterExposeButton {
|
expose_widget(node_id, index, FrontendGraphDataType::Number, input.is_exposed()),
|
||||||
exposed,
|
WidgetHolder::unrelated_seperator(),
|
||||||
data_type: FrontendGraphDataType::Number,
|
WidgetHolder::text_widget(name),
|
||||||
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()
|
|
||||||
})),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if let NodeInput::Value {
|
if let NodeInput::Value {
|
||||||
|
@ -166,21 +131,11 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<Layo
|
||||||
} = document_node.inputs[index]
|
} = document_node.inputs[index]
|
||||||
{
|
{
|
||||||
widgets.extend_from_slice(&[
|
widgets.extend_from_slice(&[
|
||||||
WidgetHolder::new(Widget::Separator(Separator {
|
WidgetHolder::unrelated_seperator(),
|
||||||
separator_type: SeparatorType::Unrelated,
|
|
||||||
direction: SeparatorDirection::Horizontal,
|
|
||||||
})),
|
|
||||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
value: Some(x as f64),
|
value: Some(x as f64),
|
||||||
mode: NumberInputMode::Increment,
|
mode: NumberInputMode::Increment,
|
||||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
on_update: update_value(|number_input: &NumberInput| TaggedValue::F32(number_input.value.unwrap() as f32), node_id, index),
|
||||||
NodeGraphMessage::SetInputValue {
|
|
||||||
node: node_id,
|
|
||||||
input_index: index,
|
|
||||||
value: TaggedValue::F32(number_input.value.unwrap() as f32),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}),
|
|
||||||
..NumberInput::default()
|
..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)]
|
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> {
|
fn unknown_node_properties(document_node: &DocumentNode) -> Vec<LayoutGroup> {
|
||||||
vec![LayoutGroup::Row {
|
string_properties(format!("Node '{}' cannot be found in library", document_node.name))
|
||||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
}
|
||||||
value: format!("Node '{}' cannot be found in library", document_node.name),
|
|
||||||
..Default::default()
|
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 {
|
pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId) -> LayoutGroup {
|
||||||
|
|
|
@ -136,6 +136,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
responses.push_back(NodeGraphMessage::CloseNodeGraph.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResendActiveProperties => {
|
ResendActiveProperties => {
|
||||||
|
|
|
@ -6,11 +6,11 @@ use crate::messages::prelude::*;
|
||||||
use graphene::color::Color;
|
use graphene::color::Color;
|
||||||
use graphene::document::Document;
|
use graphene::document::Document;
|
||||||
use graphene::layers::style::{self, Fill, Stroke};
|
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::{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};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ use graphene::intersection::Quad;
|
||||||
use graphene::layers::layer_info::LayerDataType;
|
use graphene::layers::layer_info::LayerDataType;
|
||||||
use graphene::layers::style::{self, Fill, Stroke};
|
use graphene::layers::style::{self, Fill, Stroke};
|
||||||
use graphene::layers::text_layer::FontCache;
|
use graphene::layers::text_layer::FontCache;
|
||||||
use graphene::layers::vector::subpath::Subpath;
|
|
||||||
use graphene::{LayerId, Operation};
|
use graphene::{LayerId, Operation};
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use bezier_rs::ComputeType;
|
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::{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 glam::DVec2;
|
||||||
use graphene::document::Document;
|
use graphene::document::Document;
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::messages::prelude::*;
|
||||||
|
|
||||||
use graphene::layers::layer_info::{Layer, LayerDataType};
|
use graphene::layers::layer_info::{Layer, LayerDataType};
|
||||||
use graphene::layers::style::{self, Stroke};
|
use graphene::layers::style::{self, Stroke};
|
||||||
use graphene::layers::vector::consts::ManipulatorType;
|
|
||||||
use graphene::{LayerId, Operation};
|
use graphene::{LayerId, Operation};
|
||||||
|
use graphene_std::vector::consts::ManipulatorType;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use std::f64::consts::PI;
|
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 crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||||
|
|
||||||
use graphene::intersection::Quad;
|
use graphene::intersection::Quad;
|
||||||
use graphene::layers::vector::consts::ManipulatorType;
|
use graphene_std::vector::consts::ManipulatorType;
|
||||||
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use serde::{Deserialize, Serialize};
|
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 crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||||
|
|
||||||
use graphene::layers::style;
|
use graphene::layers::style;
|
||||||
use graphene::layers::vector::consts::ManipulatorType;
|
|
||||||
use graphene::layers::vector::manipulator_group::ManipulatorGroup;
|
|
||||||
use graphene::LayerId;
|
use graphene::LayerId;
|
||||||
use graphene::Operation;
|
use graphene::Operation;
|
||||||
|
use graphene_std::vector::consts::ManipulatorType;
|
||||||
|
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -40,27 +40,18 @@
|
||||||
--color-f-white-rgb: 255, 255, 255;
|
--color-f-white-rgb: 255, 255, 255;
|
||||||
|
|
||||||
--color-data-general: #c5c5c5;
|
--color-data-general: #c5c5c5;
|
||||||
--color-data-general-rgb: 197, 197, 197;
|
|
||||||
--color-data-general-dim: #767676;
|
--color-data-general-dim: #767676;
|
||||||
--color-data-general-dim-rgb: 118, 118, 118;
|
|
||||||
--color-data-vector: #65bbe5;
|
--color-data-vector: #65bbe5;
|
||||||
--color-data-vector-rgb: 101, 187, 229;
|
|
||||||
--color-data-vector-dim: #4b778c;
|
--color-data-vector-dim: #4b778c;
|
||||||
--color-data-vector-dim-rgb: 75, 119, 140;
|
|
||||||
--color-data-raster: #e4bb72;
|
--color-data-raster: #e4bb72;
|
||||||
--color-data-raster-rgb: 228, 187, 114;
|
|
||||||
--color-data-raster-dim: #8b7752;
|
--color-data-raster-dim: #8b7752;
|
||||||
--color-data-raster-dim-rgb: 139, 119, 82;
|
|
||||||
--color-data-mask: #8d85c7;
|
--color-data-mask: #8d85c7;
|
||||||
--color-data-mask-rgb: 141, 133, 199;
|
|
||||||
--color-data-number: #d6536e;
|
--color-data-number: #d6536e;
|
||||||
--color-data-number-rgb: 214, 83, 110;
|
|
||||||
--color-data-number-dim: #803242;
|
--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: #70a898;
|
||||||
--color-data-color-rgb: 112, 168, 152;
|
|
||||||
--color-data-color-dim: #43645b;
|
--color-data-color-dim: #43645b;
|
||||||
--color-data-color-dim-rgb: 67, 100, 91;
|
|
||||||
|
|
||||||
--color-none: white;
|
--color-none: white;
|
||||||
--color-none-repeat: no-repeat;
|
--color-none-repeat: no-repeat;
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<LayoutCol class="node-graph">
|
<LayoutCol class="node-graph">
|
||||||
<LayoutRow class="options-bar"></LayoutRow>
|
<LayoutRow class="options-bar"></LayoutRow>
|
||||||
<div class="node-list">
|
|
||||||
<LayoutRow>Nodes:</LayoutRow>
|
|
||||||
<LayoutRow>
|
|
||||||
<TextButton v-for="nodeType in nodeTypes" v-bind:key="String(nodeType)" :label="nodeType.name + ' Node'" :action="() => createNode(nodeType.name)"></TextButton>
|
|
||||||
</LayoutRow>
|
|
||||||
</div>
|
|
||||||
<LayoutRow
|
<LayoutRow
|
||||||
class="graph"
|
class="graph"
|
||||||
ref="graph"
|
ref="graph"
|
||||||
|
@ -21,6 +15,14 @@
|
||||||
'--dot-radius': `${dotRadius}px`,
|
'--dot-radius': `${dotRadius}px`,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
<LayoutCol class="node-list" v-if="nodeListLocation" :style="{ marginLeft: `${nodeListX}px`, marginTop: `${nodeListY}px` }">
|
||||||
|
<TextInput placeholder="Search Nodes..." :value="searchTerm" @update:value="(val) => (searchTerm = val)" v-focus />
|
||||||
|
<LayoutCol v-for="nodeCategory in nodeCategories" :key="nodeCategory[0]">
|
||||||
|
<TextLabel>{{ nodeCategory[0] }}</TextLabel>
|
||||||
|
<TextButton v-for="nodeType in nodeCategory[1]" v-bind:key="String(nodeType)" :label="nodeType.name + ' Node'" :action="() => createNode(nodeType.name)" />
|
||||||
|
</LayoutCol>
|
||||||
|
<TextLabel v-if="nodeCategories.length === 0">No search results :(</TextLabel>
|
||||||
|
</LayoutCol>
|
||||||
<div
|
<div
|
||||||
class="nodes"
|
class="nodes"
|
||||||
ref="nodesContainer"
|
ref="nodesContainer"
|
||||||
|
@ -37,8 +39,6 @@
|
||||||
:style="{
|
:style="{
|
||||||
'--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0),
|
'--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0),
|
||||||
'--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0),
|
'--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0),
|
||||||
'--data-color': 'var(--color-data-raster)',
|
|
||||||
'--data-color-dim': 'var(--color-data-raster-dim)',
|
|
||||||
}"
|
}"
|
||||||
:data-node="node.id"
|
:data-node="node.id"
|
||||||
>
|
>
|
||||||
|
@ -111,10 +111,9 @@
|
||||||
.node-list {
|
.node-list {
|
||||||
width: max-content;
|
width: max-content;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding: 20px;
|
padding: 5px;
|
||||||
margin: 40px 10px;
|
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
background-color: var(--color-4-dimgray);
|
background-color: var(--color-3-darkgray);
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-bar {
|
.options-bar {
|
||||||
|
@ -181,7 +180,7 @@
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: var(--color-4-dimgray);
|
background: var(--color-4-dimgray);
|
||||||
left: calc((var(--offset-left) + 0.5) * 24px);
|
left: calc((var(--offset-left) + 0.5) * 24px);
|
||||||
top: calc((var(--offset-top) + 0.5) * 24px);
|
top: calc((var(--offset-top) - 0.5) * 24px);
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border: 1px solid var(--color-e-nearwhite);
|
border: 1px solid var(--color-e-nearwhite);
|
||||||
|
@ -209,6 +208,7 @@
|
||||||
|
|
||||||
.arguments {
|
.arguments {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -297,6 +297,7 @@ import type { IconName } from "@/utility-functions/icons";
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import TextButton from "@/components/widgets/buttons/TextButton.vue";
|
import TextButton from "@/components/widgets/buttons/TextButton.vue";
|
||||||
|
import TextInput from "@/components/widgets/inputs/TextInput.vue";
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
||||||
|
|
||||||
|
@ -316,6 +317,8 @@ export default defineComponent({
|
||||||
linkInProgressFromConnector: undefined as HTMLDivElement | undefined,
|
linkInProgressFromConnector: undefined as HTMLDivElement | undefined,
|
||||||
linkInProgressToConnector: undefined as HTMLDivElement | DOMRect | undefined,
|
linkInProgressToConnector: undefined as HTMLDivElement | DOMRect | undefined,
|
||||||
nodeLinkPaths: [] as [string, string][],
|
nodeLinkPaths: [] as [string, string][],
|
||||||
|
searchTerm: "",
|
||||||
|
nodeListLocation: undefined as { x: number; y: number } | undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -335,8 +338,24 @@ export default defineComponent({
|
||||||
nodes() {
|
nodes() {
|
||||||
return this.nodeGraph.state.nodes;
|
return this.nodeGraph.state.nodes;
|
||||||
},
|
},
|
||||||
nodeTypes() {
|
nodeCategories() {
|
||||||
return this.nodeGraph.state.nodeTypes;
|
const categories = new Map();
|
||||||
|
this.nodeGraph.state.nodeTypes.forEach((node) => {
|
||||||
|
if (this.searchTerm.length && !node.name.toLowerCase().includes(this.searchTerm.toLowerCase()) && !node.category.toLowerCase().includes(this.searchTerm.toLowerCase())) return;
|
||||||
|
|
||||||
|
const category = categories.get(node.category);
|
||||||
|
if (category) category.push(node);
|
||||||
|
else categories.set(node.category, [node]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = Array.from(categories);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
nodeListX() {
|
||||||
|
return ((this.nodeListLocation?.x || 0) * GRID_SIZE + this.transform.x) * this.transform.scale;
|
||||||
|
},
|
||||||
|
nodeListY() {
|
||||||
|
return ((this.nodeListLocation?.y || 0) * GRID_SIZE + this.transform.y) * this.transform.scale;
|
||||||
},
|
},
|
||||||
linkPathInProgress(): [string, string] | undefined {
|
linkPathInProgress(): [string, string] | undefined {
|
||||||
if (this.linkInProgressFromConnector && this.linkInProgressToConnector) {
|
if (this.linkInProgressFromConnector && this.linkInProgressToConnector) {
|
||||||
|
@ -461,8 +480,19 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pointerDown(e: PointerEvent) {
|
pointerDown(e: PointerEvent) {
|
||||||
|
if (e.button === 2) {
|
||||||
|
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
||||||
|
const graph = graphDiv?.getBoundingClientRect() || new DOMRect();
|
||||||
|
this.nodeListLocation = {
|
||||||
|
x: Math.round(((e.clientX - graph.x) / this.transform.scale - this.transform.x) / GRID_SIZE),
|
||||||
|
y: Math.round(((e.clientY - graph.y) / this.transform.scale - this.transform.y) / GRID_SIZE),
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement;
|
const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement;
|
||||||
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
|
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
|
||||||
|
const nodeList = (e.target as HTMLElement).closest(".node-list") as HTMLElement | undefined;
|
||||||
|
|
||||||
if (port) {
|
if (port) {
|
||||||
const isOutput = Boolean(port.getAttribute("data-port") === "output");
|
const isOutput = Boolean(port.getAttribute("data-port") === "output");
|
||||||
|
@ -488,7 +518,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
||||||
} else {
|
} else if (!nodeList) {
|
||||||
this.selected = [];
|
this.selected = [];
|
||||||
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
||||||
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
||||||
|
@ -560,7 +590,10 @@ export default defineComponent({
|
||||||
this.linkInProgressToConnector = undefined;
|
this.linkInProgressToConnector = undefined;
|
||||||
},
|
},
|
||||||
createNode(nodeType: string): void {
|
createNode(nodeType: string): void {
|
||||||
this.editor.instance.createNode(nodeType);
|
if (!this.nodeListLocation) return;
|
||||||
|
|
||||||
|
this.editor.instance.createNode(nodeType, this.nodeListLocation.x, this.nodeListLocation.y);
|
||||||
|
this.nodeListLocation = undefined;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -578,6 +611,7 @@ export default defineComponent({
|
||||||
LayoutRow,
|
LayoutRow,
|
||||||
TextLabel,
|
TextLabel,
|
||||||
TextButton,
|
TextButton,
|
||||||
|
TextInput,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<LayoutRow class="parameter-expose-button">
|
<LayoutRow class="parameter-expose-button">
|
||||||
<button :class="{ exposed }" :style="{ '--data-type-color': dataTypeColor }" @click="(e: MouseEvent) => action(e)" :title="tooltip" :tabindex="0"></button>
|
<button :class="{ exposed }" :style="{ '--data-type-color': `var(--color-data-${dataType})` }" @click="(e: MouseEvent) => action(e)" :title="tooltip" :tabindex="0"></button>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -53,21 +53,6 @@ export default defineComponent({
|
||||||
// Callbacks
|
// Callbacks
|
||||||
action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true },
|
action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true },
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
dataTypeColor(): string {
|
|
||||||
// TODO: Move this function somewhere where it can be reused by other components
|
|
||||||
const colorsMap = {
|
|
||||||
general: "var(--color-data-general)",
|
|
||||||
vector: "var(--color-data-vector)",
|
|
||||||
raster: "var(--color-data-raster)",
|
|
||||||
mask: "var(--color-data-mask)",
|
|
||||||
number: "var(--color-data-number)",
|
|
||||||
color: "var(--color-data-color)",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
return colorsMap[this.dataType as keyof typeof colorsMap] || colorsMap.general;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { LayoutRow },
|
components: { LayoutRow },
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
v-model="inputValue"
|
v-model="inputValue"
|
||||||
:spellcheck="spellcheck"
|
:spellcheck="spellcheck"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
|
:placeholder="placeholder"
|
||||||
@focus="() => $emit('textFocused')"
|
@focus="() => $emit('textFocused')"
|
||||||
@blur="() => $emit('textChanged')"
|
@blur="() => $emit('textChanged')"
|
||||||
@change="() => $emit('textChanged')"
|
@change="() => $emit('textChanged')"
|
||||||
|
@ -148,6 +149,7 @@ export default defineComponent({
|
||||||
textarea: { type: Boolean as PropType<boolean>, default: false },
|
textarea: { type: Boolean as PropType<boolean>, default: false },
|
||||||
tooltip: { type: String as PropType<string | undefined>, required: false },
|
tooltip: { type: String as PropType<string | undefined>, required: false },
|
||||||
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
|
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
|
||||||
|
placeholder: { type: String as PropType<string>, required: false },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
:spellcheck="true"
|
:spellcheck="true"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:tooltip="tooltip"
|
:tooltip="tooltip"
|
||||||
|
:placeholder="placeholder"
|
||||||
:style="{ 'min-width': minWidth > 0 ? `${minWidth}px` : undefined }"
|
:style="{ 'min-width': minWidth > 0 ? `${minWidth}px` : undefined }"
|
||||||
:sharpRightCorners="sharpRightCorners"
|
:sharpRightCorners="sharpRightCorners"
|
||||||
@textFocused="() => onTextFocused()"
|
@textFocused="() => onTextFocused()"
|
||||||
|
@ -41,6 +42,7 @@ export default defineComponent({
|
||||||
// Label
|
// Label
|
||||||
label: { type: String as PropType<string>, required: false },
|
label: { type: String as PropType<string>, required: false },
|
||||||
tooltip: { type: String as PropType<string | undefined>, required: false },
|
tooltip: { type: String as PropType<string | undefined>, required: false },
|
||||||
|
placeholder: { type: String as PropType<string>, required: false },
|
||||||
|
|
||||||
// Disabled
|
// Disabled
|
||||||
disabled: { type: Boolean as PropType<boolean>, default: false },
|
disabled: { type: Boolean as PropType<boolean>, default: false },
|
||||||
|
|
|
@ -17,5 +17,20 @@ import App from "@/App.vue";
|
||||||
await initWasm();
|
await initWasm();
|
||||||
|
|
||||||
// Initialize the Vue application
|
// Initialize the Vue application
|
||||||
createApp(App).mount("#app");
|
createApp(App)
|
||||||
|
.directive("focus", {
|
||||||
|
// When the bound element is inserted into the DOM
|
||||||
|
mounted(el) {
|
||||||
|
let focus = el;
|
||||||
|
|
||||||
|
// Find actual relevant child
|
||||||
|
while (focus.children.length) focus = focus.children[0];
|
||||||
|
|
||||||
|
// Random timeout needed?
|
||||||
|
setTimeout(() => {
|
||||||
|
focus.focus(); // Focus the element
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.mount("#app");
|
||||||
};
|
};
|
||||||
|
|
|
@ -102,6 +102,8 @@ export class FrontendNodeLink {
|
||||||
|
|
||||||
export class FrontendNodeType {
|
export class FrontendNodeType {
|
||||||
readonly name!: string;
|
readonly name!: string;
|
||||||
|
|
||||||
|
readonly category!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IndexedDbDocumentDetails extends DocumentDetails {
|
export class IndexedDbDocumentDetails extends DocumentDetails {
|
||||||
|
|
|
@ -552,8 +552,8 @@ impl JsEditorHandle {
|
||||||
|
|
||||||
/// Creates a new document node in the node graph
|
/// Creates a new document node in the node graph
|
||||||
#[wasm_bindgen(js_name = createNode)]
|
#[wasm_bindgen(js_name = createNode)]
|
||||||
pub fn create_node(&self, node_type: String) {
|
pub fn create_node(&self, node_type: String, x: i32, y: i32) {
|
||||||
let message = NodeGraphMessage::CreateNode { node_id: None, node_type };
|
let message = NodeGraphMessage::CreateNode { node_id: None, node_type, x, y };
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ license = "Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
graph-craft = { path = "../node-graph/graph-craft", features = ["serde"] }
|
graph-craft = { path = "../node-graph/graph-craft", features = ["serde"] }
|
||||||
|
graphene-std = { path = "../node-graph/gstd", features = ["serde"] }
|
||||||
image = { version = "0.24", default-features = false }
|
image = { version = "0.24", default-features = false }
|
||||||
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -8,9 +8,10 @@ use crate::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||||
use crate::layers::shape_layer::ShapeLayer;
|
use crate::layers::shape_layer::ShapeLayer;
|
||||||
use crate::layers::style::RenderData;
|
use crate::layers::style::RenderData;
|
||||||
use crate::layers::text_layer::{Font, FontCache, TextLayer};
|
use crate::layers::text_layer::{Font, FontCache, TextLayer};
|
||||||
use crate::layers::vector::subpath::Subpath;
|
|
||||||
use crate::{DocumentError, DocumentResponse, Operation};
|
use crate::{DocumentError, DocumentResponse, Operation};
|
||||||
|
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::boolean_ops::{split_path_seg, subdivide_path_seg};
|
use crate::boolean_ops::{split_path_seg, subdivide_path_seg};
|
||||||
use crate::consts::{F64LOOSE, F64PRECISE};
|
use crate::consts::{F64LOOSE, F64PRECISE};
|
||||||
use crate::layers::vector::subpath::Subpath;
|
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, ParamCurveExtrema, PathSeg, Point, QuadBez, Rect, Shape, Vec2};
|
use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, ParamCurveExtrema, PathSeg, Point, QuadBez, Rect, Shape, Vec2};
|
||||||
|
|
|
@ -6,12 +6,13 @@ use super::nodegraph_layer::NodeGraphFrameLayer;
|
||||||
use super::shape_layer::ShapeLayer;
|
use super::shape_layer::ShapeLayer;
|
||||||
use super::style::{PathStyle, RenderData};
|
use super::style::{PathStyle, RenderData};
|
||||||
use super::text_layer::TextLayer;
|
use super::text_layer::TextLayer;
|
||||||
use super::vector::subpath::Subpath;
|
|
||||||
use crate::intersection::Quad;
|
use crate::intersection::Quad;
|
||||||
use crate::layers::text_layer::FontCache;
|
use crate::layers::text_layer::FontCache;
|
||||||
use crate::DocumentError;
|
use crate::DocumentError;
|
||||||
use crate::LayerId;
|
use crate::LayerId;
|
||||||
|
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -96,6 +97,34 @@ impl From<&LayerDataType> for LayerDataTypeDiscriminant {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ** CONVERSIONS **
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a mut Layer> for &'a mut Subpath {
|
||||||
|
type Error = &'static str;
|
||||||
|
/// Convert a mutable layer into a mutable [Subpath].
|
||||||
|
fn try_from(layer: &'a mut Layer) -> Result<&'a mut Subpath, Self::Error> {
|
||||||
|
match &mut layer.data {
|
||||||
|
LayerDataType::Shape(layer) => Ok(&mut layer.shape),
|
||||||
|
// TODO Resolve converting text into a Subpath at the layer level
|
||||||
|
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
|
||||||
|
_ => Err("Did not find any shape data in the layer"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a Layer> for &'a Subpath {
|
||||||
|
type Error = &'static str;
|
||||||
|
/// Convert a reference to a layer into a reference of a [Subpath].
|
||||||
|
fn try_from(layer: &'a Layer) -> Result<&'a Subpath, Self::Error> {
|
||||||
|
match &layer.data {
|
||||||
|
LayerDataType::Shape(layer) => Ok(&layer.shape),
|
||||||
|
// TODO Resolve converting text into a Subpath at the layer level
|
||||||
|
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
|
||||||
|
_ => Err("Did not find any shape data in the layer"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Defines shared behavior for every layer type.
|
/// Defines shared behavior for every layer type.
|
||||||
pub trait LayerData {
|
pub trait LayerData {
|
||||||
/// Render the layer as an SVG tag to a given string.
|
/// Render the layer as an SVG tag to a given string.
|
||||||
|
|
|
@ -20,7 +20,6 @@ pub mod base64_serde;
|
||||||
pub mod blend_mode;
|
pub mod blend_mode;
|
||||||
/// Contains the [FolderLayer](folder_layer::FolderLayer) type that encapsulates other layers, including more folders.
|
/// Contains the [FolderLayer](folder_layer::FolderLayer) type that encapsulates other layers, including more folders.
|
||||||
pub mod folder_layer;
|
pub mod folder_layer;
|
||||||
pub mod id_vec;
|
|
||||||
/// Contains the [ImageLayer](image_layer::ImageLayer) type that contains a bitmap image.
|
/// Contains the [ImageLayer](image_layer::ImageLayer) type that contains a bitmap image.
|
||||||
pub mod image_layer;
|
pub mod image_layer;
|
||||||
/// Contains the [ImaginateLayer](imaginate_layer::ImaginateLayer) type that contains a bitmap image generated based on a prompt and optionally the layers beneath it.
|
/// Contains the [ImaginateLayer](imaginate_layer::ImaginateLayer) type that contains a bitmap image generated based on a prompt and optionally the layers beneath it.
|
||||||
|
@ -34,4 +33,3 @@ pub mod shape_layer;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
/// Contains the [TextLayer](text_layer::TextLayer) type.
|
/// Contains the [TextLayer](text_layer::TextLayer) type.
|
||||||
pub mod text_layer;
|
pub mod text_layer;
|
||||||
pub mod vector;
|
|
||||||
|
|
|
@ -119,49 +119,12 @@ impl Default for NodeGraphFrameLayer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
use graph_craft::document::*;
|
use graph_craft::document::*;
|
||||||
use graph_craft::proto::NodeIdentifier;
|
use graph_craft::proto::NodeIdentifier;
|
||||||
let brighten_network = NodeNetwork {
|
|
||||||
inputs: vec![0, 0],
|
|
||||||
output: 0,
|
|
||||||
nodes: [(
|
|
||||||
0,
|
|
||||||
DocumentNode {
|
|
||||||
name: "Brighten Image Node".into(),
|
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
|
||||||
"graphene_std::raster::BrightenImageNode",
|
|
||||||
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
|
||||||
)),
|
|
||||||
metadata: DocumentNodeMetadata::default(),
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let hue_shift_network = NodeNetwork {
|
|
||||||
inputs: vec![0, 0],
|
|
||||||
output: 0,
|
|
||||||
nodes: [(
|
|
||||||
0,
|
|
||||||
DocumentNode {
|
|
||||||
name: "Hue Shift Image Node".into(),
|
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
|
||||||
"graphene_std::raster::HueShiftImage",
|
|
||||||
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
|
||||||
)),
|
|
||||||
metadata: DocumentNodeMetadata::default(),
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
mime: String::new(),
|
mime: String::new(),
|
||||||
network: NodeNetwork {
|
network: NodeNetwork {
|
||||||
inputs: vec![0],
|
inputs: vec![0],
|
||||||
output: 3,
|
output: 1,
|
||||||
nodes: [
|
nodes: [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
|
@ -174,41 +137,11 @@ impl Default for NodeGraphFrameLayer {
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
1,
|
1,
|
||||||
DocumentNode {
|
|
||||||
name: "Hue Shift Image".into(),
|
|
||||||
inputs: vec![
|
|
||||||
NodeInput::Node(0),
|
|
||||||
NodeInput::Value {
|
|
||||||
tagged_value: value::TaggedValue::F32(50.),
|
|
||||||
exposed: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
implementation: DocumentNodeImplementation::Network(hue_shift_network),
|
|
||||||
metadata: DocumentNodeMetadata { position: (8 + 7, 4 + 2) },
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
2,
|
|
||||||
DocumentNode {
|
|
||||||
name: "Brighten Image".into(),
|
|
||||||
inputs: vec![
|
|
||||||
NodeInput::Node(1),
|
|
||||||
NodeInput::Value {
|
|
||||||
tagged_value: value::TaggedValue::F32(10.),
|
|
||||||
exposed: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
implementation: DocumentNodeImplementation::Network(brighten_network),
|
|
||||||
metadata: DocumentNodeMetadata { position: (8 + 7 * 2, 4 + 2 * 2) },
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
3,
|
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Output".into(),
|
name: "Output".into(),
|
||||||
inputs: vec![NodeInput::Node(2)],
|
inputs: vec![NodeInput::Node(0)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])),
|
||||||
metadata: DocumentNodeMetadata { position: (8 + 7 * 3, 4) },
|
metadata: DocumentNodeMetadata { position: (20, 4) },
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use super::layer_info::LayerData;
|
use super::layer_info::LayerData;
|
||||||
use super::style::{self, PathStyle, RenderData, ViewMode};
|
use super::style::{self, PathStyle, RenderData, ViewMode};
|
||||||
use super::vector::subpath::Subpath;
|
|
||||||
use crate::intersection::{intersect_quad_bez_path, Quad};
|
use crate::intersection::{intersect_quad_bez_path, Quad};
|
||||||
use crate::layers::text_layer::FontCache;
|
use crate::layers::text_layer::FontCache;
|
||||||
use crate::LayerId;
|
use crate::LayerId;
|
||||||
|
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use super::layer_info::LayerData;
|
use super::layer_info::LayerData;
|
||||||
use super::style::{PathStyle, RenderData, ViewMode};
|
use super::style::{PathStyle, RenderData, ViewMode};
|
||||||
use super::vector::subpath::Subpath;
|
|
||||||
use crate::intersection::{intersect_quad_bez_path, Quad};
|
use crate::intersection::{intersect_quad_bez_path, Quad};
|
||||||
use crate::LayerId;
|
use crate::LayerId;
|
||||||
pub use font_cache::{Font, FontCache};
|
pub use font_cache::{Font, FontCache};
|
||||||
|
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use rustybuzz::Face;
|
use rustybuzz::Face;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::layers::vector::consts::ManipulatorType;
|
use graphene_std::vector::consts::ManipulatorType;
|
||||||
use crate::layers::vector::manipulator_group::ManipulatorGroup;
|
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||||
use crate::layers::vector::manipulator_point::ManipulatorPoint;
|
use graphene_std::vector::manipulator_point::ManipulatorPoint;
|
||||||
use crate::layers::vector::subpath::Subpath;
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
|
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
|
||||||
|
|
|
@ -3,11 +3,12 @@ use crate::layers::blend_mode::BlendMode;
|
||||||
use crate::layers::imaginate_layer::{ImaginateMaskFillContent, ImaginateMaskPaintMode, ImaginateSamplingMethod, ImaginateStatus};
|
use crate::layers::imaginate_layer::{ImaginateMaskFillContent, ImaginateMaskPaintMode, ImaginateSamplingMethod, ImaginateStatus};
|
||||||
use crate::layers::layer_info::Layer;
|
use crate::layers::layer_info::Layer;
|
||||||
use crate::layers::style::{self, Stroke};
|
use crate::layers::style::{self, Stroke};
|
||||||
use crate::layers::vector::consts::ManipulatorType;
|
|
||||||
use crate::layers::vector::manipulator_group::ManipulatorGroup;
|
|
||||||
use crate::layers::vector::subpath::Subpath;
|
|
||||||
use crate::LayerId;
|
use crate::LayerId;
|
||||||
|
|
||||||
|
use graphene_std::vector::consts::ManipulatorType;
|
||||||
|
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
|
@ -14,10 +14,15 @@ documentation = "https://docs.rs/dyn-any"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dyn-any-derive = { path = "derive", version = "0.2.0", optional = true }
|
dyn-any-derive = { path = "derive", version = "0.2.0", optional = true }
|
||||||
log = { version = "0.4", optional = true }
|
log = { version = "0.4", optional = true }
|
||||||
|
glam = { version = "0.17", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
derive = ["dyn-any-derive"]
|
derive = ["dyn-any-derive"]
|
||||||
log-bad-types = ["log"]
|
log-bad-types = ["log"]
|
||||||
|
# Opt into impls for Rc<T> and Arc<T>.
|
||||||
|
rc = []
|
||||||
|
# Opt into impls for some glam types
|
||||||
|
glam = ["dep:glam"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
|
@ -190,13 +190,30 @@ use std::{
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl_type!(Option<T>,Result<T, E>,Cell<T>,UnsafeCell<T>,RefCell<T>,MaybeUninit<T>,
|
impl_type!(
|
||||||
Vec<T>, String, BTreeMap<K,V>,BTreeSet<V>, LinkedList<T>, VecDeque<T>,
|
Option<T>, Result<T, E>, Cell<T>, UnsafeCell<T>, RefCell<T>, MaybeUninit<T>,
|
||||||
BinaryHeap<T>, ManuallyDrop<T>, PhantomData<T>, PhantomPinned,Empty<T>,
|
Vec<T>, String, BTreeMap<K,V>,BTreeSet<V>, LinkedList<T>, VecDeque<T>,
|
||||||
Wrapping<T>, Duration, Once, Mutex<T>, RwLock<T>, bool, f32, f64, char,
|
BinaryHeap<T>, ManuallyDrop<T>, PhantomData<T>, PhantomPinned, Empty<T>,
|
||||||
u8, AtomicU8, u16,AtomicU16, u32,AtomicU32, u64,AtomicU64, usize,AtomicUsize,
|
Wrapping<T>, Duration, Once, Mutex<T>, RwLock<T>, bool, f32, f64, char,
|
||||||
i8,AtomicI8, i16,AtomicI16, i32,AtomicI32, i64,AtomicI64, isize,AtomicIsize,
|
u8, AtomicU8, u16, AtomicU16, u32, AtomicU32, u64, AtomicU64, usize, AtomicUsize,
|
||||||
i128, u128, AtomicBool, AtomicPtr<T>
|
i8, AtomicI8, i16, AtomicI16, i32, AtomicI32, i64, AtomicI64, isize, AtomicIsize,
|
||||||
|
i128, u128, AtomicBool, AtomicPtr<T>
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "rc")]
|
||||||
|
use std::{rc::Rc, sync::Arc};
|
||||||
|
#[cfg(feature = "rc")]
|
||||||
|
impl_type!(Rc<T>, Arc<T>);
|
||||||
|
|
||||||
|
#[cfg(feature = "glam")]
|
||||||
|
use glam::*;
|
||||||
|
#[cfg(feature = "glam")]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
impl_type!(
|
||||||
|
IVec2, IVec3, IVec4, UVec2, UVec3, UVec4, BVec2, BVec3, BVec4,
|
||||||
|
Vec2, Vec3, Vec3A, Vec4, DVec2, DVec3, DVec4,
|
||||||
|
Mat2, Mat3, Mat3A, Mat4, DMat2, DMat3, DMat4,
|
||||||
|
Quat, Affine2, Affine3A, DAffine2, DAffine3, DQuat
|
||||||
);
|
);
|
||||||
|
|
||||||
impl<T: crate::StaticType + ?Sized> crate::StaticType for Box<T> {
|
impl<T: crate::StaticType + ?Sized> crate::StaticType for Box<T> {
|
||||||
|
|
|
@ -209,9 +209,9 @@ impl Color {
|
||||||
} else {
|
} else {
|
||||||
(max_channel - min_channel) / (2. - max_channel - min_channel)
|
(max_channel - min_channel) / (2. - max_channel - min_channel)
|
||||||
};
|
};
|
||||||
let hue = if self.red > self.green && self.red > self.blue {
|
let hue = if self.red >= self.green && self.red >= self.blue {
|
||||||
(self.green - self.blue) / (max_channel - min_channel)
|
(self.green - self.blue) / (max_channel - min_channel)
|
||||||
} else if self.green > self.red && self.green > self.blue {
|
} else if self.green >= self.red && self.green >= self.blue {
|
||||||
2. + (self.blue - self.red) / (max_channel - min_channel)
|
2. + (self.blue - self.red) / (max_channel - min_channel)
|
||||||
} else {
|
} else {
|
||||||
4. + (self.red - self.green) / (max_channel - min_channel)
|
4. + (self.red - self.green) / (max_channel - min_channel)
|
||||||
|
@ -282,6 +282,7 @@ fn hsl_roundtrip() {
|
||||||
(95, 79, 88),
|
(95, 79, 88),
|
||||||
(13, 34, 4),
|
(13, 34, 4),
|
||||||
(82, 84, 84),
|
(82, 84, 84),
|
||||||
|
(255, 255, 178),
|
||||||
] {
|
] {
|
||||||
let col = Color::from_rgb8(red, green, blue);
|
let col = Color::from_rgb8(red, green, blue);
|
||||||
let [hue, saturation, luminance, alpha] = col.to_hsla();
|
let [hue, saturation, luminance, alpha] = col.to_hsla();
|
||||||
|
|
|
@ -9,13 +9,14 @@ license = "MIT OR Apache-2.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
graphene-core = { path = "../gcore", features = ["async", "std"] }
|
graphene-core = { path = "../gcore", features = ["async", "std"] }
|
||||||
graphene-std = { path = "../gstd" }
|
graphene-std = { path = "../gstd" }
|
||||||
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types"] }
|
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "rc", "glam"] }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
borrow_stack = { path = "../borrow_stack" }
|
borrow_stack = { path = "../borrow_stack" }
|
||||||
dyn-clone = "1.0"
|
dyn-clone = "1.0"
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive", "rc"], optional = true }
|
||||||
|
glam = { version = "0.17" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
serde = ["dep:serde", "graphene-std/serde"]
|
serde = ["dep:serde", "graphene-std/serde", "glam/serde"]
|
||||||
|
|
|
@ -58,6 +58,7 @@ impl DocumentNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_proto_node(mut self) -> ProtoNode {
|
fn resolve_proto_node(mut self) -> ProtoNode {
|
||||||
|
assert_ne!(self.inputs.len(), 0, "Resolving document node {:#?} with no inputs", self);
|
||||||
let first = self.inputs.remove(0);
|
let first = self.inputs.remove(0);
|
||||||
if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation {
|
if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation {
|
||||||
let (input, mut args) = match first {
|
let (input, mut args) = match first {
|
||||||
|
@ -102,6 +103,9 @@ pub enum NodeInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeInput {
|
impl NodeInput {
|
||||||
|
pub const fn value(tagged_value: value::TaggedValue, exposed: bool) -> Self {
|
||||||
|
Self::Value { tagged_value, exposed }
|
||||||
|
}
|
||||||
fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
|
fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
|
||||||
if let NodeInput::Node(id) = self {
|
if let NodeInput::Node(id) = self {
|
||||||
*self = NodeInput::Node(f(*id))
|
*self = NodeInput::Node(f(*id))
|
||||||
|
|
|
@ -1,28 +1,40 @@
|
||||||
use dyn_any::StaticType;
|
use dyn_any::StaticType;
|
||||||
|
|
||||||
use dyn_clone::DynClone;
|
|
||||||
|
|
||||||
use dyn_any::{DynAny, Upcast};
|
use dyn_any::{DynAny, Upcast};
|
||||||
|
use dyn_clone::DynClone;
|
||||||
|
use glam::DVec2;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A type that is known, allowing serialization (serde::Deserialize is not object safe)
|
/// A type that is known, allowing serialization (serde::Deserialize is not object safe)
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum TaggedValue {
|
pub enum TaggedValue {
|
||||||
|
None,
|
||||||
String(String),
|
String(String),
|
||||||
U32(u32),
|
U32(u32),
|
||||||
F32(f32),
|
F32(f32),
|
||||||
|
F64(f64),
|
||||||
|
Bool(bool),
|
||||||
|
DVec2(DVec2),
|
||||||
Image(graphene_std::raster::Image),
|
Image(graphene_std::raster::Image),
|
||||||
Color(graphene_core::raster::color::Color),
|
Color(graphene_core::raster::color::Color),
|
||||||
|
Subpath(graphene_std::vector::subpath::Subpath),
|
||||||
|
RcSubpath(Arc<graphene_std::vector::subpath::Subpath>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaggedValue {
|
impl TaggedValue {
|
||||||
pub fn to_value(self) -> Value {
|
pub fn to_value(self) -> Value {
|
||||||
match self {
|
match self {
|
||||||
|
TaggedValue::None => Box::new(()),
|
||||||
TaggedValue::String(x) => Box::new(x),
|
TaggedValue::String(x) => Box::new(x),
|
||||||
TaggedValue::U32(x) => Box::new(x),
|
TaggedValue::U32(x) => Box::new(x),
|
||||||
TaggedValue::F32(x) => Box::new(x),
|
TaggedValue::F32(x) => Box::new(x),
|
||||||
|
TaggedValue::F64(x) => Box::new(x),
|
||||||
|
TaggedValue::Bool(x) => Box::new(x),
|
||||||
|
TaggedValue::DVec2(x) => Box::new(x),
|
||||||
TaggedValue::Image(x) => Box::new(x),
|
TaggedValue::Image(x) => Box::new(x),
|
||||||
TaggedValue::Color(x) => Box::new(x),
|
TaggedValue::Color(x) => Box::new(x),
|
||||||
|
TaggedValue::Subpath(x) => Box::new(x),
|
||||||
|
TaggedValue::RcSubpath(x) => Box::new(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use borrow_stack::FixedSizeStack;
|
use borrow_stack::FixedSizeStack;
|
||||||
|
use glam::DVec2;
|
||||||
use graphene_core::generic::FnNode;
|
use graphene_core::generic::FnNode;
|
||||||
use graphene_core::ops::AddNode;
|
use graphene_core::ops::AddNode;
|
||||||
use graphene_core::raster::color::Color;
|
use graphene_core::raster::color::Color;
|
||||||
|
@ -9,6 +10,7 @@ use graphene_core::Node;
|
||||||
use graphene_std::any::DowncastBothNode;
|
use graphene_std::any::DowncastBothNode;
|
||||||
use graphene_std::any::{Any, DowncastNode, DynAnyNode, IntoTypeErasedNode, TypeErasedNode};
|
use graphene_std::any::{Any, DowncastNode, DynAnyNode, IntoTypeErasedNode, TypeErasedNode};
|
||||||
use graphene_std::raster::Image;
|
use graphene_std::raster::Image;
|
||||||
|
use graphene_std::vector::subpath::Subpath;
|
||||||
|
|
||||||
use crate::proto::Type;
|
use crate::proto::Type;
|
||||||
use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNode, ProtoNodeInput, Type::Concrete};
|
use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNode, ProtoNodeInput, Type::Concrete};
|
||||||
|
@ -48,8 +50,8 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
||||||
stack.push_fn(move |nodes| {
|
stack.push_fn(move |nodes| {
|
||||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Add Node constructed with out rhs input node") };
|
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Add Node constructed with out rhs input node") };
|
||||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
let input_node: DowncastBothNode<_, (), f64> = DowncastBothNode::new(value_node);
|
||||||
let node: DynAnyNode<_, f32, _, _> = DynAnyNode::new(ConsNode::new(input_node).then(graphene_core::ops::AddNode));
|
let node: DynAnyNode<_, f64, _, _> = DynAnyNode::new(ConsNode::new(input_node).then(graphene_core::ops::AddNode));
|
||||||
|
|
||||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
@ -297,14 +299,28 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
(NodeIdentifier::new("graphene_std::raster::InvertImageColorNode", &[]), |proto_node, stack| {
|
||||||
|
stack.push_fn(move |nodes| {
|
||||||
|
let node = DynAnyNode::new(graphene_std::raster::InvertImageColorNode);
|
||||||
|
|
||||||
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
(pre_node).then(node).into_type_erased()
|
||||||
|
} else {
|
||||||
|
node.into_type_erased()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
NodeIdentifier::new("graphene_std::raster::ShiftImageHSLNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(move |nodes| {
|
stack.push_fn(move |nodes| {
|
||||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Hue Shift Image Node constructed with out shift input node") };
|
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ShiftImageHSLNode Node constructed without inputs") };
|
||||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
|
||||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
let hue: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||||
let node = DynAnyNode::new(graphene_std::raster::HueShiftImage::new(input_node));
|
let saturation: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[1] as usize).unwrap());
|
||||||
|
let luminance: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[2] as usize).unwrap());
|
||||||
|
let node = DynAnyNode::new(graphene_std::raster::ShiftImageHSLNode::new(hue, saturation, luminance));
|
||||||
|
|
||||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
@ -316,13 +332,82 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
NodeIdentifier::new("graphene_std::raster::ImageBrightnessAndContrast", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(move |nodes| {
|
stack.push_fn(move |nodes| {
|
||||||
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Brighten Image Node constructed with out brighten input node") };
|
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ImageBrightnessAndContrast Node constructed without inputs") };
|
||||||
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
|
||||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
let brightness: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||||
let node = DynAnyNode::new(graphene_std::raster::BrightenImageNode::new(input_node));
|
let contrast: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[1] as usize).unwrap());
|
||||||
|
let node = DynAnyNode::new(graphene_std::raster::ImageBrightnessAndContrast::new(brightness, contrast));
|
||||||
|
|
||||||
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
(pre_node).then(node).into_type_erased()
|
||||||
|
} else {
|
||||||
|
node.into_type_erased()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NodeIdentifier::new("graphene_std::raster::ImageGammaNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||||
|
|proto_node, stack| {
|
||||||
|
stack.push_fn(move |nodes| {
|
||||||
|
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ImageGammaNode Node constructed without inputs") };
|
||||||
|
let gamma: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||||
|
let node = DynAnyNode::new(graphene_std::raster::ImageGammaNode::new(gamma));
|
||||||
|
|
||||||
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
(pre_node).then(node).into_type_erased()
|
||||||
|
} else {
|
||||||
|
node.into_type_erased()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NodeIdentifier::new("graphene_std::raster::ImageOpacityNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||||
|
|proto_node, stack| {
|
||||||
|
stack.push_fn(move |nodes| {
|
||||||
|
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ImageOpacityNode Node constructed without inputs") };
|
||||||
|
let opacity: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||||
|
let node = DynAnyNode::new(graphene_std::raster::ImageOpacityNode::new(opacity));
|
||||||
|
|
||||||
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
(pre_node).then(node).into_type_erased()
|
||||||
|
} else {
|
||||||
|
node.into_type_erased()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NodeIdentifier::new("graphene_std::raster::Posterize", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||||
|
|proto_node, stack| {
|
||||||
|
stack.push_fn(move |nodes| {
|
||||||
|
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Posterize Node constructed without inputs") };
|
||||||
|
let value: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||||
|
let node = DynAnyNode::new(graphene_std::raster::Posterize::new(value));
|
||||||
|
|
||||||
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
(pre_node).then(node).into_type_erased()
|
||||||
|
} else {
|
||||||
|
node.into_type_erased()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
NodeIdentifier::new("graphene_std::raster::ExposureNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||||
|
|proto_node, stack| {
|
||||||
|
stack.push_fn(move |nodes| {
|
||||||
|
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("ExposureNode constructed without inputs") };
|
||||||
|
let value: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||||
|
let node = DynAnyNode::new(graphene_std::raster::ExposureNode::new(value));
|
||||||
|
|
||||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
@ -378,6 +463,45 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]), |_proto_node, stack| {
|
||||||
|
stack.push_fn(|_nodes| DynAnyNode::new(graphene_std::vector::generator_nodes::UnitCircleGenerator).into_type_erased())
|
||||||
|
}),
|
||||||
|
(NodeIdentifier::new("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]), |_proto_node, stack| {
|
||||||
|
stack.push_fn(|_nodes| DynAnyNode::new(graphene_std::vector::generator_nodes::UnitSquareGenerator).into_type_erased())
|
||||||
|
}),
|
||||||
|
(NodeIdentifier::new("graphene_std::vector::generator_nodes::BlitSubpath", &[]), |proto_node, stack| {
|
||||||
|
stack.push_fn(move |nodes| {
|
||||||
|
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("BlitSubpath without subpath input") };
|
||||||
|
let value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||||
|
let input_node: DowncastBothNode<_, (), Subpath> = DowncastBothNode::new(value_node);
|
||||||
|
let node = DynAnyNode::new(graphene_std::vector::generator_nodes::BlitSubpath::new(input_node));
|
||||||
|
|
||||||
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
(pre_node).then(node).into_type_erased()
|
||||||
|
} else {
|
||||||
|
node.into_type_erased()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
(NodeIdentifier::new("graphene_std::vector::generator_nodes::TransformSubpathNode", &[]), |proto_node, stack| {
|
||||||
|
stack.push_fn(move |nodes| {
|
||||||
|
let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("TransformSubpathNode without subpath input") };
|
||||||
|
let translate_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[0] as usize).unwrap());
|
||||||
|
let rotate_node: DowncastBothNode<_, (), f64> = DowncastBothNode::new(nodes.get(construction_nodes[1] as usize).unwrap());
|
||||||
|
let scale_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[2] as usize).unwrap());
|
||||||
|
let shear_node: DowncastBothNode<_, (), DVec2> = DowncastBothNode::new(nodes.get(construction_nodes[3] as usize).unwrap());
|
||||||
|
|
||||||
|
let node = DynAnyNode::new(graphene_std::vector::generator_nodes::TransformSubpathNode::new(translate_node, rotate_node, scale_node, shear_node));
|
||||||
|
|
||||||
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
(pre_node).then(node).into_type_erased()
|
||||||
|
} else {
|
||||||
|
node.into_type_erased()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn push_node(proto_node: ProtoNode, stack: &FixedSizeStack<TypeErasedNode<'static>>) {
|
pub fn push_node(proto_node: ProtoNode, stack: &FixedSizeStack<TypeErasedNode<'static>>) {
|
||||||
|
|
|
@ -27,6 +27,13 @@ quote = {version = "1.0", default-features = false }
|
||||||
image = "*"
|
image = "*"
|
||||||
dyn-clone = "1.0"
|
dyn-clone = "1.0"
|
||||||
|
|
||||||
|
log = "0.4"
|
||||||
|
bezier-rs = { path = "../../libraries/bezier-rs" }
|
||||||
|
kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
|
||||||
|
"serde",
|
||||||
|
] }
|
||||||
|
glam = { version = "0.17", features = ["serde"] }
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// `macro_use` puts the log macros (`error!`, `warn!`, `debug!`, `info!` and `trace!`) in scope for the crate
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
//pub mod value;
|
//pub mod value;
|
||||||
//#![feature(const_type_name)]
|
//#![feature(const_type_name)]
|
||||||
|
|
||||||
|
@ -5,6 +9,7 @@
|
||||||
pub mod memo;
|
pub mod memo;
|
||||||
|
|
||||||
pub mod raster;
|
pub mod raster;
|
||||||
|
pub mod vector;
|
||||||
|
|
||||||
pub mod any;
|
pub mod any;
|
||||||
|
|
||||||
|
|
|
@ -176,89 +176,286 @@ pub fn export_image_node<'n>() -> impl Node<(Image, &'n str), Output = Result<()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn grayscale_image(mut image: Image) -> Image {
|
||||||
|
for pixel in &mut image.data {
|
||||||
|
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
||||||
|
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct GrayscaleImageNode;
|
pub struct GrayscaleImageNode;
|
||||||
|
|
||||||
impl Node<Image> for GrayscaleImageNode {
|
impl Node<Image> for GrayscaleImageNode {
|
||||||
type Output = Image;
|
type Output = Image;
|
||||||
fn eval(self, mut image: Image) -> Image {
|
fn eval(self, image: Image) -> Image {
|
||||||
for pixel in &mut image.data {
|
grayscale_image(image)
|
||||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
|
||||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
|
||||||
}
|
|
||||||
image
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Node<Image> for &GrayscaleImageNode {
|
impl Node<Image> for &GrayscaleImageNode {
|
||||||
type Output = Image;
|
type Output = Image;
|
||||||
fn eval(self, mut image: Image) -> Image {
|
fn eval(self, image: Image) -> Image {
|
||||||
for pixel in &mut image.data {
|
grayscale_image(image)
|
||||||
let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.;
|
|
||||||
*pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a());
|
|
||||||
}
|
|
||||||
image
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn invert_image(mut image: Image) -> Image {
|
||||||
|
for pixel in &mut image.data {
|
||||||
|
*pixel = Color::from_rgbaf32_unchecked(1. - pixel.r(), 1. - pixel.g(), 1. - pixel.b(), pixel.a());
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct BrightenImageNode<N: Node<(), Output = f32>>(N);
|
pub struct InvertImageColorNode;
|
||||||
|
|
||||||
impl<N: Node<(), Output = f32>> Node<Image> for BrightenImageNode<N> {
|
impl Node<Image> for InvertImageColorNode {
|
||||||
type Output = Image;
|
type Output = Image;
|
||||||
fn eval(self, mut image: Image) -> Image {
|
fn eval(self, image: Image) -> Image {
|
||||||
let brightness = self.0.eval(());
|
invert_image(image)
|
||||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
|
||||||
for pixel in &mut image.data {
|
|
||||||
*pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a());
|
|
||||||
}
|
|
||||||
image
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<N: Node<(), Output = f32> + Copy> Node<Image> for &BrightenImageNode<N> {
|
impl Node<Image> for &InvertImageColorNode {
|
||||||
type Output = Image;
|
type Output = Image;
|
||||||
fn eval(self, mut image: Image) -> Image {
|
fn eval(self, image: Image) -> Image {
|
||||||
let brightness = self.0.eval(());
|
invert_image(image)
|
||||||
let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.);
|
|
||||||
for pixel in &mut image.data {
|
|
||||||
*pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a());
|
|
||||||
}
|
|
||||||
image
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: Node<(), Output = f32> + Copy> BrightenImageNode<N> {
|
fn shift_image_hsl(mut image: Image, hue_shift: f32, saturation_shift: f32, luminance_shift: f32) -> Image {
|
||||||
|
for pixel in &mut image.data {
|
||||||
|
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
||||||
|
*pixel = Color::from_hsla(
|
||||||
|
(hue + hue_shift / 360.) % 1.,
|
||||||
|
(saturation + saturation_shift / 100.).clamp(0., 1.),
|
||||||
|
(luminance + luminance_shift / 100.).clamp(0., 1.),
|
||||||
|
alpha,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ShiftImageHSLNode<Hue, Sat, Lum>
|
||||||
|
where
|
||||||
|
Hue: Node<(), Output = f64>,
|
||||||
|
Sat: Node<(), Output = f64>,
|
||||||
|
Lum: Node<(), Output = f64>,
|
||||||
|
{
|
||||||
|
hue: Hue,
|
||||||
|
saturation: Sat,
|
||||||
|
luminance: Lum,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Hue, Sat, Lum> Node<Image> for ShiftImageHSLNode<Hue, Sat, Lum>
|
||||||
|
where
|
||||||
|
Hue: Node<(), Output = f64>,
|
||||||
|
Sat: Node<(), Output = f64>,
|
||||||
|
Lum: Node<(), Output = f64>,
|
||||||
|
{
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
shift_image_hsl(image, self.hue.eval(()) as f32, self.saturation.eval(()) as f32, self.luminance.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<Hue, Sat, Lum> Node<Image> for &ShiftImageHSLNode<Hue, Sat, Lum>
|
||||||
|
where
|
||||||
|
Hue: Node<(), Output = f64> + Copy,
|
||||||
|
Sat: Node<(), Output = f64> + Copy,
|
||||||
|
Lum: Node<(), Output = f64> + Copy,
|
||||||
|
{
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
shift_image_hsl(image, self.hue.eval(()) as f32, self.saturation.eval(()) as f32, self.luminance.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Hue, Sat, Lum> ShiftImageHSLNode<Hue, Sat, Lum>
|
||||||
|
where
|
||||||
|
Hue: Node<(), Output = f64>,
|
||||||
|
Sat: Node<(), Output = f64>,
|
||||||
|
Lum: Node<(), Output = f64>,
|
||||||
|
{
|
||||||
|
pub fn new(hue: Hue, saturation: Sat, luminance: Lum) -> Self {
|
||||||
|
Self { hue, saturation, luminance }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy pasta from https://stackoverflow.com/questions/2976274/adjust-bitmap-image-brightness-contrast-using-c
|
||||||
|
fn adjust_image_brightness_and_contrast(mut image: Image, brightness_shift: f32, contrast: f32) -> Image {
|
||||||
|
let factor = (259. * (contrast + 255.)) / (255. * (259. - contrast));
|
||||||
|
let channel = |channel: f32| ((factor * (channel * 255. + brightness_shift - 128.) + 128.) / 255.).clamp(0., 1.);
|
||||||
|
|
||||||
|
for pixel in &mut image.data {
|
||||||
|
*pixel = Color::from_rgbaf32_unchecked(channel(pixel.r()), channel(pixel.g()), channel(pixel.b()), pixel.a())
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ImageBrightnessAndContrast<Brightness, Contrast>
|
||||||
|
where
|
||||||
|
Brightness: Node<(), Output = f64>,
|
||||||
|
Contrast: Node<(), Output = f64>,
|
||||||
|
{
|
||||||
|
brightness: Brightness,
|
||||||
|
contrast: Contrast,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Brightness, Contrast> Node<Image> for ImageBrightnessAndContrast<Brightness, Contrast>
|
||||||
|
where
|
||||||
|
Brightness: Node<(), Output = f64>,
|
||||||
|
Contrast: Node<(), Output = f64>,
|
||||||
|
{
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
adjust_image_brightness_and_contrast(image, self.brightness.eval(()) as f32, self.contrast.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Brightness, Contrast> Node<Image> for &ImageBrightnessAndContrast<Brightness, Contrast>
|
||||||
|
where
|
||||||
|
Brightness: Node<(), Output = f64> + Copy,
|
||||||
|
Contrast: Node<(), Output = f64> + Copy,
|
||||||
|
{
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
adjust_image_brightness_and_contrast(image, self.brightness.eval(()) as f32, self.contrast.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Brightness, Contrast> ImageBrightnessAndContrast<Brightness, Contrast>
|
||||||
|
where
|
||||||
|
Brightness: Node<(), Output = f64>,
|
||||||
|
Contrast: Node<(), Output = f64>,
|
||||||
|
{
|
||||||
|
pub fn new(brightness: Brightness, contrast: Contrast) -> Self {
|
||||||
|
Self { brightness, contrast }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.dfstudios.co.uk/articles/programming/image-programming-algorithms/image-processing-algorithms-part-6-gamma-correction/
|
||||||
|
fn image_gamma(mut image: Image, gamma: f32) -> Image {
|
||||||
|
let inverse_gamma = 1. / gamma;
|
||||||
|
let channel = |channel: f32| channel.powf(inverse_gamma);
|
||||||
|
for pixel in &mut image.data {
|
||||||
|
*pixel = Color::from_rgbaf32_unchecked(channel(pixel.r()), channel(pixel.g()), channel(pixel.b()), pixel.a())
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ImageGammaNode<N: Node<(), Output = f64>>(N);
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = f64>> Node<Image> for ImageGammaNode<N> {
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
image_gamma(image, self.0.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<N: Node<(), Output = f64> + Copy> Node<Image> for &ImageGammaNode<N> {
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
image_gamma(image, self.0.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = f64> + Copy> ImageGammaNode<N> {
|
||||||
|
pub fn new(node: N) -> Self {
|
||||||
|
Self(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn image_opacity(mut image: Image, opacity_multiplier: f32) -> Image {
|
||||||
|
for pixel in &mut image.data {
|
||||||
|
*pixel = Color::from_rgbaf32_unchecked(pixel.r(), pixel.g(), pixel.b(), pixel.a() * opacity_multiplier)
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on http://www.axiomx.com/posterize.htm
|
||||||
|
fn posterize(mut image: Image, posterize_value: f32) -> Image {
|
||||||
|
let number_of_areas = posterize_value.recip();
|
||||||
|
let size_of_areas = (posterize_value - 1.).recip();
|
||||||
|
let channel = |channel: f32| (channel / number_of_areas).floor() * size_of_areas;
|
||||||
|
for pixel in &mut image.data {
|
||||||
|
*pixel = Color::from_rgbaf32_unchecked(channel(pixel.r()), channel(pixel.g()), channel(pixel.b()), pixel.a())
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on https://stackoverflow.com/questions/12166117/what-is-the-math-behind-exposure-adjustment-on-photoshop
|
||||||
|
fn exposure(mut image: Image, exposure: f32) -> Image {
|
||||||
|
let multiplier = 2f32.powf(exposure);
|
||||||
|
let channel = |channel: f32| channel * multiplier;
|
||||||
|
for pixel in &mut image.data {
|
||||||
|
*pixel = Color::from_rgbaf32_unchecked(channel(pixel.r()), channel(pixel.g()), channel(pixel.b()), pixel.a())
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Posterize<N: Node<(), Output = f64>>(N);
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = f64>> Node<Image> for Posterize<N> {
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
posterize(image, self.0.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<N: Node<(), Output = f64> + Copy> Node<Image> for &Posterize<N> {
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
posterize(image, self.0.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = f64> + Copy> Posterize<N> {
|
||||||
pub fn new(node: N) -> Self {
|
pub fn new(node: N) -> Self {
|
||||||
Self(node)
|
Self(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct HueShiftImage<N: Node<(), Output = f32>>(N);
|
pub struct ImageOpacityNode<N: Node<(), Output = f64>>(N);
|
||||||
|
|
||||||
impl<N: Node<(), Output = f32>> Node<Image> for HueShiftImage<N> {
|
impl<N: Node<(), Output = f64>> Node<Image> for ImageOpacityNode<N> {
|
||||||
type Output = Image;
|
type Output = Image;
|
||||||
fn eval(self, mut image: Image) -> Image {
|
fn eval(self, image: Image) -> Image {
|
||||||
let hue_shift = self.0.eval(());
|
image_opacity(image, self.0.eval(()) as f32)
|
||||||
for pixel in &mut image.data {
|
|
||||||
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
|
||||||
*pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha);
|
|
||||||
}
|
|
||||||
image
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<N: Node<(), Output = f32> + Copy> Node<Image> for &HueShiftImage<N> {
|
impl<N: Node<(), Output = f64> + Copy> Node<Image> for &ImageOpacityNode<N> {
|
||||||
type Output = Image;
|
type Output = Image;
|
||||||
fn eval(self, mut image: Image) -> Image {
|
fn eval(self, image: Image) -> Image {
|
||||||
let hue_shift = self.0.eval(());
|
image_opacity(image, self.0.eval(()) as f32)
|
||||||
for pixel in &mut image.data {
|
|
||||||
let [hue, saturation, luminance, alpha] = pixel.to_hsla();
|
|
||||||
*pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha);
|
|
||||||
}
|
|
||||||
image
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: Node<(), Output = f32> + Copy> HueShiftImage<N> {
|
impl<N: Node<(), Output = f64> + Copy> ImageOpacityNode<N> {
|
||||||
|
pub fn new(node: N) -> Self {
|
||||||
|
Self(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct ExposureNode<N: Node<(), Output = f64>>(N);
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = f64>> Node<Image> for ExposureNode<N> {
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
exposure(image, self.0.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<N: Node<(), Output = f64> + Copy> Node<Image> for &ExposureNode<N> {
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, image: Image) -> Image {
|
||||||
|
exposure(image, self.0.eval(()) as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = f64> + Copy> ExposureNode<N> {
|
||||||
pub fn new(node: N) -> Self {
|
pub fn new(node: N) -> Self {
|
||||||
Self(node)
|
Self(node)
|
||||||
}
|
}
|
||||||
|
|
158
node-graph/gstd/src/vector/generator_nodes.rs
Normal file
158
node-graph/gstd/src/vector/generator_nodes.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graphene_core::Node;
|
||||||
|
|
||||||
|
use super::subpath::Subpath;
|
||||||
|
|
||||||
|
type VectorData = Subpath;
|
||||||
|
|
||||||
|
pub struct UnitCircleGenerator;
|
||||||
|
|
||||||
|
impl Node<()> for UnitCircleGenerator {
|
||||||
|
type Output = VectorData;
|
||||||
|
fn eval(self, input: ()) -> Self::Output {
|
||||||
|
(&self).eval(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node<()> for &UnitCircleGenerator {
|
||||||
|
type Output = VectorData;
|
||||||
|
fn eval(self, _input: ()) -> Self::Output {
|
||||||
|
Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct UnitSquareGenerator;
|
||||||
|
|
||||||
|
impl Node<()> for UnitSquareGenerator {
|
||||||
|
type Output = VectorData;
|
||||||
|
fn eval(self, input: ()) -> Self::Output {
|
||||||
|
(&self).eval(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node<()> for &UnitSquareGenerator {
|
||||||
|
type Output = VectorData;
|
||||||
|
fn eval(self, _input: ()) -> Self::Output {
|
||||||
|
Subpath::new_rect(DVec2::ZERO, DVec2::ONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PathGenerator(Arc<Subpath>);
|
||||||
|
|
||||||
|
impl Node<()> for PathGenerator {
|
||||||
|
type Output = VectorData;
|
||||||
|
fn eval(self, input: ()) -> Self::Output {
|
||||||
|
(&self).eval(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node<()> for &PathGenerator {
|
||||||
|
type Output = VectorData;
|
||||||
|
fn eval(self, _input: ()) -> Self::Output {
|
||||||
|
(*self.0).clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
use crate::raster::Image;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct BlitSubpath<N: Node<(), Output = Subpath>>(N);
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = Subpath>> Node<Image> for BlitSubpath<N> {
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, input: Image) -> Self::Output {
|
||||||
|
let subpath = self.0.eval(());
|
||||||
|
info!("Blitting subpath {subpath:?}");
|
||||||
|
input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = Subpath> + Copy> Node<Image> for &BlitSubpath<N> {
|
||||||
|
type Output = Image;
|
||||||
|
fn eval(self, input: Image) -> Self::Output {
|
||||||
|
let subpath = self.0.eval(());
|
||||||
|
info!("Blitting subpath {subpath:?}");
|
||||||
|
input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = Subpath>> BlitSubpath<N> {
|
||||||
|
pub fn new(node: N) -> Self {
|
||||||
|
Self(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||||
|
where
|
||||||
|
Translation: Node<(), Output = DVec2>,
|
||||||
|
Rotation: Node<(), Output = f64>,
|
||||||
|
Scale: Node<(), Output = DVec2>,
|
||||||
|
Shear: Node<(), Output = DVec2>,
|
||||||
|
{
|
||||||
|
translate_node: Translation,
|
||||||
|
rotate_node: Rotation,
|
||||||
|
scale_node: Scale,
|
||||||
|
shear_node: Shear,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Translation, Rotation, Scale, Shear> TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||||
|
where
|
||||||
|
Translation: Node<(), Output = DVec2>,
|
||||||
|
Rotation: Node<(), Output = f64>,
|
||||||
|
Scale: Node<(), Output = DVec2>,
|
||||||
|
Shear: Node<(), Output = DVec2>,
|
||||||
|
{
|
||||||
|
pub fn new(translate_node: Translation, rotate_node: Rotation, scale_node: Scale, shear_node: Shear) -> Self {
|
||||||
|
Self {
|
||||||
|
translate_node,
|
||||||
|
rotate_node,
|
||||||
|
scale_node,
|
||||||
|
shear_node,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Translation, Rotation, Scale, Shear> Node<Subpath> for TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||||
|
where
|
||||||
|
Translation: Node<(), Output = DVec2>,
|
||||||
|
Rotation: Node<(), Output = f64>,
|
||||||
|
Scale: Node<(), Output = DVec2>,
|
||||||
|
Shear: Node<(), Output = DVec2>,
|
||||||
|
{
|
||||||
|
type Output = Subpath;
|
||||||
|
fn eval(self, mut subpath: Subpath) -> Subpath {
|
||||||
|
let translate = self.translate_node.eval(());
|
||||||
|
let rotate = self.rotate_node.eval(());
|
||||||
|
let scale = self.scale_node.eval(());
|
||||||
|
let shear = self.shear_node.eval(());
|
||||||
|
|
||||||
|
let (sin, cos) = rotate.sin_cos();
|
||||||
|
|
||||||
|
subpath.apply_affine(DAffine2::from_cols_array(&[scale.x + cos, shear.y + sin, shear.x - sin, scale.y + cos, translate.x, translate.y]));
|
||||||
|
subpath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<Translation, Rotation, Scale, Shear> Node<Subpath> for &TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||||
|
where
|
||||||
|
Translation: Node<(), Output = DVec2> + Copy,
|
||||||
|
Rotation: Node<(), Output = f64> + Copy,
|
||||||
|
Scale: Node<(), Output = DVec2> + Copy,
|
||||||
|
Shear: Node<(), Output = DVec2> + Copy,
|
||||||
|
{
|
||||||
|
type Output = Subpath;
|
||||||
|
fn eval(self, mut subpath: Subpath) -> Subpath {
|
||||||
|
let translate = self.translate_node.eval(());
|
||||||
|
let rotate = self.rotate_node.eval(());
|
||||||
|
let scale = self.scale_node.eval(());
|
||||||
|
let shear = self.shear_node.eval(());
|
||||||
|
|
||||||
|
let (sin, cos) = rotate.sin_cos();
|
||||||
|
|
||||||
|
subpath.apply_affine(DAffine2::from_cols_array(&[scale.x + cos, shear.y + sin, shear.x - sin, scale.y + cos, translate.x, translate.y]));
|
||||||
|
subpath
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,15 @@ pub struct IdBackedVec<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> IdBackedVec<T> {
|
impl<T> IdBackedVec<T> {
|
||||||
|
/// Creates a new empty vector
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
IdBackedVec {
|
||||||
|
elements: vec![],
|
||||||
|
element_ids: vec![],
|
||||||
|
next_id: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Push a new element to the start of the vector
|
/// Push a new element to the start of the vector
|
||||||
pub fn push_front(&mut self, element: T) -> Option<ElementId> {
|
pub fn push_front(&mut self, element: T) -> Option<ElementId> {
|
||||||
self.next_id += 1;
|
self.next_id += 1;
|
||||||
|
@ -135,11 +144,7 @@ impl<T> IdBackedVec<T> {
|
||||||
|
|
||||||
impl<T> Default for IdBackedVec<T> {
|
impl<T> Default for IdBackedVec<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
IdBackedVec {
|
Self::new()
|
||||||
elements: vec![],
|
|
||||||
element_ids: vec![],
|
|
||||||
next_id: 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
pub mod generator_nodes;
|
||||||
|
pub mod id_vec;
|
||||||
pub mod manipulator_group;
|
pub mod manipulator_group;
|
||||||
pub mod manipulator_point;
|
pub mod manipulator_point;
|
||||||
pub mod subpath;
|
pub mod subpath;
|
|
@ -1,9 +1,9 @@
|
||||||
use super::consts::ManipulatorType;
|
use super::consts::ManipulatorType;
|
||||||
|
use super::id_vec::IdBackedVec;
|
||||||
use super::manipulator_group::ManipulatorGroup;
|
use super::manipulator_group::ManipulatorGroup;
|
||||||
use super::manipulator_point::ManipulatorPoint;
|
use super::manipulator_point::ManipulatorPoint;
|
||||||
use crate::layers::id_vec::IdBackedVec;
|
|
||||||
use crate::layers::layer_info::{Layer, LayerDataType};
|
|
||||||
|
|
||||||
|
use dyn_any::{DynAny, StaticType};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use kurbo::{BezPath, PathEl, Shape};
|
use kurbo::{BezPath, PathEl, Shape};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -11,15 +11,15 @@ use serde::{Deserialize, Serialize};
|
||||||
/// [Subpath] represents a single vector path, containing many [ManipulatorGroups].
|
/// [Subpath] represents a single vector path, containing many [ManipulatorGroups].
|
||||||
/// For each closed shape we keep a [Subpath] which contains the [ManipulatorGroup]s (handles and anchors) that define that shape.
|
/// For each closed shape we keep a [Subpath] which contains the [ManipulatorGroup]s (handles and anchors) that define that shape.
|
||||||
// TODO Add "closed" bool to subpath
|
// TODO Add "closed" bool to subpath
|
||||||
#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize, DynAny)]
|
||||||
pub struct Subpath(IdBackedVec<ManipulatorGroup>);
|
pub struct Subpath(IdBackedVec<ManipulatorGroup>);
|
||||||
|
|
||||||
impl Subpath {
|
impl Subpath {
|
||||||
// ** INITIALIZATION **
|
// ** INITIALIZATION **
|
||||||
|
|
||||||
/// Create a new [Subpath] with no [ManipulatorGroup]s.
|
/// Create a new [Subpath] with no [ManipulatorGroup]s.
|
||||||
pub fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Subpath { ..Default::default() }
|
Subpath(IdBackedVec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a [Subpath] from a point iterator
|
/// Construct a [Subpath] from a point iterator
|
||||||
|
@ -421,34 +421,6 @@ impl Subpath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ** CONVERSIONS **
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a mut Layer> for &'a mut Subpath {
|
|
||||||
type Error = &'static str;
|
|
||||||
/// Convert a mutable layer into a mutable [Subpath].
|
|
||||||
fn try_from(layer: &'a mut Layer) -> Result<&'a mut Subpath, Self::Error> {
|
|
||||||
match &mut layer.data {
|
|
||||||
LayerDataType::Shape(layer) => Ok(&mut layer.shape),
|
|
||||||
// TODO Resolve converting text into a Subpath at the layer level
|
|
||||||
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
|
|
||||||
_ => Err("Did not find any shape data in the layer"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a Layer> for &'a Subpath {
|
|
||||||
type Error = &'static str;
|
|
||||||
/// Convert a reference to a layer into a reference of a [Subpath].
|
|
||||||
fn try_from(layer: &'a Layer) -> Result<&'a Subpath, Self::Error> {
|
|
||||||
match &layer.data {
|
|
||||||
LayerDataType::Shape(layer) => Ok(&layer.shape),
|
|
||||||
// TODO Resolve converting text into a Subpath at the layer level
|
|
||||||
// LayerDataType::Text(text) => Some(Subpath::new(path_to_shape.to_vec(), viewport_transform, true)),
|
|
||||||
_ => Err("Did not find any shape data in the layer"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper around [`bezier_rs::Bezier`] containing also the IDs for the [`ManipulatorGroup`]s where the points are from.
|
/// A wrapper around [`bezier_rs::Bezier`] containing also the IDs for the [`ManipulatorGroup`]s where the points are from.
|
||||||
pub struct BezierId {
|
pub struct BezierId {
|
||||||
/// The internal [`bezier_rs::Bezier`].
|
/// The internal [`bezier_rs::Bezier`].
|
Loading…
Add table
Add a link
Reference in a new issue