Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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: 1 addition & 1 deletion demo-artwork/changing-seasons.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/isometric-fountain.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/painted-dreams.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/parametric-dunescape.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/procedural-string-lights.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/red-dress.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/valley-of-spires.graphite

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,21 @@ impl<T: TableItemLayout> TableItemLayout for List<T> {
}
}

impl<T: TableItemLayout> TableItemLayout for Arc<T> {
fn type_name() -> &'static str {
T::type_name()
}
fn identifier(&self) -> String {
self.as_ref().identifier()
}
fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance {
self.as_ref().value_widget(target, data)
}
fn value_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.as_ref().value_page(data)
}
}

impl TableItemLayout for Artboard {
fn type_name() -> &'static str {
"Artboard"
Expand Down Expand Up @@ -955,6 +970,7 @@ macro_rules! known_item_types {
$apply!(
List<Artboard>,
List<Graphic>,
Arc<List<Graphic>>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
Expand Down
17 changes: 7 additions & 10 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ use graph_craft::application_io::wgpu_available;
use graph_craft::descriptor;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
use graphene_std::graphic::is_paint_present;
use graphene_std::math::quad::Quad;
use graphene_std::path_bool_nodes::boolean_intersect;
use graphene_std::raster::BlendMode;
use graphene_std::subpath::Subpath;
use graphene_std::vector::PointId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::misc::dvec2_to_point;
use graphene_std::vector::style::{Fill, RenderMode};
use graphene_std::vector::style::{Gradient, RenderMode};
use kurbo::{Affine, BezPath, Line, PathSeg};
use std::collections::HashSet;
use std::path::PathBuf;
Expand Down Expand Up @@ -125,10 +126,10 @@ pub struct DocumentMessageHandler {
#[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.
/// Fill nodes whose legacy bounding-box-relative gradient was decomposed into the value model, but whose transform still
/// needs the bounding box baked in. The deferred migration bakes them after the first graph run (when bounds are available) and clears this.
#[serde(skip)]
pub(crate) pending_gradient_migration: bool,
pub(crate) pending_gradient_bbox_bake: Vec<(NodeId, Gradient)>,
/// 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 @@ -187,7 +188,7 @@ impl Default for DocumentMessageHandler {
name: DEFAULT_DOCUMENT_NAME.to_string(),
path: None,
// TODO: Eventually remove this document upgrade code
pending_gradient_migration: false,
pending_gradient_bbox_bake: Vec::new(),
breadcrumb_network_path: Vec::new(),
selection_network_path: Vec::new(),
document_undo_history: VecDeque::new(),
Expand Down Expand Up @@ -2525,11 +2526,7 @@ impl DocumentMessageHandler {
let fill_graphic_list = self.network_interface.document_metadata().layer_fill_attributes.get(&layer);
let stroke_graphic_list = self.network_interface.document_metadata().layer_stroke_attributes.get(&layer);

let has_fill = if let Some(list) = fill_graphic_list {
list.element(0).is_some()
} else {
!matches!(style.fill, Fill::None)
};
let has_fill = fill_graphic_list.is_some_and(|g| is_paint_present(g));
// `style.stroke` is `Some` whenever a `Stroke` node is in the chain, even with weight 0 or a transparent color.
// So `is_some()` would treat invisibly-stroked fill-only layers as having a stroke.
// `ATTR_STROKE` is the source of truth when set; fall back to `style.stroke.color` only when no attribute is present.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use graphene_std::raster::BlendMode;
use graphene_std::raster_types::Image;
use graphene_std::subpath::Subpath;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{Fill, GradientSpreadMethod, GradientType, Stroke};
use graphene_std::vector::style::{Fill, GradientSpreadMethod, GradientType, Stroke, build_transform_with_y_preservation};
use graphene_std::vector::{GradientStops, PointId, Vector, VectorModification, VectorModificationType};
use graphene_std::{Artboard, Color, Graphic, NodeInputDecleration};

Expand Down Expand Up @@ -454,29 +454,64 @@ impl<'a> ModifyInputsContext<'a> {
}

pub fn fill_set(&mut self, fill: Fill) {
let fill_index = 1;
let backup_color_index = 2;
let backup_gradient_index = 3;

let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
return;
};
let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::INDEX);

match &fill {
Fill::None => {
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(None), false), true);
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupColorInput::INDEX);
self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::Color(None), false), true);

self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(None), false), false);
}
Fill::Solid(color) => {
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Some(*color)), false), true);
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupColorInput::INDEX);
self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::Color(Some(*color)), false), true);

self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Some(*color)), false), false);
}
Fill::Gradient(gradient) => {
let input_connector = InputConnector::node(fill_node_id, backup_gradient_index);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::FillGradient(gradient.clone()), false), true);
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput::INDEX);
self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::Gradient(gradient.stops.clone()), false), true);

self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::INDEX),
NodeInput::value(TaggedValue::Gradient(gradient.stops.clone()), false),
false,
);

let old_transform: DAffine2 = self
.network_interface
.document_network()
.nodes
.get(&fill_node_id)
.and_then(|node| node.inputs.get(graphene_std::vector::fill::TransformInput::INDEX))
.and_then(|input| input.as_value())
.and_then(|value| if let TaggedValue::OptionalDAffine2(transform) = value { *transform } else { None })
.unwrap_or(DAffine2::IDENTITY);

let new_transform = build_transform_with_y_preservation(old_transform, gradient.start, gradient.end);
self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::TransformInput::INDEX),
NodeInput::value(TaggedValue::OptionalDAffine2(Some(new_transform)), false),
false,
);

