Correctly apply transforms to vector data and strokes (#1977)

* Fix adding a layer to a transformed group

* Fix assorted transform issues

* Default stroke transform

* Fix bench

* Transform gradient

* Gradient fix

* Add gradient reversal buttons to Fill node in the Properties panel

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
James Lindsay 2024-09-15 23:26:11 +01:00 committed by GitHub
parent 2fa8773092
commit dd4a97b09f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 152 additions and 59 deletions

View file

@ -266,7 +266,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
apply_usvg_fill(path.fill(), modify_inputs, transform * usvg_transform(node.abs_transform()), bounds_transform);
apply_usvg_stroke(path.stroke(), modify_inputs);
apply_usvg_stroke(path.stroke(), modify_inputs, transform * usvg_transform(node.abs_transform()));
}
usvg::Node::Image(_image) => {
warn!("Skip image")
@ -279,7 +279,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
}
}
fn apply_usvg_stroke(stroke: Option<&usvg::Stroke>, modify_inputs: &mut ModifyInputsContext) {
fn apply_usvg_stroke(stroke: Option<&usvg::Stroke>, modify_inputs: &mut ModifyInputsContext, transform: DAffine2) {
if let Some(stroke) = stroke {
if let usvg::Paint::Color(color) = &stroke.paint() {
modify_inputs.stroke_set(Stroke {
@ -299,6 +299,7 @@ fn apply_usvg_stroke(stroke: Option<&usvg::Stroke>, modify_inputs: &mut ModifyIn
usvg::LineJoin::Bevel => LineJoin::Bevel,
},
line_join_miter_limit: stroke.miterlimit().get() as f64,
transform,
})
} else {
warn!("Skip non-solid stroke")

View file

@ -2597,7 +2597,28 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context:
let fill_type_switch = {
let mut row = vec![TextLabel::new("").widget_holder()];
add_blank_assist(&mut row);
match fill {
Fill::Solid(_) | Fill::None => add_blank_assist(&mut row),
Fill::Gradient(gradient) => {
let reverse_button = IconButton::new("Reverse", 24)
.tooltip("Reverse the gradient color stops")
.on_update(update_value(
{
let gradient = gradient.clone();
move |_| {
let mut gradient = gradient.clone();
gradient.stops = gradient.stops.reversed();
TaggedValue::Fill(Fill::Gradient(gradient))
}
},
node_id,
fill_index,
))
.widget_holder();
row.push(Separator::new(SeparatorType::Unrelated).widget_holder());
row.push(reverse_button);
}
}
let entries = vec![
RadioEntryData::new("solid")
@ -2619,9 +2640,35 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context:
};
widgets.push(fill_type_switch);
if let Fill::Gradient(gradient) = fill {
if let Fill::Gradient(gradient) = fill.clone() {
let mut row = vec![TextLabel::new("").widget_holder()];
add_blank_assist(&mut row);
match gradient.gradient_type {
GradientType::Linear => add_blank_assist(&mut row),
GradientType::Radial => {
let orientation = if (gradient.end.x - gradient.start.x).abs() > f64::EPSILON * 1e6 {
gradient.end.x > gradient.start.x
} else {
(gradient.start.x + gradient.start.y) < (gradient.end.x + gradient.end.y)
};
let reverse_radial_gradient_button = IconButton::new(if orientation { "ReverseRadialGradientToRight" } else { "ReverseRadialGradientToLeft" }, 24)
.tooltip("Reverse which end the gradient radiates from")
.on_update(update_value(
{
let gradient = gradient.clone();
move |_| {
let mut gradient = gradient.clone();
std::mem::swap(&mut gradient.start, &mut gradient.end);
TaggedValue::Fill(Fill::Gradient(gradient))
}
},
node_id,
fill_index,
))
.widget_holder();
row.push(Separator::new(SeparatorType::Unrelated).widget_holder());
row.push(reverse_radial_gradient_button);
}
}
let new_gradient1 = gradient.clone();
let new_gradient2 = gradient.clone();

View file

@ -206,16 +206,15 @@ impl Fsm for EllipseToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(true), responses);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
shape_data.layer = Some(layer);
responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
shape_data.layer = Some(layer);
EllipseToolFsmState::Drawing
}

View file

@ -233,7 +233,7 @@ impl Fsm for FreehandToolFsmState {
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
tool_data.layer = Some(layer);
let transform = document.metadata().transform_to_viewport(layer);
let transform = document.metadata().transform_to_viewport(parent);
let position = transform.inverse().transform_point2(input.mouse.position);
extend_path_with_next_segment(tool_data, position, responses);

View file

@ -86,11 +86,11 @@ impl LayoutHolder for GradientTool {
let gradient_type = RadioInput::new(vec![
RadioEntryData::new("linear")
.label("Linear")
.tooltip("Linear Gradient")
.tooltip("Linear gradient")
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()),
RadioEntryData::new("radial")
.label("Radial")
.tooltip("Radial Gradient")
.tooltip("Radial gradient")
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()),
])
.selected_index(Some((self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial) as u32))

View file

@ -188,15 +188,14 @@ impl Fsm for LineToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(false), responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
tool_data.layer = Some(layer);
responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
tool_data.layer = Some(layer);
tool_data.layer = Some(layer);
tool_data.weight = tool_options.line_weight;

View file

@ -265,16 +265,15 @@ impl Fsm for PolygonToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(false), responses);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
polygon_data.layer = Some(layer);
responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
polygon_data.layer = Some(layer);
PolygonToolFsmState::Drawing
}

View file

@ -212,16 +212,15 @@ impl Fsm for RectangleToolFsmState {
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(true), responses);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
shape_data.layer = Some(layer);
responses.add(GraphOperationMessage::TransformSet {
layer,
transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position),
transform_in: TransformIn::Viewport,
skip_rerender: false,
});
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
shape_data.layer = Some(layer);
RectangleToolFsmState::Drawing
}

View file

@ -284,7 +284,7 @@ impl NodeRuntime {
for monitor_node_path in &self.monitor_nodes {
// The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID
let Some(parent_network_node_id) = monitor_node_path.get(monitor_node_path.len() - 2).copied() else {
let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else {
warn!("Monitor node has invalid node id");
continue;