mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 08:08:12 +08:00
Refactor the 'Fill' and 'Stroke' nodes to set "fill" and "stroke" attributes for paints (#4257)
* Allow using any graphics type for fill * Adapt gradient/fill property panels and tools to handle List<T> * Make the initial gradient transform covers the target's bounding box * Introduce AnyGraphicListDyn to avoid combinatorial explosion * Allow using any graphics type for stroke paint * Add FIll node migration * Add `for_each_vector_list_mut` instead of `set_paint_attribute` * Adapt paint flow to read and write attributes instead of legacy Fill/Stroke.color * Fix responsibilities of paint related node input setters * Fix Morph by storing List<Graphic> for attributes rather than the concrete types Store fill/stroke paints as List<Graphic> so Color/Gradient transitions do not hit set_attribute_value_dyn's type-mismatch fallback to default paint. * Preserve paint attributes in vector editing ops * Enhance the clarity between direct and chained fill gradients * Update demo arts * Consolidate Fill node gradient appearance inputs * Fix after the cubic review * Revert "Consolidate Fill node gradient appearance inputs" This reverts commit 9622feb20196e2c4da99e98ca95dcc2e34c2c98e. * Replace AnyGraphicListDyn with generic paint connectors on the Fill and Stroke nodes * Canonicalize paint attribute storage to List<Graphic> with a single write helper * Fix Solidify Stroke missing fills stored in the legacy style * Fix Solidify Stroke producing invisible strokes for legacy-only stroke colors * Fix the initial gradient transform ignoring the bounding box's vertical extent * Step paint at the morph midpoint instead of dropping it for unmixable pairings * Remove migration-stage comments * Clarify the initial gradient transform helper's doc and name * Correct the bake_paint_transforms doc and prune dead tolerance arms * Delete the unused Gradient::lerp * Fix a comment typo * Thread network paths through the legacy gradient bake so nested fills migrate * Restore the legacy fill fallback in Expand Fill and Stroke * Read gradient stops from the node's own input in the Fill properties panel * Use the transform input constant instead of a hardcoded index * Remove the unreachable wired color fallback in the Fill properties solid branch * Narrow the fill overlay redraw check to actual fill inputs * Coalesce the fill setter's graph runs into a single dispatch * Position the Gradient Value node inserted for gradient stops * Bake backup gradient placement during migration * Persist pending gradient bakes so unfinished migrations retry on reopen * Migrate the backup gradient's type and spread method * Harden the gradient-migration pass against document switches and stale bakes * Keep the Fill properties UI for layerless and nested Fill nodes * Leave a wired gradient transform input connected instead of overwriting it * Refuse to start a gradient chain ahead of existing layer content * Decode a Fill node's gradient through one shared reader * Nudge a degenerate bounding box so the Fill gradient transform stays invertible * Broadcast Fill and Stroke paint with a single attribute-column pass * Fix Morph stepping the target's stroke in near the source instead of the target * Tidy conventions: clippy get_first, comment periods, sentence-case test messages * Reattach the gradient orientation doc to its function * Re-save demo artwork --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
f82b0a8fca
commit
9f9899cfd0
@@ -5,9 +5,8 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo
|
||||
use crate::messages::prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::NodeInputDecleration;
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::raster::BlendMode;
|
||||
@@ -15,8 +14,9 @@ use graphene_std::raster_types::{CPU, GPU, Image, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::style::{Fill, FillChoice, Gradient, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::vector::{GradientStops, PointId, SegmentId, VectorModificationType};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, Gradient, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, initial_gradient_transform_for_bounding_box};
|
||||
use graphene_std::vector::{GradientSpreadMethod, GradientStops, GradientType, PointId, SegmentId, VectorModificationType};
|
||||
use graphene_std::{Color, Graphic};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Returns the ID of the first Spline node in the horizontal flow which is not followed by a `Path` node, or `None` if none exists.
|
||||
@@ -271,11 +271,18 @@ pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeN
|
||||
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * center)
|
||||
}
|
||||
|
||||
/// Get the closest Fill node's ID to the provided layer, if any.
|
||||
pub fn get_fill_node_id_with_direct_fill_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
||||
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||
matches!(fill_node.inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX)?, NodeInput::Value { .. }).then_some(fill_node_id)
|
||||
}
|
||||
|
||||
/// Determine the input connector where the gradient chain enters the layer.
|
||||
/// Returns Fill's fill input if the layer has a "Fill" node, otherwise returns the layer's content input.
|
||||
pub fn gradient_chain_target_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> InputConnector {
|
||||
if let Some(fill_node_id) = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)) {
|
||||
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<Fill>::INDEX)
|
||||
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX)
|
||||
} else {
|
||||
InputConnector::node(layer.to_node(), 1)
|
||||
}
|
||||
@@ -292,89 +299,29 @@ pub fn get_upstream_gradient_value_node_id(layer: LayerNodeIdentifier, network_i
|
||||
.find(|node_id| network_interface.reference(node_id, &[]).as_ref() == Some(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER)))
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
/// Get the layer's "Fill" node itself (whose `fill` input holds the paint value), not the node feeding that input.
|
||||
pub fn get_fill_node_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))
|
||||
}
|
||||
|
||||
/// Get the node connected to Fill's fill input, if any.
|
||||
pub fn get_fill_input_node_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
|
||||
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
||||
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||
let NodeInput::Node { node_id, .. } = fill_node.inputs.get(graphene_std::vector::fill::FillInput::<Fill>::INDEX)? else {
|
||||
let NodeInput::Node { node_id, .. } = fill_node.inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX)? else {
|
||||
return None;
|
||||
};
|
||||
Some(*node_id)
|
||||
}
|
||||
|
||||
/// Get the current gradient of a layer from the closest "Fill" node.
|
||||
pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
|
||||
let fill_index = 1;
|
||||
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
||||
let TaggedValue::Fill(Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(gradient.clone())
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
/// The legacy bounding-box-relative gradient (`absolute == false`) in a "Fill" node's active `fill` input, if any.
|
||||
fn legacy_active_gradient_in_fill_node(fill_node_id: NodeId, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
|
||||
let node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||
let TaggedValue::Fill(Fill::Gradient(gradient)) = node.inputs.get(graphene_std::vector::fill::FillInput::<Fill>::INDEX)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
(!gradient.absolute).then(|| gradient.clone())
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
/// The legacy bounding-box-relative gradient (`absolute == false`) stashed in a "Fill" node's `_backup_gradient` input, if any.
|
||||
/// The backup is inert until the fill is toggled back to a gradient, at which point it becomes the active fill, so it needs converting too.
|
||||
fn legacy_backup_gradient_in_fill_node(fill_node_id: NodeId, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
|
||||
let node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||
let TaggedValue::FillGradient(gradient) = node.inputs.get(graphene_std::vector::fill::BackupGradientInput::INDEX)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
(!gradient.absolute).then(|| gradient.clone())
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
/// Convert a "Fill" node's legacy gradients (the active `fill` and/or the stashed `_backup_gradient`) to absolute space using
|
||||
/// the geometry's measured bounding box, writing each back in place. The active fill is written as a `Fill`, the backup as a bare `FillGradient`.
|
||||
pub fn migrate_fill_node_gradients_to_absolute(fill_node_id: NodeId, network_interface: &mut NodeNetworkInterface, bounding_box: DAffine2, layer_transform: DAffine2) {
|
||||
if let Some(gradient) = legacy_active_gradient_in_fill_node(fill_node_id, network_interface) {
|
||||
let absolute = gradient.to_absolute(bounding_box, layer_transform);
|
||||
let input = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<Fill>::INDEX);
|
||||
network_interface.set_input(&input, NodeInput::value(TaggedValue::Fill(Fill::Gradient(absolute)), false), &[]);
|
||||
}
|
||||
if let Some(gradient) = legacy_backup_gradient_in_fill_node(fill_node_id, network_interface) {
|
||||
let absolute = gradient.to_absolute(bounding_box, layer_transform);
|
||||
let input = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput::INDEX);
|
||||
network_interface.set_input(&input, NodeInput::value(TaggedValue::FillGradient(absolute), false), &[]);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
/// Find every root-network "Fill" node holding a legacy bounding-box-relative gradient, either as its active `fill` or as its `_backup_gradient`.
|
||||
///
|
||||
/// Scans the document network structurally instead of walking each layer's primary flow, so it also catches fills on
|
||||
/// secondary inputs and in hidden, disabled, or orphaned branches. Fills nested inside subgraph node networks are skipped.
|
||||
pub fn legacy_gradient_fill_nodes(network_interface: &NodeNetworkInterface) -> Vec<NodeId> {
|
||||
let fill_identifier = DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER);
|
||||
network_interface
|
||||
.document_network()
|
||||
.nodes
|
||||
.keys()
|
||||
.copied()
|
||||
.filter(|node_id| network_interface.reference(node_id, &[]).as_ref() == Some(&fill_identifier))
|
||||
.filter(|&node_id| legacy_active_gradient_in_fill_node(node_id, network_interface).is_some() || legacy_backup_gradient_in_fill_node(node_id, network_interface).is_some())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the gradient stops of a layer, if any.
|
||||
pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientStops> {
|
||||
// Try to find the gradient stops value that is created by a Fill node first
|
||||
if let Some(fill_node_id) = get_fill_node_id_with_direct_fill_input(layer, network_interface) {
|
||||
return network_interface
|
||||
.document_network()
|
||||
.nodes
|
||||
.get(&fill_node_id)
|
||||
.and_then(|node| node.inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX))
|
||||
.and_then(|input| input.as_value())
|
||||
.and_then(|value| if let TaggedValue::Gradient(gradient) = value { Some(gradient.clone()) } else { None });
|
||||
}
|
||||
|
||||
let gradient_value_node = network_interface.document_network().nodes.get(&get_upstream_gradient_value_node_id(layer, network_interface)?)?;
|
||||
let TaggedValue::Gradient(stops) = gradient_value_node.inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else {
|
||||
return None;
|
||||
@@ -398,14 +345,6 @@ pub fn gradient_space_transform(layer: LayerNodeIdentifier, network_interface: &
|
||||
.unwrap_or(metadata.document_to_viewport);
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
// Only an existing legacy `Fill::Gradient` is in (0, 0)..(1, 1) bounding-box space; migrated and newly-created gradients are absolute (layer space).
|
||||
if get_gradient(layer, network_interface).is_some_and(|gradient| !gradient.absolute) {
|
||||
let bounds = metadata.nonzero_bounding_box(layer);
|
||||
let bound_transform = glam::DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
return metadata.transform_to_viewport(layer) * bound_transform;
|
||||
}
|
||||
|
||||
metadata.transform_to_viewport(layer)
|
||||
}
|
||||
|
||||
@@ -423,13 +362,11 @@ pub fn gradient_orientation_rightward(start: glam::DVec2, end: glam::DVec2, tran
|
||||
|
||||
/// Get the current fill of a layer from the closest "Fill" node.
|
||||
pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Color> {
|
||||
let fill_index = 1;
|
||||
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
||||
let &TaggedValue::Fill(Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
|
||||
let &TaggedValue::Color(color) = inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(color)
|
||||
color
|
||||
}
|
||||
|
||||
/// Get the current blend mode of a layer from the closest upstream "Blend Mode" node.
|
||||
@@ -686,16 +623,74 @@ pub fn set_stroke_weight_for_selected_layers(weight: f64, document: &DocumentMes
|
||||
}
|
||||
}
|
||||
|
||||
/// A Fill node's decoded gradient inputs, with the transform kept in its raw form (not yet baked into `start`/`end`).
|
||||
pub struct FillNodeGradient {
|
||||
pub stops: GradientStops,
|
||||
pub gradient_type: GradientType,
|
||||
pub spread_method: GradientSpreadMethod,
|
||||
pub transform: DAffine2,
|
||||
/// Whether the transform input holds a plain value (so it may be written to) rather than a wire.
|
||||
pub transform_is_value: bool,
|
||||
}
|
||||
|
||||
/// Decode a Fill node's gradient metadata inputs, resolving an unset transform to the default over `bounding_box`. Returns `None` when the fill input isn't a gradient value.
|
||||
pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOnce() -> [DVec2; 2]) -> Option<FillNodeGradient> {
|
||||
use graphene_std::vector::fill;
|
||||
|
||||
let TaggedValue::Gradient(stops) = fill_node.inputs.get(fill::FillInput::<List<Graphic>>::INDEX)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
let gradient_type = match fill_node.inputs.get(fill::GradientTypeInput::INDEX).and_then(|input| input.as_value()) {
|
||||
Some(&TaggedValue::GradientType(value)) => value,
|
||||
_ => GradientType::default(),
|
||||
};
|
||||
let spread_method = match fill_node.inputs.get(fill::SpreadMethodInput::INDEX).and_then(|input| input.as_value()) {
|
||||
Some(&TaggedValue::GradientSpreadMethod(value)) => value,
|
||||
_ => GradientSpreadMethod::default(),
|
||||
};
|
||||
let transform_input = fill_node.inputs.get(fill::TransformInput::INDEX).and_then(|input| input.as_value());
|
||||
let transform = match transform_input {
|
||||
Some(&TaggedValue::OptionalDAffine2(value)) => value.unwrap_or_else(|| initial_gradient_transform_for_bounding_box(bounding_box())),
|
||||
_ => DAffine2::IDENTITY,
|
||||
};
|
||||
|
||||
Some(FillNodeGradient {
|
||||
stops: stops.clone(),
|
||||
gradient_type,
|
||||
spread_method,
|
||||
transform,
|
||||
transform_is_value: transform_input.is_some(),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Update this to return Graphic once the legacy `Fill` enum has been eliminated
|
||||
/// Returns the `Fill` value from a layer's upstream Fill node.
|
||||
pub fn get_fill_value(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Fill> {
|
||||
let fill_index = graphene_std::vector::fill::FillInput::<Fill>::INDEX;
|
||||
let tagged = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER), fill_index)?;
|
||||
if let TaggedValue::Fill(fill) = tagged { Some(fill.clone()) } else { None }
|
||||
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
|
||||
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||
|
||||
match fill_node.inputs.get(graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX)?.as_value()? {
|
||||
&TaggedValue::Color(color) => Some(color.map_or(Fill::None, Fill::Solid)),
|
||||
TaggedValue::Gradient(_) => {
|
||||
let gradient = read_fill_node_gradient(fill_node, || network_interface.document_metadata().nonzero_bounding_box(layer))?;
|
||||
Some(Fill::Gradient(Gradient {
|
||||
stops: gradient.stops,
|
||||
gradient_type: gradient.gradient_type,
|
||||
spread_method: gradient.spread_method,
|
||||
start: gradient.transform.transform_point2(DVec2::ZERO),
|
||||
end: gradient.transform.transform_point2(DVec2::X),
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
absolute: true,
|
||||
transform: DAffine2::IDENTITY,
|
||||
}))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the stroke color from a layer's upstream Stroke node.
|
||||
pub fn get_stroke_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Option<Color>> {
|
||||
let color_index = graphene_std::vector::stroke::ColorInput::INDEX;
|
||||
let color_index = graphene_std::vector::stroke::PaintInput::<List<Graphic>>::INDEX;
|
||||
let tagged = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER), color_index)?;
|
||||
if let TaggedValue::Color(color) = tagged { Some(*color) } else { None }
|
||||
}
|
||||
@@ -816,7 +811,7 @@ pub fn set_stroke_color_for_selected_layers(color: Option<Color>, weight: f64, d
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
||||
for layer in layers {
|
||||
if let Some(node_id) = get_stroke_id(layer, &document.network_interface) {
|
||||
let input_index = graphene_std::vector::stroke::ColorInput::INDEX;
|
||||
let input_index = graphene_std::vector::stroke::PaintInput::<List<Graphic>>::INDEX;
|
||||
let value = TaggedValue::Color(color);
|
||||
responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value });
|
||||
} else {
|
||||
|
||||
@@ -203,16 +203,16 @@ impl Fsm for FillToolFsmState {
|
||||
mod test_fill {
|
||||
pub use crate::test_utils::test_prelude::*;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::vector::fill;
|
||||
use graphene_std::vector::style::Fill;
|
||||
|
||||
async fn get_fills(editor: &mut EditorTestUtils) -> Vec<Fill> {
|
||||
async fn get_fills(editor: &mut EditorTestUtils) -> Vec<List<Color>> {
|
||||
let instrumented = match editor.eval_graph().await {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
||||
};
|
||||
|
||||
instrumented.grab_all_input::<fill::FillInput<Fill>>(&editor.runtime).collect()
|
||||
instrumented.grab_all_input::<fill::FillInput<List<Color>>>(&editor.runtime).collect()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -242,7 +242,8 @@ mod test_fill {
|
||||
editor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::empty()).await;
|
||||
let fills = get_fills(&mut editor).await;
|
||||
assert_eq!(fills.len(), 1);
|
||||
assert_eq!(SRGBA8::from(fills[0].as_solid().unwrap()), SRGBA8::from(Color::GREEN));
|
||||
let color = fills.first().unwrap().element(0).expect("Color is stored in the list");
|
||||
assert_eq!(SRGBA8::from(*color), SRGBA8::from(Color::GREEN));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -254,6 +255,7 @@ mod test_fill {
|
||||
editor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::SHIFT).await;
|
||||
let fills = get_fills(&mut editor).await;
|
||||
assert_eq!(fills.len(), 1);
|
||||
assert_eq!(SRGBA8::from(fills[0].as_solid().unwrap()), SRGBA8::from(Color::YELLOW));
|
||||
let color = fills.first().unwrap().element(0).expect("Color is stored in the list");
|
||||
assert_eq!(SRGBA8::from(*color), SRGBA8::from(Color::YELLOW));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ use crate::messages::portfolio::document::overlays::utility_types::{GizmoEmphasi
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface};
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer, get_gradient_stops, gradient_chain_target_input};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{
|
||||
self, NodeGraphLayer, get_fill_node_id_with_direct_fill_input, get_gradient_stops, get_upstream_gradient_value_node_id, gradient_chain_target_input,
|
||||
};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::color::SRGBA8;
|
||||
@@ -355,7 +357,12 @@ fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessa
|
||||
// TODO: Remove this whole function once all gradients are stored via the modern `Gradient(GradientStops)` slot
|
||||
fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
|
||||
if let Some(stops) = get_gradient_stops(layer, network_interface) {
|
||||
// Try to construct a gradient out of a chain, which is directly connected to a layer
|
||||
// A Fill node holding a direct gradient value decodes through the shared reader
|
||||
if get_fill_node_id_with_direct_fill_input(layer, network_interface).is_some() {
|
||||
return graph_modification_utils::get_fill_value(layer, network_interface)?.as_gradient().cloned();
|
||||
}
|
||||
|
||||
// Then, try to construct a gradient out of a chain, which is directly connected to a Fill node or a layer
|
||||
let chain_state = read_gradient_chain_state(layer, network_interface);
|
||||
Some(Gradient {
|
||||
stops,
|
||||
@@ -368,8 +375,7 @@ fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
||||
transform: DAffine2::IDENTITY,
|
||||
})
|
||||
} else {
|
||||
// Try to find a legacy Fill::Gradient that is selected in a Fill node
|
||||
graph_modification_utils::get_gradient(layer, network_interface)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,7 +487,7 @@ struct SelectedGradient {
|
||||
dragging: GradientDragTarget,
|
||||
initial_gradient: Gradient,
|
||||
// TODO: Remove (and the matching branches in `render_gradient` / pointer-up) once `List<GradientStops>` replaces legacy `Fill::Gradient`
|
||||
is_gradient_list: bool,
|
||||
is_gradient_chain: bool,
|
||||
}
|
||||
|
||||
fn calculate_insertion(start: DVec2, end: DVec2, stops: &GradientStops, mouse: DVec2) -> Option<f64> {
|
||||
@@ -531,7 +537,7 @@ impl SelectedGradient {
|
||||
gradient: gradient.clone(),
|
||||
dragging: GradientDragTarget::End,
|
||||
initial_gradient: gradient,
|
||||
is_gradient_list: get_gradient_stops(layer, &document.network_interface).is_some(),
|
||||
is_gradient_chain: get_upstream_gradient_value_node_id(layer, &document.network_interface).is_some(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,7 +754,7 @@ impl SelectedGradient {
|
||||
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
|
||||
if let Some(layer) = self.layer {
|
||||
// TODO: Drop the `Fill::Gradient` branch when all gradients become `List<GradientStops>`
|
||||
if self.is_gradient_list {
|
||||
if self.is_gradient_chain {
|
||||
dispatch_gradient_writes(layer, &self.gradient, responses);
|
||||
} else {
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
@@ -1173,7 +1179,7 @@ impl Fsm for GradientToolFsmState {
|
||||
// The gradient has only one point and so should become a fill
|
||||
// TODO: Drop the legacy `Fill::Solid` branch when all gradients become `List<GradientStops>`
|
||||
if selected_gradient.gradient.stops.len() == 1 {
|
||||
if selected_gradient.is_gradient_list {
|
||||
if selected_gradient.is_gradient_chain {
|
||||
selected_gradient.render_gradient(responses);
|
||||
} else if let Some(layer) = selected_gradient.layer {
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
@@ -1270,7 +1276,7 @@ impl Fsm for GradientToolFsmState {
|
||||
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
|
||||
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
let is_gradient_list = get_gradient_stops(layer, &document.network_interface).is_some();
|
||||
let is_gradient_chain = get_upstream_gradient_value_node_id(layer, &document.network_interface).is_some();
|
||||
|
||||
// Check for dragging a midpoint diamond
|
||||
if drag_hint.is_none() {
|
||||
@@ -1298,7 +1304,7 @@ impl Fsm for GradientToolFsmState {
|
||||
gradient: gradient.clone(),
|
||||
dragging: GradientDragTarget::Midpoint(i),
|
||||
initial_gradient: gradient.clone(),
|
||||
is_gradient_list,
|
||||
is_gradient_chain,
|
||||
});
|
||||
|
||||
break;
|
||||
@@ -1339,7 +1345,7 @@ impl Fsm for GradientToolFsmState {
|
||||
gradient: gradient.clone(),
|
||||
dragging: drag_target,
|
||||
initial_gradient: gradient.clone(),
|
||||
is_gradient_list,
|
||||
is_gradient_chain,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1356,7 +1362,7 @@ impl Fsm for GradientToolFsmState {
|
||||
gradient: gradient.clone(),
|
||||
dragging: dragging_target,
|
||||
initial_gradient: gradient.clone(),
|
||||
is_gradient_list,
|
||||
is_gradient_chain,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1779,7 +1785,7 @@ fn apply_gradient_update(
|
||||
|
||||
// Only check for the gradient list once we know we'll write back, since this is a graph traversal per layer
|
||||
// TODO: Drop the `Fill::Gradient` branch when all gradients become `List<GradientStops>`
|
||||
if get_gradient_stops(layer, &context.document.network_interface).is_some() {
|
||||
if get_upstream_gradient_value_node_id(layer, &context.document.network_interface).is_some() {
|
||||
dispatch_gradient_writes(layer, &gradient, responses);
|
||||
} else {
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
@@ -1821,7 +1827,7 @@ fn apply_stops_update(data: &mut GradientToolData, context: &mut ToolActionMessa
|
||||
continue;
|
||||
}
|
||||
|
||||
if get_gradient_stops(layer, &context.document.network_interface).is_some() {
|
||||
if get_upstream_gradient_value_node_id(layer, &context.document.network_interface).is_some() {
|
||||
responses.add(GraphOperationMessage::GradientStopsSet { layer, stops: stops.clone() });
|
||||
updated_any_layer = true;
|
||||
} else if let Some(mut gradient) = get_gradient(layer, &context.document.network_interface) {
|
||||
@@ -1924,40 +1930,62 @@ mod test_gradient {
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_fill_node_id_with_direct_fill_input;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_upstream_gradient_value_node_id;
|
||||
pub use crate::test_utils::test_prelude::*;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::vector::style::{Fill, Gradient};
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::vector::style::{Gradient, GradientSpreadMethod};
|
||||
use graphene_std::vector::{GradientStop, GradientStops, fill};
|
||||
use graphene_std::{Graphic, NodeInputDecleration};
|
||||
|
||||
use super::gradient_space_transform;
|
||||
|
||||
async fn get_fills(editor: &mut EditorTestUtils) -> Vec<(Fill, DAffine2)> {
|
||||
let instrumented = match editor.eval_graph().await {
|
||||
Ok(instrumented) => instrumented,
|
||||
Err(e) => panic!("Failed to evaluate graph: {e}"),
|
||||
};
|
||||
|
||||
async fn get_gradients_from_fill(editor: &mut EditorTestUtils) -> Vec<(Gradient, DAffine2)> {
|
||||
let document = editor.active_document();
|
||||
let layers = document.metadata().all_layers();
|
||||
layers
|
||||
document
|
||||
.metadata()
|
||||
.all_layers()
|
||||
.filter_map(|layer| {
|
||||
let fill = instrumented.grab_input_from_layer::<fill::FillInput<Fill>>(layer, &document.network_interface, &editor.runtime)?;
|
||||
// Only read Fill-owned gradient values, not chains
|
||||
get_fill_node_id_with_direct_fill_input(layer, &document.network_interface)?;
|
||||
|
||||
let gradient = super::get_gradient(layer, &document.network_interface)?;
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
Some((fill, transform))
|
||||
Some((gradient, transform))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn get_gradient(editor: &mut EditorTestUtils) -> (Gradient, DAffine2) {
|
||||
let fills = get_fills(editor).await;
|
||||
assert_eq!(fills.len(), 1, "Expected 1 gradient fill, found {}", fills.len());
|
||||
async fn get_gradients_from_chain(editor: &mut EditorTestUtils) -> Vec<(Gradient, DAffine2)> {
|
||||
let document = editor.active_document();
|
||||
document
|
||||
.metadata()
|
||||
.all_layers()
|
||||
.filter_map(|layer| {
|
||||
// Only read actual gradient chains, not Fill-owned gradient values
|
||||
get_upstream_gradient_value_node_id(layer, &document.network_interface)?;
|
||||
|
||||
let (fill, transform) = fills.first().unwrap();
|
||||
let gradient = fill.as_gradient().expect("Expected gradient fill type");
|
||||
let gradient = super::get_gradient(layer, &document.network_interface)?;
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
Some((gradient, transform))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
(gradient.clone(), *transform)
|
||||
async fn get_gradient_from_fill(editor: &mut EditorTestUtils) -> (Gradient, DAffine2) {
|
||||
let gradients = get_gradients_from_fill(editor).await;
|
||||
assert_eq!(gradients.len(), 1, "Expected 1 gradient fill, found {}", gradients.len());
|
||||
|
||||
gradients.into_iter().next().unwrap()
|
||||
}
|
||||
|
||||
async fn get_gradient_from_chain(editor: &mut EditorTestUtils) -> (Gradient, DAffine2) {
|
||||
let gradients = get_gradients_from_chain(editor).await;
|
||||
assert_eq!(gradients.len(), 1, "Expected 1 gradient chain, found {}", gradients.len());
|
||||
gradients.into_iter().next().unwrap()
|
||||
}
|
||||
|
||||
fn assert_stops_at_positions(actual_positions: &[f64], expected_positions: &[f64], tolerance: f64) {
|
||||
@@ -2010,13 +2038,51 @@ mod test_gradient {
|
||||
layer
|
||||
}
|
||||
|
||||
async fn create_fill_gradient_chain_layer(editor: &mut EditorTestUtils) -> LayerNodeIdentifier {
|
||||
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
|
||||
let document = editor.active_document();
|
||||
let layer = document.metadata().all_layers().next().unwrap();
|
||||
let fill_node_id = get_fill_node_id_with_direct_fill_input(layer, &document.network_interface).expect("Fill node should exist");
|
||||
|
||||
let gradient_node_id = editor.create_node_by_name(DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER)).await;
|
||||
|
||||
editor
|
||||
.handle_message(NodeGraphMessage::CreateWire {
|
||||
output_connector: OutputConnector::node(gradient_node_id, 0),
|
||||
input_connector: InputConnector::node(fill_node_id, fill::FillInput::<List<Graphic>>::INDEX),
|
||||
})
|
||||
.await;
|
||||
|
||||
editor
|
||||
.handle_message(NodeGraphMessage::SetInputValue {
|
||||
node_id: gradient_node_id,
|
||||
input_index: 1,
|
||||
value: TaggedValue::Gradient(GradientStops::new([
|
||||
GradientStop {
|
||||
position: 0.,
|
||||
midpoint: 0.5,
|
||||
color: Color::RED,
|
||||
},
|
||||
GradientStop {
|
||||
position: 1.,
|
||||
midpoint: 0.5,
|
||||
color: Color::BLUE,
|
||||
},
|
||||
])),
|
||||
})
|
||||
.await;
|
||||
|
||||
layer
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ignore_artboard() {
|
||||
let mut editor = EditorTestUtils::create();
|
||||
editor.new_document().await;
|
||||
editor.drag_tool(ToolType::Artboard, 0., 0., 100., 100., ModifierKeys::empty()).await;
|
||||
editor.drag_tool(ToolType::Gradient, 2., 2., 4., 4., ModifierKeys::empty()).await;
|
||||
assert!(get_fills(&mut editor).await.is_empty());
|
||||
assert!(get_gradients_from_fill(&mut editor).await.is_empty());
|
||||
assert!(get_gradients_from_chain(&mut editor).await.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -2025,7 +2091,8 @@ mod test_gradient {
|
||||
editor.new_document().await;
|
||||
editor.create_raster_image(Image::new(100, 100, Color::WHITE), Some((0., 0.))).await;
|
||||
editor.drag_tool(ToolType::Gradient, 2., 2., 4., 4., ModifierKeys::empty()).await;
|
||||
assert!(get_fills(&mut editor).await.is_empty());
|
||||
assert!(get_gradients_from_fill(&mut editor).await.is_empty());
|
||||
assert!(get_gradients_from_chain(&mut editor).await.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -2037,7 +2104,7 @@ mod test_gradient {
|
||||
editor.select_secondary_color(Color::BLUE).await;
|
||||
editor.drag_tool(ToolType::Gradient, 2., 3., 24., 4., ModifierKeys::empty()).await;
|
||||
|
||||
let (gradient, transform) = get_gradient(&mut editor).await;
|
||||
let (gradient, transform) = get_gradient_from_fill(&mut editor).await;
|
||||
|
||||
// Gradient goes from primary color to secondary color
|
||||
let stops = gradient.stops.iter().map(|stop| (stop.position, SRGBA8::from(stop.color))).collect::<Vec<_>>();
|
||||
@@ -2046,6 +2113,21 @@ mod test_gradient {
|
||||
assert!(transform.transform_point2(gradient.end).abs_diff_eq(DVec2::new(24., 4.), 1e-10));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn draw_updates_fill_gradient_chain_line() {
|
||||
let mut editor = EditorTestUtils::create();
|
||||
editor.new_document().await;
|
||||
let layer = create_fill_gradient_chain_layer(&mut editor).await;
|
||||
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }).await;
|
||||
editor.drag_tool(ToolType::Gradient, 2., 3., 24., 4., ModifierKeys::empty()).await;
|
||||
|
||||
let (gradient, transform) = get_gradient_from_chain(&mut editor).await;
|
||||
|
||||
// Gradient line is updated while existing stops are preserved
|
||||
assert!(transform.transform_point2(gradient.start).abs_diff_eq(DVec2::new(2., 3.), 1e-10));
|
||||
assert!(transform.transform_point2(gradient.end).abs_diff_eq(DVec2::new(24., 4.), 1e-10));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn snap_simple_draw() {
|
||||
let mut editor = EditorTestUtils::create();
|
||||
@@ -2060,7 +2142,7 @@ mod test_gradient {
|
||||
editor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;
|
||||
editor.drag_tool(ToolType::Gradient, start.x, start.y, end.x, end.y, ModifierKeys::SHIFT).await;
|
||||
|
||||
let (gradient, transform) = get_gradient(&mut editor).await;
|
||||
let (gradient, transform) = get_gradient_from_fill(&mut editor).await;
|
||||
|
||||
assert!(transform.transform_point2(gradient.start).abs_diff_eq(start, 1e-10));
|
||||
|
||||
@@ -2103,7 +2185,7 @@ mod test_gradient {
|
||||
|
||||
editor.drag_tool(ToolType::Gradient, 2., 3., 24., 4., ModifierKeys::empty()).await;
|
||||
|
||||
let (gradient, transform) = get_gradient(&mut editor).await;
|
||||
let (gradient, transform) = get_gradient_from_fill(&mut editor).await;
|
||||
|
||||
assert!(transform.transform_point2(gradient.start).abs_diff_eq(DVec2::new(2., 3.), 1e-10));
|
||||
assert!(transform.transform_point2(gradient.end).abs_diff_eq(DVec2::new(24., 4.), 1e-10));
|
||||
@@ -2120,7 +2202,7 @@ mod test_gradient {
|
||||
editor.drag_tool(ToolType::Gradient, 0., 0., 100., 0., ModifierKeys::empty()).await;
|
||||
|
||||
// Get initial gradient state (should have 2 stops)
|
||||
let (initial_gradient, _) = get_gradient(&mut editor).await;
|
||||
let (initial_gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(initial_gradient.stops.len(), 2, "Expected 2 stops, found {}", initial_gradient.stops.len());
|
||||
|
||||
editor.select_tool(ToolType::Gradient).await;
|
||||
@@ -2129,7 +2211,7 @@ mod test_gradient {
|
||||
editor.left_mouseup(25., 0., ModifierKeys::empty()).await;
|
||||
|
||||
// Check that a new stop has been added
|
||||
let (updated_gradient, _) = get_gradient(&mut editor).await;
|
||||
let (updated_gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(updated_gradient.stops.len(), 3, "Expected 3 stops, found {}", updated_gradient.stops.len());
|
||||
|
||||
let positions: Vec<f64> = updated_gradient.stops.iter().map(|stop| stop.position).collect();
|
||||
@@ -2165,7 +2247,7 @@ mod test_gradient {
|
||||
editor.drag_tool(ToolType::Gradient, 0., 0., 100., 0., ModifierKeys::empty()).await;
|
||||
|
||||
// Get the initial gradient state
|
||||
let (initial_gradient, transform) = get_gradient(&mut editor).await;
|
||||
let (initial_gradient, transform) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(initial_gradient.stops.len(), 2, "Expected 2 stops, found {}", initial_gradient.stops.len());
|
||||
|
||||
// Verify initial gradient endpoints in viewport space
|
||||
@@ -2195,7 +2277,7 @@ mod test_gradient {
|
||||
.await;
|
||||
|
||||
// Check the updated gradient
|
||||
let (updated_gradient, transform) = get_gradient(&mut editor).await;
|
||||
let (updated_gradient, transform) = get_gradient_from_fill(&mut editor).await;
|
||||
|
||||
// Verify the start point hasn't changed
|
||||
let updated_start = transform.transform_point2(updated_gradient.start);
|
||||
@@ -2223,7 +2305,7 @@ mod test_gradient {
|
||||
editor.left_mousedown(25., 0., ModifierKeys::empty()).await;
|
||||
editor.left_mouseup(25., 0., ModifierKeys::empty()).await;
|
||||
|
||||
let (initial_gradient, _) = get_gradient(&mut editor).await;
|
||||
let (initial_gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(initial_gradient.stops.len(), 3, "Expected 3 stops, found {}", initial_gradient.stops.len());
|
||||
|
||||
// Verify initial stop positions and colors
|
||||
@@ -2262,7 +2344,7 @@ mod test_gradient {
|
||||
)
|
||||
.await;
|
||||
|
||||
let (updated_gradient, _) = get_gradient(&mut editor).await;
|
||||
let (updated_gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(updated_gradient.stops.len(), 3, "Expected 3 stops after dragging, found {}", updated_gradient.stops.len());
|
||||
|
||||
// Verify updated stop positions and colors
|
||||
@@ -2290,7 +2372,7 @@ mod test_gradient {
|
||||
editor.drag_tool(ToolType::Gradient, 0., 0., 100., 0., ModifierKeys::empty()).await;
|
||||
|
||||
// Get initial gradient state (should have 2 stops)
|
||||
let (initial_gradient, _) = get_gradient(&mut editor).await;
|
||||
let (initial_gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(initial_gradient.stops.len(), 2, "Expected 2 stops, found {}", initial_gradient.stops.len());
|
||||
|
||||
editor.select_tool(ToolType::Gradient).await;
|
||||
@@ -2304,7 +2386,7 @@ mod test_gradient {
|
||||
editor.left_mousedown(75., 0., ModifierKeys::empty()).await;
|
||||
editor.left_mouseup(75., 0., ModifierKeys::empty()).await;
|
||||
|
||||
let (updated_gradient, _) = get_gradient(&mut editor).await;
|
||||
let (updated_gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(updated_gradient.stops.len(), 4, "Expected 4 stops, found {}", updated_gradient.stops.len());
|
||||
|
||||
let positions: Vec<f64> = updated_gradient.stops.iter().map(|stop| stop.position).collect();
|
||||
@@ -2330,7 +2412,7 @@ mod test_gradient {
|
||||
editor.press(Key::Delete, ModifierKeys::empty()).await;
|
||||
|
||||
// Verify we now have 3 stops
|
||||
let (final_gradient, _) = get_gradient(&mut editor).await;
|
||||
let (final_gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(final_gradient.stops.len(), 3, "Expected 3 stops after deletion, found {}", final_gradient.stops.len());
|
||||
|
||||
let final_positions: Vec<f64> = final_gradient.stops.iter().map(|stop| stop.position).collect();
|
||||
@@ -2374,15 +2456,13 @@ mod test_gradient {
|
||||
|
||||
#[tokio::test]
|
||||
async fn change_spread_method() {
|
||||
use graphene_std::vector::style::GradientSpreadMethod;
|
||||
|
||||
let mut editor = EditorTestUtils::create();
|
||||
editor.new_document().await;
|
||||
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
|
||||
editor.drag_tool(ToolType::Gradient, 10., 10., 90., 90., ModifierKeys::empty()).await;
|
||||
|
||||
// Verify default spread method is Pad
|
||||
let (gradient, _) = get_gradient(&mut editor).await;
|
||||
let (gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(gradient.spread_method, GradientSpreadMethod::Pad);
|
||||
|
||||
// Update spread method to Repeat
|
||||
@@ -2392,7 +2472,7 @@ mod test_gradient {
|
||||
})
|
||||
.await;
|
||||
|
||||
let (gradient, _) = get_gradient(&mut editor).await;
|
||||
let (gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(gradient.spread_method, GradientSpreadMethod::Repeat);
|
||||
|
||||
// Update spread method to Reflect
|
||||
@@ -2402,12 +2482,45 @@ mod test_gradient {
|
||||
})
|
||||
.await;
|
||||
|
||||
let (gradient, _) = get_gradient(&mut editor).await;
|
||||
let (gradient, _) = get_gradient_from_fill(&mut editor).await;
|
||||
assert_eq!(gradient.spread_method, GradientSpreadMethod::Reflect);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gradient_list_drag_endpoint() {
|
||||
async fn change_spread_method_chain() {
|
||||
let mut editor = EditorTestUtils::create();
|
||||
editor.new_document().await;
|
||||
let layer = create_fill_gradient_chain_layer(&mut editor).await;
|
||||
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }).await;
|
||||
editor.select_tool(ToolType::Gradient).await;
|
||||
|
||||
// Verify default spread method is Pad
|
||||
let (gradient, _) = get_gradient_from_chain(&mut editor).await;
|
||||
assert_eq!(gradient.spread_method, GradientSpreadMethod::Pad);
|
||||
|
||||
// Update spread method to Repeat
|
||||
editor
|
||||
.handle_message(GradientToolMessage::UpdateOptions {
|
||||
options: GradientOptionsUpdate::SetSpreadMethod(GradientSpreadMethod::Repeat),
|
||||
})
|
||||
.await;
|
||||
|
||||
let (gradient, _) = get_gradient_from_chain(&mut editor).await;
|
||||
assert_eq!(gradient.spread_method, GradientSpreadMethod::Repeat);
|
||||
|
||||
// Update spread method to Reflect
|
||||
editor
|
||||
.handle_message(GradientToolMessage::UpdateOptions {
|
||||
options: GradientOptionsUpdate::SetSpreadMethod(GradientSpreadMethod::Reflect),
|
||||
})
|
||||
.await;
|
||||
|
||||
let (gradient, _) = get_gradient_from_chain(&mut editor).await;
|
||||
assert_eq!(gradient.spread_method, GradientSpreadMethod::Reflect);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gradient_list_layer_drag_endpoint() {
|
||||
let mut editor = EditorTestUtils::create();
|
||||
editor.new_document().await;
|
||||
let layer = create_gradient_list_layer(&mut editor).await;
|
||||
@@ -2479,7 +2592,7 @@ mod test_gradient {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gradient_list_preserves_stops() {
|
||||
async fn gradient_list_layer_preserves_stops() {
|
||||
let mut editor = EditorTestUtils::create();
|
||||
editor.new_document().await;
|
||||
let layer = create_gradient_list_layer(&mut editor).await;
|
||||
|
||||
Reference in New Issue
Block a user