self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::GradientTypeInput::INDEX),
NodeInput::value(TaggedValue::GradientType(gradient.gradient_type), false),
false,
);

self.set_input_with_refresh(
InputConnector::node(fill_node_id, graphene_std::vector::fill::SpreadMethodInput::INDEX),
NodeInput::value(TaggedValue::GradientSpreadMethod(gradient.spread_method), false),
false,
);
}
}
let input_connector = InputConnector::node(fill_node_id, fill_index);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Fill(fill), false), false);
}

pub fn blend_mode_set(&mut self, blend_mode: BlendMode) {
Expand Down Expand Up @@ -536,8 +571,20 @@ impl<'a> ModifyInputsContext<'a> {
/// Write the gradient stops to the 'Gradient Value' node feeding the layer.
pub fn gradient_stops_set(&mut self, stops: GradientStops) {
let Some(output_layer) = self.get_output_layer() else { return };
let Some(gradient_value_id) = get_upstream_gradient_value_node_id(output_layer, self.network_interface) else {
return;

let gradient_value_id = match get_upstream_gradient_value_node_id(output_layer, self.network_interface) {
Some(id) => id,
None => {
let target = gradient_chain_target_input(output_layer, self.network_interface);
let Some(node_definition) = resolve_proto_node_type(graphene_std::math_nodes::gradient_value::IDENTIFIER) else {
return;
};
let node_id = NodeId::new();
self.network_interface.insert_node(node_id, node_definition.default_node_template(), &[]);
self.network_interface.set_input(&target, NodeInput::node(node_id, 0), &[]);
Comment thread
YohYamasaki marked this conversation as resolved.

node_id
}
};

let input_connector = InputConnector::node(gradient_value_id, graphene_std::math_nodes::gradient_value::GradientInput::INDEX);
Expand Down Expand Up @@ -615,6 +662,7 @@ impl<'a> ModifyInputsContext<'a> {
/// from the default (`Linear`).
pub fn gradient_type_set(&mut self, gradient_type: GradientType) {
let Some(output_layer) = self.get_output_layer() else { return };

let target_input = gradient_chain_target_input(output_layer, self.network_interface);
let identifier = graphene_std::math_nodes::gradient_type::IDENTIFIER;
let create_if_nonexistent = gradient_type != GradientType::default();
Expand All @@ -630,6 +678,7 @@ impl<'a> ModifyInputsContext<'a> {
/// from the default (`Pad`).
pub fn gradient_spread_method_set(&mut self, spread_method: GradientSpreadMethod) {
let Some(output_layer) = self.get_output_layer() else { return };

let target_input = gradient_chain_target_input(output_layer, self.network_interface);
let identifier = graphene_std::math_nodes::spread_method::IDENTIFIER;
let create_if_nonexistent = spread_method != GradientSpreadMethod::default();
Expand All @@ -655,7 +704,7 @@ impl<'a> ModifyInputsContext<'a> {
return;
};

let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::INDEX);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke.color), false), true);
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
Expand Down Expand Up @@ -804,40 +853,3 @@ impl<'a> ModifyInputsContext<'a> {
}
}
}

/// Rebuild the y-axis so its (parallel, perpendicular) components in the x-axis-aligned frame stay constant, both
/// rescaled by `|new_x| / |old_x|`. This holds the (x, y) parallelogram's aspect ratio and skew fixed across an endpoint
/// drag, so a radial ellipse stays the same shape (just rotated and resized) instead of distorting as x grows or shrinks.
/// Falls back to a +90° rotation of `new_x` when `old_x` is degenerate.
fn scale_y_axis_to_match_new_x(old_x: DVec2, old_y: DVec2, new_x: DVec2) -> DVec2 {
let old_x_length = old_x.length();
if old_x_length < 1e-9 {
return DVec2::new(-new_x.y, new_x.x);
}
let ex_old = old_x / old_x_length;
let ey_old = DVec2::new(-ex_old.y, ex_old.x);

let new_x_length = new_x.length();
if new_x_length < 1e-9 {
return DVec2::ZERO;
}
let ex_new = new_x / new_x_length;
let ey_new = DVec2::new(-ex_new.y, ex_new.x);

let parallel = old_y.dot(ex_old);
let perpendicular = old_y.dot(ey_old);
let scale = new_x_length / old_x_length;

scale * (parallel * ex_new + perpendicular * ey_new)
}

/// Build a new affine that maps canonical (0,0) -> (1,0) to (new_start, new_end), preserving the y-axis
/// shape of `old` proportionally to the x-axis length change.
fn build_transform_with_y_preservation(old: DAffine2, new_start: DVec2, new_end: DVec2) -> DAffine2 {
let new_x_axis = new_end - new_start;
let preserved_y_axis = scale_y_axis_to_match_new_x(old.matrix2.x_axis, old.matrix2.y_axis, new_x_axis);
DAffine2 {
matrix2: glam::DMat2::from_cols(new_x_axis, preserved_y_axis),
translation: new_start,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1764,7 +1764,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
use graphene_std::vector::generator_nodes::*;

let is_fill = matches!(value, TaggedValue::Fill(_));
let is_fill = matches!(value, TaggedValue::Gradient(_) | TaggedValue::Color(_));
let reference = network_interface.reference(&node_id, selection_network_path);
let is_text_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER));
let is_stroke_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER));
Expand Down
Loading
Loading