mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 02:18:11 +08:00
Remove the legacy Fill, Gradient, and PathStyle types now that paints live in attributes (#4297)
* Remove `Stroke.color` * Remove legacy Gradient struct usage * Fix gradient tool tests * Remove `Fill` enum * Fix direction of `gradient_orientation_rightward` * Remove `Gradient` struct * Remove `PathStyle` struct * Fix old `VectorData` migration * Refactor - Remove unnecessary wrapper functions - Use `as_ref` instead of `clone` for stroke if possible - Comment cleanup * Clean up stale style comments and bindings left by the paint attribute migration --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
9f9899cfd0
commit
6a54dcb5da
@@ -402,11 +402,11 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
|
||||
});
|
||||
|
||||
// Add default fill and stroke to the layer
|
||||
let fill = graphene_std::vector::style::Fill::solid(Color::WHITE);
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
responses.add(GraphOperationMessage::FillColorSet { layer, color: Some(Color::WHITE) });
|
||||
|
||||
let stroke = graphene_std::vector::style::Stroke::new(Some(Color::BLACK), DEFAULT_STROKE_WIDTH);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
|
||||
let color = Some(Color::BLACK);
|
||||
let stroke = graphene_std::vector::style::Stroke::new(DEFAULT_STROKE_WIDTH);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
|
||||
|
||||
// Create new point ids and add those into the existing Vector path
|
||||
let mut points_map = HashMap::new();
|
||||
|
||||
@@ -13,7 +13,7 @@ use graphene_std::list::List;
|
||||
use graphene_std::memo::IORecord;
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillChoiceUI, GradientSpreadMethod, GradientType};
|
||||
use graphene_std::vector::style::{FillChoice, FillChoiceUI, GradientSpreadMethod, GradientType};
|
||||
use graphene_std::{Artboard, Color, Context, Graphic};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
@@ -385,61 +385,7 @@ impl TableItemLayout for Vector {
|
||||
VectorTableTab::Properties => {
|
||||
table_rows.push(column_headings(&["property", "value"]));
|
||||
|
||||
match self.style.fill.clone() {
|
||||
Fill::None => table_rows.push(vec![
|
||||
TextLabel::new("Fill").narrow(true).widget_instance(),
|
||||
ColorInput::new(FillChoiceUI::None)
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
.widget_instance(),
|
||||
]),
|
||||
Fill::Solid(color) => table_rows.push(vec![
|
||||
TextLabel::new("Fill").narrow(true).widget_instance(),
|
||||
ColorInput::new(FillChoiceUI::from(&FillChoice::Solid(color)))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
.widget_instance(),
|
||||
]),
|
||||
Fill::Gradient(gradient) => {
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill").narrow(true).widget_instance(),
|
||||
ColorInput::new(FillChoiceUI::from(&FillChoice::Gradient(gradient.stops)))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
.widget_instance(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill Gradient Type").narrow(true).widget_instance(),
|
||||
TextLabel::new(gradient.gradient_type.to_string()).narrow(true).widget_instance(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill Gradient Start").narrow(true).widget_instance(),
|
||||
TextLabel::new(format_dvec2(gradient.start)).narrow(true).widget_instance(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill Gradient End").narrow(true).widget_instance(),
|
||||
TextLabel::new(format_dvec2(gradient.end)).narrow(true).widget_instance(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill Gradient Transform").narrow(true).widget_instance(),
|
||||
TextLabel::new(format_transform_matrix(gradient.transform)).narrow(true).widget_instance(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(stroke) = self.style.stroke.clone() {
|
||||
let color = if let Some(color) = stroke.color { FillChoice::Solid(color) } else { FillChoice::None };
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke").narrow(true).widget_instance(),
|
||||
ColorInput::new(FillChoiceUI::from(&color))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
.widget_instance(),
|
||||
]);
|
||||
if let Some(stroke) = self.stroke.as_ref() {
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Weight").narrow(true).widget_instance(),
|
||||
TextLabel::new(format!("{} px", stroke.weight)).narrow(true).widget_instance(),
|
||||
|
||||
@@ -42,10 +42,10 @@ 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, Gradient, RenderMode};
|
||||
use graphene_std::vector::style::RenderMode;
|
||||
use graphene_std::vector::{PointId, graphic_types};
|
||||
use kurbo::{Affine, BezPath, Line, PathSeg};
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
@@ -125,7 +125,7 @@ pub struct DocumentMessageHandler {
|
||||
/// network path, the node itself, and its original relative gradient. The deferred migration removes each entry as its bake lands.
|
||||
/// Transient migration state, but persisted in the saved document so unfinished bakes retry on the next open instead of losing placement.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub(crate) pending_gradient_bbox_bake: Vec<(Vec<NodeId>, NodeId, Gradient)>,
|
||||
pub(crate) pending_gradient_bbox_bake: Vec<(Vec<NodeId>, NodeId, graphic_types::migrations::legacy::Gradient)>,
|
||||
|
||||
// =============================================
|
||||
// Fields omitted from the saved document format
|
||||
@@ -2763,30 +2763,20 @@ impl DocumentMessageHandler {
|
||||
let mut resulting_layers: Vec<NodeId> = Vec::new();
|
||||
|
||||
for layer in selected_layers {
|
||||
let style = self.network_interface.document_metadata().layer_vector_data.get(&layer).map(|arc| arc.style.clone());
|
||||
let Some(style) = style else {
|
||||
let Some(vector_data) = self.network_interface.document_metadata().layer_vector_data.get(&layer) else {
|
||||
resulting_layers.push(layer.to_node());
|
||||
continue;
|
||||
};
|
||||
let stroke = vector_data.stroke.as_ref();
|
||||
|
||||
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);
|
||||
|
||||
// `ATTR_FILL` is the source of truth when set; fall back to the legacy `style.fill` only when no attribute is present
|
||||
let has_fill = if let Some(list) = fill_graphic_list {
|
||||
is_paint_present(list)
|
||||
} else {
|
||||
!matches!(style.fill, Fill::None)
|
||||
};
|
||||
// `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.
|
||||
let stroke_visible = if let Some(list) = stroke_graphic_list {
|
||||
list.element(0).is_some_and(|g| !g.is_fully_transparent())
|
||||
} else {
|
||||
style.stroke.as_ref().and_then(|s| s.color()).is_some_and(|c| c.a() != 0.)
|
||||
};
|
||||
let has_stroke = style.stroke.as_ref().is_some_and(|s| s.has_renderable_stroke()) && stroke_visible;
|
||||
let has_fill = fill_graphic_list.is_some_and(|list| is_paint_present(list));
|
||||
// `Vector.stroke` captures stroke geometry, even with weight 0 or transparent paint.
|
||||
// So stroke visibility must be checked from `ATTR_STROKE`, the paint source of truth.
|
||||
let stroke_visible = stroke_graphic_list.is_some_and(|list| list.element(0).is_some_and(|g| !g.is_fully_transparent()));
|
||||
let has_stroke = stroke.as_ref().is_some_and(|s| s.has_renderable_stroke()) && stroke_visible;
|
||||
|
||||
// No stroke means there's nothing to solidify. Fill-only layers are already in the desired form, so skip.
|
||||
if !has_stroke {
|
||||
@@ -4017,7 +4007,7 @@ mod document_message_handler_tests {
|
||||
#[test]
|
||||
fn pending_gradient_bakes_round_trip_through_serialization() {
|
||||
let document = DocumentMessageHandler {
|
||||
pending_gradient_bbox_bake: vec![(vec![NodeId(7)], NodeId(42), Gradient::default())],
|
||||
pending_gradient_bbox_bake: vec![(vec![NodeId(7)], NodeId(42), graphic_types::migrations::legacy::Gradient::default())],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
@@ -10,15 +10,22 @@ 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::{GradientSpreadMethod, GradientType, Stroke};
|
||||
use graphene_std::vector::{GradientStops, PointId, VectorModificationType};
|
||||
|
||||
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GraphOperationMessage {
|
||||
FillSet {
|
||||
FillColorSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
fill: Fill,
|
||||
color: Option<Color>,
|
||||
},
|
||||
FillGradientSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
gradient: GradientStops,
|
||||
gradient_type: GradientType,
|
||||
spread_method: GradientSpreadMethod,
|
||||
transform: DAffine2,
|
||||
},
|
||||
BlendingFillSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
@@ -28,10 +35,9 @@ pub enum GraphOperationMessage {
|
||||
layer: LayerNodeIdentifier,
|
||||
stops: GradientStops,
|
||||
},
|
||||
GradientLineSet {
|
||||
GradientTransformSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
start: DVec2,
|
||||
end: DVec2,
|
||||
transform: DAffine2,
|
||||
},
|
||||
GradientTypeSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
@@ -54,6 +60,7 @@ pub enum GraphOperationMessage {
|
||||
},
|
||||
StrokeSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
color: Option<Color>,
|
||||
stroke: Stroke,
|
||||
},
|
||||
TransformChange {
|
||||
|
||||
+54
-59
@@ -13,7 +13,7 @@ use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::list::List;
|
||||
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};
|
||||
use graphene_std::vector::style::{GradientSpreadMethod, GradientStop, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::{Artboard, Color};
|
||||
|
||||
#[derive(ExtractField)]
|
||||
@@ -34,9 +34,20 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
let GraphOperationMessageContext { network_interface, .. } = context;
|
||||
|
||||
match message {
|
||||
GraphOperationMessage::FillSet { layer, fill } => {
|
||||
GraphOperationMessage::FillColorSet { layer, color } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.fill_set(fill);
|
||||
modify_inputs.fill_color_set(color);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::FillGradientSet {
|
||||
layer,
|
||||
gradient,
|
||||
gradient_type,
|
||||
spread_method,
|
||||
transform,
|
||||
} => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.fill_gradient_set(gradient, gradient_type, spread_method, transform);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::BlendingFillSet { layer, fill } => {
|
||||
@@ -49,9 +60,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
modify_inputs.gradient_stops_set(stops);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::GradientLineSet { layer, start, end } => {
|
||||
GraphOperationMessage::GradientTransformSet { layer, transform } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.gradient_line_set(start, end);
|
||||
modify_inputs.gradient_transform_set(transform);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::GradientTypeSet { layer, gradient_type } => {
|
||||
@@ -80,9 +91,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
|
||||
modify_inputs.clip_mode_toggle(clip_mode);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::StrokeSet { layer, stroke } => {
|
||||
GraphOperationMessage::StrokeSet { layer, color, stroke } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
|
||||
modify_inputs.stroke_set(stroke);
|
||||
modify_inputs.stroke_set(color, stroke);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::TransformChange {
|
||||
@@ -622,7 +633,7 @@ fn import_usvg_node(
|
||||
usvg::Node::Text(text) => {
|
||||
let font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_std::consts::DEFAULT_FONT_STYLE.to_string());
|
||||
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, TypesettingConfig::default(), layer);
|
||||
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
|
||||
modify_inputs.fill_color_set(Some(Color::BLACK));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -674,7 +685,7 @@ fn import_usvg_node_inner(
|
||||
usvg::Node::Text(text) => {
|
||||
let font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_std::consts::DEFAULT_FONT_STYLE.to_string());
|
||||
modify_inputs.insert_text(text.chunks().iter().map(|chunk| chunk.text()).collect(), font, TypesettingConfig::default(), layer);
|
||||
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
|
||||
modify_inputs.fill_color_set(Some(Color::BLACK));
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -762,27 +773,29 @@ fn set_import_child_positions(
|
||||
|
||||
fn apply_usvg_stroke(stroke: &usvg::Stroke, modify_inputs: &mut ModifyInputsContext, transform: DAffine2) {
|
||||
if let usvg::Paint::Color(color) = &stroke.paint() {
|
||||
modify_inputs.stroke_set(Stroke {
|
||||
color: Some(usvg_color(*color, stroke.opacity().get())),
|
||||
weight: stroke.width().get() as f64,
|
||||
dash_lengths: stroke.dasharray().as_ref().map(|lengths| lengths.iter().map(|&length| length as f64).collect()).unwrap_or_default(),
|
||||
dash_offset: stroke.dashoffset() as f64,
|
||||
cap: match stroke.linecap() {
|
||||
usvg::LineCap::Butt => StrokeCap::Butt,
|
||||
usvg::LineCap::Round => StrokeCap::Round,
|
||||
usvg::LineCap::Square => StrokeCap::Square,
|
||||
modify_inputs.stroke_set(
|
||||
Some(usvg_color(*color, stroke.opacity().get())),
|
||||
Stroke {
|
||||
weight: stroke.width().get() as f64,
|
||||
dash_lengths: stroke.dasharray().as_ref().map(|lengths| lengths.iter().map(|&length| length as f64).collect()).unwrap_or_default(),
|
||||
dash_offset: stroke.dashoffset() as f64,
|
||||
cap: match stroke.linecap() {
|
||||
usvg::LineCap::Butt => StrokeCap::Butt,
|
||||
usvg::LineCap::Round => StrokeCap::Round,
|
||||
usvg::LineCap::Square => StrokeCap::Square,
|
||||
},
|
||||
join: match stroke.linejoin() {
|
||||
usvg::LineJoin::Miter => StrokeJoin::Miter,
|
||||
usvg::LineJoin::MiterClip => StrokeJoin::Miter,
|
||||
usvg::LineJoin::Round => StrokeJoin::Round,
|
||||
usvg::LineJoin::Bevel => StrokeJoin::Bevel,
|
||||
},
|
||||
join_miter_limit: stroke.miterlimit().get() as f64,
|
||||
align: StrokeAlign::Center,
|
||||
paint_order: PaintOrder::StrokeAbove,
|
||||
transform,
|
||||
},
|
||||
join: match stroke.linejoin() {
|
||||
usvg::LineJoin::Miter => StrokeJoin::Miter,
|
||||
usvg::LineJoin::MiterClip => StrokeJoin::Miter,
|
||||
usvg::LineJoin::Round => StrokeJoin::Round,
|
||||
usvg::LineJoin::Bevel => StrokeJoin::Bevel,
|
||||
},
|
||||
join_miter_limit: stroke.miterlimit().get() as f64,
|
||||
align: StrokeAlign::Center,
|
||||
paint_order: PaintOrder::StrokeAbove,
|
||||
transform,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,16 +808,18 @@ fn convert_spread_method(spread_method: usvg::SpreadMethod) -> GradientSpreadMet
|
||||
}
|
||||
|
||||
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())),
|
||||
match &fill.paint() {
|
||||
usvg::Paint::Color(color) => modify_inputs.fill_color_set(Some(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 direction = end - start;
|
||||
let transform = DAffine2::from_cols(direction, direction.perp(), start);
|
||||
|
||||
let gradient_type = GradientType::Linear;
|
||||
|
||||
let stops = match graphite_gradient_stops.get(linear.id()) {
|
||||
let gradient = match graphite_gradient_stops.get(linear.id()) {
|
||||
Some(graphite_stops) => graphite_stops.clone(),
|
||||
None => {
|
||||
let stops = linear.stops().iter().map(|stop| GradientStop {
|
||||
@@ -816,27 +831,19 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, g
|
||||
}
|
||||
};
|
||||
let spread_method = convert_spread_method(linear.spread_method());
|
||||
|
||||
Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
gradient_type,
|
||||
stops,
|
||||
spread_method,
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
absolute: true,
|
||||
transform: DAffine2::IDENTITY,
|
||||
})
|
||||
modify_inputs.fill_gradient_set(gradient, gradient_type, spread_method, transform);
|
||||
}
|
||||
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 direction = end - start;
|
||||
let transform = DAffine2::from_cols(direction, direction.perp(), start);
|
||||
|
||||
let gradient_type = GradientType::Radial;
|
||||
|
||||
let stops = match graphite_gradient_stops.get(radial.id()) {
|
||||
let gradient = match graphite_gradient_stops.get(radial.id()) {
|
||||
Some(graphite_stops) => graphite_stops.clone(),
|
||||
None => {
|
||||
let stops = radial.stops().iter().map(|stop| GradientStop {
|
||||
@@ -849,20 +856,8 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, g
|
||||
};
|
||||
let spread_method = convert_spread_method(radial.spread_method());
|
||||
|
||||
Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
gradient_type,
|
||||
stops,
|
||||
spread_method,
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
absolute: true,
|
||||
transform: DAffine2::IDENTITY,
|
||||
})
|
||||
modify_inputs.fill_gradient_set(gradient, gradient_type, spread_method, transform);
|
||||
}
|
||||
usvg::Paint::Pattern(_) => {
|
||||
warn!("SVG patterns are not currently supported");
|
||||
return;
|
||||
}
|
||||
});
|
||||
usvg::Paint::Pattern(_) => warn!("SVG patterns are not currently supported"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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, build_transform_with_y_preservation};
|
||||
use graphene_std::vector::style::{GradientSpreadMethod, GradientType, Stroke};
|
||||
use graphene_std::vector::{GradientStops, PointId, Vector, VectorModification, VectorModificationType};
|
||||
use graphene_std::{Artboard, Color, Graphic, NodeInputDecleration};
|
||||
|
||||
@@ -453,74 +453,59 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
Some(node_id)
|
||||
}
|
||||
|
||||
pub fn fill_set(&mut self, fill: Fill) {
|
||||
pub fn fill_color_set(&mut self, color: Option<Color>) {
|
||||
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::<List<Graphic>>::INDEX);
|
||||
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupColorInput::INDEX);
|
||||
|
||||
match &fill {
|
||||
Fill::None => {
|
||||
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(backup_input_connector, NodeInput::value(TaggedValue::Color(color), false), true);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(color), false), false);
|
||||
}
|
||||
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(None), false), false);
|
||||
}
|
||||
Fill::Solid(color) => {
|
||||
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);
|
||||
pub fn fill_gradient_set(&mut self, gradient: GradientStops, gradient_type: GradientType, spread_method: GradientSpreadMethod, transform: DAffine2) {
|
||||
let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput::INDEX);
|
||||
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Some(*color)), false), false);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
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(backup_input_connector, NodeInput::value(TaggedValue::Gradient(gradient.clone()), false), true);
|
||||
|
||||
// Skip the rerender on all but the last input so the whole update triggers a single graph run
|
||||
self.set_input_with_refresh(
|
||||
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX),
|
||||
NodeInput::value(TaggedValue::Gradient(gradient.stops.clone()), false),
|
||||
true,
|
||||
);
|
||||
// Skip the rerender on all but the last input so the whole update triggers a single graph run
|
||||
self.set_input_with_refresh(
|
||||
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<List<Graphic>>::INDEX),
|
||||
NodeInput::value(TaggedValue::Gradient(gradient), false),
|
||||
true,
|
||||
);
|
||||
|
||||
// Reposition the gradient only when the transform is a plain value, leaving a wired transform source connected
|
||||
let old_transform: Option<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())
|
||||
.map(|value| {
|
||||
if let TaggedValue::OptionalDAffine2(transform) = value {
|
||||
transform.unwrap_or(DAffine2::IDENTITY)
|
||||
} else {
|
||||
DAffine2::IDENTITY
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(old_transform) = old_transform {
|
||||
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),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
self.set_input_with_refresh(
|
||||
InputConnector::node(fill_node_id, graphene_std::vector::fill::GradientTypeInput::INDEX),
|
||||
NodeInput::value(TaggedValue::GradientType(gradient.gradient_type), false),
|
||||
true,
|
||||
);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
// Reposition the gradient only when the transform is a plain value, leaving a wired transform source connected
|
||||
let transform_is_value = self
|
||||
.network_interface
|
||||
.document_network()
|
||||
.nodes
|
||||
.get(&fill_node_id)
|
||||
.and_then(|node| node.inputs.get(graphene_std::vector::fill::TransformInput::INDEX))
|
||||
.is_some_and(|input| input.as_value().is_some());
|
||||
if transform_is_value {
|
||||
self.set_input_with_refresh(
|
||||
InputConnector::node(fill_node_id, graphene_std::vector::fill::TransformInput::INDEX),
|
||||
NodeInput::value(TaggedValue::OptionalDAffine2(Some(transform)), false),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
self.set_input_with_refresh(
|
||||
InputConnector::node(fill_node_id, graphene_std::vector::fill::GradientTypeInput::INDEX),
|
||||
NodeInput::value(TaggedValue::GradientType(gradient_type), false),
|
||||
true,
|
||||
);
|
||||
|
||||
self.set_input_with_refresh(
|
||||
InputConnector::node(fill_node_id, graphene_std::vector::fill::SpreadMethodInput::INDEX),
|
||||
NodeInput::value(TaggedValue::GradientSpreadMethod(spread_method), false),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn blend_mode_set(&mut self, blend_mode: BlendMode) {
|
||||
@@ -622,10 +607,10 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(stops), false), false);
|
||||
}
|
||||
|
||||
/// Update the gradient line so its endpoints are at `new_start` and `new_end`.
|
||||
/// Update the transform to map the unit gradient ((0,0), (1, 0)) to the geometry's local space.
|
||||
/// With multiple `Transform` nodes the last one (closest to the layer) is modified so the chain still composes to the target.
|
||||
/// With none, one is inserted unless the target is the identity.
|
||||
pub fn gradient_line_set(&mut self, new_start: DVec2, new_end: DVec2) {
|
||||
pub fn gradient_transform_set(&mut self, transform: DAffine2) {
|
||||
let Some(output_layer) = self.get_output_layer() else { return };
|
||||
|
||||
let walk_from = if let Some(fill_input_node_id) = get_fill_input_node_id(output_layer, self.network_interface) {
|
||||
@@ -661,14 +646,9 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
.map_or(acc, |document_node| acc * transform_utils::get_current_transform(&document_node.inputs))
|
||||
})
|
||||
};
|
||||
let composed_old = compose(&upstream_transforms);
|
||||
let prior_combined = compose(prior_transforms);
|
||||
|
||||
// Rebuild the y-axis from the new x-axis using the old (parallel, perpendicular) decomposition and length ratio,
|
||||
// so the gradient's aspect ratio and skew survive an endpoint drag (so an ellipse stays the same ellipse) instead of
|
||||
// the old y-axis vector remaining fixed while x changes
|
||||
let new_composed = build_transform_with_y_preservation(composed_old, new_start, new_end);
|
||||
let last_transform_value = new_composed * prior_combined.inverse();
|
||||
let last_transform_value = transform * prior_combined.inverse();
|
||||
|
||||
let target_input = gradient_chain_target_input(output_layer, self.network_interface);
|
||||
let transform_node_id = if let Some(id) = last_transform_node_id {
|
||||
@@ -730,13 +710,13 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Bool(clip), false), false);
|
||||
}
|
||||
|
||||
pub fn stroke_set(&mut self, stroke: Stroke) {
|
||||
pub fn stroke_set(&mut self, color: Option<Color>, stroke: Stroke) {
|
||||
let Some(stroke_node_id) = self.existing_proto_node_id(graphene_std::vector::stroke::IDENTIFIER, true) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintInput::<List<Graphic>>::INDEX);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke.color), false), true);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(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);
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);
|
||||
|
||||
@@ -2428,10 +2428,10 @@ fn root_layer_for_chain_node(node_id: NodeId, context: &mut NodePropertiesContex
|
||||
|
||||
/// Resolve the viewport-space orientation of a Fill node's gradient by walking downstream to its owning layer
|
||||
/// and reusing the same helper the Gradient tool uses, so canvas tilt and layer transforms behave identically.
|
||||
fn gradient_orientation_in_fill_node(node_id: NodeId, start: DVec2, end: DVec2, context: &mut NodePropertiesContext) -> Option<bool> {
|
||||
fn gradient_orientation_in_fill_node(node_id: NodeId, gradient_transform: DAffine2, context: &mut NodePropertiesContext) -> Option<bool> {
|
||||
let layer = root_layer_for_chain_node(node_id, context)?;
|
||||
let transform = graph_modification_utils::gradient_space_transform(layer, context.network_interface);
|
||||
Some(graph_modification_utils::gradient_orientation_rightward(start, end, transform))
|
||||
Some(graph_modification_utils::gradient_orientation_rightward(transform * gradient_transform))
|
||||
}
|
||||
|
||||
/// Fill Node Widgets LayoutGroup
|
||||
@@ -2659,7 +2659,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
let start = transform.transform_point2(DVec2::ZERO);
|
||||
let end = transform.transform_point2(DVec2::X);
|
||||
let new_transform = build_transform_with_y_preservation(transform, end, start);
|
||||
let orientation_rightward = gradient_orientation_in_fill_node(node_id, start, end, context).unwrap_or(true);
|
||||
let orientation_rightward = gradient_orientation_in_fill_node(node_id, transform, context).unwrap_or(true);
|
||||
|
||||
let reverse_direction_button = IconButton::new(if orientation_rightward { "ReverseRadialGradientToRight" } else { "ReverseRadialGradientToLeft" }, 24)
|
||||
.tooltip_label("Reverse Direction")
|
||||
|
||||
@@ -27,7 +27,7 @@ impl FrontendGraphDataType {
|
||||
match TaggedValue::from_type_or_none(input) {
|
||||
TaggedValue::U32(_) | TaggedValue::U64(_) | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Array(_) | TaggedValue::DAffine2(_) => Self::Number,
|
||||
TaggedValue::Color(_) => Self::Color,
|
||||
TaggedValue::FillGradient(_) | TaggedValue::Gradient(_) => Self::Gradient,
|
||||
TaggedValue::LegacyGradient(_) | TaggedValue::Gradient(_) => Self::Gradient,
|
||||
TaggedValue::String(_) => Self::Typography,
|
||||
// Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name.
|
||||
TaggedValue::TypeDefault(td) => match td.name.as_ref() {
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ impl TypeSource {
|
||||
FrontendGraphDataType::Number
|
||||
}
|
||||
TaggedValue::Color(_) => FrontendGraphDataType::Color,
|
||||
TaggedValue::FillGradient(_) | TaggedValue::Gradient(_) => FrontendGraphDataType::Gradient,
|
||||
TaggedValue::LegacyGradient(_) | TaggedValue::Gradient(_) => FrontendGraphDataType::Gradient,
|
||||
TaggedValue::String(_) => FrontendGraphDataType::Typography,
|
||||
// Types whose `TaggedValue` variant has been removed are routed through `TypeDefault` and identified by the descriptor's type name.
|
||||
TaggedValue::TypeDefault(td) => match td.name.as_ref() {
|
||||
|
||||
@@ -14,7 +14,8 @@ use graphene_std::ProtoNodeIdentifier;
|
||||
use graphene_std::text::{TextAlign, TypesettingConfig};
|
||||
use graphene_std::transform::ScaleType;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::style::{Fill, PaintOrder, StrokeAlign};
|
||||
use graphene_std::vector::graphic_types;
|
||||
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
|
||||
use std::collections::HashMap;
|
||||
use std::f64::consts::PI;
|
||||
use std::ops::Range;
|
||||
@@ -1583,19 +1584,19 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
|
||||
// Fill: a literal Fill value is decomposed, and a wired input (`List<GradientStops> / List<Color>`) is kept as-is
|
||||
match old_inputs[1].as_value() {
|
||||
Some(TaggedValue::Fill(old_fill)) => {
|
||||
Some(TaggedValue::LegacyFill(old_fill)) => {
|
||||
let exposed = old_inputs[1].is_exposed();
|
||||
let fill_value = match old_fill {
|
||||
Fill::None => TaggedValue::Color(None),
|
||||
Fill::Solid(color) => TaggedValue::Color(Some(*color)),
|
||||
Fill::Gradient(gradient) => TaggedValue::Gradient(gradient.stops.clone()),
|
||||
graphic_types::migrations::legacy::Fill::None => TaggedValue::Color(None),
|
||||
graphic_types::migrations::legacy::Fill::Solid(color) => TaggedValue::Color(Some(*color)),
|
||||
graphic_types::migrations::legacy::Fill::Gradient(gradient) => TaggedValue::Gradient(gradient.stops.clone()),
|
||||
};
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(fill_value, exposed), network_path);
|
||||
|
||||
// Gradient metadata (4, 5, 6): applies only to a literal gradient, solids/none keep the template defaults
|
||||
if let Fill::Gradient(gradient) = old_fill {
|
||||
if let graphic_types::migrations::legacy::Fill::Gradient(gradient) = old_fill {
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 4),
|
||||
NodeInput::value(TaggedValue::GradientType(gradient.gradient_type), false),
|
||||
@@ -1620,7 +1621,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
}
|
||||
}
|
||||
// Wired/exposed fill keeps the connection.
|
||||
// The generic paint connector accepts `List<Color>`/`List<GradientStops>` sources directly, and there were no other nodes which can generate output type that implements `From` for `Fill`.
|
||||
// The generic paint connector accepts the existing `List<Color>`/`List<GradientStops>` paint sources directly.
|
||||
_ => {
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
}
|
||||
@@ -1630,13 +1631,18 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
|
||||
// Gradient backup: extract stops
|
||||
if let Some(TaggedValue::FillGradient(g)) = old_inputs[3].as_value() {
|
||||
if let Some(TaggedValue::LegacyGradient(g)) = old_inputs[3].as_value() {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(g.stops.clone()), false), network_path);
|
||||
|
||||
// A solid/no-fill node leaves the gradient metadata inputs unused, so seed them from the backup gradient for a later Solid -> Gradient toggle to restore
|
||||
if matches!(old_inputs[1].as_value(), Some(TaggedValue::Fill(Fill::None | Fill::Solid(_)))) {
|
||||
if matches!(
|
||||
old_inputs[1].as_value(),
|
||||
Some(TaggedValue::LegacyFill(
|
||||
graphic_types::migrations::legacy::Fill::None | graphic_types::migrations::legacy::Fill::Solid(_)
|
||||
))
|
||||
) {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::GradientType(g.gradient_type), false), network_path);
|
||||
|
||||
@@ -60,8 +60,7 @@ impl ToolColorOptions {
|
||||
return;
|
||||
}
|
||||
if let Some(FillChoice::Solid(color)) = &self.fill_choice {
|
||||
let fill = graphene_std::vector::style::Fill::Solid(*color);
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
responses.add(GraphOperationMessage::FillColorSet { layer, color: Some(*color) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +69,9 @@ impl ToolColorOptions {
|
||||
return;
|
||||
}
|
||||
if let Some(FillChoice::Solid(color)) = &self.fill_choice {
|
||||
let stroke = graphene_std::vector::style::Stroke::new(Some(*color), weight);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
|
||||
let color = Some(*color);
|
||||
let stroke = graphene_std::vector::style::Stroke::new(weight);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,8 +175,8 @@ impl DrawingToolState {
|
||||
return;
|
||||
}
|
||||
let Some(FillChoice::Solid(color)) = &self.stroke.fill_choice else { return };
|
||||
let color = Some(*color);
|
||||
let stroke = graphene_std::vector::style::Stroke {
|
||||
color: Some(*color),
|
||||
weight: self.effective_line_weight(),
|
||||
align: self.stroke_align.unwrap_or_default(),
|
||||
cap: self.stroke_cap.unwrap_or_default(),
|
||||
@@ -187,7 +187,7 @@ impl DrawingToolState {
|
||||
dash_offset: self.dash_offset.unwrap_or(0.),
|
||||
transform: glam::DAffine2::IDENTITY,
|
||||
};
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ 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, initial_gradient_transform_for_bounding_box};
|
||||
use graphene_std::vector::style::{FillChoice, 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;
|
||||
@@ -330,8 +330,8 @@ pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNe
|
||||
}
|
||||
|
||||
/// Compute the transform from a gradient's local space to viewport space for the given layer. For a `List<GradientStops>`
|
||||
/// layer this is the layer's incoming footprint transform; for the legacy `Fill::Gradient` path it composes the layer's
|
||||
/// viewport transform with the [0,1]² → bounding-box mapping.
|
||||
/// layer this is the layer's incoming footprint transform; for a Fill-owned gradient value it composes the layer's viewport
|
||||
/// transform with the [0,1]² → bounding-box mapping.
|
||||
pub fn gradient_space_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> glam::DAffine2 {
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
||||
|
||||
@@ -350,9 +350,9 @@ pub fn gradient_space_transform(layer: LayerNodeIdentifier, network_interface: &
|
||||
|
||||
/// True when start→end (mapped through `transform` into viewport space) points predominantly rightward. For purely
|
||||
/// vertical lines we fall back to a stable tiebreaker on (x + y) so the choice doesn't flicker between equal alternatives.
|
||||
pub fn gradient_orientation_rightward(start: glam::DVec2, end: glam::DVec2, transform: glam::DAffine2) -> bool {
|
||||
let viewport_start = transform.transform_point2(start);
|
||||
let viewport_end = transform.transform_point2(end);
|
||||
pub fn gradient_orientation_rightward(transform: glam::DAffine2) -> bool {
|
||||
let viewport_start = transform.transform_point2(DVec2::ZERO);
|
||||
let viewport_end = transform.transform_point2(DVec2::X);
|
||||
if (viewport_end.x - viewport_start.x).abs() > f64::EPSILON * 1e6 {
|
||||
viewport_end.x > viewport_start.x
|
||||
} else {
|
||||
@@ -617,8 +617,9 @@ pub fn set_stroke_weight_for_selected_layers(weight: f64, document: &DocumentMes
|
||||
let value = TaggedValue::F64(weight);
|
||||
responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value });
|
||||
} else if weight > 0. {
|
||||
let color = Some(Color::BLACK);
|
||||
let stroke = graphene_std::vector::style::Stroke::default().with_weight(weight);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -662,32 +663,6 @@ pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOn
|
||||
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_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::PaintInput::<List<Graphic>>::INDEX;
|
||||
@@ -717,10 +692,21 @@ pub struct SelectedStrokeState {
|
||||
pub fn selected_fill_state(document: &DocumentMessageHandler) -> Option<SelectedFillState> {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let mut per_layer = selected_nodes.selected_layers_except_artboards(&document.network_interface).map(|layer| {
|
||||
if get_fill_id(layer, &document.network_interface).is_none() {
|
||||
let Some(fill_node_id) = get_fill_id(layer, &document.network_interface) else {
|
||||
return (false, FillChoice::None);
|
||||
}
|
||||
let fill_choice = get_fill_value(layer, &document.network_interface).map_or(FillChoice::None, FillChoice::from);
|
||||
};
|
||||
|
||||
let fill_choice = (|| {
|
||||
let fill_node = document.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(FillChoice::None, FillChoice::Solid)),
|
||||
TaggedValue::Gradient(stops) => Some(FillChoice::Gradient(stops.clone())),
|
||||
_ => None,
|
||||
}
|
||||
})()
|
||||
.unwrap_or(FillChoice::None);
|
||||
|
||||
(true, fill_choice)
|
||||
});
|
||||
|
||||
@@ -795,12 +781,40 @@ pub fn selected_stroke_state(document: &DocumentMessageHandler) -> Option<Select
|
||||
pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
|
||||
for layer in layers {
|
||||
let existing_gradient = get_fill_value(layer, &document.network_interface).and_then(|f| match f {
|
||||
Fill::Gradient(g) => Some(g),
|
||||
_ => None,
|
||||
});
|
||||
let fill = fill_choice.clone().to_fill(existing_gradient.as_ref());
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
match &fill_choice {
|
||||
FillChoice::None => responses.add(GraphOperationMessage::FillColorSet { layer, color: None }),
|
||||
FillChoice::Solid(color) => responses.add(GraphOperationMessage::FillColorSet { layer, color: Some(*color) }),
|
||||
FillChoice::Gradient(stops) => {
|
||||
let fill_node = NodeGraphLayer::new(layer, &document.network_interface)
|
||||
.upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))
|
||||
.and_then(|id| document.network_interface.document_network().nodes.get(&id));
|
||||
|
||||
let read = |index: usize| fill_node.and_then(|node| node.inputs.get(index)).and_then(|input| input.as_value());
|
||||
|
||||
let gradient_type = match read(graphene_std::vector::fill::GradientTypeInput::INDEX) {
|
||||
Some(TaggedValue::GradientType(value)) => *value,
|
||||
_ => GradientType::default(),
|
||||
};
|
||||
let spread_method = match read(graphene_std::vector::fill::SpreadMethodInput::INDEX) {
|
||||
Some(TaggedValue::GradientSpreadMethod(value)) => *value,
|
||||
_ => GradientSpreadMethod::default(),
|
||||
};
|
||||
let transform = match read(graphene_std::vector::fill::TransformInput::INDEX) {
|
||||
Some(TaggedValue::OptionalDAffine2(value)) => {
|
||||
value.unwrap_or_else(|| initial_gradient_transform_for_bounding_box(document.network_interface.document_metadata().nonzero_bounding_box(layer)))
|
||||
}
|
||||
_ => DAffine2::IDENTITY,
|
||||
};
|
||||
|
||||
responses.add(GraphOperationMessage::FillGradientSet {
|
||||
layer,
|
||||
gradient: stops.clone(),
|
||||
gradient_type,
|
||||
spread_method,
|
||||
transform,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -815,8 +829,8 @@ pub fn set_stroke_color_for_selected_layers(color: Option<Color>, weight: f64, d
|
||||
let value = TaggedValue::Color(color);
|
||||
responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value });
|
||||
} else {
|
||||
let stroke = graphene_std::vector::style::Stroke::new(color, weight);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
|
||||
let stroke = graphene_std::vector::style::Stroke::new(weight);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::messages::tool::common_functionality::color_selector::solid;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::raster::color::Color;
|
||||
use graphene_std::vector::style::{Fill, FillChoiceUI};
|
||||
use graphene_std::vector::style::FillChoiceUI;
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct FillTool {
|
||||
@@ -161,14 +161,17 @@ impl Fsm for FillToolFsmState {
|
||||
if NodeGraphLayer::is_raster_layer(layer_identifier, &mut document.network_interface) {
|
||||
return self;
|
||||
}
|
||||
let fill = match color_event {
|
||||
FillToolMessage::FillPrimaryColor => Fill::Solid(global_tool_data.primary_color),
|
||||
FillToolMessage::FillSecondaryColor => Fill::Solid(global_tool_data.secondary_color),
|
||||
let color = match color_event {
|
||||
FillToolMessage::FillPrimaryColor => global_tool_data.primary_color,
|
||||
FillToolMessage::FillSecondaryColor => global_tool_data.secondary_color,
|
||||
_ => return self,
|
||||
};
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
responses.add(GraphOperationMessage::FillSet { layer: layer_identifier, fill });
|
||||
responses.add(GraphOperationMessage::FillColorSet {
|
||||
layer: layer_identifier,
|
||||
color: Some(color),
|
||||
});
|
||||
|
||||
FillToolFsmState::Filling
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2837,11 +2837,11 @@ impl Fsm for PathToolFsmState {
|
||||
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
|
||||
|
||||
// Defaults chosen because the pasted geometry has no inherent associated style
|
||||
let stroke = graphene_std::vector::style::Stroke::new(Some(Color::BLACK), DEFAULT_STROKE_WIDTH);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
|
||||
let color = Some(Color::BLACK);
|
||||
let stroke = graphene_std::vector::style::Stroke::new(DEFAULT_STROKE_WIDTH);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
|
||||
|
||||
let fill = graphene_std::vector::style::Fill::solid(Color::WHITE);
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
responses.add(GraphOperationMessage::FillColorSet { layer, color: Some(Color::WHITE) });
|
||||
|
||||
new_layers.push(layer);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ use graphene_std::choice_type::ChoiceTypeStatic;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, TextAlign, TypesettingConfig, lines_clipping};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillChoiceUI};
|
||||
use graphene_std::vector::style::{FillChoice, FillChoiceUI};
|
||||
use graphene_std::{Color, NodeInputDecleration};
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
@@ -571,9 +571,9 @@ impl TextToolData {
|
||||
parent: document.new_layer_parent(true),
|
||||
insert_index: 0,
|
||||
});
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
responses.add(GraphOperationMessage::FillColorSet {
|
||||
layer: self.layer,
|
||||
fill: editing_text.color.map_or(Fill::None, Fill::Solid),
|
||||
color: editing_text.color,
|
||||
});
|
||||
let transform = editing_text.transform;
|
||||
self.editing_text = Some(editing_text);
|
||||
|
||||
@@ -14,8 +14,7 @@ use graphene_std::memo::IORecord;
|
||||
use graphene_std::raster::{CPU, Raster};
|
||||
use graphene_std::renderer::{RenderMetadata, graphic_list_bounding_box};
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::Gradient;
|
||||
use graphene_std::vector::{Vector, graphic_types};
|
||||
use graphene_std::{ATTR_TRANSFORM, Context, Graphic, NodeInputDecleration};
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
|
||||
use std::any::Any;
|
||||
@@ -83,7 +82,7 @@ struct ExecutionContext {
|
||||
/// Set when this execution is a gradient-migration measurement run, carrying the "Fill" node (addressed by its enclosing
|
||||
/// network path) and its original relative gradient. The evaluated geometry is read back from the inspect result to size the
|
||||
/// gradient; such runs never touch the visible artwork. Carrying the entry keeps a stale re-dispatched response paired with the fill it measured.
|
||||
measure_fill: Option<(Vec<NodeId>, NodeId, Gradient)>,
|
||||
measure_fill: Option<(Vec<NodeId>, NodeId, graphic_types::migrations::legacy::Gradient)>,
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
@@ -93,7 +92,7 @@ struct ExecutionContext {
|
||||
#[derive(Debug, Clone)]
|
||||
struct GradientMigration {
|
||||
document_id: DocumentId,
|
||||
remaining: VecDeque<(Vec<NodeId>, NodeId, Gradient)>,
|
||||
remaining: VecDeque<(Vec<NodeId>, NodeId, graphic_types::migrations::legacy::Gradient)>,
|
||||
resolution: UVec2,
|
||||
scale: f64,
|
||||
}
|
||||
@@ -493,7 +492,7 @@ impl NodeGraphExecutor {
|
||||
}
|
||||
|
||||
// Snapshot the queue but leave `pending_gradient_bbox_bake` populated, so subsequent render requests keep deferring here (and hit the guard above); each entry is removed from the document as its bake lands.
|
||||
let remaining: VecDeque<(Vec<NodeId>, NodeId, Gradient)> = document.pending_gradient_bbox_bake.iter().cloned().collect();
|
||||
let remaining: VecDeque<(Vec<NodeId>, NodeId, graphic_types::migrations::legacy::Gradient)> = document.pending_gradient_bbox_bake.iter().cloned().collect();
|
||||
let Some((first_network_path, first_fill, first_gradient)) = remaining.front().cloned() else {
|
||||
return false;
|
||||
};
|
||||
@@ -520,7 +519,7 @@ impl NodeGraphExecutor {
|
||||
document_id: DocumentId,
|
||||
network_path: Vec<NodeId>,
|
||||
fill_node_id: NodeId,
|
||||
gradient: Gradient,
|
||||
gradient: graphic_types::migrations::legacy::Gradient,
|
||||
resolution: UVec2,
|
||||
scale: f64,
|
||||
responses: &mut VecDeque<Message>,
|
||||
@@ -589,7 +588,7 @@ impl NodeGraphExecutor {
|
||||
&mut self,
|
||||
document: &mut DocumentMessageHandler,
|
||||
document_id: DocumentId,
|
||||
bake_target: (Vec<NodeId>, NodeId, Gradient),
|
||||
bake_target: (Vec<NodeId>, NodeId, graphic_types::migrations::legacy::Gradient),
|
||||
inspect_result: Option<InspectResult>,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user