mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 15:08:11 +08:00
Integrate the node graph as a Node Graph Frame layer type (#812)
* Add node graph frame tool * Add a brighten * Use the node graph * Fix topological_sort * Update UI * Add icons for the tool and layer type * Avoid serde & use bitmaps to improve performance * Allow serialising a node graph * Fix missing ..Default::default() * Fix incorrect comments * Cache node graph output image * Suppress no-cycle import warning Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
596b9f4531
commit
9cdebfb1f0
Generated
+9
@@ -360,6 +360,7 @@ dependencies = [
|
|||||||
"graphene-std",
|
"graphene-std",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -405,11 +406,17 @@ version = "0.0.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bezier-rs",
|
"bezier-rs",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
"borrow_stack",
|
||||||
"derivative",
|
"derivative",
|
||||||
|
"dyn-any",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graph-craft",
|
||||||
|
"graphene-core",
|
||||||
|
"graphene-std",
|
||||||
"graphite-graphene",
|
"graphite-graphene",
|
||||||
"graphite-proc-macros",
|
"graphite-proc-macros",
|
||||||
|
"image",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -429,6 +436,8 @@ dependencies = [
|
|||||||
"base64",
|
"base64",
|
||||||
"bezier-rs",
|
"bezier-rs",
|
||||||
"glam",
|
"glam",
|
||||||
|
"graph-craft",
|
||||||
|
"image",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ remain = "0.2.2"
|
|||||||
derivative = "2.2.0"
|
derivative = "2.2.0"
|
||||||
once_cell = "1.13.0" # Remove when `core::cell::OnceCell` is stabilized (<https://doc.rust-lang.org/core/cell/struct.OnceCell.html>)
|
once_cell = "1.13.0" # Remove when `core::cell::OnceCell` is stabilized (<https://doc.rust-lang.org/core/cell/struct.OnceCell.html>)
|
||||||
|
|
||||||
|
# Node graph
|
||||||
|
image = { version = "0.24", default-features = false, features = ["bmp"] }
|
||||||
|
graph-craft = { path = "../node-graph/graph-craft" }
|
||||||
|
borrow_stack = { path = "../node-graph/borrow_stack" }
|
||||||
|
dyn-any = { path = "../libraries/dyn-any" }
|
||||||
|
graphene-core = { path = "../node-graph/gcore" }
|
||||||
|
graphene-std = { path = "../node-graph/gstd" }
|
||||||
|
|
||||||
[dependencies.graphene]
|
[dependencies.graphene]
|
||||||
path = "../graphene"
|
path = "../graphene"
|
||||||
package = "graphite-graphene"
|
package = "graphite-graphene"
|
||||||
|
|||||||
@@ -86,6 +86,14 @@ pub enum FrontendMessage {
|
|||||||
},
|
},
|
||||||
TriggerLoadAutoSaveDocuments,
|
TriggerLoadAutoSaveDocuments,
|
||||||
TriggerLoadPreferences,
|
TriggerLoadPreferences,
|
||||||
|
TriggerNodeGraphFrameGenerate {
|
||||||
|
#[serde(rename = "documentId")]
|
||||||
|
document_id: u64,
|
||||||
|
#[serde(rename = "layerPath")]
|
||||||
|
layer_path: Vec<LayerId>,
|
||||||
|
svg: String,
|
||||||
|
size: glam::DVec2,
|
||||||
|
},
|
||||||
TriggerOpenDocument,
|
TriggerOpenDocument,
|
||||||
TriggerPaste,
|
TriggerPaste,
|
||||||
TriggerRasterDownload {
|
TriggerRasterDownload {
|
||||||
|
|||||||
@@ -123,6 +123,13 @@ pub fn default_mapping() -> Mapping {
|
|||||||
entry!(KeyDown(Escape); action_dispatch=ImaginateToolMessage::Abort),
|
entry!(KeyDown(Escape); action_dispatch=ImaginateToolMessage::Abort),
|
||||||
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=ImaginateToolMessage::Resize { center: Alt, lock_ratio: Shift }),
|
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=ImaginateToolMessage::Resize { center: Alt, lock_ratio: Shift }),
|
||||||
//
|
//
|
||||||
|
// NodeGraphFrameToolMessage
|
||||||
|
entry!(KeyDown(Lmb); action_dispatch=NodeGraphFrameToolMessage::DragStart),
|
||||||
|
entry!(KeyUp(Lmb); action_dispatch=NodeGraphFrameToolMessage::DragStop),
|
||||||
|
entry!(KeyDown(Rmb); action_dispatch=NodeGraphFrameToolMessage::Abort),
|
||||||
|
entry!(KeyDown(Escape); action_dispatch=NodeGraphFrameToolMessage::Abort),
|
||||||
|
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=NodeGraphFrameToolMessage::Resize { center: Alt, lock_ratio: Shift }),
|
||||||
|
//
|
||||||
// EllipseToolMessage
|
// EllipseToolMessage
|
||||||
entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart),
|
entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop),
|
entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop),
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ pub enum DocumentMessage {
|
|||||||
FolderChanged {
|
FolderChanged {
|
||||||
affected_folder_path: Vec<LayerId>,
|
affected_folder_path: Vec<LayerId>,
|
||||||
},
|
},
|
||||||
|
FrameClear,
|
||||||
GroupSelectedLayers,
|
GroupSelectedLayers,
|
||||||
ImaginateClear,
|
|
||||||
ImaginateGenerate,
|
ImaginateGenerate,
|
||||||
ImaginateTerminate,
|
ImaginateTerminate,
|
||||||
LayerChanged {
|
LayerChanged {
|
||||||
@@ -89,6 +89,7 @@ pub enum DocumentMessage {
|
|||||||
layer_path: Vec<LayerId>,
|
layer_path: Vec<LayerId>,
|
||||||
delta: (f64, f64),
|
delta: (f64, f64),
|
||||||
},
|
},
|
||||||
|
NodeGraphFrameGenerate,
|
||||||
NudgeSelectedLayers {
|
NudgeSelectedLayers {
|
||||||
delta_x: f64,
|
delta_x: f64,
|
||||||
delta_y: f64,
|
delta_y: f64,
|
||||||
|
|||||||
@@ -377,6 +377,30 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||||||
let affected_layer_path = affected_folder_path;
|
let affected_layer_path = affected_folder_path;
|
||||||
responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]);
|
responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]);
|
||||||
}
|
}
|
||||||
|
FrameClear => {
|
||||||
|
let mut selected_frame_layers = self
|
||||||
|
.selected_layers_with_type(LayerDataTypeDiscriminant::Imaginate)
|
||||||
|
.chain(self.selected_layers_with_type(LayerDataTypeDiscriminant::NodeGraphFrame));
|
||||||
|
// Get what is hopefully the only selected Imaginate/NodeGraphFrame layer
|
||||||
|
let layer_path = selected_frame_layers.next();
|
||||||
|
// Abort if we didn't have any Imaginate/NodeGraphFrame layer, or if there are additional ones also selected
|
||||||
|
if layer_path.is_none() || selected_frame_layers.next().is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let layer_path = layer_path.unwrap();
|
||||||
|
|
||||||
|
let layer = self.graphene_document.layer(layer_path).expect("Clearing Imaginate/NodeGraphFrame image for invalid layer");
|
||||||
|
let previous_blob_url = match &layer.data {
|
||||||
|
LayerDataType::Imaginate(imaginate) => &imaginate.blob_url,
|
||||||
|
LayerDataType::NodeGraphFrame(node_graph_frame) => &node_graph_frame.blob_url,
|
||||||
|
x => panic!("Cannot find blob url for layer type {}", LayerDataTypeDiscriminant::from(x)),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(url) = previous_blob_url {
|
||||||
|
responses.push_back(FrontendMessage::TriggerRevokeBlobUrl { url: url.clone() }.into());
|
||||||
|
}
|
||||||
|
responses.push_back(DocumentOperation::ClearBlobURL { path: layer_path.into() }.into());
|
||||||
|
}
|
||||||
GroupSelectedLayers => {
|
GroupSelectedLayers => {
|
||||||
let mut new_folder_path = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec();
|
let mut new_folder_path = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec();
|
||||||
|
|
||||||
@@ -406,24 +430,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ImaginateClear => {
|
|
||||||
let mut selected_imaginate_layers = self.selected_layers_with_type(LayerDataTypeDiscriminant::Imaginate);
|
|
||||||
// Get what is hopefully the only selected Imaginate layer
|
|
||||||
let layer_path = selected_imaginate_layers.next();
|
|
||||||
// Abort if we didn't have any Imaginate layer, or if there are additional ones also selected
|
|
||||||
if layer_path.is_none() || selected_imaginate_layers.next().is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let layer_path = layer_path.unwrap();
|
|
||||||
|
|
||||||
let layer = self.graphene_document.layer(layer_path).expect("Clearing Imaginate image for invalid layer");
|
|
||||||
let previous_blob_url = &layer.as_imaginate().unwrap().blob_url;
|
|
||||||
|
|
||||||
if let Some(url) = previous_blob_url {
|
|
||||||
responses.push_back(FrontendMessage::TriggerRevokeBlobUrl { url: url.clone() }.into());
|
|
||||||
}
|
|
||||||
responses.push_back(DocumentOperation::ImaginateClear { path: layer_path.into() }.into());
|
|
||||||
}
|
|
||||||
ImaginateGenerate => {
|
ImaginateGenerate => {
|
||||||
if let Some(message) = self.call_imaginate(document_id, preferences, persistent_data) {
|
if let Some(message) = self.call_imaginate(document_id, preferences, persistent_data) {
|
||||||
// TODO: Eventually remove this after a message system ordering architectural change
|
// TODO: Eventually remove this after a message system ordering architectural change
|
||||||
@@ -505,6 +511,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||||||
responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta }.into());
|
responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta }.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
NodeGraphFrameGenerate => {
|
||||||
|
if let Some(message) = self.call_node_graph_frame(document_id, preferences, persistent_data) {
|
||||||
|
responses.push_back(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
NudgeSelectedLayers { delta_x, delta_y } => {
|
NudgeSelectedLayers { delta_x, delta_y } => {
|
||||||
self.backup(responses);
|
self.backup(responses);
|
||||||
for path in self.selected_layers().map(|path| path.to_vec()) {
|
for path in self.selected_layers().map(|path| path.to_vec()) {
|
||||||
@@ -705,8 +716,16 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||||||
responses.push_back(FrontendMessage::TriggerRevokeBlobUrl { url: url.clone() }.into());
|
responses.push_back(FrontendMessage::TriggerRevokeBlobUrl { url: url.clone() }.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LayerDataType::NodeGraphFrame(node_graph_frame) => {
|
||||||
|
if let Some(url) = &node_graph_frame.blob_url {
|
||||||
|
responses.push_back(FrontendMessage::TriggerRevokeBlobUrl { url: url.clone() }.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
LayerDataType::Image(_) => {}
|
LayerDataType::Image(_) => {}
|
||||||
other => panic!("Setting blob URL for invalid layer type, which must be an `Imaginate` or `Image`. Found: `{:?}`", other),
|
other => panic!(
|
||||||
|
"Setting blob URL for invalid layer type, which must be an `Imaginate`, `NodeGraphFrame` or `Image`. Found: `{:?}`",
|
||||||
|
other
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
@@ -936,7 +955,7 @@ impl DocumentMessageHandler {
|
|||||||
};
|
};
|
||||||
let base_image = if imaginate_layer.use_img2img {
|
let base_image = if imaginate_layer.use_img2img {
|
||||||
// Calculate the size of the region to be exported
|
// Calculate the size of the region to be exported
|
||||||
let size = transform.transform_point2(DVec2::ONE) - transform.transform_point2(DVec2::ZERO);
|
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||||
|
|
||||||
let old_transforms = self.remove_document_transform();
|
let old_transforms = self.remove_document_transform();
|
||||||
let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
|
let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
|
||||||
@@ -960,6 +979,32 @@ impl DocumentMessageHandler {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn call_node_graph_frame(&mut self, document_id: u64, _preferences: &PreferencesMessageHandler, persistent_data: &PersistentData) -> Option<Message> {
|
||||||
|
let layer_path = {
|
||||||
|
let mut selected_nodegraph_layers = self.selected_layers_with_type(LayerDataTypeDiscriminant::NodeGraphFrame);
|
||||||
|
|
||||||
|
// Get what is hopefully the only selected nodegraph layer
|
||||||
|
match selected_nodegraph_layers.next() {
|
||||||
|
// Continue only if there are no additional nodegraph layers also selected
|
||||||
|
Some(layer_path) if selected_nodegraph_layers.next().is_none() => layer_path.to_owned(),
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare the node graph base image base image
|
||||||
|
|
||||||
|
// Calculate the size of the region to be exported
|
||||||
|
|
||||||
|
let old_transforms = self.remove_document_transform();
|
||||||
|
let transform = self.graphene_document.multiply_transforms(&layer_path).unwrap();
|
||||||
|
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||||
|
|
||||||
|
let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path));
|
||||||
|
self.restore_document_transform(old_transforms);
|
||||||
|
|
||||||
|
Some(FrontendMessage::TriggerNodeGraphFrameGenerate { document_id, layer_path, svg, size }.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove the artwork and artboard pan/tilt/zoom to render it without the user's viewport navigation, and save it to be restored at the end
|
/// Remove the artwork and artboard pan/tilt/zoom to render it without the user's viewport navigation, and save it to be restored at the end
|
||||||
fn remove_document_transform(&mut self) -> [DAffine2; 2] {
|
fn remove_document_transform(&mut self) -> [DAffine2; 2] {
|
||||||
let old_artwork_transform = self.graphene_document.root.transform;
|
let old_artwork_transform = self.graphene_document.root.transform;
|
||||||
@@ -1474,6 +1519,15 @@ impl DocumentMessageHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LayerDataType::NodeGraphFrame(node_graph_frame) => {
|
||||||
|
if let Some(data) = &node_graph_frame.image_data {
|
||||||
|
image_data.push(FrontendImageData {
|
||||||
|
path: path.clone(),
|
||||||
|
image_data: data.image_data.clone(),
|
||||||
|
mime: node_graph_frame.mime.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ use crate::messages::prelude::*;
|
|||||||
use graphene::color::Color;
|
use graphene::color::Color;
|
||||||
use graphene::document::pick_layer_safe_imaginate_resolution;
|
use graphene::document::pick_layer_safe_imaginate_resolution;
|
||||||
use graphene::layers::imaginate_layer::{ImaginateLayer, ImaginateSamplingMethod, ImaginateStatus};
|
use graphene::layers::imaginate_layer::{ImaginateLayer, ImaginateSamplingMethod, ImaginateStatus};
|
||||||
use graphene::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant};
|
use graphene::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant};
|
||||||
|
use graphene::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||||
use graphene::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
|
use graphene::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
|
||||||
use graphene::layers::text_layer::{FontCache, TextLayer};
|
use graphene::layers::text_layer::{FontCache, TextLayer};
|
||||||
|
|
||||||
@@ -251,6 +252,11 @@ pub fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque
|
|||||||
tooltip: "Imaginate".into(),
|
tooltip: "Imaginate".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
|
LayerDataType::NodeGraphFrame(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||||
|
icon: "NodeNodes".into(),
|
||||||
|
tooltip: "Node Graph Frame".into(),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
WidgetHolder::new(Widget::Separator(Separator {
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
separator_type: SeparatorType::Related,
|
separator_type: SeparatorType::Related,
|
||||||
@@ -307,6 +313,9 @@ pub fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque
|
|||||||
LayerDataType::Imaginate(imaginate) => {
|
LayerDataType::Imaginate(imaginate) => {
|
||||||
vec![node_section_transform(layer, persistent_data), node_section_imaginate(imaginate, layer, persistent_data, responses)]
|
vec![node_section_transform(layer, persistent_data), node_section_imaginate(imaginate, layer, persistent_data, responses)]
|
||||||
}
|
}
|
||||||
|
LayerDataType::NodeGraphFrame(node_graph_frame) => {
|
||||||
|
vec![node_section_transform(layer, persistent_data), node_section_node_graph_frame(node_graph_frame)]
|
||||||
|
}
|
||||||
LayerDataType::Folder(_) => {
|
LayerDataType::Folder(_) => {
|
||||||
vec![node_section_transform(layer, persistent_data)]
|
vec![node_section_transform(layer, persistent_data)]
|
||||||
}
|
}
|
||||||
@@ -659,8 +668,8 @@ fn node_section_imaginate(imaginate_layer: &ImaginateLayer, layer: &Layer, persi
|
|||||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||||
label: "Clear".into(),
|
label: "Clear".into(),
|
||||||
tooltip: "Remove generated image from the layer frame".into(),
|
tooltip: "Remove generated image from the layer frame".into(),
|
||||||
disabled: imaginate_layer.blob_url == None,
|
disabled: imaginate_layer.blob_url.is_none(),
|
||||||
on_update: WidgetCallback::new(|_| DocumentMessage::ImaginateClear.into()),
|
on_update: WidgetCallback::new(|_| DocumentMessage::FrameClear.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
@@ -1009,6 +1018,55 @@ fn node_section_imaginate(imaginate_layer: &ImaginateLayer, layer: &Layer, persi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_section_node_graph_frame(node_graph_frame: &NodeGraphFrameLayer) -> LayoutGroup {
|
||||||
|
LayoutGroup::Section {
|
||||||
|
name: "Node Graph Frame".into(),
|
||||||
|
layout: vec![
|
||||||
|
LayoutGroup::Row {
|
||||||
|
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Temporary layer that applies a greyscale to the layers below it.".into(),
|
||||||
|
..TextLabel::default()
|
||||||
|
}))],
|
||||||
|
},
|
||||||
|
LayoutGroup::Row {
|
||||||
|
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Powered by the node graph :)".into(),
|
||||||
|
..TextLabel::default()
|
||||||
|
}))],
|
||||||
|
},
|
||||||
|
LayoutGroup::Row {
|
||||||
|
widgets: vec![WidgetHolder::new(Widget::TextButton(TextButton {
|
||||||
|
label: "Open Node Graph UI (todo)".into(),
|
||||||
|
tooltip: "Open the node graph associated with this layer".into(),
|
||||||
|
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(800) }.into()),
|
||||||
|
..Default::default()
|
||||||
|
}))],
|
||||||
|
},
|
||||||
|
LayoutGroup::Row {
|
||||||
|
widgets: vec![
|
||||||
|
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||||
|
label: "Generate".into(),
|
||||||
|
tooltip: "Fill layer frame by generating a new image".into(),
|
||||||
|
on_update: WidgetCallback::new(|_| DocumentMessage::NodeGraphFrameGenerate.into()),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
separator_type: SeparatorType::Related,
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||||
|
label: "Clear".into(),
|
||||||
|
tooltip: "Remove generated image from the layer frame".into(),
|
||||||
|
disabled: node_graph_frame.blob_url.is_none(),
|
||||||
|
on_update: WidgetCallback::new(|_| DocumentMessage::FrameClear.into()),
|
||||||
|
..Default::default()
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn node_section_font(layer: &TextLayer) -> LayoutGroup {
|
fn node_section_font(layer: &TextLayer) -> LayoutGroup {
|
||||||
let font = layer.font.clone();
|
let font = layer.font.clone();
|
||||||
let size = layer.size;
|
let size = layer.size;
|
||||||
@@ -1425,7 +1483,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutGroup {
|
|||||||
direction: SeparatorDirection::Horizontal,
|
direction: SeparatorDirection::Horizontal,
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
value: Some(stroke.weight() as f64),
|
value: Some(stroke.weight()),
|
||||||
is_integer: false,
|
is_integer: false,
|
||||||
min: Some(0.),
|
min: Some(0.),
|
||||||
unit: " px".into(),
|
unit: " px".into(),
|
||||||
@@ -1473,7 +1531,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutGroup {
|
|||||||
direction: SeparatorDirection::Horizontal,
|
direction: SeparatorDirection::Horizontal,
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
value: Some(stroke.dash_offset() as f64),
|
value: Some(stroke.dash_offset()),
|
||||||
is_integer: true,
|
is_integer: true,
|
||||||
min: Some(0.),
|
min: Some(0.),
|
||||||
unit: " px".into(),
|
unit: " px".into(),
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ pub enum PortfolioMessage {
|
|||||||
data: String,
|
data: String,
|
||||||
},
|
},
|
||||||
PrevDocument,
|
PrevDocument,
|
||||||
|
ProcessNodeGraphFrame {
|
||||||
|
document_id: u64,
|
||||||
|
layer_path: Vec<LayerId>,
|
||||||
|
image_data: Vec<u8>,
|
||||||
|
size: (u32, u32),
|
||||||
|
},
|
||||||
SelectDocument {
|
SelectDocument {
|
||||||
document_id: u64,
|
document_id: u64,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ use super::utility_types::PersistentData;
|
|||||||
use crate::application::generate_uuid;
|
use crate::application::generate_uuid;
|
||||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
||||||
use crate::messages::dialog::simple_dialogs;
|
use crate::messages::dialog::simple_dialogs;
|
||||||
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
|
use crate::messages::frontend::utility_types::{FrontendDocumentDetails, FrontendImageData};
|
||||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||||
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||||
use crate::messages::portfolio::utility_types::ImaginateServerStatus;
|
use crate::messages::portfolio::utility_types::ImaginateServerStatus;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use graphene::layers::layer_info::LayerDataTypeDiscriminant;
|
use graphene::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
|
||||||
use graphene::layers::text_layer::Font;
|
use graphene::layers::text_layer::Font;
|
||||||
use graphene::Operation as DocumentOperation;
|
use graphene::{LayerId, Operation as DocumentOperation};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct PortfolioMessageHandler {
|
pub struct PortfolioMessageHandler {
|
||||||
@@ -413,6 +413,86 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||||||
responses.push_back(PortfolioMessage::SelectDocument { document_id: prev_id }.into());
|
responses.push_back(PortfolioMessage::SelectDocument { document_id: prev_id }.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PortfolioMessage::ProcessNodeGraphFrame {
|
||||||
|
document_id,
|
||||||
|
layer_path,
|
||||||
|
image_data,
|
||||||
|
size,
|
||||||
|
} => {
|
||||||
|
fn read_image(document: Option<&DocumentMessageHandler>, layer_path: &[LayerId], image_data: Vec<u8>, (width, height): (u32, u32)) -> Result<Vec<u8>, String> {
|
||||||
|
use graphene_std::raster::Image;
|
||||||
|
use image::{ImageBuffer, Rgba};
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
let data = image_data.chunks_exact(4).map(|v| graphene_core::raster::color::Color::from_rgba8(v[0], v[1], v[2], v[3])).collect();
|
||||||
|
let image = graphene_std::raster::Image { width, height, data };
|
||||||
|
|
||||||
|
let document = document.ok_or_else(|| "Invalid document".to_string())?;
|
||||||
|
let layer = document.graphene_document.layer(layer_path).map_err(|e| format!("No layer: {e:?}"))?;
|
||||||
|
let node_graph_frame = match &layer.data {
|
||||||
|
LayerDataType::NodeGraphFrame(frame) => Ok(frame),
|
||||||
|
_ => Err("Invalid layer type".to_string()),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// Execute the node graph
|
||||||
|
|
||||||
|
let mut network = node_graph_frame.network.clone();
|
||||||
|
|
||||||
|
let stack = borrow_stack::FixedSizeStack::new(256);
|
||||||
|
network.flatten(0);
|
||||||
|
|
||||||
|
let mut proto_network = network.into_proto_network();
|
||||||
|
proto_network.reorder_ids();
|
||||||
|
|
||||||
|
for (_id, node) in proto_network.nodes {
|
||||||
|
graph_craft::node_registry::push_node(node, &stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
use borrow_stack::BorrowStack;
|
||||||
|
use dyn_any::IntoDynAny;
|
||||||
|
use graphene_core::Node;
|
||||||
|
let result = unsafe { stack.get().last().unwrap().eval(image.into_dyn()) };
|
||||||
|
let result = *dyn_any::downcast::<Image>(result).unwrap();
|
||||||
|
|
||||||
|
let mut bytes: Vec<u8> = Vec::new();
|
||||||
|
let [result_width, result_height] = [result.width, result.height];
|
||||||
|
let size_estimate = (result_width * result_height * 4) as usize;
|
||||||
|
|
||||||
|
let mut result_bytes = Vec::with_capacity(size_estimate);
|
||||||
|
result_bytes.extend(result.data.into_iter().flat_map(|colour| colour.to_rgba8()));
|
||||||
|
let output: ImageBuffer<Rgba<u8>, _> = image::ImageBuffer::from_raw(result_width, result_height, result_bytes).ok_or_else(|| "Invalid image size".to_string())?;
|
||||||
|
output.write_to(&mut Cursor::new(&mut bytes), image::ImageOutputFormat::Bmp).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
match read_image(self.documents.get(&document_id), &layer_path, image_data, size) {
|
||||||
|
Ok(image_data) => {
|
||||||
|
responses.push_back(
|
||||||
|
DocumentOperation::SetNodeGraphFrameImageData {
|
||||||
|
layer_path: layer_path.clone(),
|
||||||
|
image_data: image_data.clone(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
let mime = "image/bmp".to_string();
|
||||||
|
responses.push_back(
|
||||||
|
FrontendMessage::UpdateImageData {
|
||||||
|
document_id,
|
||||||
|
image_data: vec![FrontendImageData { path: layer_path, image_data, mime }],
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(description) => responses.push_back(
|
||||||
|
DialogMessage::DisplayDialogError {
|
||||||
|
title: "Failed to update image".to_string(),
|
||||||
|
description,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
PortfolioMessage::SelectDocument { document_id } => {
|
PortfolioMessage::SelectDocument { document_id } => {
|
||||||
if let Some(document) = self.active_document() {
|
if let Some(document) = self.active_document() {
|
||||||
if !document.is_auto_saved() {
|
if !document.is_auto_saved() {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ pub use crate::messages::tool::tool_messages::gradient_tool::{GradientToolMessag
|
|||||||
pub use crate::messages::tool::tool_messages::imaginate_tool::{ImaginateToolMessage, ImaginateToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::imaginate_tool::{ImaginateToolMessage, ImaginateToolMessageDiscriminant};
|
||||||
pub use crate::messages::tool::tool_messages::line_tool::{LineToolMessage, LineToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::line_tool::{LineToolMessage, LineToolMessageDiscriminant};
|
||||||
pub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant};
|
||||||
|
pub use crate::messages::tool::tool_messages::node_graph_frame_tool::{NodeGraphFrameToolMessage, NodeGraphFrameToolMessageDiscriminant};
|
||||||
pub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant};
|
||||||
pub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant};
|
||||||
pub use crate::messages::tool::tool_messages::rectangle_tool::{RectangleToolMessage, RectangleToolMessageDiscriminant};
|
pub use crate::messages::tool::tool_messages::rectangle_tool::{RectangleToolMessage, RectangleToolMessageDiscriminant};
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ pub enum ToolMessage {
|
|||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
#[child]
|
#[child]
|
||||||
Imaginate(ImaginateToolMessage),
|
Imaginate(ImaginateToolMessage),
|
||||||
|
#[remain::unsorted]
|
||||||
|
#[child]
|
||||||
|
NodeGraphFrame(NodeGraphFrameToolMessage),
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
@@ -114,6 +117,8 @@ pub enum ToolMessage {
|
|||||||
|
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
ActivateToolImaginate,
|
ActivateToolImaginate,
|
||||||
|
#[remain::unsorted]
|
||||||
|
ActivateToolNodeGraphFrame,
|
||||||
|
|
||||||
ActivateTool {
|
ActivateTool {
|
||||||
tool_type: ToolType,
|
tool_type: ToolType,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub mod gradient_tool;
|
|||||||
pub mod imaginate_tool;
|
pub mod imaginate_tool;
|
||||||
pub mod line_tool;
|
pub mod line_tool;
|
||||||
pub mod navigate_tool;
|
pub mod navigate_tool;
|
||||||
|
pub mod node_graph_frame_tool;
|
||||||
pub mod path_tool;
|
pub mod path_tool;
|
||||||
pub mod pen_tool;
|
pub mod pen_tool;
|
||||||
pub mod rectangle_tool;
|
pub mod rectangle_tool;
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
use crate::consts::DRAG_THRESHOLD;
|
||||||
|
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||||
|
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
|
||||||
|
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
use crate::messages::tool::common_functionality::resize::Resize;
|
||||||
|
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||||
|
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||||
|
|
||||||
|
use graphene::Operation;
|
||||||
|
|
||||||
|
use glam::DAffine2;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NodeGraphFrameTool {
|
||||||
|
fsm_state: NodeGraphToolFsmState,
|
||||||
|
tool_data: NodeGraphToolData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
|
#[impl_message(Message, ToolMessage, NodeGraphFrame)]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum NodeGraphFrameToolMessage {
|
||||||
|
// Standard messages
|
||||||
|
#[remain::unsorted]
|
||||||
|
Abort,
|
||||||
|
|
||||||
|
// Tool-specific messages
|
||||||
|
DragStart,
|
||||||
|
DragStop,
|
||||||
|
Resize {
|
||||||
|
center: Key,
|
||||||
|
lock_ratio: Key,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PropertyHolder for NodeGraphFrameTool {}
|
||||||
|
|
||||||
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NodeGraphFrameTool {
|
||||||
|
fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||||
|
if message == ToolMessage::UpdateHints {
|
||||||
|
self.fsm_state.update_hints(responses);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if message == ToolMessage::UpdateCursor {
|
||||||
|
self.fsm_state.update_cursor(responses);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses);
|
||||||
|
|
||||||
|
if self.fsm_state != new_state {
|
||||||
|
self.fsm_state = new_state;
|
||||||
|
self.fsm_state.update_hints(responses);
|
||||||
|
self.fsm_state.update_cursor(responses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actions(&self) -> ActionList {
|
||||||
|
use NodeGraphToolFsmState::*;
|
||||||
|
|
||||||
|
match self.fsm_state {
|
||||||
|
Ready => actions!(NodeGraphFrameToolMessageDiscriminant;
|
||||||
|
DragStart,
|
||||||
|
),
|
||||||
|
Drawing => actions!(NodeGraphFrameToolMessageDiscriminant;
|
||||||
|
DragStop,
|
||||||
|
Abort,
|
||||||
|
Resize,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolMetadata for NodeGraphFrameTool {
|
||||||
|
fn icon_name(&self) -> String {
|
||||||
|
"RasterNodesTool".into()
|
||||||
|
}
|
||||||
|
fn tooltip(&self) -> String {
|
||||||
|
"Node Graph Frame Tool".into()
|
||||||
|
}
|
||||||
|
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
|
||||||
|
ToolType::NodeGraphFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolTransition for NodeGraphFrameTool {
|
||||||
|
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||||
|
EventToMessageMap {
|
||||||
|
document_dirty: None,
|
||||||
|
tool_abort: Some(NodeGraphFrameToolMessage::Abort.into()),
|
||||||
|
selection_changed: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum NodeGraphToolFsmState {
|
||||||
|
Ready,
|
||||||
|
Drawing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NodeGraphToolFsmState {
|
||||||
|
fn default() -> Self {
|
||||||
|
NodeGraphToolFsmState::Ready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct NodeGraphToolData {
|
||||||
|
data: Resize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fsm for NodeGraphToolFsmState {
|
||||||
|
type ToolData = NodeGraphToolData;
|
||||||
|
type ToolOptions = ();
|
||||||
|
|
||||||
|
fn transition(
|
||||||
|
self,
|
||||||
|
event: ToolMessage,
|
||||||
|
tool_data: &mut Self::ToolData,
|
||||||
|
(document, _document_id, _global_tool_data, input, font_cache): ToolActionHandlerData,
|
||||||
|
_tool_options: &Self::ToolOptions,
|
||||||
|
responses: &mut VecDeque<Message>,
|
||||||
|
) -> Self {
|
||||||
|
use NodeGraphFrameToolMessage::*;
|
||||||
|
use NodeGraphToolFsmState::*;
|
||||||
|
|
||||||
|
let mut shape_data = &mut tool_data.data;
|
||||||
|
|
||||||
|
if let ToolMessage::NodeGraphFrame(event) = event {
|
||||||
|
match (self, event) {
|
||||||
|
(Ready, DragStart) => {
|
||||||
|
shape_data.start(responses, document, input.mouse.position, font_cache);
|
||||||
|
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||||
|
shape_data.path = Some(document.get_path_for_new_layer());
|
||||||
|
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||||
|
|
||||||
|
responses.push_back(
|
||||||
|
Operation::AddNodeGraphFrame {
|
||||||
|
path: shape_data.path.clone().unwrap(),
|
||||||
|
insert_index: -1,
|
||||||
|
transform: DAffine2::ZERO.to_cols_array(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Drawing
|
||||||
|
}
|
||||||
|
(state, Resize { center, lock_ratio }) => {
|
||||||
|
if let Some(message) = shape_data.calculate_transform(responses, document, center, lock_ratio, input) {
|
||||||
|
responses.push_back(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
(Drawing, DragStop) => {
|
||||||
|
match shape_data.drag_start.distance(input.mouse.position) <= DRAG_THRESHOLD {
|
||||||
|
true => responses.push_back(DocumentMessage::AbortTransaction.into()),
|
||||||
|
false => responses.push_back(DocumentMessage::CommitTransaction.into()),
|
||||||
|
}
|
||||||
|
|
||||||
|
shape_data.cleanup(responses);
|
||||||
|
|
||||||
|
Ready
|
||||||
|
}
|
||||||
|
(Drawing, Abort) => {
|
||||||
|
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||||
|
|
||||||
|
shape_data.cleanup(responses);
|
||||||
|
|
||||||
|
Ready
|
||||||
|
}
|
||||||
|
_ => self,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||||
|
let hint_data = match self {
|
||||||
|
NodeGraphToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
|
HintInfo {
|
||||||
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
|
label: String::from("Draw Repaint Frame"),
|
||||||
|
plus: false,
|
||||||
|
},
|
||||||
|
HintInfo {
|
||||||
|
key_groups: vec![KeysGroup(vec![Key::Shift])],
|
||||||
|
key_groups_mac: None,
|
||||||
|
mouse: None,
|
||||||
|
label: String::from("Constrain Square"),
|
||||||
|
plus: true,
|
||||||
|
},
|
||||||
|
HintInfo {
|
||||||
|
key_groups: vec![KeysGroup(vec![Key::Alt])],
|
||||||
|
key_groups_mac: None,
|
||||||
|
mouse: None,
|
||||||
|
label: String::from("From Center"),
|
||||||
|
plus: true,
|
||||||
|
},
|
||||||
|
])]),
|
||||||
|
NodeGraphToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
||||||
|
HintInfo {
|
||||||
|
key_groups: vec![KeysGroup(vec![Key::Shift])],
|
||||||
|
key_groups_mac: None,
|
||||||
|
mouse: None,
|
||||||
|
label: String::from("Constrain Square"),
|
||||||
|
plus: false,
|
||||||
|
},
|
||||||
|
HintInfo {
|
||||||
|
key_groups: vec![KeysGroup(vec![Key::Alt])],
|
||||||
|
key_groups_mac: None,
|
||||||
|
mouse: None,
|
||||||
|
label: String::from("From Center"),
|
||||||
|
plus: false,
|
||||||
|
},
|
||||||
|
])]),
|
||||||
|
};
|
||||||
|
|
||||||
|
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
|
||||||
|
responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -281,6 +281,7 @@ pub enum ToolType {
|
|||||||
Detail,
|
Detail,
|
||||||
Relight,
|
Relight,
|
||||||
Imaginate,
|
Imaginate,
|
||||||
|
NodeGraphFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ToolAvailability {
|
enum ToolAvailability {
|
||||||
@@ -293,28 +294,29 @@ fn list_tools_in_groups() -> Vec<Vec<ToolAvailability>> {
|
|||||||
vec![
|
vec![
|
||||||
vec![
|
vec![
|
||||||
// General tool group
|
// General tool group
|
||||||
ToolAvailability::Available(Box::new(select_tool::SelectTool::default())),
|
ToolAvailability::Available(Box::<select_tool::SelectTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(artboard_tool::ArtboardTool::default())),
|
ToolAvailability::Available(Box::<artboard_tool::ArtboardTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(navigate_tool::NavigateTool::default())),
|
ToolAvailability::Available(Box::<navigate_tool::NavigateTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(eyedropper_tool::EyedropperTool::default())),
|
ToolAvailability::Available(Box::<eyedropper_tool::EyedropperTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(fill_tool::FillTool::default())),
|
ToolAvailability::Available(Box::<fill_tool::FillTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(gradient_tool::GradientTool::default())),
|
ToolAvailability::Available(Box::<gradient_tool::GradientTool>::default()),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
// Vector tool group
|
// Vector tool group
|
||||||
ToolAvailability::Available(Box::new(path_tool::PathTool::default())),
|
ToolAvailability::Available(Box::<path_tool::PathTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(pen_tool::PenTool::default())),
|
ToolAvailability::Available(Box::<pen_tool::PenTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(freehand_tool::FreehandTool::default())),
|
ToolAvailability::Available(Box::<freehand_tool::FreehandTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(spline_tool::SplineTool::default())),
|
ToolAvailability::Available(Box::<spline_tool::SplineTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(line_tool::LineTool::default())),
|
ToolAvailability::Available(Box::<line_tool::LineTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(rectangle_tool::RectangleTool::default())),
|
ToolAvailability::Available(Box::<rectangle_tool::RectangleTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(ellipse_tool::EllipseTool::default())),
|
ToolAvailability::Available(Box::<ellipse_tool::EllipseTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(shape_tool::ShapeTool::default())),
|
ToolAvailability::Available(Box::<shape_tool::ShapeTool>::default()),
|
||||||
ToolAvailability::Available(Box::new(text_tool::TextTool::default())),
|
ToolAvailability::Available(Box::<text_tool::TextTool>::default()),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
// Raster tool group
|
// Raster tool group
|
||||||
ToolAvailability::Available(Box::new(imaginate_tool::ImaginateTool::default())),
|
ToolAvailability::Available(Box::<imaginate_tool::ImaginateTool>::default()),
|
||||||
|
ToolAvailability::Available(Box::<node_graph_frame_tool::NodeGraphFrameTool>::default()),
|
||||||
ToolAvailability::ComingSoon(ToolEntry {
|
ToolAvailability::ComingSoon(ToolEntry {
|
||||||
tool_type: ToolType::Brush,
|
tool_type: ToolType::Brush,
|
||||||
icon_name: "RasterBrushTool".into(),
|
icon_name: "RasterBrushTool".into(),
|
||||||
@@ -384,6 +386,7 @@ pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType {
|
|||||||
// ToolMessage::Detail(_) => ToolType::Detail,
|
// ToolMessage::Detail(_) => ToolType::Detail,
|
||||||
// ToolMessage::Relight(_) => ToolType::Relight,
|
// ToolMessage::Relight(_) => ToolType::Relight,
|
||||||
ToolMessage::Imaginate(_) => ToolType::Imaginate,
|
ToolMessage::Imaginate(_) => ToolType::Imaginate,
|
||||||
|
ToolMessage::NodeGraphFrame(_) => ToolType::NodeGraphFrame,
|
||||||
_ => panic!(
|
_ => panic!(
|
||||||
"Conversion from ToolMessage to ToolType impossible because the given ToolMessage does not have a matching ToolType. Got: {:?}",
|
"Conversion from ToolMessage to ToolType impossible because the given ToolMessage does not have a matching ToolType. Got: {:?}",
|
||||||
tool_message
|
tool_message
|
||||||
@@ -420,6 +423,7 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis
|
|||||||
// ToolType::Detail => ToolMessageDiscriminant::ActivateToolDetail,
|
// ToolType::Detail => ToolMessageDiscriminant::ActivateToolDetail,
|
||||||
// ToolType::Relight => ToolMessageDiscriminant::ActivateToolRelight,
|
// ToolType::Relight => ToolMessageDiscriminant::ActivateToolRelight,
|
||||||
ToolType::Imaginate => ToolMessageDiscriminant::ActivateToolImaginate,
|
ToolType::Imaginate => ToolMessageDiscriminant::ActivateToolImaginate,
|
||||||
|
ToolType::NodeGraphFrame => ToolMessageDiscriminant::ActivateToolNodeGraphFrame,
|
||||||
_ => panic!(
|
_ => panic!(
|
||||||
"Conversion from ToolType to ToolMessage impossible because the given ToolType does not have a matching ToolMessage. Got: {:?}",
|
"Conversion from ToolType to ToolMessage impossible because the given ToolType does not have a matching ToolMessage. Got: {:?}",
|
||||||
tool_type
|
tool_type
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polygon points="3,1 1,1 0,1 0,2 0,4 1,4 1,2 3,2" />
|
||||||
|
<polygon points="15,1 13,1 13,2 15,2 15,4 16,4 16,2 16,1" />
|
||||||
|
<polygon points="1,14 1,12 0,12 0,14 0,15 1,15 3,15 3,14" />
|
||||||
|
<polygon points="15,12 15,14 13,14 13,15 15,15 16,15 16,14 16,12" />
|
||||||
|
<path d="M11,7h2c0.5,0,1-0.5,1-1V4c0-0.5-0.5-1-1-1h-2c-0.5,0-1,0.5-1,1v0.5L9.8,4.5C9.1,4.8,8.6,5.3,7.9,5.8C7.1,6.5,6.3,7.2,5,7.5V7c0-0.5-0.5-1-1-1H2C1.5,6,1,6.5,1,7v2c0,0.5,0.5,1,1,1h2c0.5,0,1-0.5,1-1V8.5c1.3,0.3,2.1,1,2.9,1.7c0.6,0.5,1.2,1,1.9,1.3l0.2,0.1V12c0,0.5,0.5,1,1,1h2c0.5,0,1-0.5,1-1v-2c0-0.5-0.5-1-1-1h-2c-0.5,0-1,0.5-1,1v0.5c-0.5-0.2-0.9-0.6-1.4-1C8,9,7.4,8.4,6.5,8C7.4,7.6,8,7,8.6,6.5c0.5-0.4,0.9-0.8,1.4-1V6C10,6.5,10.5,7,11,7z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 763 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path class="color-raster" d="M23,18c0,1.1-0.9,2-2,2h-4c-1.1,0-2-0.9-2-2v-2c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2V18z" />
|
||||||
|
<path class="color-raster" d="M23,8c0,1.1-0.9,2-2,2h-4c-1.1,0-2-0.9-2-2V6c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2V8z" />
|
||||||
|
<path class="color-raster" d="M9,13c0,1.1-0.9,2-2,2H3c-1.1,0-2-0.9-2-2v-2c0-1.1,0.9-2,2-2h4c1.1,0,2,0.9,2,2V13z" />
|
||||||
|
<path class="color-solid" d="M12.9,9.3c0.2-1,0.4-1.6,1.1-1.8v-1l-0.1,0c-1.5,0.3-1.7,1.6-2,2.6c-0.3,1.2-0.5,2.1-1.9,2.4v1c1.5,0.2,1.7,1.2,1.9,2.4c0.2,1,0.5,2.3,2,2.6l0.1,0v-1c-0.7-0.2-0.9-0.8-1.1-1.8c-0.2-0.9-0.4-2-1.4-2.7C12.5,11.3,12.7,10.2,12.9,9.3z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 677 B |
@@ -3,7 +3,7 @@ import { reactive, readonly } from "vue";
|
|||||||
|
|
||||||
import { downloadFileText, downloadFileBlob, upload } from "@/utility-functions/files";
|
import { downloadFileText, downloadFileBlob, upload } from "@/utility-functions/files";
|
||||||
import { imaginateGenerate, imaginateCheckConnection, imaginateTerminate, preloadAndSetImaginateBlobURL } from "@/utility-functions/imaginate";
|
import { imaginateGenerate, imaginateCheckConnection, imaginateTerminate, preloadAndSetImaginateBlobURL } from "@/utility-functions/imaginate";
|
||||||
import { rasterizeSVG } from "@/utility-functions/rasterization";
|
import { rasterizeSVG, rasterizeSVGCanvas } from "@/utility-functions/rasterization";
|
||||||
import { type Editor } from "@/wasm-communication/editor";
|
import { type Editor } from "@/wasm-communication/editor";
|
||||||
import {
|
import {
|
||||||
type FrontendDocumentDetails,
|
type FrontendDocumentDetails,
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
TriggerImaginateGenerate,
|
TriggerImaginateGenerate,
|
||||||
TriggerImaginateTerminate,
|
TriggerImaginateTerminate,
|
||||||
TriggerImaginateCheckServerStatus,
|
TriggerImaginateCheckServerStatus,
|
||||||
|
TriggerNodeGraphFrameGenerate,
|
||||||
UpdateActiveDocument,
|
UpdateActiveDocument,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
UpdateImageData,
|
UpdateImageData,
|
||||||
@@ -100,6 +101,14 @@ export function createPortfolioState(editor: Editor) {
|
|||||||
editor.instance.setImageBlobURL(updateImageData.documentId, element.path, blobURL, image.naturalWidth, image.naturalHeight);
|
editor.instance.setImageBlobURL(updateImageData.documentId, element.path, blobURL, image.naturalWidth, image.naturalHeight);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(TriggerNodeGraphFrameGenerate, async (triggerNodeGraphFrameGenerate) => {
|
||||||
|
const { documentId, layerPath, svg, size } = triggerNodeGraphFrameGenerate;
|
||||||
|
|
||||||
|
// Rasterize the SVG to an image file
|
||||||
|
const imageData = (await rasterizeSVGCanvas(svg, size[0], size[1])).getContext("2d")?.getImageData(0, 0, size[0], size[1]);
|
||||||
|
|
||||||
|
if (imageData) editor.instance.processNodeGraphFrame(documentId, layerPath, new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||||
|
});
|
||||||
editor.subscriptions.subscribeJsMessage(TriggerRevokeBlobUrl, async (triggerRevokeBlobUrl) => {
|
editor.subscriptions.subscribeJsMessage(TriggerRevokeBlobUrl, async (triggerRevokeBlobUrl) => {
|
||||||
URL.revokeObjectURL(triggerRevokeBlobUrl.url);
|
URL.revokeObjectURL(triggerRevokeBlobUrl.url);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ import NodeImaginate from "@/../assets/icon-16px-solid/node-imaginate.svg";
|
|||||||
import NodeMagicWand from "@/../assets/icon-16px-solid/node-magic-wand.svg";
|
import NodeMagicWand from "@/../assets/icon-16px-solid/node-magic-wand.svg";
|
||||||
import NodeMask from "@/../assets/icon-16px-solid/node-mask.svg";
|
import NodeMask from "@/../assets/icon-16px-solid/node-mask.svg";
|
||||||
import NodeMotionBlur from "@/../assets/icon-16px-solid/node-motion-blur.svg";
|
import NodeMotionBlur from "@/../assets/icon-16px-solid/node-motion-blur.svg";
|
||||||
|
import NodeNodes from "@/../assets/icon-16px-solid/node-nodes.svg";
|
||||||
import NodeOutput from "@/../assets/icon-16px-solid/node-output.svg";
|
import NodeOutput from "@/../assets/icon-16px-solid/node-output.svg";
|
||||||
import NodeShape from "@/../assets/icon-16px-solid/node-shape.svg";
|
import NodeShape from "@/../assets/icon-16px-solid/node-shape.svg";
|
||||||
import NodeText from "@/../assets/icon-16px-solid/node-text.svg";
|
import NodeText from "@/../assets/icon-16px-solid/node-text.svg";
|
||||||
@@ -168,6 +169,7 @@ const SOLID_16PX = {
|
|||||||
NodeMagicWand: { component: NodeMagicWand, size: 16 },
|
NodeMagicWand: { component: NodeMagicWand, size: 16 },
|
||||||
NodeMask: { component: NodeMask, size: 16 },
|
NodeMask: { component: NodeMask, size: 16 },
|
||||||
NodeMotionBlur: { component: NodeMotionBlur, size: 16 },
|
NodeMotionBlur: { component: NodeMotionBlur, size: 16 },
|
||||||
|
NodeNodes: { component: NodeNodes, size: 16 },
|
||||||
NodeOutput: { component: NodeOutput, size: 16 },
|
NodeOutput: { component: NodeOutput, size: 16 },
|
||||||
NodeShape: { component: NodeShape, size: 16 },
|
NodeShape: { component: NodeShape, size: 16 },
|
||||||
NodeText: { component: NodeText, size: 16 },
|
NodeText: { component: NodeText, size: 16 },
|
||||||
@@ -228,6 +230,7 @@ import RasterCloneTool from "@/../assets/icon-24px-two-tone/raster-clone-tool.sv
|
|||||||
import RasterDetailTool from "@/../assets/icon-24px-two-tone/raster-detail-tool.svg";
|
import RasterDetailTool from "@/../assets/icon-24px-two-tone/raster-detail-tool.svg";
|
||||||
import RasterHealTool from "@/../assets/icon-24px-two-tone/raster-heal-tool.svg";
|
import RasterHealTool from "@/../assets/icon-24px-two-tone/raster-heal-tool.svg";
|
||||||
import RasterImaginateTool from "@/../assets/icon-24px-two-tone/raster-imaginate-tool.svg";
|
import RasterImaginateTool from "@/../assets/icon-24px-two-tone/raster-imaginate-tool.svg";
|
||||||
|
import RasterNodesTool from "@/../assets/icon-24px-two-tone/raster-nodes-tool.svg";
|
||||||
import RasterPatchTool from "@/../assets/icon-24px-two-tone/raster-patch-tool.svg";
|
import RasterPatchTool from "@/../assets/icon-24px-two-tone/raster-patch-tool.svg";
|
||||||
import RasterRelightTool from "@/../assets/icon-24px-two-tone/raster-relight-tool.svg";
|
import RasterRelightTool from "@/../assets/icon-24px-two-tone/raster-relight-tool.svg";
|
||||||
import VectorEllipseTool from "@/../assets/icon-24px-two-tone/vector-ellipse-tool.svg";
|
import VectorEllipseTool from "@/../assets/icon-24px-two-tone/vector-ellipse-tool.svg";
|
||||||
@@ -248,6 +251,7 @@ const TWO_TONE_24PX = {
|
|||||||
GeneralNavigateTool: { component: GeneralNavigateTool, size: 24 },
|
GeneralNavigateTool: { component: GeneralNavigateTool, size: 24 },
|
||||||
GeneralSelectTool: { component: GeneralSelectTool, size: 24 },
|
GeneralSelectTool: { component: GeneralSelectTool, size: 24 },
|
||||||
RasterImaginateTool: { component: RasterImaginateTool, size: 24 },
|
RasterImaginateTool: { component: RasterImaginateTool, size: 24 },
|
||||||
|
RasterNodesTool: { component: RasterNodesTool, size: 24 },
|
||||||
RasterBrushTool: { component: RasterBrushTool, size: 24 },
|
RasterBrushTool: { component: RasterBrushTool, size: 24 },
|
||||||
RasterCloneTool: { component: RasterCloneTool, size: 24 },
|
RasterCloneTool: { component: RasterCloneTool, size: 24 },
|
||||||
RasterDetailTool: { component: RasterDetailTool, size: 24 },
|
RasterDetailTool: { component: RasterDetailTool, size: 24 },
|
||||||
|
|||||||
@@ -9,6 +9,20 @@ export type Editor = Readonly<ReturnType<typeof createEditor>>;
|
|||||||
|
|
||||||
// `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()`
|
// `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()`
|
||||||
let wasmImport: WasmRawInstance | undefined;
|
let wasmImport: WasmRawInstance | undefined;
|
||||||
|
let editorInstance: WasmEditorInstance | undefined;
|
||||||
|
|
||||||
|
export async function updateImage(path: BigUint64Array, mime: string, imageData: Uint8Array, documentId: bigint): Promise<void> {
|
||||||
|
const blob = new Blob([imageData], { type: mime });
|
||||||
|
|
||||||
|
const blobURL = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
// Pre-decode the image so it is ready to be drawn instantly once it's placed into the viewport SVG
|
||||||
|
const image = new Image();
|
||||||
|
image.src = blobURL;
|
||||||
|
await image.decode();
|
||||||
|
|
||||||
|
editorInstance?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight);
|
||||||
|
}
|
||||||
|
|
||||||
// Should be called asynchronously before `createEditor()`
|
// Should be called asynchronously before `createEditor()`
|
||||||
export async function initWasm(): Promise<void> {
|
export async function initWasm(): Promise<void> {
|
||||||
@@ -16,6 +30,7 @@ export async function initWasm(): Promise<void> {
|
|||||||
if (wasmImport !== undefined) return;
|
if (wasmImport !== undefined) return;
|
||||||
|
|
||||||
// Import the WASM module JS bindings and wrap them in the panic proxy
|
// Import the WASM module JS bindings and wrap them in the panic proxy
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
wasmImport = await import("@/../wasm/pkg").then(panicProxy);
|
wasmImport = await import("@/../wasm/pkg").then(panicProxy);
|
||||||
|
|
||||||
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
|
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
|
||||||
@@ -36,6 +51,7 @@ export function createEditor() {
|
|||||||
// We pass along the first two arguments then add our own `raw` and `instance` context for the last two arguments
|
// We pass along the first two arguments then add our own `raw` and `instance` context for the last two arguments
|
||||||
subscriptions.handleJsMessage(messageType, messageData, raw, instance);
|
subscriptions.handleJsMessage(messageType, messageData, raw, instance);
|
||||||
});
|
});
|
||||||
|
editorInstance = instance;
|
||||||
|
|
||||||
// Subscriptions: Allows subscribing to messages in JS that are sent from the WASM backend
|
// Subscriptions: Allows subscribing to messages in JS that are sent from the WASM backend
|
||||||
const subscriptions: SubscriptionRouter = createSubscriptionRouter();
|
const subscriptions: SubscriptionRouter = createSubscriptionRouter();
|
||||||
|
|||||||
@@ -514,6 +514,16 @@ export class TriggerImaginateTerminate extends JsMessage {
|
|||||||
readonly hostname!: string;
|
readonly hostname!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TriggerNodeGraphFrameGenerate extends JsMessage {
|
||||||
|
readonly documentId!: bigint;
|
||||||
|
|
||||||
|
readonly layerPath!: BigUint64Array;
|
||||||
|
|
||||||
|
readonly svg!: string;
|
||||||
|
|
||||||
|
readonly size!: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
export class TriggerRefreshBoundsOfViewports extends JsMessage {}
|
export class TriggerRefreshBoundsOfViewports extends JsMessage {}
|
||||||
|
|
||||||
export class TriggerRevokeBlobUrl extends JsMessage {
|
export class TriggerRevokeBlobUrl extends JsMessage {
|
||||||
@@ -641,7 +651,7 @@ export class LayerMetadata {
|
|||||||
selected!: boolean;
|
selected!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LayerType = "Imaginate" | "Folder" | "Image" | "Shape" | "Text";
|
export type LayerType = "Imaginate" | "NodeGraphFrame" | "Folder" | "Image" | "Shape" | "Text";
|
||||||
|
|
||||||
export type LayerTypeData = {
|
export type LayerTypeData = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -651,6 +661,7 @@ export type LayerTypeData = {
|
|||||||
export function layerTypeData(layerType: LayerType): LayerTypeData | undefined {
|
export function layerTypeData(layerType: LayerType): LayerTypeData | undefined {
|
||||||
const entries: Record<string, LayerTypeData> = {
|
const entries: Record<string, LayerTypeData> = {
|
||||||
Imaginate: { name: "Imaginate", icon: "NodeImaginate" },
|
Imaginate: { name: "Imaginate", icon: "NodeImaginate" },
|
||||||
|
NodeGraphFrame: { name: "Node Graph Frame", icon: "NodeNodes" },
|
||||||
Folder: { name: "Folder", icon: "NodeFolder" },
|
Folder: { name: "Folder", icon: "NodeFolder" },
|
||||||
Image: { name: "Image", icon: "NodeImage" },
|
Image: { name: "Image", icon: "NodeImage" },
|
||||||
Shape: { name: "Shape", icon: "NodeShape" },
|
Shape: { name: "Shape", icon: "NodeShape" },
|
||||||
@@ -1218,6 +1229,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
TriggerImaginateCheckServerStatus,
|
TriggerImaginateCheckServerStatus,
|
||||||
TriggerImaginateGenerate,
|
TriggerImaginateGenerate,
|
||||||
TriggerImaginateTerminate,
|
TriggerImaginateTerminate,
|
||||||
|
TriggerNodeGraphFrameGenerate,
|
||||||
TriggerFileDownload,
|
TriggerFileDownload,
|
||||||
TriggerFontLoad,
|
TriggerFontLoad,
|
||||||
TriggerImport,
|
TriggerImport,
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ pub fn set_random_seed(seed: u64) {
|
|||||||
editor::application::set_uuid_seed(seed);
|
editor::application::set_uuid_seed(seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We directly interface with the updateImage JS function for massively increased performance over serializing and deserializing.
|
||||||
|
/// This avoids creating a json with a list millions of numbers long.
|
||||||
|
#[wasm_bindgen(module = "@/wasm-communication/editor")]
|
||||||
|
extern "C" {
|
||||||
|
fn updateImage(path: Vec<u64>, mime: String, imageData: Vec<u8>, document_id: u64);
|
||||||
|
}
|
||||||
|
|
||||||
/// Provides a handle to access the raw WASM memory
|
/// Provides a handle to access the raw WASM memory
|
||||||
#[wasm_bindgen(js_name = wasmMemory)]
|
#[wasm_bindgen(js_name = wasmMemory)]
|
||||||
pub fn wasm_memory() -> JsValue {
|
pub fn wasm_memory() -> JsValue {
|
||||||
@@ -90,6 +97,14 @@ impl JsEditorHandle {
|
|||||||
|
|
||||||
// Sends a FrontendMessage to JavaScript
|
// Sends a FrontendMessage to JavaScript
|
||||||
fn send_frontend_message_to_js(&self, message: FrontendMessage) {
|
fn send_frontend_message_to_js(&self, message: FrontendMessage) {
|
||||||
|
// Special case for update image data to avoid serialization times.
|
||||||
|
if let FrontendMessage::UpdateImageData { document_id, image_data } = message {
|
||||||
|
for image in image_data {
|
||||||
|
updateImage(image.path, image.mime, image.image_data, document_id);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let message_type = message.to_discriminant().local_name();
|
let message_type = message.to_discriminant().local_name();
|
||||||
|
|
||||||
let serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
|
let serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
|
||||||
@@ -512,6 +527,18 @@ impl JsEditorHandle {
|
|||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends the blob URL generated by JS to the Imaginate layer in the respective document
|
||||||
|
#[wasm_bindgen(js_name = processNodeGraphFrame)]
|
||||||
|
pub fn process_node_graph_frame(&self, document_id: u64, layer_path: Vec<LayerId>, image_data: Vec<u8>, width: u32, height: u32) {
|
||||||
|
let message = PortfolioMessage::ProcessNodeGraphFrame {
|
||||||
|
document_id,
|
||||||
|
layer_path,
|
||||||
|
image_data,
|
||||||
|
size: (width, height),
|
||||||
|
};
|
||||||
|
self.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
/// Pastes an image
|
/// Pastes an image
|
||||||
#[wasm_bindgen(js_name = pasteImage)]
|
#[wasm_bindgen(js_name = pasteImage)]
|
||||||
pub fn paste_image(&self, mime: String, image_data: Vec<u8>, mouse_x: Option<f64>, mouse_y: Option<f64>) {
|
pub fn paste_image(&self, mime: String, image_data: Vec<u8>, mouse_x: Option<f64>, mouse_y: Option<f64>) {
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ repository = "https://github.com/GraphiteEditor/Graphite"
|
|||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
graph-craft = { path = "../node-graph/graph-craft", features = ["serde"] }
|
||||||
|
image = { version = "0.24", default-features = false }
|
||||||
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
bezier-rs = { path = "../libraries/bezier-rs" }
|
bezier-rs = { path = "../libraries/bezier-rs" }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::layers::folder_layer::FolderLayer;
|
|||||||
use crate::layers::image_layer::ImageLayer;
|
use crate::layers::image_layer::ImageLayer;
|
||||||
use crate::layers::imaginate_layer::{ImaginateImageData, ImaginateLayer, ImaginateStatus};
|
use crate::layers::imaginate_layer::{ImaginateImageData, ImaginateLayer, ImaginateStatus};
|
||||||
use crate::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant};
|
use crate::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant};
|
||||||
|
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};
|
||||||
@@ -595,6 +596,22 @@ impl Document {
|
|||||||
|
|
||||||
Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
|
Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
|
||||||
}
|
}
|
||||||
|
Operation::AddNodeGraphFrame { path, insert_index, transform } => {
|
||||||
|
let layer = Layer::new(LayerDataType::NodeGraphFrame(NodeGraphFrameLayer::default()), transform);
|
||||||
|
|
||||||
|
self.set_layer(&path, layer, insert_index)?;
|
||||||
|
|
||||||
|
Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
|
||||||
|
}
|
||||||
|
Operation::SetNodeGraphFrameImageData { layer_path, image_data } => {
|
||||||
|
let layer = self.layer_mut(&layer_path).expect("Setting NodeGraphFrame image data for invalid layer");
|
||||||
|
if let LayerDataType::NodeGraphFrame(node_graph_frame) = &mut layer.data {
|
||||||
|
node_graph_frame.image_data = Some(crate::layers::nodegraph_layer::ImageData { image_data });
|
||||||
|
} else {
|
||||||
|
panic!("Incorrectly trying to set image data for a layer that is not an NodeGraphFrame layer type");
|
||||||
|
}
|
||||||
|
Some(vec![LayerChanged { path: layer_path.clone() }])
|
||||||
|
}
|
||||||
Operation::SetTextEditability { path, editable } => {
|
Operation::SetTextEditability { path, editable } => {
|
||||||
self.layer_mut(&path)?.as_text_mut()?.editable = editable;
|
self.layer_mut(&path)?.as_text_mut()?.editable = editable;
|
||||||
self.mark_as_dirty(&path)?;
|
self.mark_as_dirty(&path)?;
|
||||||
@@ -797,11 +814,15 @@ impl Document {
|
|||||||
image.blob_url = Some(blob_url);
|
image.blob_url = Some(blob_url);
|
||||||
image.dimensions = resolution.into();
|
image.dimensions = resolution.into();
|
||||||
}
|
}
|
||||||
|
LayerDataType::NodeGraphFrame(node_graph_frame) => {
|
||||||
|
node_graph_frame.blob_url = Some(blob_url);
|
||||||
|
node_graph_frame.dimensions = resolution.into();
|
||||||
|
}
|
||||||
LayerDataType::Imaginate(imaginate) => {
|
LayerDataType::Imaginate(imaginate) => {
|
||||||
imaginate.blob_url = Some(blob_url);
|
imaginate.blob_url = Some(blob_url);
|
||||||
imaginate.dimensions = resolution.into();
|
imaginate.dimensions = resolution.into();
|
||||||
}
|
}
|
||||||
_ => panic!("Incorrectly trying to set the image blob URL for a layer that is not an Image or Imaginate layer type"),
|
_ => panic!("Incorrectly trying to set the image blob URL for a layer that is not an Image, NodeGraphFrame or Imaginate layer type"),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mark_as_dirty(&layer_path)?;
|
self.mark_as_dirty(&layer_path)?;
|
||||||
@@ -833,15 +854,20 @@ impl Document {
|
|||||||
}
|
}
|
||||||
Some(vec![LayerChanged { path: path.clone() }])
|
Some(vec![LayerChanged { path: path.clone() }])
|
||||||
}
|
}
|
||||||
Operation::ImaginateClear { path } => {
|
Operation::ClearBlobURL { path } => {
|
||||||
let layer = self.layer_mut(&path).expect("Clearing Imaginate image for invalid layer");
|
let layer = self.layer_mut(&path).expect("Clearing Imaginate image for invalid layer");
|
||||||
if let LayerDataType::Imaginate(imaginate) = &mut layer.data {
|
match &mut layer.data {
|
||||||
imaginate.image_data = None;
|
LayerDataType::Imaginate(imaginate) => {
|
||||||
imaginate.blob_url = None;
|
imaginate.image_data = None;
|
||||||
imaginate.status = ImaginateStatus::Idle;
|
imaginate.blob_url = None;
|
||||||
imaginate.percent_complete = 0.;
|
imaginate.status = ImaginateStatus::Idle;
|
||||||
} else {
|
imaginate.percent_complete = 0.;
|
||||||
panic!("Incorrectly trying to clear the blob URL for a layer that is not an Imaginate layer type");
|
}
|
||||||
|
LayerDataType::NodeGraphFrame(node_graph) => {
|
||||||
|
node_graph.image_data = None;
|
||||||
|
node_graph.blob_url = None;
|
||||||
|
}
|
||||||
|
e => panic!("Incorrectly trying to clear the blob URL for layer of type {}", LayerDataTypeDiscriminant::from(&*e)),
|
||||||
}
|
}
|
||||||
self.mark_as_dirty(&path)?;
|
self.mark_as_dirty(&path)?;
|
||||||
Some([vec![DocumentChanged, LayerChanged { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
|
Some([vec![DocumentChanged, LayerChanged { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use super::blend_mode::BlendMode;
|
|||||||
use super::folder_layer::FolderLayer;
|
use super::folder_layer::FolderLayer;
|
||||||
use super::image_layer::ImageLayer;
|
use super::image_layer::ImageLayer;
|
||||||
use super::imaginate_layer::ImaginateLayer;
|
use super::imaginate_layer::ImaginateLayer;
|
||||||
|
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;
|
||||||
@@ -27,8 +28,10 @@ pub enum LayerDataType {
|
|||||||
Text(TextLayer),
|
Text(TextLayer),
|
||||||
/// A layer that wraps an [ImageLayer] struct.
|
/// A layer that wraps an [ImageLayer] struct.
|
||||||
Image(ImageLayer),
|
Image(ImageLayer),
|
||||||
/// A layer that wraps an [ImageLayer] struct.
|
/// A layer that wraps an [ImaginateLayer] struct.
|
||||||
Imaginate(ImaginateLayer),
|
Imaginate(ImaginateLayer),
|
||||||
|
/// A layer that wraps an [NodeGraphFrameLayer] struct.
|
||||||
|
NodeGraphFrame(NodeGraphFrameLayer),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerDataType {
|
impl LayerDataType {
|
||||||
@@ -39,6 +42,7 @@ impl LayerDataType {
|
|||||||
LayerDataType::Text(t) => t,
|
LayerDataType::Text(t) => t,
|
||||||
LayerDataType::Image(i) => i,
|
LayerDataType::Image(i) => i,
|
||||||
LayerDataType::Imaginate(a) => a,
|
LayerDataType::Imaginate(a) => a,
|
||||||
|
LayerDataType::NodeGraphFrame(n) => n,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +53,7 @@ impl LayerDataType {
|
|||||||
LayerDataType::Text(t) => t,
|
LayerDataType::Text(t) => t,
|
||||||
LayerDataType::Image(i) => i,
|
LayerDataType::Image(i) => i,
|
||||||
LayerDataType::Imaginate(a) => a,
|
LayerDataType::Imaginate(a) => a,
|
||||||
|
LayerDataType::NodeGraphFrame(n) => n,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,6 +65,7 @@ pub enum LayerDataTypeDiscriminant {
|
|||||||
Text,
|
Text,
|
||||||
Image,
|
Image,
|
||||||
Imaginate,
|
Imaginate,
|
||||||
|
NodeGraphFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for LayerDataTypeDiscriminant {
|
impl fmt::Display for LayerDataTypeDiscriminant {
|
||||||
@@ -70,6 +76,7 @@ impl fmt::Display for LayerDataTypeDiscriminant {
|
|||||||
LayerDataTypeDiscriminant::Text => write!(f, "Text"),
|
LayerDataTypeDiscriminant::Text => write!(f, "Text"),
|
||||||
LayerDataTypeDiscriminant::Image => write!(f, "Image"),
|
LayerDataTypeDiscriminant::Image => write!(f, "Image"),
|
||||||
LayerDataTypeDiscriminant::Imaginate => write!(f, "Imaginate"),
|
LayerDataTypeDiscriminant::Imaginate => write!(f, "Imaginate"),
|
||||||
|
LayerDataTypeDiscriminant::NodeGraphFrame => write!(f, "NodeGraphFrame"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,6 +91,7 @@ impl From<&LayerDataType> for LayerDataTypeDiscriminant {
|
|||||||
Text(_) => LayerDataTypeDiscriminant::Text,
|
Text(_) => LayerDataTypeDiscriminant::Text,
|
||||||
Image(_) => LayerDataTypeDiscriminant::Image,
|
Image(_) => LayerDataTypeDiscriminant::Image,
|
||||||
Imaginate(_) => LayerDataTypeDiscriminant::Imaginate,
|
Imaginate(_) => LayerDataTypeDiscriminant::Imaginate,
|
||||||
|
NodeGraphFrame(_) => LayerDataTypeDiscriminant::NodeGraphFrame,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ pub mod image_layer;
|
|||||||
pub mod imaginate_layer;
|
pub mod imaginate_layer;
|
||||||
/// Contains the base [Layer](layer_info::Layer) type, an abstraction over the different types of layers.
|
/// Contains the base [Layer](layer_info::Layer) type, an abstraction over the different types of layers.
|
||||||
pub mod layer_info;
|
pub mod layer_info;
|
||||||
|
/// Contains the [NodegraphLayer](nodegraph_layer::NodegraphLayer) type that contains a node graph.
|
||||||
|
pub mod nodegraph_layer;
|
||||||
/// Contains the [ShapeLayer](shape_layer::ShapeLayer) type, a generic SVG element defined using Bezier paths.
|
/// Contains the [ShapeLayer](shape_layer::ShapeLayer) type, a generic SVG element defined using Bezier paths.
|
||||||
pub mod shape_layer;
|
pub mod shape_layer;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
use super::base64_serde;
|
||||||
|
use super::layer_info::LayerData;
|
||||||
|
use super::style::{RenderData, ViewMode};
|
||||||
|
use crate::intersection::{intersect_quad_bez_path, Quad};
|
||||||
|
use crate::layers::text_layer::FontCache;
|
||||||
|
use crate::LayerId;
|
||||||
|
|
||||||
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
|
use kurbo::{Affine, BezPath, Shape as KurboShape};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct NodeGraphFrameLayer {
|
||||||
|
// Image stored in layer after generation completes
|
||||||
|
pub mime: String,
|
||||||
|
|
||||||
|
/// The document node network that this layer contains
|
||||||
|
pub network: graph_craft::document::NodeNetwork,
|
||||||
|
|
||||||
|
// TODO: Have the browser dispose of this blob URL when this is dropped (like when the layer is deleted)
|
||||||
|
#[serde(skip)]
|
||||||
|
pub blob_url: Option<String>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub dimensions: DVec2,
|
||||||
|
pub image_data: Option<ImageData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct ImageData {
|
||||||
|
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
|
||||||
|
pub image_data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayerData for NodeGraphFrameLayer {
|
||||||
|
fn render(&mut self, svg: &mut String, _svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: RenderData) {
|
||||||
|
let transform = self.transform(transforms, render_data.view_mode);
|
||||||
|
let inverse = transform.inverse();
|
||||||
|
|
||||||
|
let (width, height) = (transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||||
|
|
||||||
|
if !inverse.is_finite() {
|
||||||
|
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = writeln!(svg, r#"<g transform="matrix("#);
|
||||||
|
inverse.to_cols_array().iter().enumerate().for_each(|(i, entry)| {
|
||||||
|
let _ = svg.write_str(&(entry.to_string() + if i == 5 { "" } else { "," }));
|
||||||
|
});
|
||||||
|
let _ = svg.write_str(r#")">"#);
|
||||||
|
|
||||||
|
let matrix = (transform * DAffine2::from_scale((width, height).into()).inverse())
|
||||||
|
.to_cols_array()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(String::new(), |val, (i, entry)| val + &(entry.to_string() + if i == 5 { "" } else { "," }));
|
||||||
|
|
||||||
|
if let Some(blob_url) = &self.blob_url {
|
||||||
|
let _ = write!(
|
||||||
|
svg,
|
||||||
|
r#"<image width="{}" height="{}" preserveAspectRatio="none" href="{}" transform="matrix({})" />"#,
|
||||||
|
width.abs(),
|
||||||
|
height.abs(),
|
||||||
|
blob_url,
|
||||||
|
matrix
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let _ = write!(
|
||||||
|
svg,
|
||||||
|
r#"<rect width="{}" height="{}" fill="none" stroke="var(--color-data-vector)" stroke-width="3" stroke-dasharray="8" transform="matrix({})" />"#,
|
||||||
|
width.abs(),
|
||||||
|
height.abs(),
|
||||||
|
matrix,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = svg.write_str(r#"</g>"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounding_box(&self, transform: glam::DAffine2, _font_cache: &FontCache) -> Option<[DVec2; 2]> {
|
||||||
|
let mut path = self.bounds();
|
||||||
|
|
||||||
|
if transform.matrix2 == DMat2::ZERO {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
path.apply_affine(glam_to_kurbo(transform));
|
||||||
|
|
||||||
|
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
|
||||||
|
Some([(x0, y0).into(), (x1, y1).into()])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _font_cache: &FontCache) {
|
||||||
|
if intersect_quad_bez_path(quad, &self.bounds(), true) {
|
||||||
|
intersections.push(path.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeGraphFrameLayer {
|
||||||
|
pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 {
|
||||||
|
let start = match mode {
|
||||||
|
ViewMode::Outline => 0,
|
||||||
|
_ => (transforms.len() as i32 - 1).max(0) as usize,
|
||||||
|
};
|
||||||
|
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self) -> BezPath {
|
||||||
|
kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.)).to_path(0.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn glam_to_kurbo(transform: DAffine2) -> Affine {
|
||||||
|
Affine::new(transform.to_cols_array())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NodeGraphFrameLayer {
|
||||||
|
fn default() -> Self {
|
||||||
|
use graph_craft::document::*;
|
||||||
|
use graph_craft::proto::NodeIdentifier;
|
||||||
|
Self {
|
||||||
|
mime: String::new(),
|
||||||
|
network: NodeNetwork {
|
||||||
|
inputs: vec![1],
|
||||||
|
output: 1,
|
||||||
|
nodes: [
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
DocumentNode {
|
||||||
|
name: "grayscale".into(),
|
||||||
|
inputs: vec![NodeInput::Network],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[])),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1,
|
||||||
|
DocumentNode {
|
||||||
|
name: "map image".into(),
|
||||||
|
inputs: vec![NodeInput::Network, NodeInput::Node(0)],
|
||||||
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::MapImageNode", &[])),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
blob_url: None,
|
||||||
|
dimensions: DVec2::ZERO,
|
||||||
|
image_data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,15 @@ pub enum Operation {
|
|||||||
insert_index: isize,
|
insert_index: isize,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
},
|
},
|
||||||
|
AddNodeGraphFrame {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
insert_index: isize,
|
||||||
|
transform: [f64; 6],
|
||||||
|
},
|
||||||
|
SetNodeGraphFrameImageData {
|
||||||
|
layer_path: Vec<LayerId>,
|
||||||
|
image_data: Vec<u8>,
|
||||||
|
},
|
||||||
/// Sets a blob URL as the image source for an Image or Imaginate layer type.
|
/// Sets a blob URL as the image source for an Image or Imaginate layer type.
|
||||||
/// **Be sure to call `FrontendMessage::TriggerRevokeBlobUrl` together with this.**
|
/// **Be sure to call `FrontendMessage::TriggerRevokeBlobUrl` together with this.**
|
||||||
SetLayerBlobUrl {
|
SetLayerBlobUrl {
|
||||||
@@ -64,9 +73,9 @@ pub enum Operation {
|
|||||||
blob_url: String,
|
blob_url: String,
|
||||||
resolution: (f64, f64),
|
resolution: (f64, f64),
|
||||||
},
|
},
|
||||||
/// Clears the image to leave the Imaginate layer un-rendered.
|
/// Clears the image to leave the layer un-rendered.
|
||||||
/// **Be sure to call `FrontendMessage::TriggerRevokeBlobUrl` together with this.**
|
/// **Be sure to call `FrontendMessage::TriggerRevokeBlobUrl` together with this.**
|
||||||
ImaginateClear {
|
ClearBlobURL {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
},
|
},
|
||||||
ImaginateSetGeneratingStatus {
|
ImaginateSetGeneratingStatus {
|
||||||
|
|||||||
@@ -14,3 +14,8 @@ 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"
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
version = "1.0"
|
||||||
|
optional = true
|
||||||
|
features = ["derive"]
|
||||||
|
|||||||
@@ -28,9 +28,8 @@ fn merge_ids(a: u64, b: u64) -> u64 {
|
|||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fqn = NodeIdentifier<'static>;
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct DocumentNode {
|
pub struct DocumentNode {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub inputs: Vec<NodeInput>,
|
pub inputs: Vec<NodeInput>,
|
||||||
@@ -55,9 +54,9 @@ impl DocumentNode {
|
|||||||
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 {
|
||||||
NodeInput::Value(value) => {
|
NodeInput::Value(tagged_value) => {
|
||||||
assert_eq!(self.inputs.len(), 0);
|
assert_eq!(self.inputs.len(), 0);
|
||||||
(ProtoNodeInput::None, ConstructionArgs::Value(value))
|
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value.to_value()))
|
||||||
}
|
}
|
||||||
NodeInput::Node(id) => (ProtoNodeInput::Node(id), ConstructionArgs::Nodes(vec![])),
|
NodeInput::Node(id) => (ProtoNodeInput::Node(id), ConstructionArgs::Nodes(vec![])),
|
||||||
NodeInput::Network => (ProtoNodeInput::Network, ConstructionArgs::Nodes(vec![])),
|
NodeInput::Network => (ProtoNodeInput::Network, ConstructionArgs::Nodes(vec![])),
|
||||||
@@ -82,10 +81,11 @@ impl DocumentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum NodeInput {
|
pub enum NodeInput {
|
||||||
Node(NodeId),
|
Node(NodeId),
|
||||||
Value(value::Value),
|
Value(value::TaggedValue),
|
||||||
Network,
|
Network,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,13 +107,15 @@ impl PartialEq for NodeInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum DocumentNodeImplementation {
|
pub enum DocumentNodeImplementation {
|
||||||
Network(NodeNetwork),
|
Network(NodeNetwork),
|
||||||
Unresolved(Fqn),
|
Unresolved(NodeIdentifier),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct NodeNetwork {
|
pub struct NodeNetwork {
|
||||||
pub inputs: Vec<NodeId>,
|
pub inputs: Vec<NodeId>,
|
||||||
pub output: NodeId,
|
pub output: NodeId,
|
||||||
@@ -160,7 +162,7 @@ impl NodeNetwork {
|
|||||||
network_input.populate_first_network_input(node, *offset);
|
network_input.populate_first_network_input(node, *offset);
|
||||||
}
|
}
|
||||||
NodeInput::Value(value) => {
|
NodeInput::Value(value) => {
|
||||||
let name = format!("Value: {:?}", value);
|
let name = format!("Value: {:?}", value.clone().to_value());
|
||||||
let new_id = map_ids(id, gen_id());
|
let new_id = map_ids(id, gen_id());
|
||||||
let value_node = DocumentNode {
|
let value_node = DocumentNode {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
@@ -284,7 +286,7 @@ mod test {
|
|||||||
1,
|
1,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Inc".into(),
|
name: "Inc".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Value(2_u32.into_any())],
|
inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::U32(2))],
|
||||||
implementation: DocumentNodeImplementation::Network(add_network()),
|
implementation: DocumentNodeImplementation::Network(add_network()),
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
@@ -384,7 +386,7 @@ mod test {
|
|||||||
14,
|
14,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Value: 2".into(),
|
name: "Value: 2".into(),
|
||||||
inputs: vec![NodeInput::Value(2_u32.into_any())],
|
inputs: vec![NodeInput::Value(value::TaggedValue::U32(2))],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,6 +4,26 @@ use dyn_clone::DynClone;
|
|||||||
|
|
||||||
use dyn_any::{DynAny, Upcast};
|
use dyn_any::{DynAny, Upcast};
|
||||||
|
|
||||||
|
/// A type that is known, allowing serialization (serde::Deserialize is not object safe)
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub enum TaggedValue {
|
||||||
|
String(String),
|
||||||
|
U32(u32),
|
||||||
|
//Image(graphene_std::raster::Image),
|
||||||
|
Color(graphene_core::raster::color::Color),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaggedValue {
|
||||||
|
pub fn to_value(self) -> Value {
|
||||||
|
match self {
|
||||||
|
TaggedValue::String(x) => Box::new(x),
|
||||||
|
TaggedValue::U32(x) => Box::new(x),
|
||||||
|
TaggedValue::Color(x) => Box::new(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Value = Box<dyn ValueTrait>;
|
pub type Value = Box<dyn ValueTrait>;
|
||||||
|
|
||||||
pub trait ValueTrait: DynAny<'static> + Upcast<dyn DynAny<'static>> + std::fmt::Debug + DynClone {}
|
pub trait ValueTrait: DynAny<'static> + Upcast<dyn DynAny<'static>> + std::fmt::Debug + DynClone {}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ mod tests {
|
|||||||
use graphene_core::value::ValueNode;
|
use graphene_core::value::ValueNode;
|
||||||
use graphene_core::{structural::*, RefNode};
|
use graphene_core::{structural::*, RefNode};
|
||||||
|
|
||||||
use crate::document::value::IntoValue;
|
|
||||||
use borrow_stack::BorrowStack;
|
use borrow_stack::BorrowStack;
|
||||||
use borrow_stack::FixedSizeStack;
|
use borrow_stack::FixedSizeStack;
|
||||||
use dyn_any::{downcast, IntoDynAny};
|
use dyn_any::{downcast, IntoDynAny};
|
||||||
@@ -68,7 +67,10 @@ mod tests {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "cons".into(),
|
name: "cons".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Concrete("u32"), Type::Concrete("u32")])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||||
|
"graphene_core::structural::ConsNode",
|
||||||
|
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -76,7 +78,10 @@ mod tests {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "add".into(),
|
name: "add".into(),
|
||||||
inputs: vec![NodeInput::Node(0)],
|
inputs: vec![NodeInput::Node(0)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete("u32"), Type::Concrete("u32")])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||||
|
"graphene_core::ops::AddNode",
|
||||||
|
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
|
)),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -92,7 +97,7 @@ mod tests {
|
|||||||
0,
|
0,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Inc".into(),
|
name: "Inc".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Value(1_u32.into_any())],
|
inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::U32(1))],
|
||||||
implementation: DocumentNodeImplementation::Network(add_network()),
|
implementation: DocumentNodeImplementation::Network(add_network()),
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use borrow_stack::FixedSizeStack;
|
use borrow_stack::FixedSizeStack;
|
||||||
use graphene_core::generic::FnNode;
|
use graphene_core::generic::FnNode;
|
||||||
use graphene_core::ops::{AddNode, IdNode};
|
use graphene_core::ops::AddNode;
|
||||||
use graphene_core::raster::color::Color;
|
use graphene_core::raster::color::Color;
|
||||||
use graphene_core::structural::{ConsNode, Then};
|
use graphene_core::structural::{ConsNode, Then};
|
||||||
use graphene_core::{AsRefNode, Node};
|
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;
|
||||||
@@ -13,17 +11,12 @@ use graphene_std::raster::Image;
|
|||||||
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};
|
||||||
|
|
||||||
use dyn_any::Upcast;
|
|
||||||
|
|
||||||
type NodeConstructor = fn(ProtoNode, &FixedSizeStack<TypeErasedNode<'static>>);
|
type NodeConstructor = fn(ProtoNode, &FixedSizeStack<TypeErasedNode<'static>>);
|
||||||
|
|
||||||
//TODO: turn into hasmap
|
//TODO: turn into hasmap
|
||||||
static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new("graphene_core::ops::IdNode", &[Concrete(std::borrow::Cow::Borrowed("Any<'_>"))]),
|
||||||
name: "graphene_core::ops::IdNode",
|
|
||||||
types: &[Concrete("Any<'_>")],
|
|
||||||
},
|
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(|nodes| {
|
stack.push_fn(|nodes| {
|
||||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
||||||
@@ -32,24 +25,18 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic]), |proto_node, stack| {
|
||||||
|
stack.push_fn(|nodes| {
|
||||||
|
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
||||||
|
let node = pre_node.then(graphene_core::ops::IdNode);
|
||||||
|
node.into_type_erased()
|
||||||
|
})
|
||||||
|
}),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new(
|
||||||
name: "graphene_core::ops::IdNode",
|
"graphene_core::ops::AddNode",
|
||||||
types: &[Type::Generic],
|
&[Concrete(std::borrow::Cow::Borrowed("u32")), Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
},
|
),
|
||||||
|proto_node, stack| {
|
|
||||||
stack.push_fn(|nodes| {
|
|
||||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
|
||||||
let node = pre_node.then(graphene_core::ops::IdNode);
|
|
||||||
node.into_type_erased()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
NodeIdentifier {
|
|
||||||
name: "graphene_core::ops::AddNode",
|
|
||||||
types: &[Concrete("u32"), Concrete("u32")],
|
|
||||||
},
|
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(|nodes| {
|
stack.push_fn(|nodes| {
|
||||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
||||||
@@ -61,10 +48,10 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new(
|
||||||
name: "graphene_core::ops::AddNode",
|
"graphene_core::ops::AddNode",
|
||||||
types: &[Concrete("&u32"), Concrete("&u32")],
|
&[Concrete(std::borrow::Cow::Borrowed("&u32")), Concrete(std::borrow::Cow::Borrowed("&u32"))],
|
||||||
},
|
),
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(|nodes| {
|
stack.push_fn(|nodes| {
|
||||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
||||||
@@ -76,10 +63,10 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new(
|
||||||
name: "graphene_core::ops::AddNode",
|
"graphene_core::ops::AddNode",
|
||||||
types: &[Concrete("&u32"), Concrete("u32")],
|
&[Concrete(std::borrow::Cow::Borrowed("&u32")), Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
},
|
),
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(|nodes| {
|
stack.push_fn(|nodes| {
|
||||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
||||||
@@ -91,10 +78,10 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new(
|
||||||
name: "graphene_core::structural::ConsNode",
|
"graphene_core::structural::ConsNode",
|
||||||
types: &[Concrete("&u32"), Concrete("u32")],
|
&[Concrete(std::borrow::Cow::Borrowed("&u32")), Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
},
|
),
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args {
|
if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args {
|
||||||
stack.push_fn(move |nodes| {
|
stack.push_fn(move |nodes| {
|
||||||
@@ -118,10 +105,10 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new(
|
||||||
name: "graphene_core::structural::ConsNode",
|
"graphene_core::structural::ConsNode",
|
||||||
types: &[Concrete("u32"), Concrete("u32")],
|
&[Concrete(std::borrow::Cow::Borrowed("u32")), Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
},
|
),
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args {
|
if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args {
|
||||||
stack.push_fn(move |nodes| {
|
stack.push_fn(move |nodes| {
|
||||||
@@ -146,10 +133,10 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
),
|
),
|
||||||
// TODO: create macro to impl for all types
|
// TODO: create macro to impl for all types
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new(
|
||||||
name: "graphene_core::structural::ConsNode",
|
"graphene_core::structural::ConsNode",
|
||||||
types: &[Concrete("&u32"), Concrete("&u32")],
|
&[Concrete(std::borrow::Cow::Borrowed("&u32")), Concrete(std::borrow::Cow::Borrowed("&u32"))],
|
||||||
},
|
),
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
let node_id = proto_node.input.unwrap_node() as usize;
|
let node_id = proto_node.input.unwrap_node() as usize;
|
||||||
if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args {
|
if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args {
|
||||||
@@ -168,10 +155,7 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new("graphene_core::any::DowncastNode", &[Concrete(std::borrow::Cow::Borrowed("&u32"))]),
|
||||||
name: "graphene_core::any::DowncastNode",
|
|
||||||
types: &[Concrete("&u32")],
|
|
||||||
},
|
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(|nodes| {
|
stack.push_fn(|nodes| {
|
||||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
||||||
@@ -181,10 +165,7 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new("graphene_core::value::ValueNode", &[Concrete(std::borrow::Cow::Borrowed("Any<'_>"))]),
|
||||||
name: "graphene_core::value::ValueNode",
|
|
||||||
types: &[Concrete("Any<'_>")],
|
|
||||||
},
|
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(|_nodes| {
|
stack.push_fn(|_nodes| {
|
||||||
if let ConstructionArgs::Value(value) = proto_node.construction_args {
|
if let ConstructionArgs::Value(value) = proto_node.construction_args {
|
||||||
@@ -197,69 +178,48 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic]), |proto_node, stack| {
|
||||||
NodeIdentifier {
|
stack.push_fn(|_nodes| {
|
||||||
name: "graphene_core::value::ValueNode",
|
if let ConstructionArgs::Value(value) = proto_node.construction_args {
|
||||||
types: &[Type::Generic],
|
let node = FnNode::new(move |_| value.clone().up_box() as Any<'static>);
|
||||||
},
|
node.into_type_erased()
|
||||||
|proto_node, stack| {
|
|
||||||
stack.push_fn(|_nodes| {
|
|
||||||
if let ConstructionArgs::Value(value) = proto_node.construction_args {
|
|
||||||
let node = FnNode::new(move |_| value.clone().up_box() as Any<'static>);
|
|
||||||
node.into_type_erased()
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
NodeIdentifier {
|
|
||||||
name: "graphene_core::raster::GrayscaleNode",
|
|
||||||
types: &[],
|
|
||||||
},
|
|
||||||
|proto_node, stack| {
|
|
||||||
stack.push_fn(|nodes| {
|
|
||||||
let node = DynAnyNode::new(graphene_core::raster::GrayscaleNode);
|
|
||||||
|
|
||||||
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
|
||||||
let pre_node = nodes.get(pre_id as usize).unwrap();
|
|
||||||
(pre_node).then(node).into_type_erased()
|
|
||||||
} else {
|
|
||||||
node.into_type_erased()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
NodeIdentifier {
|
|
||||||
name: "graphene_std::raster::MapImageNode",
|
|
||||||
types: &[],
|
|
||||||
},
|
|
||||||
|proto_node, stack| {
|
|
||||||
let node_id = proto_node.input.unwrap_node() as usize;
|
|
||||||
if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args {
|
|
||||||
stack.push_fn(move |nodes| {
|
|
||||||
let pre_node = nodes.get(node_id).unwrap();
|
|
||||||
|
|
||||||
let operation_node = nodes.get(operation_node_id[0] as usize).unwrap();
|
|
||||||
let operation_node: DowncastBothNode<_, Color, Color> = DowncastBothNode::new(operation_node);
|
|
||||||
let map_node = DynAnyNode::new(graphene_std::raster::MapImageNode::new(operation_node));
|
|
||||||
|
|
||||||
let node = (pre_node).then(map_node);
|
|
||||||
|
|
||||||
node.into_type_erased()
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
unimplemented!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
),
|
}),
|
||||||
|
(NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[]), |proto_node, stack| {
|
||||||
|
stack.push_fn(|nodes| {
|
||||||
|
let node = DynAnyNode::new(graphene_core::raster::GrayscaleNode);
|
||||||
|
|
||||||
|
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
||||||
|
let pre_node = nodes.get(pre_id as usize).unwrap();
|
||||||
|
(pre_node).then(node).into_type_erased()
|
||||||
|
} else {
|
||||||
|
node.into_type_erased()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
(NodeIdentifier::new("graphene_std::raster::MapImageNode", &[]), |proto_node, stack| {
|
||||||
|
if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args {
|
||||||
|
stack.push_fn(move |nodes| {
|
||||||
|
let operation_node = nodes.get(operation_node_id[0] as usize).unwrap();
|
||||||
|
let operation_node: DowncastBothNode<_, Color, Color> = DowncastBothNode::new(operation_node);
|
||||||
|
let map_node = DynAnyNode::new(graphene_std::raster::MapImageNode::new(operation_node));
|
||||||
|
|
||||||
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
|
(pre_node).then(map_node).into_type_erased()
|
||||||
|
} else {
|
||||||
|
map_node.into_type_erased()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new("graphene_std::raster::ImageNode", &[Concrete(std::borrow::Cow::Borrowed("&str"))]),
|
||||||
name: "graphene_std::raster::ImageNode",
|
|
||||||
types: &[Concrete("&str")],
|
|
||||||
},
|
|
||||||
|_proto_node, stack| {
|
|_proto_node, stack| {
|
||||||
stack.push_fn(|_nodes| {
|
stack.push_fn(|_nodes| {
|
||||||
let image = FnNode::new(|s: &str| graphene_std::raster::image_node::<&str>().eval(s).unwrap());
|
let image = FnNode::new(|s: &str| graphene_std::raster::image_node::<&str>().eval(s).unwrap());
|
||||||
@@ -269,10 +229,7 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new("graphene_std::raster::ExportImageNode", &[Concrete(std::borrow::Cow::Borrowed("&str"))]),
|
||||||
name: "graphene_std::raster::ExportImageNode",
|
|
||||||
types: &[Concrete("&str")],
|
|
||||||
},
|
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(|nodes| {
|
stack.push_fn(|nodes| {
|
||||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
||||||
@@ -285,10 +242,10 @@ static NODE_REGISTRY: &[(NodeIdentifier<'static>, NodeConstructor)] = &[
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier {
|
NodeIdentifier::new(
|
||||||
name: "graphene_core::structural::ConsNode",
|
"graphene_core::structural::ConsNode",
|
||||||
types: &[Concrete("Image"), Concrete("&str")],
|
&[Concrete(std::borrow::Cow::Borrowed("Image")), Concrete(std::borrow::Cow::Borrowed("&str"))],
|
||||||
},
|
),
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
let node_id = proto_node.input.unwrap_node() as usize;
|
let node_id = proto_node.input.unwrap_node() as usize;
|
||||||
if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args {
|
if let ConstructionArgs::Nodes(cons_node_arg) = proto_node.construction_args {
|
||||||
@@ -322,11 +279,6 @@ mod protograph_testing {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Lookup a node by th suffix of the name (for testing only)
|
|
||||||
fn simple_lookup(suffix: &str) -> &(NodeIdentifier, fn(ProtoNode, &FixedSizeStack<TypeErasedNode<'static>>)) {
|
|
||||||
NODE_REGISTRY.iter().find(|node| node.0.name.ends_with(suffix)).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_values() {
|
fn add_values() {
|
||||||
let stack = FixedSizeStack::new(256);
|
let stack = FixedSizeStack::new(256);
|
||||||
@@ -339,14 +291,20 @@ mod protograph_testing {
|
|||||||
let cons_protonode = ProtoNode {
|
let cons_protonode = ProtoNode {
|
||||||
construction_args: ConstructionArgs::Nodes(vec![1]),
|
construction_args: ConstructionArgs::Nodes(vec![1]),
|
||||||
input: ProtoNodeInput::Node(0),
|
input: ProtoNodeInput::Node(0),
|
||||||
identifier: NodeIdentifier::new("graphene_core::structural::ConsNode", &[Concrete("u32"), Concrete("u32")]),
|
identifier: NodeIdentifier::new(
|
||||||
|
"graphene_core::structural::ConsNode",
|
||||||
|
&[Concrete(std::borrow::Cow::Borrowed("u32")), Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
|
),
|
||||||
};
|
};
|
||||||
push_node(cons_protonode, &stack);
|
push_node(cons_protonode, &stack);
|
||||||
|
|
||||||
let add_protonode = ProtoNode {
|
let add_protonode = ProtoNode {
|
||||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||||
input: ProtoNodeInput::Node(2),
|
input: ProtoNodeInput::Node(2),
|
||||||
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[Concrete("u32"), Concrete("u32")]),
|
identifier: NodeIdentifier::new(
|
||||||
|
"graphene_core::ops::AddNode",
|
||||||
|
&[Concrete(std::borrow::Cow::Borrowed("u32")), Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
|
),
|
||||||
};
|
};
|
||||||
push_node(add_protonode, &stack);
|
push_node(add_protonode, &stack);
|
||||||
|
|
||||||
@@ -379,7 +337,7 @@ mod protograph_testing {
|
|||||||
let image_protonode = ProtoNode {
|
let image_protonode = ProtoNode {
|
||||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||||
input: ProtoNodeInput::None,
|
input: ProtoNodeInput::None,
|
||||||
identifier: NodeIdentifier::new("graphene_std::raster::ImageNode", &[Concrete("&str")]),
|
identifier: NodeIdentifier::new("graphene_std::raster::ImageNode", &[Concrete(std::borrow::Cow::Borrowed("&str"))]),
|
||||||
};
|
};
|
||||||
push_node(image_protonode, &stack);
|
push_node(image_protonode, &stack);
|
||||||
|
|
||||||
@@ -394,7 +352,7 @@ mod protograph_testing {
|
|||||||
let image_protonode = ProtoNode {
|
let image_protonode = ProtoNode {
|
||||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||||
input: ProtoNodeInput::None,
|
input: ProtoNodeInput::None,
|
||||||
identifier: NodeIdentifier::new("graphene_std::raster::ImageNode", &[Concrete("&str")]),
|
identifier: NodeIdentifier::new("graphene_std::raster::ImageNode", &[Concrete(std::borrow::Cow::Borrowed("&str"))]),
|
||||||
};
|
};
|
||||||
push_node(image_protonode, &stack);
|
push_node(image_protonode, &stack);
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,49 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use crate::document::value;
|
use crate::document::value;
|
||||||
use crate::document::NodeId;
|
use crate::document::NodeId;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct NodeIdentifier<'a> {
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub name: &'a str,
|
pub struct NodeIdentifier {
|
||||||
pub types: &'a [Type<'a>],
|
pub name: std::borrow::Cow<'static, str>,
|
||||||
|
pub types: std::borrow::Cow<'static, [Type]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Type<'a> {
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub enum Type {
|
||||||
Generic,
|
Generic,
|
||||||
Concrete(&'a str),
|
Concrete(std::borrow::Cow<'static, str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a str> for Type<'a> {
|
impl From<&'static str> for Type {
|
||||||
fn from(s: &'a str) -> Self {
|
fn from(s: &'static str) -> Self {
|
||||||
Type::Concrete(s)
|
Type::Concrete(std::borrow::Cow::Borrowed(s))
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> From<&'a str> for NodeIdentifier<'a> {
|
|
||||||
fn from(s: &'a str) -> Self {
|
|
||||||
NodeIdentifier { name: s, types: &[] }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NodeIdentifier<'a> {
|
impl Type {
|
||||||
pub fn new(name: &'a str, types: &'a [Type<'a>]) -> Self {
|
pub const fn from_str(concrete: &'static str) -> Self {
|
||||||
NodeIdentifier { name, types }
|
Type::Concrete(std::borrow::Cow::Borrowed(concrete))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for NodeIdentifier {
|
||||||
|
fn from(s: &'static str) -> Self {
|
||||||
|
NodeIdentifier {
|
||||||
|
name: std::borrow::Cow::Borrowed(s),
|
||||||
|
types: std::borrow::Cow::Borrowed(&[]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeIdentifier {
|
||||||
|
pub const fn new(name: &'static str, types: &'static [Type]) -> Self {
|
||||||
|
NodeIdentifier {
|
||||||
|
name: std::borrow::Cow::Borrowed(name),
|
||||||
|
types: std::borrow::Cow::Borrowed(types),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +74,7 @@ impl PartialEq for ConstructionArgs {
|
|||||||
pub struct ProtoNode {
|
pub struct ProtoNode {
|
||||||
pub construction_args: ConstructionArgs,
|
pub construction_args: ConstructionArgs,
|
||||||
pub input: ProtoNodeInput,
|
pub input: ProtoNodeInput,
|
||||||
pub identifier: NodeIdentifier<'static>,
|
pub identifier: NodeIdentifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq)]
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
@@ -83,10 +97,7 @@ impl ProtoNodeInput {
|
|||||||
impl ProtoNode {
|
impl ProtoNode {
|
||||||
pub fn value(value: ConstructionArgs) -> Self {
|
pub fn value(value: ConstructionArgs) -> Self {
|
||||||
Self {
|
Self {
|
||||||
identifier: NodeIdentifier {
|
identifier: NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic]),
|
||||||
name: "graphene_core::value::ValueNode",
|
|
||||||
types: &[Type::Generic],
|
|
||||||
},
|
|
||||||
construction_args: value,
|
construction_args: value,
|
||||||
input: ProtoNodeInput::None,
|
input: ProtoNodeInput::None,
|
||||||
}
|
}
|
||||||
@@ -110,7 +121,22 @@ impl ProtoNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProtoNetwork {
|
impl ProtoNetwork {
|
||||||
fn reverse_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
|
pub fn collect_outwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
|
||||||
|
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
|
||||||
|
for (id, node) in &self.nodes {
|
||||||
|
if let ProtoNodeInput::Node(ref_id) = &node.input {
|
||||||
|
edges.entry(*ref_id).or_default().push(*id)
|
||||||
|
}
|
||||||
|
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
|
||||||
|
for ref_id in ref_nodes {
|
||||||
|
edges.entry(*ref_id).or_default().push(*id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edges
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
|
||||||
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
|
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
|
||||||
for (id, node) in &self.nodes {
|
for (id, node) in &self.nodes {
|
||||||
if let ProtoNodeInput::Node(ref_id) = &node.input {
|
if let ProtoNodeInput::Node(ref_id) = &node.input {
|
||||||
@@ -125,38 +151,28 @@ impl ProtoNetwork {
|
|||||||
edges
|
edges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Based on https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
|
||||||
pub fn topological_sort(&self) -> Vec<NodeId> {
|
pub fn topological_sort(&self) -> Vec<NodeId> {
|
||||||
let mut visited = HashSet::new();
|
|
||||||
let mut stack = Vec::new();
|
|
||||||
let mut sorted = Vec::new();
|
let mut sorted = Vec::new();
|
||||||
let graph = self.reverse_edges();
|
let outwards_edges = self.collect_outwards_edges();
|
||||||
// TODO: remove
|
let mut inwards_edges = self.collect_inwards_edges();
|
||||||
println!("{:#?}", graph);
|
let mut no_incoming_edges: Vec<_> = self.nodes.iter().map(|entry| entry.0).filter(|id| !inwards_edges.contains_key(id)).collect();
|
||||||
|
|
||||||
for (id, _) in &self.nodes {
|
assert_ne!(no_incoming_edges.len(), 0, "Acyclic graphs must have at least one node with no incoming edge");
|
||||||
if !visited.contains(id) {
|
|
||||||
stack.push(*id);
|
|
||||||
|
|
||||||
while let Some(id) = stack.pop() {
|
while let Some(node_id) = no_incoming_edges.pop() {
|
||||||
//TODO remove
|
sorted.push(node_id);
|
||||||
println!("{:?}", stack);
|
|
||||||
if !visited.contains(&id) {
|
if let Some(outwards_edges) = outwards_edges.get(&node_id) {
|
||||||
visited.insert(id);
|
for &ref_id in outwards_edges {
|
||||||
if let Some(refs) = graph.get(&id) {
|
let dependencies = inwards_edges.get_mut(&ref_id).unwrap();
|
||||||
for ref_id in refs {
|
dependencies.retain(|&id| id != node_id);
|
||||||
if !visited.contains(ref_id) {
|
if dependencies.is_empty() {
|
||||||
stack.push(id);
|
no_incoming_edges.push(ref_id)
|
||||||
stack.push(*ref_id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sorted.push(id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sorted.reverse();
|
|
||||||
sorted
|
sorted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user