Brush tool live preview (#1116)
* Disable vector preview for brush tool * Fix brush preview * Fix warping * Left and right square brackets to change size * Add linear interpolation * Modfiy existing selected brush layer * Resolve warnings --------- Co-authored-by: Dennis Kobert <dennis@kobert.dev> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
c7d14c2a7b
commit
ed6140b4a7
|
|
@ -62,6 +62,9 @@ pub const CREATE_CURVE_THRESHOLD: f64 = 5.;
|
|||
// Line tool
|
||||
pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||
|
||||
// Brush tool
|
||||
pub const BRUSH_SIZE_CHANGE_KEYBOARD: f64 = 5.;
|
||||
|
||||
// Scrollbars
|
||||
pub const SCROLLBAR_SPACING: f64 = 0.1;
|
||||
pub const ASYMPTOTIC_EFFECT: f64 = 0.5;
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ pub struct FrontendDocumentDetails {
|
|||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||
pub struct FrontendImageData {
|
||||
pub path: Vec<LayerId>,
|
||||
pub mime: String,
|
||||
#[serde(skip)]
|
||||
pub image_data: std::sync::Arc<Vec<u8>>,
|
||||
pub transform: Option<[f64; 6]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, specta::Type)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::consts::{BIG_NUDGE_AMOUNT, NUDGE_AMOUNT};
|
||||
use crate::consts::{BIG_NUDGE_AMOUNT, BRUSH_SIZE_CHANGE_KEYBOARD, NUDGE_AMOUNT};
|
||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates};
|
||||
use crate::messages::input_mapper::utility_types::macros::*;
|
||||
|
|
@ -6,6 +6,7 @@ use crate::messages::input_mapper::utility_types::misc::MappingEntry;
|
|||
use crate::messages::input_mapper::utility_types::misc::{KeyMappingEntries, Mapping};
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::brush_tool::BrushToolMessageOptionsUpdate;
|
||||
|
||||
use glam::DVec2;
|
||||
|
||||
|
|
@ -209,6 +210,8 @@ pub fn default_mapping() -> Mapping {
|
|||
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
|
||||
entry!(KeyDown(Lmb); action_dispatch=BrushToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=BrushToolMessage::DragStop),
|
||||
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD))),
|
||||
entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD))),
|
||||
//
|
||||
// ToolMessage
|
||||
entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect),
|
||||
|
|
|
|||
|
|
@ -1628,6 +1628,7 @@ impl DocumentMessageHandler {
|
|||
path: path.clone(),
|
||||
image_data: data.image_data.clone(),
|
||||
mime: node_graph_frame.mime.clone(),
|
||||
transform: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::prelude::*;
|
||||
|
|
@ -9,11 +10,9 @@ use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap,
|
|||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
||||
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||
use graphene_core::vector::style::Stroke;
|
||||
use graphene_core::Cow;
|
||||
|
||||
use glam::DVec2;
|
||||
|
|
@ -60,6 +59,7 @@ pub enum BrushToolMessage {
|
|||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
|
||||
pub enum BrushToolMessageOptionsUpdate {
|
||||
ChangeDiameter(f64),
|
||||
Diameter(f64),
|
||||
Flow(f64),
|
||||
Hardness(f64),
|
||||
|
|
@ -119,6 +119,18 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTo
|
|||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
BrushToolMessageOptionsUpdate::ChangeDiameter(change) => {
|
||||
let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5;
|
||||
if needs_rounding && change > 0. {
|
||||
self.options.diameter = (self.options.diameter / change.abs()).ceil() * change.abs();
|
||||
} else if needs_rounding && change < 0. {
|
||||
self.options.diameter = (self.options.diameter / change.abs()).floor() * change.abs();
|
||||
} else {
|
||||
self.options.diameter = (self.options.diameter / change.abs()).round() * change.abs() + change;
|
||||
}
|
||||
self.options.diameter = self.options.diameter.max(1.);
|
||||
self.register_properties(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
BrushToolMessageOptionsUpdate::Diameter(diameter) => self.options.diameter = diameter,
|
||||
BrushToolMessageOptionsUpdate::Hardness(hardness) => self.options.hardness = hardness,
|
||||
BrushToolMessageOptionsUpdate::Flow(flow) => self.options.flow = flow,
|
||||
|
|
@ -137,11 +149,13 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTo
|
|||
DragStart,
|
||||
DragStop,
|
||||
Abort,
|
||||
UpdateOptions,
|
||||
),
|
||||
Drawing => actions!(BrushToolMessageDiscriminant;
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Abort,
|
||||
UpdateOptions,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
@ -166,6 +180,19 @@ struct BrushToolData {
|
|||
path: Option<Vec<LayerId>>,
|
||||
}
|
||||
|
||||
impl BrushToolData {
|
||||
fn update_points(&self, responses: &mut VecDeque<Message>) {
|
||||
if let Some(layer_path) = self.path.clone() {
|
||||
responses.add(NodeGraphMessage::SetQualifiedInputValue {
|
||||
layer_path,
|
||||
node_path: vec![0],
|
||||
input_index: 1,
|
||||
value: TaggedValue::VecDVec2(self.points.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for BrushToolFsmState {
|
||||
type ToolData = BrushToolData;
|
||||
type ToolOptions = BrushOptions;
|
||||
|
|
@ -189,8 +216,15 @@ impl Fsm for BrushToolFsmState {
|
|||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
let existing_points = load_existing_points(document);
|
||||
let new_layer = existing_points.is_none();
|
||||
if let Some((layer_path, points)) = existing_points {
|
||||
tool_data.path = Some(layer_path);
|
||||
tool_data.points = points;
|
||||
} else {
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
}
|
||||
|
||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
|
|
@ -200,7 +234,11 @@ impl Fsm for BrushToolFsmState {
|
|||
tool_data.hardness = tool_options.hardness;
|
||||
tool_data.flow = tool_options.flow;
|
||||
|
||||
add_polyline(tool_data, global_tool_data, responses);
|
||||
if new_layer {
|
||||
add_brush_render(tool_data, global_tool_data, responses);
|
||||
} else {
|
||||
tool_data.update_points(responses);
|
||||
}
|
||||
|
||||
Drawing
|
||||
}
|
||||
|
|
@ -208,17 +246,22 @@ impl Fsm for BrushToolFsmState {
|
|||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
if tool_data.points.last() != Some(&pos) {
|
||||
// Linear interpolation for when the mouse has moved a lot between frames
|
||||
if let Some(&last_point) = tool_data.points.last() {
|
||||
let distance = (last_point - pos).length();
|
||||
let extra_points = (distance / (tool_data.diameter / 2.)).floor() as usize;
|
||||
tool_data.points.extend((0..extra_points).map(|i| last_point.lerp(pos, (i as f64 + 1.) / (extra_points as f64 + 1.))));
|
||||
}
|
||||
|
||||
tool_data.points.push(pos);
|
||||
}
|
||||
|
||||
add_polyline(tool_data, global_tool_data, responses);
|
||||
tool_data.update_points(responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, DragStop) | (Drawing, Abort) => {
|
||||
if !tool_data.points.is_empty() {
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
add_brush_render(tool_data, global_tool_data, responses);
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
} else {
|
||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||
|
|
@ -250,21 +293,6 @@ impl Fsm for BrushToolFsmState {
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_preview(data: &BrushToolData) -> Message {
|
||||
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into()
|
||||
}
|
||||
|
||||
fn add_polyline(data: &BrushToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||
let layer_path = data.path.clone().unwrap();
|
||||
let subpath = bezier_rs::Subpath::from_anchors(data.points.iter().copied(), false);
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_data.primary_color.apply_opacity(data.flow as f32 / 100.), data.diameter),
|
||||
});
|
||||
}
|
||||
|
||||
fn add_brush_render(data: &BrushToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||
let layer_path = data.path.clone().unwrap();
|
||||
let brush_node = DocumentNode {
|
||||
|
|
@ -288,3 +316,22 @@ fn add_brush_render(data: &BrushToolData, tool_data: &DocumentToolData, response
|
|||
network.push_output_node();
|
||||
graph_modification_utils::new_custom_layer(network, layer_path.clone(), responses);
|
||||
}
|
||||
|
||||
fn load_existing_points(document: &DocumentMessageHandler) -> Option<(Vec<LayerId>, Vec<DVec2>)> {
|
||||
if document.selected_layers().count() != 1 {
|
||||
return None;
|
||||
}
|
||||
let layer_path = document.selected_layers().next()?.to_vec();
|
||||
let network = document.document_legacy.layer(&layer_path).ok().and_then(|layer| layer.as_node_graph().ok())?;
|
||||
let brush_node = network.nodes.get(&0)?;
|
||||
if brush_node.implementation != DocumentNodeImplementation::Unresolved("graphene_std::brush::BrushNode".into()) {
|
||||
return None;
|
||||
}
|
||||
let points_input = brush_node.inputs.get(1)?;
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::VecDVec2(points),
|
||||
..
|
||||
} = points_input else { return None };
|
||||
|
||||
Some((layer_path, points.clone()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use document_legacy::LayerId;
|
|||
use document_legacy::Operation;
|
||||
use graphene_core::vector::style::Stroke;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -217,9 +217,9 @@ fn remove_preview(data: &FreehandToolData) -> Message {
|
|||
}
|
||||
|
||||
fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||
let layer_path = data.path.clone().unwrap();
|
||||
let subpath = bezier_rs::Subpath::from_anchors(data.points.iter().copied(), false);
|
||||
let position = subpath.bounding_box().unwrap_or_default().into_iter().sum::<DVec2>() / 2.;
|
||||
|
||||
let layer_path = data.path.clone().unwrap();
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
|
|
|
|||
|
|
@ -971,12 +971,6 @@ fn rerender_selected_layers(tool_data: &mut SelectToolData, responses: &mut VecD
|
|||
}
|
||||
}
|
||||
|
||||
fn rerender_duplicated_layers(tool_data: &mut SelectToolData, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in tool_data.not_duplicated_layers.iter().flatten() {
|
||||
responses.add(DocumentMessage::NodeGraphFrameGenerate { layer_path: layer_path.clone() });
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Majorly clean up these next five functions
|
||||
|
||||
fn drag_shallowest_manipulation(
|
||||
|
|
|
|||
|
|
@ -262,10 +262,10 @@ fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, s
|
|||
}
|
||||
|
||||
let subpath = bezier_rs::Subpath::new_cubic_spline(points);
|
||||
let position = subpath.bounding_box().unwrap_or_default().into_iter().sum::<DVec2>() / 2.;
|
||||
|
||||
let layer_path = tool_data.path.clone().unwrap();
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path.clone(),
|
||||
stroke: Stroke::new(global_tool_data.primary_color, tool_data.weight),
|
||||
|
|
|
|||
|
|
@ -269,9 +269,17 @@ impl NodeGraphExecutor {
|
|||
// Attempt to downcast to an image frame
|
||||
let ImageFrame { image, transform } = dyn_any::downcast(boxed_node_graph_output).map(|image_frame| *image_frame)?;
|
||||
|
||||
// Don't update the frame's transform if the new transform is DAffine2::ZERO.
|
||||
let transform = (!transform.abs_diff_eq(DAffine2::ZERO, f64::EPSILON)).then_some(transform.to_cols_array());
|
||||
|
||||
// If no image was generated, clear the frame
|
||||
if image.width == 0 || image.height == 0 {
|
||||
responses.push_back(DocumentMessage::FrameClear.into());
|
||||
|
||||
// Update the transform based on the graph output
|
||||
if let Some(transform) = transform {
|
||||
responses.push_back(Operation::SetLayerTransform { path: layer_path.clone(), transform }.into());
|
||||
}
|
||||
} else {
|
||||
// Update the image data
|
||||
let (image_data, _size) = Self::encode_img(image, None, image::ImageOutputFormat::Bmp)?;
|
||||
|
|
@ -289,17 +297,10 @@ impl NodeGraphExecutor {
|
|||
path: layer_path.clone(),
|
||||
image_data,
|
||||
mime,
|
||||
transform,
|
||||
}];
|
||||
responses.push_back(FrontendMessage::UpdateImageData { document_id, image_data }.into());
|
||||
}
|
||||
|
||||
// Don't update the frame's transform if the new transform is DAffine2::ZERO.
|
||||
if !transform.abs_diff_eq(DAffine2::ZERO, f64::EPSILON) {
|
||||
// Update the transform based on the graph output
|
||||
let transform = transform.to_cols_array();
|
||||
responses.push_back(Operation::SetLayerTransform { path: layer_path.clone(), transform }.into());
|
||||
responses.push_back(Operation::SetLayerVisibility { path: layer_path, visible: true }.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -98,11 +98,13 @@ fn handle_message(message: String) -> String {
|
|||
for image in image_data {
|
||||
let path = image.path.clone();
|
||||
let mime = image.mime.clone();
|
||||
let transform = image.transform.clone();
|
||||
images.insert(format!("{:?}_{}", &image.path, document_id), image);
|
||||
stub_data.push(FrontendImageData {
|
||||
path,
|
||||
mime,
|
||||
image_data: Arc::new(Vec::new()),
|
||||
transform,
|
||||
});
|
||||
}
|
||||
FrontendMessage::UpdateImageData { document_id, image_data: stub_data }
|
||||
|
|
|
|||
|
|
@ -529,6 +529,7 @@
|
|||
}
|
||||
|
||||
&::placeholder {
|
||||
opacity: 1;
|
||||
color: inherit;
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export function createPortfolioState(editor: Editor) {
|
|||
image.src = blobURL;
|
||||
await image.decode();
|
||||
|
||||
editor.instance.setImageBlobURL(updateImageData.documentId, element.path, blobURL, image.naturalWidth, image.naturalHeight);
|
||||
editor.instance.setImageBlobURL(updateImageData.documentId, element.path, blobURL, image.naturalWidth, image.naturalHeight, element.transform);
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerNodeGraphFrameGenerate, async (triggerNodeGraphFrameGenerate) => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export type Editor = Readonly<ReturnType<typeof createEditor>>;
|
|||
let wasmImport: WasmRawInstance | undefined;
|
||||
let editorInstance: WasmEditorInstance | undefined;
|
||||
|
||||
export async function updateImage(path: BigUint64Array, mime: string, imageData: Uint8Array, documentId: bigint): Promise<void> {
|
||||
export async function updateImage(path: BigUint64Array, mime: string, imageData: Uint8Array, transform: Float64Array, documentId: bigint): Promise<void> {
|
||||
const blob = new Blob([imageData], { type: mime });
|
||||
|
||||
const blobURL = URL.createObjectURL(blob);
|
||||
|
|
@ -21,7 +21,7 @@ export async function updateImage(path: BigUint64Array, mime: string, imageData:
|
|||
image.src = blobURL;
|
||||
await image.decode();
|
||||
|
||||
editorInstance?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight);
|
||||
editorInstance?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight,transform);
|
||||
}
|
||||
|
||||
export async function fetchImage(path: BigUint64Array, mime: string, documentId: bigint, url: string): Promise<void> {
|
||||
|
|
@ -35,7 +35,7 @@ export async function fetchImage(path: BigUint64Array, mime: string, documentId:
|
|||
image.src = blobURL;
|
||||
await image.decode();
|
||||
|
||||
editorInstance?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight);
|
||||
editorInstance?.setImageBlobURL(documentId, path, blobURL, image.naturalWidth, image.naturalHeight, undefined);
|
||||
}
|
||||
|
||||
const tauri = "__TAURI_METADATA__" in window && import("@tauri-apps/api");
|
||||
|
|
|
|||
|
|
@ -768,6 +768,8 @@ export class ImaginateImageData {
|
|||
readonly mime!: string;
|
||||
|
||||
readonly imageData!: Uint8Array;
|
||||
|
||||
readonly transform!: Float64Array ;
|
||||
}
|
||||
|
||||
export class DisplayDialogDismiss extends JsMessage {}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ pub fn set_random_seed(seed: u64) {
|
|||
/// This avoids creating a json with a list millions of numbers long.
|
||||
#[wasm_bindgen(module = "@graphite/wasm-communication/editor")]
|
||||
extern "C" {
|
||||
fn updateImage(path: Vec<u64>, mime: String, imageData: &[u8], document_id: u64);
|
||||
fn updateImage(path: Vec<u64>, mime: String, imageData: &[u8], transform: js_sys::Float64Array, document_id: u64);
|
||||
fn fetchImage(path: Vec<u64>, mime: String, document_id: u64, identifier: String);
|
||||
//fn dispatchTauri(message: String) -> String;
|
||||
fn dispatchTauri(message: String);
|
||||
|
|
@ -114,7 +114,16 @@ impl JsEditorHandle {
|
|||
if let FrontendMessage::UpdateImageData { document_id, image_data } = message {
|
||||
for image in image_data {
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
updateImage(image.path, image.mime, &image.image_data, document_id);
|
||||
{
|
||||
let transform = if let Some(transform_val) = image.transform {
|
||||
let transform = js_sys::Float64Array::new_with_length(6);
|
||||
transform.copy_from(&transform_val);
|
||||
transform
|
||||
} else {
|
||||
js_sys::Float64Array::default()
|
||||
};
|
||||
updateImage(image.path, image.mime, &image.image_data, transform, document_id);
|
||||
}
|
||||
#[cfg(feature = "tauri")]
|
||||
fetchImage(image.path.clone(), image.mime, document_id, format!("http://localhost:3001/image/{:?}_{}", &image.path, document_id));
|
||||
}
|
||||
|
|
@ -495,15 +504,22 @@ impl JsEditorHandle {
|
|||
|
||||
/// Sends the blob URL generated by JS to the Image layer
|
||||
#[wasm_bindgen(js_name = setImageBlobURL)]
|
||||
pub fn set_image_blob_url(&self, document_id: u64, layer_path: Vec<LayerId>, blob_url: String, width: f64, height: f64) {
|
||||
pub fn set_image_blob_url(&self, document_id: u64, layer_path: Vec<LayerId>, blob_url: String, width: f64, height: f64, transform: Option<js_sys::Float64Array>) {
|
||||
let resolution = (width, height);
|
||||
let message = PortfolioMessage::SetImageBlobUrl {
|
||||
document_id,
|
||||
layer_path,
|
||||
layer_path: layer_path.clone(),
|
||||
blob_url,
|
||||
resolution,
|
||||
};
|
||||
self.dispatch(message);
|
||||
|
||||
if let Some(array) = transform.filter(|array| array.length() == 6) {
|
||||
let mut transform: [f64; 6] = [0.; 6];
|
||||
array.copy_to(&mut transform);
|
||||
let message = document_legacy::Operation::SetLayerTransform { path: layer_path, transform };
|
||||
self.dispatch(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the blob URL generated by JS to the Imaginate layer in the respective document
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use core::marker::PhantomData;
|
||||
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||
use dyn_any::{StaticType, StaticTypeSized};
|
||||
|
||||
use crate::Node;
|
||||
|
||||
|
|
|
|||
|
|
@ -274,7 +274,8 @@ fn blend_image_tuple<MapFn>(images: (ImageFrame, ImageFrame), map_fn: &'any_inpu
|
|||
where
|
||||
MapFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color> + 'input + Clone,
|
||||
{
|
||||
let (mut background, foreground) = images;
|
||||
let (background, foreground) = images;
|
||||
|
||||
let node = BlendImageNode::new(ClonedNode::new(background), ValueNode::new(map_fn.clone()));
|
||||
node.eval(foreground)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use graph_craft::document::value::UpcastNode;
|
|||
use graph_craft::document::NodeId;
|
||||
use graph_craft::executor::Executor;
|
||||
use graph_craft::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, TypingContext};
|
||||
use graph_craft::{Type, TypeDescriptor};
|
||||
use graph_craft::Type;
|
||||
use graphene_std::any::{Any, TypeErasedPinned, TypeErasedPinnedRef};
|
||||
|
||||
use crate::node_registry;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use graph_craft::proto::NodeConstructor;
|
|||
|
||||
use graphene_core::{concrete, fn_type, generic, value_fn};
|
||||
use graphene_std::memo::{CacheNode, LetNode};
|
||||
use graphene_std::raster::{BlendImageTupleNode, MapImageFrameNode};
|
||||
use graphene_std::raster::BlendImageTupleNode;
|
||||
|
||||
use crate::executor::NodeContainer;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use proc_macro::TokenStream;
|
|||
use proc_macro2::Span;
|
||||
use quote::{format_ident, ToTokens};
|
||||
use syn::{
|
||||
parse_macro_input, punctuated::Punctuated, token::Comma, FnArg, GenericParam, Ident, ItemFn, Lifetime, Pat, PatIdent, PatType, PathArguments, PredicateType, ReturnType, Token, TraitBound, Type,
|
||||
TypeParam, TypeParamBound, WhereClause, WherePredicate,
|
||||
parse_macro_input, punctuated::Punctuated, token::Comma, FnArg, GenericParam, Ident, ItemFn, Lifetime, Pat, PatIdent, PathArguments, PredicateType, ReturnType, Token, TraitBound, Type, TypeParam,
|
||||
TypeParamBound, WhereClause, WherePredicate,
|
||||
};
|
||||
|
||||
#[proc_macro_attribute]
|
||||
|
|
@ -51,7 +51,7 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||
.filter(|gen| {
|
||||
if let GenericParam::Type(ty) = gen {
|
||||
!function.sig.inputs.iter().take(1).any(|param_ty| match param_ty {
|
||||
FnArg::Typed(pat_ty) => pat_ty.ty.to_token_stream().to_string() == ty.ident.to_string(),
|
||||
FnArg::Typed(pat_ty) => ty.ident == pat_ty.ty.to_token_stream().to_string(),
|
||||
_ => false,
|
||||
})
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in New Issue