Fix transforms, Brush tool, and G/R/S (#1473)

* Transform fixes

* Fix the desert artwork

* Change artboard icon

* Better handling when transforming brush strokes

* Code review pass

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-11-27 04:54:06 +00:00 committed by GitHub
parent 5ee79031ab
commit b2ca643e6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 308 additions and 238 deletions

View File

@ -156,6 +156,8 @@ impl Document {
pub fn click(&self, viewport_location: DVec2, network: &NodeNetwork) -> Option<LayerNodeIdentifier> { pub fn click(&self, viewport_location: DVec2, network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
self.click_xray(viewport_location).find(|&layer| !is_artboard(layer, network)) self.click_xray(viewport_location).find(|&layer| !is_artboard(layer, network))
} }
/// Get the combined bounding box of the click targets of the selected visible layers in viewport space
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_visible_layers() self.selected_visible_layers()
.filter_map(|layer| self.metadata.bounding_box_viewport(layer)) .filter_map(|layer| self.metadata.bounding_box_viewport(layer))

View File

@ -222,7 +222,16 @@ fn sibling_below<'a>(graph: &'a NodeNetwork, node: &DocumentNode) -> Option<(&'a
// transforms // transforms
impl DocumentMetadata { impl DocumentMetadata {
/// Update the cached transforms of the layers /// Update the cached transforms of the layers
pub fn update_transforms(&mut self, new_transforms: HashMap<LayerNodeIdentifier, DAffine2>, new_upstream_transforms: HashMap<NodeId, DAffine2>) { pub fn update_transforms(&mut self, mut new_transforms: HashMap<LayerNodeIdentifier, DAffine2>, new_upstream_transforms: HashMap<NodeId, DAffine2>) {
let mut stack = vec![(LayerNodeIdentifier::ROOT, DAffine2::IDENTITY)];
while let Some((layer, transform)) = stack.pop() {
for child in layer.children(self) {
let Some(new_transform) = new_transforms.get_mut(&child) else { continue };
*new_transform = transform * *new_transform;
stack.push((child, *new_transform));
}
}
self.transforms = new_transforms; self.transforms = new_transforms;
self.upstream_transforms = new_upstream_transforms; self.upstream_transforms = new_upstream_transforms;
} }
@ -235,6 +244,10 @@ impl DocumentMetadata {
}) })
} }
pub fn local_transform(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.transform_to_document(layer.parent(self).unwrap_or_default()).inverse() * self.transform_to_document(layer)
}
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 { pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.document_to_viewport * self.transform_to_document(layer) self.document_to_viewport * self.transform_to_document(layer)
} }
@ -299,11 +312,16 @@ impl DocumentMetadata {
self.bounding_box_with_transform(layer, self.transform_to_viewport(layer)) self.bounding_box_with_transform(layer, self.transform_to_viewport(layer))
} }
/// Calculates the document bounds used for scrolling and centring (the layer bounds or the artboard (if applicable)) /// Calculates the document bounds in viewport space
pub fn document_bounds(&self) -> Option<[DVec2; 2]> { pub fn document_bounds_viewport_space(&self) -> Option<[DVec2; 2]> {
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds) self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
} }
/// Calculates the document bounds in document space
pub fn document_bounds_document_space(&self) -> Option<[DVec2; 2]> {
self.all_layers().filter_map(|layer| self.bounding_box_document(layer)).reduce(Quad::combine_bounds)
}
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> graphene_core::vector::Subpath { pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> graphene_core::vector::Subpath {
let Some(click_targets) = self.click_targets.get(&layer) else { let Some(click_targets) = self.click_targets.get(&layer) else {
return graphene_core::vector::Subpath::new(); return graphene_core::vector::Subpath::new();
@ -546,8 +564,8 @@ impl From<NodeId> for LayerNodeIdentifier {
} }
impl From<LayerNodeIdentifier> for NodeId { impl From<LayerNodeIdentifier> for NodeId {
fn from(identifer: LayerNodeIdentifier) -> Self { fn from(identifier: LayerNodeIdentifier) -> Self {
identifer.to_node() identifier.to_node()
} }
} }

View File

@ -49,6 +49,7 @@ pub enum LayerDataTypeDiscriminant {
Folder, Folder,
Shape, Shape,
Layer, Layer,
Artboard,
} }
impl fmt::Display for LayerDataTypeDiscriminant { impl fmt::Display for LayerDataTypeDiscriminant {
@ -57,6 +58,7 @@ impl fmt::Display for LayerDataTypeDiscriminant {
LayerDataTypeDiscriminant::Folder => write!(f, "Folder"), LayerDataTypeDiscriminant::Folder => write!(f, "Folder"),
LayerDataTypeDiscriminant::Shape => write!(f, "Shape"), LayerDataTypeDiscriminant::Shape => write!(f, "Shape"),
LayerDataTypeDiscriminant::Layer => write!(f, "Layer"), LayerDataTypeDiscriminant::Layer => write!(f, "Layer"),
LayerDataTypeDiscriminant::Artboard => write!(f, "Artboard"),
} }
} }
} }

View File

@ -1,5 +1,6 @@
use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE}; use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE};
use crate::messages::debug::utility_types::MessageLoggingVerbosity; use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::DialogData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use graphene_core::text::Font; use graphene_core::text::Font;
@ -112,11 +113,11 @@ impl Dispatcher {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ()); self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
} }
Dialog(message) => { Dialog(message) => {
self.message_handlers.dialog_message_handler.process_message( let data = DialogData {
message, portfolio: &self.message_handlers.portfolio_message_handler,
&mut queue, preferences: &self.message_handlers.preferences_message_handler,
(&self.message_handlers.portfolio_message_handler, &self.message_handlers.preferences_message_handler), };
); self.message_handlers.dialog_message_handler.process_message(message, &mut queue, data);
} }
Frontend(message) => { Frontend(message) => {
// Handle these messages immediately by returning early // Handle these messages immediately by returning early

View File

@ -11,9 +11,14 @@ pub struct DialogMessageHandler {
preferences_dialog: PreferencesDialogMessageHandler, preferences_dialog: PreferencesDialogMessageHandler,
} }
impl MessageHandler<DialogMessage, (&PortfolioMessageHandler, &PreferencesMessageHandler)> for DialogMessageHandler { pub struct DialogData<'a> {
pub portfolio: &'a PortfolioMessageHandler,
pub preferences: &'a PreferencesMessageHandler,
}
impl MessageHandler<DialogMessage, DialogData<'_>> for DialogMessageHandler {
#[remain::check] #[remain::check]
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, (portfolio, preferences): (&PortfolioMessageHandler, &PreferencesMessageHandler)) { fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, DialogData { portfolio, preferences }: DialogData) {
#[remain::sorted] #[remain::sorted]
match message { match message {
#[remain::unsorted] #[remain::unsorted]

View File

@ -16,4 +16,4 @@ pub mod simple_dialogs;
#[doc(inline)] #[doc(inline)]
pub use dialog_message::{DialogMessage, DialogMessageDiscriminant}; pub use dialog_message::{DialogMessage, DialogMessageDiscriminant};
#[doc(inline)] #[doc(inline)]
pub use dialog_message_handler::DialogMessageHandler; pub use dialog_message_handler::*;

View File

@ -1,9 +1,10 @@
use crate::consts::VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use graphene_core::uuid::generate_uuid; use graphene_core::uuid::generate_uuid;
use glam::{IVec2, UVec2}; use glam::{DVec2, IVec2, UVec2};
/// A dialog to allow users to set some initial options about a new document. /// A dialog to allow users to set some initial options about a new document.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -24,13 +25,18 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
NewDocumentDialogMessage::Submit => { NewDocumentDialogMessage::Submit => {
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() }); responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });
if !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0 { let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
if create_artboard {
let id = generate_uuid(); let id = generate_uuid();
responses.add(GraphOperationMessage::NewArtboard { responses.add(GraphOperationMessage::NewArtboard {
id, id,
artboard: graphene_core::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()), artboard: graphene_core::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
}); });
responses.add(DocumentMessage::ZoomCanvasToFitAll); responses.add(NavigationMessage::FitViewportToBounds {
bounds: [DVec2::ZERO, self.dimensions.as_dvec2()],
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
prevent_zoom_past_100: true,
});
} }
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::UpdateNewNodeGraph); responses.add(NodeGraphMessage::UpdateNewNodeGraph);

