Fix gradient tool

This commit is contained in:
0hypercube 2023-08-21 13:03:18 +01:00 committed by Keavon Chambers
parent 6173662a40
commit 9a39c4a0cc
8 changed files with 159 additions and 113 deletions

View File

@ -41,6 +41,14 @@ impl DocumentMetadata {
self.all_layers()
}
pub fn selected_layers_contains(&self, layer: LayerNodeIdentifier) -> bool {
self.selected_layers().any(|selected| selected == layer)
}
pub fn selected_visible_layers(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.all_layers()
}
/// Access the [`NodeRelations`] of a layer
fn get_relations(&self, node_identifier: LayerNodeIdentifier) -> Option<&NodeRelations> {
self.structure.get(&node_identifier)
@ -90,12 +98,27 @@ impl DocumentMetadata {
.filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets)))
.find(|(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_from_document(*layer))))
}
/// Get the bounding box of the click target of the specified layer in the specified transform space
pub fn bounding_box(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
self.click_targets
.get(&layer)?
.iter()
.filter_map(|click_target| click_target.subpath.bounding_box_with_transform(transform))
.reduce(Quad::combine_bounds)
}
}
/// Id of a layer node
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LayerNodeIdentifier(NonZeroU64);
impl Default for LayerNodeIdentifier {
fn default() -> Self {
Self::ROOT
}
}
impl LayerNodeIdentifier {
const ROOT: Self = LayerNodeIdentifier::new_unchecked(0);
@ -283,6 +306,10 @@ impl LayerNodeIdentifier {
document_metadata.structure.remove(&node);
}
}
pub fn exists(&self, document_metadata: &DocumentMetadata) -> bool {
document_metadata.get_relations(*self).is_some()
}
}
impl From<NodeId> for LayerNodeIdentifier {

View File

@ -2,9 +2,10 @@ use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*;
use bezier_rs::{ManipulatorGroup, Subpath};
use document_legacy::{document_metadata::LayerNodeIdentifier, LayerId, Operation};
use document_legacy::{document::Document, document_metadata::LayerNodeIdentifier, LayerId, Operation};
use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::style::{FillType, Gradient};
use glam::DAffine2;
use std::collections::VecDeque;
@ -17,6 +18,7 @@ pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path:
});
}
/// Create a legacy node graph frame TODO: remove
pub fn new_custom_layer(network: NodeNetwork, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
responses.add(DocumentMessage::DeselectAllLayers);
responses.add(Operation::AddFrame {
@ -28,6 +30,7 @@ pub fn new_custom_layer(network: NodeNetwork, layer_path: Vec<LayerId>, response
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
}
/// Batch set all of the manipulator groups to mirror on a specific layer
pub fn set_manipulator_mirror_angle(manipulator_groups: &[ManipulatorGroup<ManipulatorGroupId>], layer_path: &[u64], mirror_angle: bool, responses: &mut VecDeque<Message>) {
for manipulator_group in manipulator_groups {
responses.add(GraphOperationMessage::Vector {
@ -40,6 +43,64 @@ pub fn set_manipulator_mirror_angle(manipulator_groups: &[ManipulatorGroup<Manip
}
}
/// Locate the subpaths from the shape nodes of a particular layer
pub fn get_subpaths(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec<Subpath<ManipulatorGroupId>>> {
if let TaggedValue::Subpaths(subpaths) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 0)? {
Some(subpaths)
} else {
None
}
}
/// Get the currently mirrored handles for a particular layer from the shape node
pub fn get_mirror_handles(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec<ManipulatorGroupId>> {
if let TaggedValue::ManipulatorGroupIds(mirror_handles) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 1)? {
Some(mirror_handles)
} else {
None
}
}
/// Get the current gradient of a layer from the closest fill node
pub fn get_gradient(layer: LayerNodeIdentifier, document: &Document) -> Option<Gradient> {
let inputs = NodeGraphLayer::new(layer, document)?.find_node_inputs("Fill")?;
let TaggedValue::FillType(FillType::Gradient) = inputs.get(1)?.as_value()? else {
return None;
};
let TaggedValue::GradientType(gradient_type) = inputs.get(3)?.as_value()? else {
return None;
};
let TaggedValue::DVec2(start) = inputs.get(4)?.as_value()? else {
return None;
};
let TaggedValue::DVec2(end) = inputs.get(5)?.as_value()? else {
return None;
};
let TaggedValue::DAffine2(transform) = inputs.get(6)?.as_value()? else {
return None;
};
let TaggedValue::GradientPositions(positions) = inputs.get(7)?.as_value()? else {
return None;
};
Some(Gradient {
start: start.clone(),
end: end.clone(),
transform: transform.clone(),
positions: positions.clone(),
gradient_type: gradient_type.clone(),
})
}
/// Convert subpaths to an iterator of manipulator groups
pub fn get_manipulator_groups(subpaths: &[Subpath<ManipulatorGroupId>]) -> impl Iterator<Item = &bezier_rs::ManipulatorGroup<ManipulatorGroupId>> + DoubleEndedIterator {
subpaths.iter().flat_map(|subpath| subpath.manipulator_groups())
}
/// Find a manipulator group with a specific id from several subpaths
pub fn get_manipulator_from_id(subpaths: &[Subpath<ManipulatorGroupId>], id: ManipulatorGroupId) -> Option<&bezier_rs::ManipulatorGroup<ManipulatorGroupId>> {
subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id))
}
/// An immutable reference to a layer within the document node graph for easy access.
pub struct NodeGraphLayer<'a> {
node_graph: &'a NodeNetwork,
@ -84,18 +145,18 @@ impl<'a> NodeGraphLayer<'a> {
self.node_graph.primary_flow_from_opt(Some(self.layer_node))
}
/// Find a specific input of a node within the layer's primary flow
pub fn find_input(&self, node_name: &str, index: usize) -> Option<&'a TaggedValue> {
/// Find all of the inputs of a specific node within the layer's primary flow
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
for (node, _id) in self.primary_layer_flow() {
if node.name == node_name {
let subpaths_input = node.inputs.get(index)?;
let NodeInput::Value { tagged_value, .. } = subpaths_input else {
continue;
};
return Some(tagged_value);
return Some(&node.inputs);
}
}
None
}
/// Find a specific input of a node within the layer's primary flow
pub fn find_input(&self, node_name: &str, index: usize) -> Option<&'a TaggedValue> {
self.find_node_inputs(node_name)?.get(index)?.as_value()
}
}

View File

@ -3,7 +3,7 @@ use crate::application::generate_uuid;
use crate::consts::VIEWPORT_GRID_ROUNDING_BIAS;
use crate::consts::{COLOR_ACCENT, HIDE_HANDLE_DISTANCE, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT};
use crate::messages::prelude::*;
use crate::messages::tool::tool_messages::pen_tool::{get_manipulator_groups, get_subpaths};
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_groups, get_subpaths};
use bezier_rs::ManipulatorGroup;
use document_legacy::document::Document;

View File

@ -1,7 +1,7 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*;
use crate::messages::tool::tool_messages::pen_tool::{get_manipulator_from_id, get_manipulator_groups, get_mirror_handles, get_subpaths};
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_from_id, get_manipulator_groups, get_mirror_handles, get_subpaths};
use bezier_rs::{Bezier, ManipulatorGroup, TValue};
use document_legacy::document::Document;

