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-26 20:54:06 -08:00
committed by GitHub
co-authored by Keavon Chambers
parent 5ee79031ab
commit b2ca643e6e
26 changed files with 308 additions and 238 deletions
@@ -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();