View File

@ -158,7 +158,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
} }
#[remain::unsorted] #[remain::unsorted]
Navigation(message) => { Navigation(message) => {
let document_bounds = self.metadata().document_bounds(); let document_bounds = self.metadata().document_bounds_viewport_space();
self.navigation_handler.process_message( self.navigation_handler.process_message(
message, message,
responses, responses,
@ -306,7 +306,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
} }
DeselectAllLayers => { DeselectAllLayers => {
responses.add_front(SetSelectedLayers { replacement_selected_layers: vec![] }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
self.layer_range_selection_reference = None; self.layer_range_selection_reference = None;
} }
DirtyRenderDocument => { DirtyRenderDocument => {
@ -623,7 +623,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
let viewport_size = ipp.viewport_bounds.size(); let viewport_size = ipp.viewport_bounds.size();
let viewport_mid = ipp.viewport_bounds.center(); let viewport_mid = ipp.viewport_bounds.center();
let [bounds1, bounds2] = self.metadata().document_bounds().unwrap_or([viewport_mid; 2]); let [bounds1, bounds2] = self.metadata().document_bounds_viewport_space().unwrap_or([viewport_mid; 2]);
let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale; let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale;
let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale; let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale;
let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING); let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING);
@ -657,20 +657,20 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}) })
} }
SelectAllLayers => { SelectAllLayers => {
let all = self.all_layers().map(|path| path.to_vec()).collect(); let all = self.metadata().all_layers().map(|layer| layer.to_node()).collect();
responses.add_front(SetSelectedLayers { replacement_selected_layers: all }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all });
} }
SelectedLayersLower => { SelectedLayersLower => {
responses.add_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 }); responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 });
} }
SelectedLayersLowerToBack => { SelectedLayersLowerToBack => {
responses.add_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN }); responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX });
} }
SelectedLayersRaise => { SelectedLayersRaise => {
responses.add_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 }); responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 });
} }
SelectedLayersRaiseToFront => { SelectedLayersRaiseToFront => {
responses.add_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX }); responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN });
} }
SelectedLayersReorder { relative_index_offset } => { SelectedLayersReorder { relative_index_offset } => {
self.selected_layers_reorder(relative_index_offset, responses); self.selected_layers_reorder(relative_index_offset, responses);
@ -813,7 +813,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
} }
SetViewMode { view_mode } => { SetViewMode { view_mode } => {
self.view_mode = view_mode; self.view_mode = view_mode;
responses.add_front(DocumentMessage::DirtyRenderDocument); responses.add_front(NodeGraphMessage::RunDocumentGraph);
} }
StartTransaction => self.backup(responses), StartTransaction => self.backup(responses),
ToggleLayerExpansion { layer } => { ToggleLayerExpansion { layer } => {
@ -876,7 +876,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add_front(NavigationMessage::SetCanvasZoom { zoom_factor: 2. }); responses.add_front(NavigationMessage::SetCanvasZoom { zoom_factor: 2. });
} }
ZoomCanvasToFitAll => { ZoomCanvasToFitAll => {
if let Some(bounds) = self.metadata().document_bounds() { if let Some(bounds) = self.metadata().document_bounds_document_space() {
responses.add(NavigationMessage::FitViewportToBounds { responses.add(NavigationMessage::FitViewportToBounds {
bounds, bounds,
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR), padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),

View File

@ -68,12 +68,10 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
responses.add(SetCanvasZoom { zoom_factor: new_scale }); responses.add(SetCanvasZoom { zoom_factor: new_scale });
} }
FitViewportToBounds { FitViewportToBounds {
bounds: [bounds_corner_a, bounds_corner_b], bounds: [pos1, pos2],
padding_scale_factor, padding_scale_factor,
prevent_zoom_past_100, prevent_zoom_past_100,
} => { } => {
let pos1 = document.metadata.document_to_viewport.inverse().transform_point2(bounds_corner_a);
let pos2 = document.metadata.document_to_viewport.inverse().transform_point2(bounds_corner_b);
let v1 = document.metadata.document_to_viewport.inverse().transform_point2(DVec2::ZERO); let v1 = document.metadata.document_to_viewport.inverse().transform_point2(DVec2::ZERO);
let v2 = document.metadata.document_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size()); let v2 = document.metadata.document_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size());
@ -98,8 +96,9 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
} }
FitViewportToSelection => { FitViewportToSelection => {
if let Some(bounds) = selection_bounds { if let Some(bounds) = selection_bounds {
let transform = document.metadata.document_to_viewport.inverse();
responses.add(FitViewportToBounds { responses.add(FitViewportToBounds {
bounds, bounds: [transform.transform_point2(bounds[0]), transform.transform_point2(bounds[1])],
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR), padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
prevent_zoom_past_100: false, prevent_zoom_past_100: false,
}) })
@ -382,16 +381,18 @@ impl NavigationMessageHandler {
} }
pub fn calculate_offset_transform(&self, viewport_center: DVec2) -> DAffine2 { pub fn calculate_offset_transform(&self, viewport_center: DVec2) -> DAffine2 {
let scaled_centre = viewport_center / self.snapped_scale();
// Try to avoid fractional coordinates to reduce anti aliasing. // Try to avoid fractional coordinates to reduce anti aliasing.
let scale = self.snapped_scale(); let scale = self.snapped_scale();
let rounded_pan = ((self.pan + viewport_center) * scale).round() / scale - viewport_center; let rounded_pan = ((self.pan + scaled_centre) * scale).round() / scale - scaled_centre;
// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors // TODO: replace with DAffine2::from_scale_angle_translation and fix the errors
let offset_transform = DAffine2::from_translation(viewport_center); let offset_transform = DAffine2::from_translation(scaled_centre);
let scale_transform = DAffine2::from_scale(DVec2::splat(scale)); let scale_transform = DAffine2::from_scale(DVec2::splat(scale));
let angle_transform = DAffine2::from_angle(self.snapped_angle()); let angle_transform = DAffine2::from_angle(self.snapped_angle());
let translation_transform = DAffine2::from_translation(rounded_pan); let translation_transform = DAffine2::from_translation(rounded_pan);
scale_transform * offset_transform * angle_transform * offset_transform.inverse() * translation_transform scale_transform * offset_transform * angle_transform * translation_transform
} }
fn create_document_transform(&self, viewport_center: DVec2, responses: &mut VecDeque<Message>) { fn create_document_transform(&self, viewport_center: DVec2, responses: &mut VecDeque<Message>) {
@ -403,7 +404,7 @@ impl NavigationMessageHandler {
let new_viewport_bounds = viewport_bounds / zoom_factor; let new_viewport_bounds = viewport_bounds / zoom_factor;
let delta_size = viewport_bounds - new_viewport_bounds; let delta_size = viewport_bounds - new_viewport_bounds;
let mouse_fraction = mouse / viewport_bounds; let mouse_fraction = mouse / viewport_bounds;
let delta = delta_size * (-mouse_fraction); let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction);
NavigationMessage::TranslateCanvas { delta }.into() NavigationMessage::TranslateCanvas { delta }.into()
} }

View File

@ -282,7 +282,7 @@ impl<'a> ModifyInputsContext<'a> {
}; };
let metadata = output_node.metadata.clone(); let metadata = output_node.metadata.clone();
let new_input = output_node.inputs[0].clone(); let new_input = output_node.inputs.first().cloned().filter(|input| input.as_node().is_some());
let node_id = generate_uuid(); let node_id = generate_uuid();
output_node.metadata.position.x += 8; output_node.metadata.position.x += 8;
@ -292,7 +292,7 @@ impl<'a> ModifyInputsContext<'a> {
warn!("Node type \"{name}\" doesn't exist"); warn!("Node type \"{name}\" doesn't exist");
return; return;
}; };
let mut new_document_node = node_type.to_document_node_default_inputs([Some(new_input)], metadata); let mut new_document_node = node_type.to_document_node_default_inputs([new_input], metadata);
update_input(&mut new_document_node.inputs, node_id, self.document_metadata); update_input(&mut new_document_node.inputs, node_id, self.document_metadata);
self.network.nodes.insert(node_id, new_document_node); self.network.nodes.insert(node_id, new_document_node);
} }
@ -575,7 +575,10 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
transform_in, transform_in,
skip_rerender, skip_rerender,
} => { } => {
let parent_transform = document.metadata.document_to_viewport * document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default(); let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network);
let parent_transform = document
.metadata
.transform_to_viewport(layer_identifier.parent(&document.metadata).unwrap_or(LayerNodeIdentifier::ROOT));
let bounds = LayerBounds::new(document, &layer); let bounds = LayerBounds::new(document, &layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender); modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
@ -597,8 +600,12 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
transform_in, transform_in,
skip_rerender, skip_rerender,
} => { } => {
let parent_transform = document.metadata.document_to_viewport * document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default(); let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network);
let current_transform = Some(document.metadata.transform_to_viewport(LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network))); let parent_transform = document
.metadata
.transform_to_viewport(layer_identifier.parent(&document.metadata).unwrap_or(LayerNodeIdentifier::ROOT));
let current_transform = Some(document.metadata.transform_to_viewport(layer_identifier));
let bounds = LayerBounds::new(document, &layer); let bounds = LayerBounds::new(document, &layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender); modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender);