View File

@ -1,11 +1,10 @@
use super::tool_prelude::*;
use crate::application::generate_uuid;
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD};
use crate::messages::tool::common_functionality::graph_modification_utils::get_gradient;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use document_legacy::intersection::Quad;
use document_legacy::layers::layer_info::Layer;
use document_legacy::layers::layer_layer::CachedOutputData;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::style::{Fill, Gradient, GradientType, PathStyle, RenderData, Stroke};
use document_legacy::LayerId;
use document_legacy::Operation;
@ -115,12 +114,12 @@ enum GradientToolFsmState {
Drawing,
}
/// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space)
fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, render_data: &RenderData) -> DAffine2 {
let bounds = layer.aabb_for_transform(DAffine2::IDENTITY, render_data).unwrap();
/// Computes the transform from gradient space to viewport space (where gradient space is 0..1)
fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
let bounds = document.document_legacy.metadata.bounding_box(layer, DAffine2::IDENTITY).unwrap();
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
let multiplied = document.document_legacy.multiply_transforms(path).unwrap();
let multiplied = document.document_legacy.metadata.transform_from_viewport(layer);
multiplied * bound_transform
}
@ -131,7 +130,7 @@ pub struct GradientOverlay {
pub handles: [Vec<LayerId>; 2],
pub line: Vec<LayerId>,
pub steps: Vec<Vec<LayerId>>,
path: Vec<LayerId>,
layer: LayerNodeIdentifier,
transform: DAffine2,
gradient: Gradient,
}
@ -174,17 +173,9 @@ impl GradientOverlay {
path
}
pub fn new(
fill: &Gradient,
dragging: Option<GradientDragTarget>,
path: &[LayerId],
layer: &Layer,
document: &DocumentMessageHandler,
responses: &mut VecDeque<Message>,
render_data: &RenderData,
) -> Self {
let transform = gradient_space_transform(path, layer, document, render_data);
let Gradient { start, end, positions, .. } = fill;
pub fn new(gradient: Gradient, dragging: Option<GradientDragTarget>, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Self {
let transform = gradient_space_transform(layer, document);
let Gradient { start, end, positions, .. } = &gradient;
let [start, end] = [transform.transform_point2(*start), transform.transform_point2(*end)];
let line = Self::generate_overlay_line(start, end, responses);
@ -197,14 +188,11 @@ impl GradientOverlay {
let create_step = |(index, pos)| Self::generate_overlay_handle(start.lerp(end, pos), responses, dragging == Some(GradientDragTarget::Step(index)));
let steps = positions.iter().map(|(pos, _)| *pos).enumerate().filter(not_at_end).map(create_step).collect();
let path = path.to_vec();
let gradient = fill.clone();
Self {
handles,
steps,
line,
path,
layer,
transform,
gradient,
}
@ -240,17 +228,17 @@ pub enum GradientDragTarget {
/// Contains information about the selected gradient handle
#[derive(Clone, Debug, Default)]
struct SelectedGradient {
path: Vec<LayerId>,
layer: LayerNodeIdentifier,
transform: DAffine2,
gradient: Gradient,
dragging: GradientDragTarget,
}
impl SelectedGradient {
pub fn new(gradient: Gradient, path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, render_data: &RenderData) -> Self {
let transform = gradient_space_transform(path, layer, document, render_data);
pub fn new(gradient: Gradient, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self {
let transform = gradient_space_transform(layer, document);
Self {
path: path.to_vec(),
layer,
transform,
gradient,
dragging: GradientDragTarget::End,
@ -258,23 +246,23 @@ impl SelectedGradient {
}
/// Update the selected gradient, checking for removal or change of gradient.
pub fn update(gradient: &mut Option<Self>, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) {
pub fn update(gradient: &mut Option<Self>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
let Some(inner_gradient) = gradient else {
return;
};
// Clear the gradient if layer deleted
let Ok(layer) = document.document_legacy.layer(&inner_gradient.path) else {
if !inner_gradient.layer.exists(&document.document_legacy.metadata) {
responses.add(ToolMessage::RefreshToolOptions);
*gradient = None;
return;
};
// Update transform
inner_gradient.transform = gradient_space_transform(&inner_gradient.path, layer, document, render_data);
inner_gradient.transform = gradient_space_transform(inner_gradient.layer, document);
// Clear if no longer a gradient
let Some(gradient) = layer.style().ok().and_then(|style| style.fill().as_gradient()) else {
let Some(gradient) = get_gradient(inner_gradient.layer, &document.document_legacy) else {
responses.add(ToolMessage::RefreshToolOptions);
*gradient = None;
return;
@ -340,7 +328,7 @@ impl SelectedGradient {
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
self.gradient.transform = self.transform;
let fill = Fill::Gradient(self.gradient.clone());
let layer = self.path.clone();
let layer = self.layer.to_path();
responses.add(GraphOperationMessage::FillSet { layer, fill });
}
}
@ -400,20 +388,16 @@ impl Fsm for GradientToolFsmState {
}
if self != GradientToolFsmState::Drawing {
SelectedGradient::update(&mut tool_data.selected_gradient, document, render_data, responses);
SelectedGradient::update(&mut tool_data.selected_gradient, document, responses);
}
for path in document.selected_visible_layers() {
let layer = document.document_legacy.layer(path).unwrap();
if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) {
for layer in document.document_legacy.metadata.selected_visible_layers() {
if let Some(gradient) = get_gradient(layer, &document.document_legacy) {
let dragging = tool_data
.selected_gradient
.as_ref()
.and_then(|selected| if selected.path == path { Some(selected.dragging) } else { None });
tool_data
.gradient_overlays
.push(GradientOverlay::new(gradient, dragging, path, layer, document, responses, render_data))
.and_then(|selected| if selected.layer == layer { Some(selected.dragging) } else { None });
tool_data.gradient_overlays.push(GradientOverlay::new(gradient, dragging, layer, document, responses))
}
}
@ -439,7 +423,7 @@ impl Fsm for GradientToolFsmState {
// The gradient has only one point and so should become a fill
if selected_gradient.gradient.positions.len() == 1 {
let fill = Fill::Solid(selected_gradient.gradient.positions[0].1.unwrap_or(Color::BLACK));
let layer = selected_gradient.path.clone();
let layer = selected_gradient.layer.to_path();
responses.add(GraphOperationMessage::FillSet { layer, fill });
return self;
}
@ -481,18 +465,15 @@ impl Fsm for GradientToolFsmState {
if let Some(index) = gradient.insert_stop(mouse, overlay.transform) {
document.backup_nonmut(responses);
let layer = document.document_legacy.layer(&overlay.path);
if let Ok(layer) = layer {
let mut selected_gradient = SelectedGradient::new(gradient, &overlay.path, layer, document, render_data);
let mut selected_gradient = SelectedGradient::new(gradient, overlay.layer, document);
// Select the new point
selected_gradient.dragging = GradientDragTarget::Step(index);
// Select the new point
selected_gradient.dragging = GradientDragTarget::Step(index);
// Update the layer fill
selected_gradient.render_gradient(responses);
// Update the layer fill
selected_gradient.render_gradient(responses);
tool_data.selected_gradient = Some(selected_gradient);
}
tool_data.selected_gradient = Some(selected_gradient);
break;
}
@ -516,7 +497,7 @@ impl Fsm for GradientToolFsmState {
if pos.distance_squared(mouse) < tolerance {
dragging = true;
tool_data.selected_gradient = Some(SelectedGradient {
path: overlay.path.clone(),
layer: overlay.layer.clone(),
transform: overlay.transform,
gradient: overlay.gradient.clone(),
dragging: GradientDragTarget::Step(index),
@ -533,7 +514,7 @@ impl Fsm for GradientToolFsmState {
dragging = true;
start_snap(&mut tool_data.snap_manager, document, input, render_data);
tool_data.selected_gradient = Some(SelectedGradient {
path: overlay.path.clone(),
layer: overlay.layer.clone(),
transform: overlay.transform,
gradient: overlay.gradient.clone(),
dragging: dragging_target,
@ -545,34 +526,30 @@ impl Fsm for GradientToolFsmState {
document.backup_nonmut(responses);
GradientToolFsmState::Drawing
} else {
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let intersection = document.document_legacy.intersects_quad_root(quad, render_data).pop();
let selected_layer = document.document_legacy.metadata.click(input.mouse.position);
// the intersection is the layer where the gradient is being applied
if let Some(intersection) = intersection {
let is_bitmap = document
.document_legacy
.layer(&intersection)
.ok()
.and_then(|layer| layer.as_layer().ok())
.map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
if is_bitmap {
return self;
}
// Apply the gradient to the selected layer
if let Some((layer, _)) = selected_layer {
// let is_bitmap = document
// .document_legacy
// .layer(&layer)
// .ok()
// .and_then(|layer| layer.as_layer().ok())
// .map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
// if is_bitmap {
// return self;
// }
if !document.selected_layers_contains(&intersection) {
let replacement_selected_layers = vec![intersection.clone()];
if !document.document_legacy.metadata.selected_layers_contains(layer) {
let replacement_selected_layers = vec![layer.to_path()];
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
}
let layer = document.document_legacy.layer(&intersection).unwrap();
responses.add(DocumentMessage::StartTransaction);
// Use the already existing gradient if it exists
let gradient = if let Some(gradient) = layer.style().ok().map(|style| style.fill()).and_then(|fill| fill.as_gradient()) {
let gradient = if let Some(gradient) = get_gradient(layer, &document.document_legacy) {
gradient.clone()
} else {
// Generate a new gradient
@ -586,7 +563,7 @@ impl Fsm for GradientToolFsmState {
tool_options.gradient_type,
)
};
let selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document, render_data).with_gradient_start(input.mouse.position);
let selected_gradient = SelectedGradient::new(gradient, layer, document).with_gradient_start(input.mouse.position);
tool_data.selected_gradient = Some(selected_gradient);

View File

@ -3,14 +3,11 @@ use crate::consts::LINE_ROTATE_SNAP_ANGLE;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::tool_messages::pen_tool::graph_modification_utils::NodeGraphLayer;
use bezier_rs::Subpath;
use document_legacy::document::Document;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::LayerId;
use graph_craft::document::value::TaggedValue;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::{ManipulatorPointId, SelectedType};
@ -767,26 +764,3 @@ fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64)
best
}
pub fn get_subpaths(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec<Subpath<ManipulatorGroupId>>> {
if let TaggedValue::Subpaths(subpaths) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 0)? {
Some(subpaths)
} else {
None
}
}
pub fn get_mirror_handles(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec<ManipulatorGroupId>> {
if let TaggedValue::ManipulatorGroupIds(mirror_handles) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 1)? {
Some(mirror_handles)
} else {
None
}
}
pub fn get_manipulator_groups(subpaths: &[Subpath<ManipulatorGroupId>]) -> impl Iterator<Item = &bezier_rs::ManipulatorGroup<ManipulatorGroupId>> + DoubleEndedIterator {
subpaths.iter().flat_map(|subpath| subpath.manipulator_groups())
}
pub fn get_manipulator_from_id(subpaths: &[Subpath<ManipulatorGroupId>], id: ManipulatorGroupId) -> Option<&bezier_rs::ManipulatorGroup<ManipulatorGroupId>> {
subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id))
}

View File

@ -5,7 +5,7 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use document_legacy::{LayerId, Operation};
use document_legacy::LayerId;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::Color;

View File

@ -239,6 +239,13 @@ impl NodeInput {
NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"),
}
}
pub fn as_value(&self) -> Option<&TaggedValue> {
if let NodeInput::Value { tagged_value, .. } = self {
Some(tagged_value)
} else {
None
}
}
}
#[derive(Clone, Debug, PartialEq, Hash, DynAny)]