Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions editor/src/messages/portfolio/document/document_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ pub enum DocumentMessage {
UpdateClickTargets {
click_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>>,
},
// TODO: Eventually remove this document upgrade code
MigrateLegacyGradients,
UpdateOutlines {
outlines: HashMap<NodeId, Vec<Arc<ClickTarget>>>,
},
Expand Down
38 changes: 38 additions & 0 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ pub struct DocumentMessageHandler {
/// The path of the to the document file.
#[serde(skip)]
pub(crate) path: Option<PathBuf>,
// TODO: Eventually remove this document upgrade code
/// Set when a freshly-opened document still has legacy bounding-box-relative gradients; the deferred gradient
/// migration converts them to absolute after the first graph run (when geometry bounds are available) and clears this.
#[serde(skip)]
pub(crate) pending_gradient_migration: bool,
/// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks
#[serde(skip)]
breadcrumb_network_path: Vec<NodeId>,
Expand Down Expand Up @@ -181,6 +186,8 @@ impl Default for DocumentMessageHandler {
// =============================================
name: DEFAULT_DOCUMENT_NAME.to_string(),
path: None,
// TODO: Eventually remove this document upgrade code
pending_gradient_migration: false,
breadcrumb_network_path: Vec::new(),
selection_network_path: Vec::new(),
document_undo_history: VecDeque::new(),
Expand Down Expand Up @@ -1365,6 +1372,37 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
.collect();
self.network_interface.update_click_targets(layer_click_targets);
}
// TODO: Eventually remove this document upgrade code
DocumentMessage::MigrateLegacyGradients => {
if self.pending_gradient_migration {
self.pending_gradient_migration = false;

// Read each layer's legacy gradient and compute its absolute form from the now-available local bounds
let layers: Vec<_> = self.metadata().all_layers().collect();
let conversions: Vec<(NodeId, Fill)> = layers
.into_iter()
.filter_map(|layer| {
let gradient = graph_modification_utils::get_gradient(layer, &self.network_interface)?;
if gradient.absolute {
return None;
}
let fill_node_id = graph_modification_utils::get_fill_node_id(layer, &self.network_interface)?;
let bounds = self.metadata().nonzero_bounding_box(layer);
let bounding_box = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
Some((fill_node_id, Fill::Gradient(gradient.to_absolute(bounding_box))))
})
.collect();

let converted_any = !conversions.is_empty();
for (fill_node_id, fill) in conversions {
self.network_interface
.set_input(&InputConnector::node(fill_node_id, 1), NodeInput::value(TaggedValue::Fill(fill), false), &[]);
}
if converted_any {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
}
DocumentMessage::UpdateOutlines { outlines } => {
let layer_outlines = outlines
.into_iter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use glam::{DAffine2, DVec2, IVec2};
use graph_craft::descriptor;
use graph_craft::document::{NodeId, NodeInput};
use graphene_std::list::List;
use graphene_std::renderer::Quad;
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{Fill, Gradient, GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
Expand Down Expand Up @@ -684,7 +683,6 @@ fn import_usvg_node_inner(
/// Helper to apply path data (vector geometry, fill, stroke, transform) to a layer.
fn import_usvg_path(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, path: &usvg::Path, layer: LayerNodeIdentifier, graphite_gradient_stops: &HashMap<String, GradientStops>) {
let subpaths = convert_usvg_path(path);
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default();

// Skip creating a Transform node entirely when the SVG-native transform is identity.
let node_transform = usvg_transform(node.abs_transform());
Expand All @@ -697,8 +695,7 @@ fn import_usvg_path(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
}

if let Some(fill) = path.fill() {
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
apply_usvg_fill(fill, modify_inputs, bounds_transform, graphite_gradient_stops);
apply_usvg_fill(fill, modify_inputs, graphite_gradient_stops);
}
if let Some(stroke) = path.stroke() {
apply_usvg_stroke(stroke, modify_inputs, node_transform);
Expand Down Expand Up @@ -797,14 +794,13 @@ fn convert_spread_method(spread_method: usvg::SpreadMethod) -> GradientSpreadMet
}
}

fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, bounds_transform: DAffine2, graphite_gradient_stops: &HashMap<String, GradientStops>) {
fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, graphite_gradient_stops: &HashMap<String, GradientStops>) {
modify_inputs.fill_set(match &fill.paint() {
usvg::Paint::Color(color) => Fill::solid(usvg_color(*color, fill.opacity().get())),
usvg::Paint::LinearGradient(linear) => {
let gradient_transform = usvg_transform(linear.transform());
let (start, end) = (DVec2::new(linear.x1() as f64, linear.y1() as f64), DVec2::new(linear.x2() as f64, linear.y2() as f64));
let (start, end) = (gradient_transform.transform_point2(start), gradient_transform.transform_point2(end));
let (start, end) = (bounds_transform.inverse().transform_point2(start), bounds_transform.inverse().transform_point2(end));

let gradient_type = GradientType::Linear;

Expand All @@ -827,14 +823,15 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, b
gradient_type,
stops,
spread_method,
// TODO: Eventually remove this document upgrade code
absolute: true,
})
}
usvg::Paint::RadialGradient(radial) => {
let gradient_transform = usvg_transform(radial.transform());
let center = DVec2::new(radial.cx() as f64, radial.cy() as f64);
let edge = center + DVec2::X * radial.r().get() as f64;
let (start, end) = (gradient_transform.transform_point2(center), gradient_transform.transform_point2(edge));
let (start, end) = (bounds_transform.inverse().transform_point2(start), bounds_transform.inverse().transform_point2(end));

let gradient_type = GradientType::Radial;

Expand All @@ -857,6 +854,8 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, b
gradient_type,
stops,
spread_method,
// TODO: Eventually remove this document upgrade code
absolute: true,
})
}
usvg::Paint::Pattern(_) => {
Expand Down
3 changes: 3 additions & 0 deletions editor/src/messages/portfolio/document_migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,9 @@ pub fn document_migration_replace_resources_referenced_by_hash(document_serializ
pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {
document.network_interface.migrate_path_modify_node();

// Legacy `Fill::Gradient`s are converted to absolute by the deferred migration pass once the first graph run yields geometry bounds
document.pending_gradient_migration = true;

let network = document.network_interface.document_network().clone();

// Apply string and node replacements to each node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,12 @@ 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))?;
Expand Down Expand Up @@ -337,10 +343,16 @@ pub fn gradient_space_transform(layer: LayerNodeIdentifier, network_interface: &
.map(|footprint| footprint.transform)
.unwrap_or(metadata.document_to_viewport);
}
let multiplied = metadata.transform_to_viewport(layer);
let bounds = metadata.nonzero_bounding_box(layer);
let bound_transform = glam::DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
multiplied * bound_transform

// 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)
}

/// True when start→end (mapped through `transform` into viewport space) points predominantly rightward. For purely
Expand Down
2 changes: 2 additions & 0 deletions editor/src/messages/tool/tool_messages/gradient_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
spread_method: chain_state.spread_method,
start: chain_state.transform.transform_point2(DVec2::ZERO),
end: chain_state.transform.transform_point2(DVec2::X),
// TODO: Eventually remove this document upgrade code
absolute: true,
})
} else {
// Try to find a legacy Fill::Gradient that is selected in a Fill node
Expand Down
5 changes: 5 additions & 0 deletions editor/src/node_graph_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,11 @@ impl NodeGraphExecutor {
first_element_source_id,
});
responses.add(DocumentMessage::UpdateClickTargets { click_targets });

// TODO: Eventually remove this document upgrade code
// Runs after click targets land (this graph run's geometry bounds) so the deferred gradient migration can use them.
responses.add(DocumentMessage::MigrateLegacyGradients);

responses.add(DocumentMessage::UpdateOutlines { outlines });
responses.add(DocumentMessage::UpdateTextFrames { text_frames });
responses.add(DocumentMessage::UpdateClipTargets { clip_targets });
Expand Down
4 changes: 2 additions & 2 deletions node-graph/libraries/core-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ pub use graphene_hash;
pub use graphene_hash::CacheHash;
pub use list::{
ATTR_BACKGROUND, ATTR_BLEND_MODE, ATTR_CLIP, ATTR_CLIPPING_MASK, ATTR_DIMENSIONS, ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_EDITOR_TEXT_FRAME, ATTR_END,
ATTR_FONT, ATTR_FONT_SIZE, ATTR_GRADIENT_TYPE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME, ATTR_OPACITY, ATTR_OPACITY_FILL,
ATTR_SPREAD_METHOD, ATTR_START, ATTR_TEXT_ALIGN, ATTR_TRANSFORM, ATTR_TYPE,
ATTR_FONT, ATTR_FONT_SIZE, ATTR_GRADIENT_LEGACY, ATTR_GRADIENT_TYPE, ATTR_LETTER_SPACING, ATTR_LETTER_TILT, ATTR_LINE_HEIGHT, ATTR_LOCATION, ATTR_MAX_HEIGHT, ATTR_MAX_WIDTH, ATTR_NAME,
ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_START, ATTR_TEXT_ALIGN, ATTR_TRANSFORM, ATTR_TYPE,
};
pub use memo::MemoHash;
pub use no_std_types::AsU32;
Expand Down
4 changes: 4 additions & 0 deletions node-graph/libraries/core-types/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ pub const ATTR_CLIP: &str = "clip";
pub const ATTR_SPREAD_METHOD: &str = "spread_method";
/// Gradient's `GradientType` (`Linear` or `Radial`).
pub const ATTR_GRADIENT_TYPE: &str = "gradient_type";
// TODO: Eventually remove this document upgrade code
/// `bool` runtime marker (never serialized) flagging a gradient that came from the legacy bounding-box-relative `Fill::Gradient`,
/// so the renderer reproduces the pre-#4241 positioning instead of the new absolute path.
pub const ATTR_GRADIENT_LEGACY: &str = "gradient_legacy";
/// Vector graphics object's filled area paint, of type List<T> where T is any graphic type.
pub const ATTR_FILL: &str = "fill";
/// Vector graphics object's stroke paint, of type List<T> where T is any graphic type.
Expand Down
21 changes: 16 additions & 5 deletions node-graph/libraries/graphic-types/src/graphic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, List};
use core_types::ops::{FromAnchorPosition, ListConvert};
use core_types::render_complexity::RenderComplexity;
use core_types::uuid::NodeId;
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_GRADIENT_TYPE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color};
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_GRADIENT_LEGACY, ATTR_GRADIENT_TYPE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use raster_types::{CPU, GPU, Raster};
Expand Down Expand Up @@ -193,15 +193,24 @@ fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic)

/// Converts a `Fill` enum into the `List<Graphic>` representation used as paint storage.
/// TODO: Remove once all fill paint sources flow through `List<Graphic>` directly without going through the `Fill` enum.
pub fn fill_to_graphic_list(fill: &Fill) -> Option<List<Graphic>> {
pub fn fill_to_graphic_list(fill: &Fill, bounding_box_transform: DAffine2) -> Option<List<Graphic>> {
match fill {
Fill::None => None,
Fill::Solid(color) => Some(List::new_from_element((*color).into())),
Fill::Gradient(gradient) => {
// TODO: Eventually remove this document upgrade code
// Absolute gradients carry their transform directly into the new pipeline. Legacy ones are bounding-box-relative,
// so bake the bbox in and flag them to reproduce the pre-#4241 rendering until the deferred migration converts them.
let (transform, legacy) = if gradient.absolute {
(gradient.to_transform(), false)
} else {
(bounding_box_transform * gradient.to_transform(), true)
};
let gradient_item = Item::new_from_element(gradient.stops.clone())
.with_attribute(ATTR_TRANSFORM, gradient.to_transform())
.with_attribute(ATTR_TRANSFORM, transform)
.with_attribute(ATTR_GRADIENT_TYPE, gradient.gradient_type)
.with_attribute(ATTR_SPREAD_METHOD, gradient.spread_method);
.with_attribute(ATTR_SPREAD_METHOD, gradient.spread_method)
.with_attribute(ATTR_GRADIENT_LEGACY, legacy);
let gradient_list = List::new_from_item(gradient_item);

Some(List::new_from_element(Graphic::Gradient(gradient_list)))
Expand Down Expand Up @@ -246,7 +255,9 @@ pub fn has_paint_at(list: &List<Vector>, index: usize, attribute: &str) -> bool
pub fn fill_graphic_list_at(list: &List<Vector>, index: usize) -> Option<Cow<'_, List<Graphic>>> {
graphic_list_at(list, index, ATTR_FILL).or_else(|| {
let vector = list.element(index)?;
fill_to_graphic_list(vector.style.fill()).map(Cow::Owned)
let bounds = vector.nonzero_bounding_box();
let bounding_box_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
fill_to_graphic_list(vector.style.fill(), bounding_box_transform).map(Cow::Owned)
})
}

Expand Down
Loading
Loading