View File

@ -34,7 +34,7 @@ impl OriginalTransforms {
match self { match self {
OriginalTransforms::Layer(layer_map) => { OriginalTransforms::Layer(layer_map) => {
for &layer in selected { for &layer in selected {
layer_map.entry(layer).or_insert_with(|| document.metadata.transform_to_document(layer)); layer_map.entry(layer).or_insert_with(|| document.metadata.local_transform(layer));
} }
} }
OriginalTransforms::Path(path_map) => { OriginalTransforms::Path(path_map) => {
@ -349,7 +349,7 @@ impl<'a> Selected<'a> {
let xy_summation = self let xy_summation = self
.selected .selected
.iter() .iter()
.filter_map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.document)) .map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.document))
.reduce(|a, b| a + b) .reduce(|a, b| a + b)
.unwrap_or_default(); .unwrap_or_default();
@ -366,6 +366,40 @@ impl<'a> Selected<'a> {
(min + max) / 2. (min + max) / 2.
} }
fn transform_layer(document: &Document, layer: LayerNodeIdentifier, original_transform: Option<&DAffine2>, transformation: DAffine2, responses: &mut VecDeque<Message>) {
let Some(&original_transform) = original_transform else { return };
let parent = layer.parent(&document.metadata);
let to = parent.map(|parent| document.metadata.transform_to_viewport(parent)).unwrap_or(document.metadata.document_to_viewport);
let new = to.inverse() * transformation * to * original_transform;
responses.add(GraphOperationMessage::TransformSet {
layer: layer.to_path(),
transform: new,
transform_in: TransformIn::Local,
skip_rerender: false,
});
}
fn transform_path(document: &Document, layer: LayerNodeIdentifier, initial_points: Option<&Vec<(ManipulatorPointId, DVec2)>>, transformation: DAffine2, responses: &mut VecDeque<Message>) {
let viewspace = document.metadata.transform_to_viewport(layer);
let layerspace_rotation = viewspace.inverse() * transformation;
let Some(initial_points) = initial_points else {
return;
};
for (point_id, position) in initial_points {
let viewport_point = viewspace.transform_point2(*position);
let new_pos_viewport = layerspace_rotation.transform_point2(viewport_point);
let point = *point_id;
let position = new_pos_viewport;
responses.add(GraphOperationMessage::Vector {
layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
}
}
pub fn update_transforms(&mut self, delta: DAffine2) { pub fn update_transforms(&mut self, delta: DAffine2) {
if !self.selected.is_empty() { if !self.selected.is_empty() {
let pivot = DAffine2::from_translation(*self.pivot); let pivot = DAffine2::from_translation(*self.pivot);
@ -374,56 +408,13 @@ impl<'a> Selected<'a> {
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481 // TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
for layer_ancestors in self.document.metadata.shallowest_unique_layers(self.selected.iter().copied()) { for layer_ancestors in self.document.metadata.shallowest_unique_layers(self.selected.iter().copied()) {
let layer = *layer_ancestors.last().unwrap(); let layer = *layer_ancestors.last().unwrap();
let parent = layer.parent(&self.document.metadata);
if *self.tool_type == ToolType::Select { match &self.original_transforms {
let original_layer_transforms = match self.original_transforms { OriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.document, layer, layer_transforms.get(&layer), transformation, self.responses),
OriginalTransforms::Layer(layer_map) => *layer_map.get(&layer).unwrap(), OriginalTransforms::Path(path_transforms) => Self::transform_path(self.document, layer, path_transforms.get(&layer), transformation, self.responses),
OriginalTransforms::Path(_path_map) => {
warn!("Found Path variant in original_transforms, returning identity transform for layer {layer:?}");
DAffine2::IDENTITY
}
};
let to = parent
.map(|parent| self.document.metadata.transform_to_viewport(parent))
.unwrap_or(self.document.metadata.document_to_viewport);
let new = to.inverse() * transformation * to * original_layer_transforms;
self.responses.add(GraphOperationMessage::TransformSet {
layer: layer.to_path(),
transform: new,
transform_in: TransformIn::Local,
skip_rerender: false,
});
} }
if *self.tool_type == ToolType::Path {
let viewspace = self.document.metadata.transform_to_viewport(layer);
let layerspace_rotation = viewspace.inverse() * transformation;
let initial_points = match self.original_transforms {
OriginalTransforms::Layer(_layer_map) => {
warn!("Found Layer variant in original_transforms when Path wanted, returning identity transform for layer");
None
}
OriginalTransforms::Path(path_map) => path_map.get(&layer),
};
let Some(original) = initial_points else {
warn!("Initial Points empty, it should not be possible to reach here without points");
continue;
};
for (point_id, position) in original {
let viewport_point = viewspace.transform_point2(*position);
let new_pos_viewport = layerspace_rotation.transform_point2(viewport_point);
let point = *point_id;
let position = new_pos_viewport;
self.responses.add(GraphOperationMessage::Vector {
layer: layer.to_path(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
}
}
self.responses.add(BroadcastEvent::DocumentIsDirty);
} }
self.responses.add(BroadcastEvent::DocumentIsDirty);
} }
} }

View File

@ -77,14 +77,16 @@ pub fn get_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option<DVec
} }
} }
pub fn get_document_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option<DVec2> { pub fn get_document_pivot(layer: LayerNodeIdentifier, document: &Document) -> DVec2 {
let [min, max] = document.metadata.nonzero_bounding_box(layer); let [min, max] = document.metadata.nonzero_bounding_box(layer);
get_pivot(layer, document).map(|pivot| document.metadata.transform_to_document(layer).transform_point2(min + (max - min) * pivot)) let pivot = get_pivot(layer, document).unwrap_or(DVec2::splat(0.5));
document.metadata.transform_to_document(layer).transform_point2(min + (max - min) * pivot)
} }
pub fn get_viewport_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option<DVec2> { pub fn get_viewport_pivot(layer: LayerNodeIdentifier, document: &Document) -> DVec2 {
let [min, max] = document.metadata.nonzero_bounding_box(layer); let [min, max] = document.metadata.nonzero_bounding_box(layer);
get_pivot(layer, document).map(|pivot| document.metadata.transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)) let pivot = get_pivot(layer, document).unwrap_or(DVec2::splat(0.5));
document.metadata.transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)
} }
/// Get the currently mirrored handles for a particular layer from the shape node /// Get the currently mirrored handles for a particular layer from the shape node

