mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 07:38:13 +08:00
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:
co-authored by
Keavon Chambers
parent
5ee79031ab
commit
b2ca643e6e
@@ -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);
|
||||
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);
|
||||
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
|
||||
|
||||
@@ -65,17 +65,16 @@ impl Pivot {
|
||||
|
||||
// If just one layer is selected we can use its inner transform (as it accounts for rotation)
|
||||
if selected_layers_count == 1 {
|
||||
if let Some(normalized_pivot) = graph_modification_utils::get_pivot(first, &document.document_legacy) {
|
||||
self.normalized_pivot = normalized_pivot;
|
||||
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
|
||||
}
|
||||
let normalized_pivot = graph_modification_utils::get_pivot(first, &document.document_legacy).unwrap_or(DVec2::splat(0.5));
|
||||
self.normalized_pivot = normalized_pivot;
|
||||
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
|
||||
} else {
|
||||
// If more than one layer is selected we use the AABB with the mean of the pivots
|
||||
let xy_summation = document
|
||||
.document_legacy
|
||||
.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)
|
||||
.unwrap_or_default();
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
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::graph_modification_utils;
|
||||
|
||||
use document_legacy::layers::layer_layer::CachedOutputData;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeInput, NodeNetwork};
|
||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graph_craft::document::{DocumentNodeMetadata, NodeInput};
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::uuid::generate_uuid;
|
||||
use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
use graphene_core::Color;
|
||||
|
||||
@@ -289,20 +289,23 @@ impl ToolTransition for BrushTool {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct BrushToolData {
|
||||
strokes: Vec<BrushStroke>,
|
||||
layer_path: Vec<LayerId>,
|
||||
layer: Option<LayerNodeIdentifier>,
|
||||
transform: DAffine2,
|
||||
}
|
||||
|
||||
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;
|
||||
if document.selected_layers().count() != 1 {
|
||||
|
||||
if document.metadata().selected_layers().count() != 1 {
|
||||
return None;
|
||||
}
|
||||
self.layer_path = document.selected_layers().next()?.to_vec();
|
||||
let layer = document.document_legacy.layer(&self.layer_path).ok().and_then(|layer| layer.as_layer().ok())?;
|
||||
let network = &layer.network;
|
||||
for (node, _node_id) in network.primary_flow() {
|
||||
let Some(layer) = document.metadata().selected_layers().next() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
self.layer = Some(layer);
|
||||
for (node, node_id) in document.network().primary_flow_from_node(Some(layer.to_node())) {
|
||||
if node.name == "Brush" {
|
||||
let points_input = node.inputs.get(2)?;
|
||||
let NodeInput::Value {
|
||||
@@ -314,21 +317,22 @@ impl BrushToolData {
|
||||
};
|
||||
self.strokes = strokes.clone();
|
||||
|
||||
return Some(&self.layer_path);
|
||||
return Some(layer);
|
||||
} 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;
|
||||
|
||||
matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)).then_some(&self.layer_path)
|
||||
None
|
||||
}
|
||||
|
||||
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();
|
||||
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, ..
|
||||
} = 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 {
|
||||
return self;
|
||||
};
|
||||
match (self, event) {
|
||||
(BrushToolFsmState::Ready, BrushToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
let layer_path = tool_data.load_existing_strokes(document);
|
||||
let new_layer = layer_path.is_none();
|
||||
if new_layer {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
tool_data.layer_path = document.get_path_for_new_layer();
|
||||
}
|
||||
let layer_position = tool_data.transform.inverse().transform_point2(document_position);
|
||||
let loaded_layer = tool_data.load_existing_strokes(document);
|
||||
|
||||
let layer = loaded_layer.unwrap_or_else(|| new_brush_layer(document, responses));
|
||||
tool_data.layer = Some(layer);
|
||||
|
||||
let parent = layer.parent(document.metadata()).unwrap_or_default();
|
||||
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: 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
|
||||
.max((tool_data.transform.matrix2 * glam::DVec2::X).length())
|
||||
.max((tool_data.transform.matrix2 * glam::DVec2::Y).length());
|
||||
.max((layer_document_scale.matrix2 * glam::DVec2::X).length())
|
||||
.max((layer_document_scale.matrix2 * glam::DVec2::Y).length());
|
||||
|
||||
// Start a new stroke with a single sample
|
||||
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);
|
||||
|
||||
BrushToolFsmState::Drawing
|
||||
}
|
||||
|
||||
(BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => {
|
||||
if let Some(stroke) = tool_data.strokes.last_mut() {
|
||||
stroke.trace.push(BrushInputSample { position: layer_position })
|
||||
if let Some(layer) = tool_data.layer {
|
||||
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);
|
||||
|
||||
@@ -438,11 +445,24 @@ impl Fsm for BrushToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_brush_render(_tool_options: &BrushOptions, data: &BrushToolData, responses: &mut VecDeque<Message>) {
|
||||
let mut network = NodeNetwork::default();
|
||||
let output_node = network.push_output_node();
|
||||
if let Some(node) = network.nodes.get_mut(&output_node) {
|
||||
node.inputs.push(NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true))
|
||||
}
|
||||
graph_modification_utils::new_custom_layer(network, data.layer_path.clone(), responses);
|
||||
fn new_brush_layer(document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
let brush_node = resolve_document_node_type("Brush")
|
||||
.expect("Brush node does not exist")
|
||||
.to_document_node_default_inputs([], DocumentNodeMetadata::position((-8, 0)));
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::consts::SLOWING_DIVISOR;
|
||||
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::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::{ToolData, ToolType};
|
||||
|
||||
@@ -23,10 +24,12 @@ pub struct TransformLayerMessageHandler {
|
||||
original_transforms: OriginalTransforms,
|
||||
pivot: DVec2,
|
||||
}
|
||||
|
||||
impl TransformLayerMessageHandler {
|
||||
pub fn is_transforming(&self) -> bool {
|
||||
self.transform_operation != TransformOperation::None
|
||||
}
|
||||
|
||||
pub fn hints(&self, responses: &mut VecDeque<Message>) {
|
||||
let axis_constraint = match self.transform_operation {
|
||||
TransformOperation::Grabbing(grabbing) => grabbing.constraint,
|
||||
@@ -40,18 +43,17 @@ impl TransformLayerMessageHandler {
|
||||
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState);
|
||||
impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformLayerMessageHandler {
|
||||
#[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::*;
|
||||
|
||||
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_n = document.metadata().selected_layers().collect::<Vec<_>>();
|
||||
let selected_layers = document.metadata().selected_layers().collect::<Vec<_>>();
|
||||
|
||||
let mut selected = Selected::new(
|
||||
&mut self.original_transforms,
|
||||
&mut self.pivot,
|
||||
&selected_layers_n,
|
||||
&selected_layers,
|
||||
responses,
|
||||
&document.document_legacy,
|
||||
Some(shape_editor),
|
||||
@@ -65,33 +67,26 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
}
|
||||
|
||||
if using_path_tool {
|
||||
if let Ok(layer) = document.document_legacy.layer(selected_layers[0]) {
|
||||
if let Some(vector_data) = layer.as_vector_data() {
|
||||
*selected.original_transforms = OriginalTransforms::default();
|
||||
let viewspace = &mut document.document_legacy.generate_transform_relative_to_viewport(selected_layers[0]).ok().unwrap_or_default();
|
||||
if let Some(subpaths) = selected_layers.first().and_then(|&layer| graph_modification_utils::get_subpaths(layer, &document.document_legacy)) {
|
||||
*selected.original_transforms = OriginalTransforms::default();
|
||||
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
|
||||
|
||||
let mut point_count: usize = 0;
|
||||
let count_point = |position| {
|
||||
point_count += 1;
|
||||
position
|
||||
};
|
||||
let get_location = |point: &ManipulatorPointId| {
|
||||
vector_data
|
||||
.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();
|
||||
let mut point_count: usize = 0;
|
||||
let get_location = |point: &ManipulatorPointId| {
|
||||
graph_modification_utils::get_manipulator_from_id(subpaths, 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 {
|
||||
*selected.pivot = selected.mean_average_of_pivots();
|
||||
}
|
||||
|
||||
*mouse_position = ipp.mouse.position;
|
||||
*start_mouse = ipp.mouse.position;
|
||||
*mouse_position = input.mouse.position;
|
||||
*start_mouse = input.mouse.position;
|
||||
selected.original_transforms.clear();
|
||||
};
|
||||
|
||||
@@ -106,9 +101,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
for layer_path in document.selected_layers() {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path: layer_path.to_vec() });
|
||||
}
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
BeginGrab => {
|
||||
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),
|
||||
ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap),
|
||||
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 {
|
||||
self.snap = new_snap;
|
||||
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() {
|
||||
let delta_pos = ipp.mouse.position - self.mouse_position;
|
||||
let delta_pos = input.mouse.position - self.mouse_position;
|
||||
|
||||
match self.transform_operation {
|
||||
TransformOperation::None => unreachable!(),
|
||||
@@ -201,7 +194,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
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 change = if self.slow { angle / SLOWING_DIVISOR } else { angle };
|
||||
@@ -212,7 +205,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
TransformOperation::Scaling(scale) => {
|
||||
let change = {
|
||||
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();
|
||||
|
||||
(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 => {
|
||||
let target_layers = document.metadata().selected_layers().collect();
|
||||
|
||||
Reference in New Issue
Block a user