View File

@ -65,17 +65,16 @@ impl Pivot {
// If just one layer is selected we can use its inner transform (as it accounts for rotation) // If just one layer is selected we can use its inner transform (as it accounts for rotation)
if selected_layers_count == 1 { if selected_layers_count == 1 {
if let Some(normalized_pivot) = graph_modification_utils::get_pivot(first, &document.document_legacy) { let normalized_pivot = graph_modification_utils::get_pivot(first, &document.document_legacy).unwrap_or(DVec2::splat(0.5));
self.normalized_pivot = normalized_pivot; self.normalized_pivot = normalized_pivot;
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document); self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot)); self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
}
} else { } else {
// If more than one layer is selected we use the AABB with the mean of the pivots // If more than one layer is selected we use the AABB with the mean of the pivots
let xy_summation = document let xy_summation = document
.document_legacy .document_legacy
.selected_visible_layers() .selected_visible_layers()
.filter_map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.document_legacy)) .map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.document_legacy))
.reduce(|a, b| a + b) .reduce(|a, b| a + b)
.unwrap_or_default(); .unwrap_or_default();

View File

@ -1,13 +1,13 @@
use super::tool_prelude::*; use super::tool_prelude::*;
use crate::messages::portfolio::document::node_graph::transform_utils::get_current_transform; use crate::messages::portfolio::document::node_graph::resolve_document_node_type;
use crate::messages::portfolio::document::node_graph::transform_utils::{get_current_normalized_pivot, get_current_transform};
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
use document_legacy::layers::layer_layer::CachedOutputData; use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::LayerId;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeInput, NodeNetwork}; use graph_craft::document::{DocumentNodeMetadata, NodeInput};
use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::raster::BlendMode;
use graphene_core::uuid::generate_uuid;
use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle}; use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
use graphene_core::Color; use graphene_core::Color;
@ -289,20 +289,23 @@ impl ToolTransition for BrushTool {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct BrushToolData { struct BrushToolData {
strokes: Vec<BrushStroke>, strokes: Vec<BrushStroke>,
layer_path: Vec<LayerId>, layer: Option<LayerNodeIdentifier>,
transform: DAffine2, transform: DAffine2,
} }
impl BrushToolData { impl BrushToolData {
fn load_existing_strokes(&mut self, document: &DocumentMessageHandler) -> Option<&Vec<LayerId>> { fn load_existing_strokes(&mut self, document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
self.transform = DAffine2::IDENTITY; self.transform = DAffine2::IDENTITY;
if document.selected_layers().count() != 1 {
if document.metadata().selected_layers().count() != 1 {
return None; return None;
} }
self.layer_path = document.selected_layers().next()?.to_vec(); let Some(layer) = document.metadata().selected_layers().next() else {
let layer = document.document_legacy.layer(&self.layer_path).ok().and_then(|layer| layer.as_layer().ok())?; return None;
let network = &layer.network; };
for (node, _node_id) in network.primary_flow() {
self.layer = Some(layer);
for (node, node_id) in document.network().primary_flow_from_node(Some(layer.to_node())) {
if node.name == "Brush" { if node.name == "Brush" {
let points_input = node.inputs.get(2)?; let points_input = node.inputs.get(2)?;
let NodeInput::Value { let NodeInput::Value {
@ -314,21 +317,22 @@ impl BrushToolData {
}; };
self.strokes = strokes.clone(); self.strokes = strokes.clone();
return Some(&self.layer_path); return Some(layer);
} else if node.name == "Transform" { } else if node.name == "Transform" {
self.transform = get_current_transform(&node.inputs) * self.transform; let upstream = document.metadata().upstream_transform(node_id);
let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs)));
self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform;
} }
} }
self.transform = DAffine2::IDENTITY; self.transform = DAffine2::IDENTITY;
None
matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)).then_some(&self.layer_path)
} }
fn update_strokes(&self, responses: &mut VecDeque<Message>) { fn update_strokes(&self, responses: &mut VecDeque<Message>) {
let layer = self.layer_path.clone(); let Some(layer) = self.layer else { return };
let strokes = self.strokes.clone(); let strokes = self.strokes.clone();
responses.add(GraphOperationMessage::Brush { layer, strokes }); responses.add(GraphOperationMessage::Brush { layer: layer.to_path(), strokes });
} }
} }
@ -341,27 +345,27 @@ impl Fsm for BrushToolFsmState {
document, global_tool_data, input, .. document, global_tool_data, input, ..
} = tool_action_data; } = tool_action_data;
let document_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
let layer_position = tool_data.transform.inverse().transform_point2(document_position);
let ToolMessage::Brush(event) = event else { let ToolMessage::Brush(event) = event else {
return self; return self;
}; };
match (self, event) { match (self, event) {
(BrushToolFsmState::Ready, BrushToolMessage::DragStart) => { (BrushToolFsmState::Ready, BrushToolMessage::DragStart) => {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let layer_path = tool_data.load_existing_strokes(document); let loaded_layer = tool_data.load_existing_strokes(document);
let new_layer = layer_path.is_none();
if new_layer { let layer = loaded_layer.unwrap_or_else(|| new_brush_layer(document, responses));
responses.add(DocumentMessage::DeselectAllLayers); tool_data.layer = Some(layer);
tool_data.layer_path = document.get_path_for_new_layer();
} let parent = layer.parent(document.metadata()).unwrap_or_default();
let layer_position = tool_data.transform.inverse().transform_point2(document_position); let parent_transform = document.metadata().transform_to_viewport(parent).inverse().transform_point2(input.mouse.position);
let layer_position = tool_data.transform.inverse().transform_point2(parent_transform);
let layer_document_scale = document.metadata().transform_to_document(parent) * tool_data.transform;
// TODO: Also scale it based on the input image ('Background' parameter). // TODO: Also scale it based on the input image ('Background' parameter).
// TODO: Resizing the input image results in a different brush size from the chosen diameter. // TODO: Resizing the input image results in a different brush size from the chosen diameter.
let layer_scale = 0.0001_f64 // Safety against division by zero let layer_scale = 0.0001_f64 // Safety against division by zero
.max((tool_data.transform.matrix2 * glam::DVec2::X).length()) .max((layer_document_scale.matrix2 * glam::DVec2::X).length())
.max((tool_data.transform.matrix2 * glam::DVec2::Y).length()); .max((layer_document_scale.matrix2 * glam::DVec2::Y).length());
// Start a new stroke with a single sample // Start a new stroke with a single sample
let blend_mode = match tool_options.draw_mode { let blend_mode = match tool_options.draw_mode {
@ -381,17 +385,20 @@ impl Fsm for BrushToolFsmState {
}, },
}); });
if new_layer {
add_brush_render(tool_options, tool_data, responses);
}
tool_data.update_strokes(responses); tool_data.update_strokes(responses);
BrushToolFsmState::Drawing BrushToolFsmState::Drawing
} }
(BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => { (BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => {
if let Some(stroke) = tool_data.strokes.last_mut() { if let Some(layer) = tool_data.layer {
stroke.trace.push(BrushInputSample { position: layer_position }) if let Some(stroke) = tool_data.strokes.last_mut() {
let parent = layer.parent(document.metadata()).unwrap_or_default();
let parent_position = document.metadata().transform_to_viewport(parent).inverse().transform_point2(input.mouse.position);
let layer_position = tool_data.transform.inverse().transform_point2(parent_position);
stroke.trace.push(BrushInputSample { position: layer_position })
}
} }
tool_data.update_strokes(responses); tool_data.update_strokes(responses);
@ -438,11 +445,24 @@ impl Fsm for BrushToolFsmState {
} }
} }
fn add_brush_render(_tool_options: &BrushOptions, data: &BrushToolData, responses: &mut VecDeque<Message>) { fn new_brush_layer(document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let mut network = NodeNetwork::default(); responses.add(DocumentMessage::DeselectAllLayers);
let output_node = network.push_output_node();
if let Some(node) = network.nodes.get_mut(&output_node) { let brush_node = resolve_document_node_type("Brush")
node.inputs.push(NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true)) .expect("Brush node does not exist")
} .to_document_node_default_inputs([], DocumentNodeMetadata::position((-8, 0)));
graph_modification_utils::new_custom_layer(network, data.layer_path.clone(), responses); let cull_node = resolve_document_node_type("Cull")
.expect("Cull node does not exist")
.to_document_node_default_inputs([Some(NodeInput::node(1, 0))], DocumentNodeMetadata::default());
let id = generate_uuid();
responses.add(GraphOperationMessage::NewCustomLayer {
id,
nodes: HashMap::from([(1, brush_node), (0, cull_node)]),
parent: document.new_layer_parent(),
insert_index: -1,
});
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
LayerNodeIdentifier::new_unchecked(id)
} }

View File

@ -2,6 +2,7 @@ use crate::consts::SLOWING_DIVISOR;
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing}; use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::utility_types::{ToolData, ToolType}; use crate::messages::tool::utility_types::{ToolData, ToolType};
@ -23,10 +24,12 @@ pub struct TransformLayerMessageHandler {
original_transforms: OriginalTransforms, original_transforms: OriginalTransforms,
pivot: DVec2, pivot: DVec2,
} }
impl TransformLayerMessageHandler { impl TransformLayerMessageHandler {
pub fn is_transforming(&self) -> bool { pub fn is_transforming(&self) -> bool {
self.transform_operation != TransformOperation::None self.transform_operation != TransformOperation::None
} }
pub fn hints(&self, responses: &mut VecDeque<Message>) { pub fn hints(&self, responses: &mut VecDeque<Message>) {
let axis_constraint = match self.transform_operation { let axis_constraint = match self.transform_operation {
TransformOperation::Grabbing(grabbing) => grabbing.constraint, TransformOperation::Grabbing(grabbing) => grabbing.constraint,
@ -40,18 +43,17 @@ impl TransformLayerMessageHandler {
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState); type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState);
impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformLayerMessageHandler { impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformLayerMessageHandler {
#[remain::check] #[remain::check]
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, ipp, tool_data, shape_editor): TransformData) { fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) {
use TransformLayerMessage::*; use TransformLayerMessage::*;
let using_path_tool = tool_data.active_tool_type == ToolType::Path; let using_path_tool = tool_data.active_tool_type == ToolType::Path;
let selected_layers = document.layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then_some(layer_path)).collect::<Vec<_>>(); let selected_layers = document.metadata().selected_layers().collect::<Vec<_>>();
let selected_layers_n = document.metadata().selected_layers().collect::<Vec<_>>();
let mut selected = Selected::new( let mut selected = Selected::new(
&mut self.original_transforms, &mut self.original_transforms,
&mut self.pivot, &mut self.pivot,
&selected_layers_n, &selected_layers,
responses, responses,
&document.document_legacy, &document.document_legacy,
Some(shape_editor), Some(shape_editor),
@ -65,33 +67,26 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
} }
if using_path_tool { if using_path_tool {
if let Ok(layer) = document.document_legacy.layer(selected_layers[0]) { if let Some(subpaths) = selected_layers.first().and_then(|&layer| graph_modification_utils::get_subpaths(layer, &document.document_legacy)) {
if let Some(vector_data) = layer.as_vector_data() { *selected.original_transforms = OriginalTransforms::default();
*selected.original_transforms = OriginalTransforms::default(); let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
let viewspace = &mut document.document_legacy.generate_transform_relative_to_viewport(selected_layers[0]).ok().unwrap_or_default();
let mut point_count: usize = 0; let mut point_count: usize = 0;
let count_point = |position| { let get_location = |point: &ManipulatorPointId| {
point_count += 1; graph_modification_utils::get_manipulator_from_id(subpaths, point.group)
position .and_then(|manipulator_group| point.manipulator_type.get_position(manipulator_group))
}; .map(|position| viewspace.transform_point2(position))
let get_location = |point: &ManipulatorPointId| { };
vector_data let points = shape_editor.selected_points();
.manipulator_from_id(point.group)
.and_then(|manipulator_group| point.manipulator_type.get_position(manipulator_group))
.map(|position| viewspace.transform_point2(position))
};
let points = shape_editor.selected_points();
*selected.pivot = points.filter_map(get_location).map(count_point).sum::<DVec2>() / point_count as f64; *selected.pivot = points.filter_map(get_location).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
}
} }
} else { } else {
*selected.pivot = selected.mean_average_of_pivots(); *selected.pivot = selected.mean_average_of_pivots();
} }
*mouse_position = ipp.mouse.position; *mouse_position = input.mouse.position;
*start_mouse = ipp.mouse.position; *start_mouse = input.mouse.position;
selected.original_transforms.clear(); selected.original_transforms.clear();
}; };
@ -106,9 +101,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
for layer_path in document.selected_layers() { responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
}
} }
BeginGrab => { BeginGrab => {
if let TransformOperation::Grabbing(_) = self.transform_operation { if let TransformOperation::Grabbing(_) = self.transform_operation {
@ -175,9 +168,9 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap), ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap),
ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap), ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap),
PointerMove { slow_key, snap_key } => { PointerMove { slow_key, snap_key } => {
self.slow = ipp.keyboard.get(slow_key as usize); self.slow = input.keyboard.get(slow_key as usize);
let new_snap = ipp.keyboard.get(snap_key as usize); let new_snap = input.keyboard.get(snap_key as usize);
if new_snap != self.snap { if new_snap != self.snap {
self.snap = new_snap; self.snap = new_snap;
let axis_constraint = match self.transform_operation { let axis_constraint = match self.transform_operation {
@ -189,7 +182,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
} }
if self.typing.digits.is_empty() { if self.typing.digits.is_empty() {
let delta_pos = ipp.mouse.position - self.mouse_position; let delta_pos = input.mouse.position - self.mouse_position;
match self.transform_operation { match self.transform_operation {
TransformOperation::None => unreachable!(), TransformOperation::None => unreachable!(),
@ -201,7 +194,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
} }
TransformOperation::Rotating(rotation) => { TransformOperation::Rotating(rotation) => {
let start_offset = *selected.pivot - self.mouse_position; let start_offset = *selected.pivot - self.mouse_position;
let end_offset = *selected.pivot - ipp.mouse.position; let end_offset = *selected.pivot - input.mouse.position;
let angle = start_offset.angle_between(end_offset); let angle = start_offset.angle_between(end_offset);
let change = if self.slow { angle / SLOWING_DIVISOR } else { angle }; let change = if self.slow { angle / SLOWING_DIVISOR } else { angle };
@ -212,7 +205,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
TransformOperation::Scaling(scale) => { TransformOperation::Scaling(scale) => {
let change = { let change = {
let previous_frame_dist = (self.mouse_position - *selected.pivot).length(); let previous_frame_dist = (self.mouse_position - *selected.pivot).length();
let current_frame_dist = (ipp.mouse.position - *selected.pivot).length(); let current_frame_dist = (input.mouse.position - *selected.pivot).length();
let start_transform_dist = (self.start_mouse - *selected.pivot).length(); let start_transform_dist = (self.start_mouse - *selected.pivot).length();
(current_frame_dist - previous_frame_dist) / start_transform_dist (current_frame_dist - previous_frame_dist) / start_transform_dist
@ -225,7 +218,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
} }
}; };
} }
self.mouse_position = ipp.mouse.position; self.mouse_position = input.mouse.position;
} }
SelectionChanged => { SelectionChanged => {
let target_layers = document.metadata().selected_layers().collect(); let target_layers = document.metadata().selected_layers().collect();

View File

@ -14,11 +14,12 @@ use graph_craft::graphene_compiler::Compiler;
use graph_craft::imaginate_input::ImaginatePreferences; use graph_craft::imaginate_input::ImaginatePreferences;
use graph_craft::{concrete, Type}; use graph_craft::{concrete, Type};
use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_core::raster::Image; use graphene_core::raster::{Image, ImageFrame};
use graphene_core::renderer::{ClickTarget, GraphicElementRendered, SvgSegment, SvgSegmentList}; use graphene_core::renderer::{ClickTarget, GraphicElementRendered, SvgSegment, SvgSegmentList};
use graphene_core::text::FontCache; use graphene_core::text::FontCache;
use graphene_core::transform::{Footprint, Transform}; use graphene_core::transform::{Footprint, Transform};
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
use graphene_core::vector::VectorData;
use graphene_core::{Color, SurfaceFrame, SurfaceId}; use graphene_core::{Color, SurfaceFrame, SurfaceId};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
@ -71,8 +72,7 @@ pub(crate) struct GenerationRequest {
generation_id: u64, generation_id: u64,
graph: NodeNetwork, graph: NodeNetwork,
path: Vec<LayerId>, path: Vec<LayerId>,
transform: DAffine2, render_config: RenderConfig,
viewport_resolution: UVec2,
} }
pub(crate) struct GenerationResponse { pub(crate) struct GenerationResponse {
@ -146,12 +146,12 @@ impl NodeRuntime {
NodeRuntimeMessage::GenerationRequest(GenerationRequest { NodeRuntimeMessage::GenerationRequest(GenerationRequest {
generation_id, generation_id,
graph, graph,
transform, render_config,
path, path,
viewport_resolution,
.. ..
}) => { }) => {
let (result, monitor_nodes) = self.execute_network(&path, graph, transform, viewport_resolution).await; let transform = render_config.viewport.transform;
let (result, monitor_nodes) = self.execute_network(&path, graph, render_config).await;
let mut responses = VecDeque::new(); let mut responses = VecDeque::new();
if let Some(ref monitor_nodes) = monitor_nodes { if let Some(ref monitor_nodes) = monitor_nodes {
self.update_thumbnails(&path, monitor_nodes, &mut responses); self.update_thumbnails(&path, monitor_nodes, &mut responses);
@ -173,7 +173,7 @@ impl NodeRuntime {
} }
} }
async fn execute_network<'a>(&'a mut self, path: &[LayerId], graph: NodeNetwork, transform: DAffine2, viewport_resolution: UVec2) -> (Result<TaggedValue, String>, Option<MonitorNodes>) { async fn execute_network<'a>(&'a mut self, path: &[LayerId], graph: NodeNetwork, render_config: RenderConfig) -> (Result<TaggedValue, String>, Option<MonitorNodes>) {
if self.wasm_io.is_none() { if self.wasm_io.is_none() {
self.wasm_io = Some(WasmApplicationIo::new().await); self.wasm_io = Some(WasmApplicationIo::new().await);
} }
@ -182,17 +182,7 @@ impl NodeRuntime {
application_io: self.wasm_io.as_ref().unwrap(), application_io: self.wasm_io.as_ref().unwrap(),
node_graph_message_sender: &self.sender, node_graph_message_sender: &self.sender,
imaginate_preferences: &self.imaginate_preferences, imaginate_preferences: &self.imaginate_preferences,
render_config: RenderConfig { render_config,
viewport: Footprint {
transform,
resolution: viewport_resolution,
..Default::default()
},
#[cfg(any(feature = "resvg", feature = "vello"))]
export_format: graphene_core::application_io::ExportFormat::Canvas,
#[cfg(not(any(feature = "resvg", feature = "vello")))]
export_format: graphene_core::application_io::ExportFormat::Svg,
},
image_frame: None, image_frame: None,
}; };
@ -325,11 +315,19 @@ impl NodeRuntime {
warn!("Failed to introspect monitor node for upstream transforms"); warn!("Failed to introspect monitor node for upstream transforms");
continue; continue;
}; };
let Some(graphic_element_data) = value.downcast_ref::<graphene_core::vector::VectorData>() else { let Some(transform) = value
warn!("Failed to downcast transform input to vector data"); .downcast_ref::<graphene_core::memo::IORecord<Footprint, VectorData>>()
.map(|vector_data| vector_data.output.transform())
.or_else(|| {
value
.downcast_ref::<graphene_core::memo::IORecord<Footprint, ImageFrame<Color>>>()
.map(|image| image.output.transform())
})
else {
warn!("Failed to downcast transform input");
continue; continue;
}; };
self.upstream_transforms.insert(node_id, graphic_element_data.transform()); self.upstream_transforms.insert(node_id, transform);
} }
} }
} }
@ -399,14 +397,13 @@ impl Default for NodeGraphExecutor {
impl NodeGraphExecutor { impl NodeGraphExecutor {
/// Execute the network by flattening it and creating a borrow stack. /// Execute the network by flattening it and creating a borrow stack.
fn queue_execution(&self, network: NodeNetwork, layer_path: Vec<LayerId>, transform: DAffine2, viewport_resolution: UVec2) -> u64 { fn queue_execution(&self, network: NodeNetwork, layer_path: Vec<LayerId>, render_config: RenderConfig) -> u64 {
let generation_id = generate_uuid(); let generation_id = generate_uuid();
let request = GenerationRequest { let request = GenerationRequest {
path: layer_path, path: layer_path,
graph: network, graph: network,
generation_id, generation_id,
transform, render_config,
viewport_resolution,
}; };
self.sender.send(NodeRuntimeMessage::GenerationRequest(request)).expect("Failed to send generation request"); self.sender.send(NodeRuntimeMessage::GenerationRequest(request)).expect("Failed to send generation request");
@ -499,11 +496,21 @@ impl NodeGraphExecutor {
layer_layer.network.clone() layer_layer.network.clone()
}; };
// Construct the input image frame let render_config = RenderConfig {
let document_transform = document.document_legacy.metadata.document_to_viewport; viewport: Footprint {
transform: document.document_legacy.metadata.document_to_viewport,
resolution: viewport_resolution,
..Default::default()
},
#[cfg(any(feature = "resvg", feature = "vello"))]
export_format: graphene_core::application_io::ExportFormat::Canvas,
#[cfg(not(any(feature = "resvg", feature = "vello")))]
export_format: graphene_core::application_io::ExportFormat::Svg,
view_mode: document.view_mode,
};
// Execute the node graph // Execute the node graph
let generation_id = self.queue_execution(network, layer_path.clone(), document_transform, viewport_resolution); let generation_id = self.queue_execution(network, layer_path.clone(), render_config);
self.futures.insert(generation_id, ExecutionContext { layer_path }); self.futures.insert(generation_id, ExecutionContext { layer_path });
@ -542,7 +549,9 @@ impl NodeGraphExecutor {
.to_string(), .to_string(),
tooltip: format!("Layer id: {node_id}"), tooltip: format!("Layer id: {node_id}"),
visible: !document.document_network.disabled.contains(&layer.to_node()), visible: !document.document_network.disabled.contains(&layer.to_node()),
layer_type: if document.metadata.is_folder(layer) { layer_type: if document.metadata.is_artboard(layer) {
LayerDataTypeDiscriminant::Artboard
} else if document.metadata.is_folder(layer) {
LayerDataTypeDiscriminant::Folder LayerDataTypeDiscriminant::Folder
} else { } else {
LayerDataTypeDiscriminant::Layer LayerDataTypeDiscriminant::Layer

View File

Before

Width:  |  Height:  |  Size: 267 B

After

Width:  |  Height:  |  Size: 267 B

View File

@ -4,14 +4,8 @@
import { beginDraggingElement } from "@graphite/io-managers/drag"; import { beginDraggingElement } from "@graphite/io-managers/drag";
import { platformIsMac } from "@graphite/utility-functions/platform"; import { platformIsMac } from "@graphite/utility-functions/platform";
import type { Editor } from "@graphite/wasm-communication/editor"; import type { Editor } from "@graphite/wasm-communication/editor";
import { import { defaultWidgetLayout, patchWidgetLayout, UpdateDocumentLayerDetails, UpdateDocumentLayerTreeStructureJs, UpdateLayerTreeOptionsLayout } from "@graphite/wasm-communication/messages";
type LayerPanelEntry, import type { LayerType, LayerPanelEntry } from "@graphite/wasm-communication/messages";
defaultWidgetLayout,
patchWidgetLayout,
UpdateDocumentLayerDetails,
UpdateDocumentLayerTreeStructureJs,
UpdateLayerTreeOptionsLayout,
} from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
@ -152,6 +146,10 @@
editor.instance.deselectAllLayers(); editor.instance.deselectAllLayers();
} }
function isGroupOrArtboard(layerType: LayerType) {
return layerType === "Folder" || layerType === "Artboard";
}
function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData { function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData {
const treeChildren = tree.div()?.children; const treeChildren = tree.div()?.children;
const treeOffset = tree.div()?.getBoundingClientRect().top; const treeOffset = tree.div()?.getBoundingClientRect().top;
@ -195,9 +193,9 @@
} }
// Inserting below current row // Inserting below current row
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) { else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
insertFolder = layer.layerType === "Folder" ? layer.path : layer.path.slice(0, layer.path.length - 1); insertFolder = isGroupOrArtboard(layer.layerType) ? layer.path : layer.path.slice(0, layer.path.length - 1);
insertIndex = layer.layerType === "Folder" ? 0 : folderIndex + 1; insertIndex = isGroupOrArtboard(layer.layerType) ? 0 : folderIndex + 1;
highlightFolder = layer.layerType === "Folder"; highlightFolder = isGroupOrArtboard("Folder");
closest = -distance; closest = -distance;
markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom; markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom;
} }
@ -338,7 +336,7 @@
<div class="indent" style:margin-left={layerIndent(listing.entry)} /> <div class="indent" style:margin-left={layerIndent(listing.entry)} />
{#if listing.entry.layerType === "Folder"} {#if isGroupOrArtboard(listing.entry.layerType)}
<button class="expand-arrow" class:expanded={listing.entry.layerMetadata.expanded} on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.path)} tabindex="0" /> <button class="expand-arrow" class:expanded={listing.entry.layerMetadata.expanded} on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.path)} tabindex="0" />
{/if} {/if}
<LayoutRow <LayoutRow

View File

@ -93,6 +93,7 @@ import AlignLeft from "@graphite-frontend/assets/icon-16px-solid/align-left.svg"
import AlignRight from "@graphite-frontend/assets/icon-16px-solid/align-right.svg"; import AlignRight from "@graphite-frontend/assets/icon-16px-solid/align-right.svg";
import AlignTop from "@graphite-frontend/assets/icon-16px-solid/align-top.svg"; import AlignTop from "@graphite-frontend/assets/icon-16px-solid/align-top.svg";
import AlignVerticalCenter from "@graphite-frontend/assets/icon-16px-solid/align-vertical-center.svg"; import AlignVerticalCenter from "@graphite-frontend/assets/icon-16px-solid/align-vertical-center.svg";
import Artboard from "@graphite-frontend/assets/icon-16px-solid/artboard.svg";
import BooleanDifference from "@graphite-frontend/assets/icon-16px-solid/boolean-difference.svg"; import BooleanDifference from "@graphite-frontend/assets/icon-16px-solid/boolean-difference.svg";
import BooleanIntersect from "@graphite-frontend/assets/icon-16px-solid/boolean-intersect.svg"; import BooleanIntersect from "@graphite-frontend/assets/icon-16px-solid/boolean-intersect.svg";
import BooleanSubtractBack from "@graphite-frontend/assets/icon-16px-solid/boolean-subtract-back.svg"; import BooleanSubtractBack from "@graphite-frontend/assets/icon-16px-solid/boolean-subtract-back.svg";
@ -118,7 +119,6 @@ import IconsGrid from "@graphite-frontend/assets/icon-16px-solid/icons-grid.svg"
import Image from "@graphite-frontend/assets/icon-16px-solid/image.svg"; import Image from "@graphite-frontend/assets/icon-16px-solid/image.svg";
import Layer from "@graphite-frontend/assets/icon-16px-solid/layer.svg"; import Layer from "@graphite-frontend/assets/icon-16px-solid/layer.svg";
import License from "@graphite-frontend/assets/icon-16px-solid/license.svg"; import License from "@graphite-frontend/assets/icon-16px-solid/license.svg";
import NodeArtboard from "@graphite-frontend/assets/icon-16px-solid/node-artboard.svg";
import NodeBlur from "@graphite-frontend/assets/icon-16px-solid/node-blur.svg"; import NodeBlur from "@graphite-frontend/assets/icon-16px-solid/node-blur.svg";
import NodeBrushwork from "@graphite-frontend/assets/icon-16px-solid/node-brushwork.svg"; import NodeBrushwork from "@graphite-frontend/assets/icon-16px-solid/node-brushwork.svg";
import NodeColorCorrection from "@graphite-frontend/assets/icon-16px-solid/node-color-correction.svg"; import NodeColorCorrection from "@graphite-frontend/assets/icon-16px-solid/node-color-correction.svg";
@ -161,6 +161,7 @@ const SOLID_16PX = {
AlignRight: { svg: AlignRight, size: 16 }, AlignRight: { svg: AlignRight, size: 16 },
AlignTop: { svg: AlignTop, size: 16 }, AlignTop: { svg: AlignTop, size: 16 },
AlignVerticalCenter: { svg: AlignVerticalCenter, size: 16 }, AlignVerticalCenter: { svg: AlignVerticalCenter, size: 16 },
Artboard: { svg: Artboard, size: 16 },
BooleanDifference: { svg: BooleanDifference, size: 16 }, BooleanDifference: { svg: BooleanDifference, size: 16 },
BooleanIntersect: { svg: BooleanIntersect, size: 16 }, BooleanIntersect: { svg: BooleanIntersect, size: 16 },
BooleanSubtractBack: { svg: BooleanSubtractBack, size: 16 }, BooleanSubtractBack: { svg: BooleanSubtractBack, size: 16 },
@ -186,7 +187,6 @@ const SOLID_16PX = {
Image: { svg: Image, size: 16 }, Image: { svg: Image, size: 16 },
Layer: { svg: Layer, size: 16 }, Layer: { svg: Layer, size: 16 },
License: { svg: License, size: 16 }, License: { svg: License, size: 16 },
NodeArtboard: { svg: NodeArtboard, size: 16 },
NodeBlur: { svg: NodeBlur, size: 16 }, NodeBlur: { svg: NodeBlur, size: 16 },
NodeBrushwork: { svg: NodeBrushwork, size: 16 }, NodeBrushwork: { svg: NodeBrushwork, size: 16 },
NodeColorCorrection: { svg: NodeColorCorrection, size: 16 }, NodeColorCorrection: { svg: NodeColorCorrection, size: 16 },

View File

@ -698,7 +698,7 @@ export class LayerMetadata {
selected!: boolean; selected!: boolean;
} }
export type LayerType = "Folder" | "Layer"; export type LayerType = "Folder" | "Layer" | "Artboard";
export class ImaginateImageData { export class ImaginateImageData {
readonly path!: BigUint64Array; readonly path!: BigUint64Array;

View File

@ -1,6 +1,7 @@
use crate::raster::ImageFrame; use crate::raster::ImageFrame;
use crate::text::FontCache; use crate::text::FontCache;
use crate::transform::{Footprint, Transform, TransformMut}; use crate::transform::{Footprint, Transform, TransformMut};
use crate::vector::style::ViewMode;
use crate::{Color, Node}; use crate::{Color, Node};
use dyn_any::{StaticType, StaticTypeSized}; use dyn_any::{StaticType, StaticTypeSized};
@ -151,6 +152,7 @@ pub enum ExportFormat {
pub struct RenderConfig { pub struct RenderConfig {
pub viewport: Footprint, pub viewport: Footprint,
pub export_format: ExportFormat, pub export_format: ExportFormat,
pub view_mode: ViewMode,
} }
pub struct EditorApi<'a, Io> { pub struct EditorApi<'a, Io> {

View File

@ -284,6 +284,7 @@ impl GraphicElementRendered for Artboard {
"g", "g",
|attributes| { |attributes| {
attributes.push("class", "artboard"); attributes.push("class", "artboard");
attributes.push("transform", format_transform_matrix(self.graphic_group.transform));
if self.clip { if self.clip {
let id = format!("artboard-{}", generate_uuid()); let id = format!("artboard-{}", generate_uuid());
let selector = format!("url(#{id})"); let selector = format!("url(#{id})");
@ -298,8 +299,15 @@ impl GraphicElementRendered for Artboard {
} }
}, },
|render| { |render| {
let old_opacity = render.opacity;
render.opacity *= self.graphic_group.opacity;
// Contents // Contents
self.graphic_group.render_svg(render, render_params); for element in self.graphic_group.iter() {
render.blend_mode = element.blend_mode;
element.graphic_element_data.render_svg(render, render_params);
}
render.opacity = old_opacity;
}, },
); );
} }

View File

@ -349,6 +349,13 @@ impl<P: Hash + Pixel> Hash for ImageFrame<P> {
} }
} }
impl<P: Pixel> ImageFrame<P> {
/// Compute the pivot in local space with the current transform applied
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
self.transform.transform_point2(normalized_pivot)
}
}
/* This does not work because of missing specialization /* This does not work because of missing specialization
* so we have to manually implement this for now * so we have to manually implement this for now
impl<S: Into<P> + Pixel, P: Pixel> From<Image<S>> for Image<P> { impl<S: Into<P> + Pixel, P: Pixel> From<Image<S>> for Image<P> {

View File

@ -37,11 +37,17 @@ impl<P: Pixel> Transform for ImageFrame<P> {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
self.transform self.transform
} }
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.local_pivot(pivot)
}
} }
impl<P: Pixel> Transform for &ImageFrame<P> { impl<P: Pixel> Transform for &ImageFrame<P> {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
self.transform self.transform
} }
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
(*self).local_pivot(pivot)
}
} }
impl<P: Pixel> TransformMut for ImageFrame<P> { impl<P: Pixel> TransformMut for ImageFrame<P> {
fn transform_mut(&mut self) -> &mut DAffine2 { fn transform_mut(&mut self) -> &mut DAffine2 {
@ -69,8 +75,8 @@ impl Transform for GraphicElementData {
GraphicElementData::VectorShape(vector_shape) => vector_shape.transform(), GraphicElementData::VectorShape(vector_shape) => vector_shape.transform(),
GraphicElementData::ImageFrame(image_frame) => image_frame.transform(), GraphicElementData::ImageFrame(image_frame) => image_frame.transform(),
GraphicElementData::Text(_) => todo!("Transform of text"), GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(_graphic_group) => DAffine2::IDENTITY, GraphicElementData::GraphicGroup(graphic_group) => graphic_group.transform(),
GraphicElementData::Artboard(_artboard) => DAffine2::IDENTITY, GraphicElementData::Artboard(artboard) => artboard.graphic_group.transform(),
} }
} }
fn local_pivot(&self, pivot: DVec2) -> DVec2 { fn local_pivot(&self, pivot: DVec2) -> DVec2 {
@ -78,23 +84,17 @@ impl Transform for GraphicElementData {
GraphicElementData::VectorShape(vector_shape) => vector_shape.local_pivot(pivot), GraphicElementData::VectorShape(vector_shape) => vector_shape.local_pivot(pivot),
GraphicElementData::ImageFrame(image_frame) => image_frame.local_pivot(pivot), GraphicElementData::ImageFrame(image_frame) => image_frame.local_pivot(pivot),
GraphicElementData::Text(_) => todo!("Transform of text"), GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(_graphic_group) => pivot, GraphicElementData::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot),
GraphicElementData::Artboard(_artboard) => pivot, GraphicElementData::Artboard(artboard) => artboard.graphic_group.local_pivot(pivot),
} }
} }
fn decompose_scale(&self) -> DVec2 { fn decompose_scale(&self) -> DVec2 {
let standard = || {
DVec2::new(
self.transform().transform_vector2((1., 0.).into()).length(),
self.transform().transform_vector2((0., 1.).into()).length(),
)
};
match self { match self {
GraphicElementData::VectorShape(vector_shape) => vector_shape.decompose_scale(), GraphicElementData::VectorShape(vector_shape) => vector_shape.decompose_scale(),
GraphicElementData::ImageFrame(image_frame) => image_frame.decompose_scale(), GraphicElementData::ImageFrame(image_frame) => image_frame.decompose_scale(),
GraphicElementData::Text(_) => todo!("Transform of text"), GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(_graphic_group) => standard(), GraphicElementData::GraphicGroup(graphic_group) => graphic_group.decompose_scale(),
GraphicElementData::Artboard(_artboard) => standard(), GraphicElementData::Artboard(artboard) => artboard.graphic_group.decompose_scale(),
} }
} }
} }
@ -104,8 +104,8 @@ impl TransformMut for GraphicElementData {
GraphicElementData::VectorShape(vector_shape) => vector_shape.transform_mut(), GraphicElementData::VectorShape(vector_shape) => vector_shape.transform_mut(),
GraphicElementData::ImageFrame(image_frame) => image_frame.transform_mut(), GraphicElementData::ImageFrame(image_frame) => image_frame.transform_mut(),
GraphicElementData::Text(_) => todo!("Transform of text"), GraphicElementData::Text(_) => todo!("Transform of text"),
GraphicElementData::GraphicGroup(_graphic_group) => todo!("Mutable transform of graphic group"), GraphicElementData::GraphicGroup(graphic_group) => graphic_group.transform_mut(),
GraphicElementData::Artboard(_artboard) => todo!("Mutable transform of artboard"), GraphicElementData::Artboard(artboard) => artboard.graphic_group.transform_mut(),
} }
} }
} }

View File

@ -6,7 +6,6 @@ use graphene_core::application_io::{ApplicationError, ApplicationIo, ExportForma
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender}; use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender};
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
use graphene_core::vector::style::ViewMode;
use graphene_core::Color; use graphene_core::Color;
use graphene_core::{ use graphene_core::{
raster::{color::SRGBA8, ImageFrame}, raster::{color::SRGBA8, ImageFrame},
@ -285,7 +284,6 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
image image
} }
pub use graph_craft::document::value::RenderOutput; pub use graph_craft::document::value::RenderOutput;
pub struct RenderNode<Data, Surface, Parameter> { pub struct RenderNode<Data, Surface, Parameter> {
data: Data, data: Data,
surface_handle: Surface, surface_handle: Surface,
@ -365,7 +363,7 @@ where
fn eval(&'input self, editor: WasmEditorApi<'a>) -> Self::Output { fn eval(&'input self, editor: WasmEditorApi<'a>) -> Self::Output {
Box::pin(async move { Box::pin(async move {
let footprint = editor.render_config.viewport; let footprint = editor.render_config.viewport;
let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::Base64, None, false); let render_params = RenderParams::new(editor.render_config.view_mode, graphene_core::renderer::ImageRenderMode::Base64, None, false);
let output_format = editor.render_config.export_format; let output_format = editor.render_config.export_format;
match output_format { match output_format {
@ -393,7 +391,7 @@ where
use graphene_core::renderer::ImageRenderMode; use graphene_core::renderer::ImageRenderMode;
let footprint = editor.render_config.viewport; let footprint = editor.render_config.viewport;
let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::Base64, None, false); let render_params = RenderParams::new(editor.render_config.view_mode, ImageRenderMode::Base64, None, false);
let output_format = editor.render_config.export_format; let output_format = editor.render_config.export_format;
match output_format { match output_format {

View File

@ -800,6 +800,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
}, },
)], )],
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [Artboard]), register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [Artboard]),
register_node!(graphene_core::transform::CullNode<_>, input: Footprint, params: [ImageFrame<Color>]),
vec![( vec![(
NodeIdentifier::new("graphene_core::transform::CullNode<_>"), NodeIdentifier::new("graphene_core::transform::CullNode<_>"),
|args| { |args| {