feat(svg-editor): implement interactive SVG node editor with parsing and editing capabilities
feat(debug-logger): add DebugSignalLogger plugin for event logging and inspection feat(plugins): introduce built-in plugins module and integrate debug logger feat(plugin-integration): create integration layer for mapping Iced Messages to CoreEvents feat(plugin-registry): establish PluginRegistry for managing plugins and event dispatching
This commit is contained in:
@@ -410,6 +410,7 @@ impl App {
|
||||
fn make_shape(&self, x1: f32, y1: f32, x2: f32, y2: f32) -> VectorShape {
|
||||
match self.shape_kind {
|
||||
ShapeKind::Line => VectorShape::Line {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -420,6 +421,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Rect => VectorShape::Rect {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -431,6 +433,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Circle => VectorShape::Circle {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -441,6 +444,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Arrow => VectorShape::Arrow {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -452,6 +456,7 @@ impl App {
|
||||
thick: self.arrow_thick,
|
||||
},
|
||||
ShapeKind::Star => VectorShape::Star {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -464,6 +469,7 @@ impl App {
|
||||
inner_radius: self.star_inner_radius,
|
||||
},
|
||||
ShapeKind::Polygon => VectorShape::Polygon {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -475,6 +481,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Rhombus => VectorShape::Rhombus {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -485,6 +492,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Cylinder => VectorShape::Cylinder {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -495,6 +503,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Heart => VectorShape::Heart {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -505,6 +514,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Bubble => VectorShape::Bubble {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -515,6 +525,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Gear => VectorShape::Gear {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -525,6 +536,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Cross => VectorShape::Cross {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -535,6 +547,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Crescent => VectorShape::Crescent {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -545,6 +558,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Bolt => VectorShape::Bolt {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -555,6 +569,7 @@ impl App {
|
||||
hardness: self.hardness,
|
||||
},
|
||||
ShapeKind::Arrow4 => VectorShape::Arrow4 {
|
||||
name: String::new(),
|
||||
x1, y1, x2, y2,
|
||||
stroke: self.stroke,
|
||||
color: self.color,
|
||||
@@ -572,6 +587,7 @@ impl App {
|
||||
[(x1 + x2) / 2.0, y2 + 50.0],
|
||||
];
|
||||
VectorShape::FreePath {
|
||||
name: String::new(),
|
||||
pts,
|
||||
closed: true,
|
||||
stroke: self.stroke,
|
||||
|
||||
+55
-4
@@ -751,8 +751,9 @@ fn render_shape(layer: &mut Layer, shape: &VectorShape) {
|
||||
draw_line(layer, pts[i].0, pts[i].1, pts[j].0, pts[j].1, px_color, *stroke, None);
|
||||
}
|
||||
}
|
||||
SvgShape { svg, x1, y1, x2, y2, fill, color, fill_color, stroke, opacity, hardness, .. } => {
|
||||
let points_list = svg_render::svg_to_points(svg, *x1, *y1, *x2, *y2).unwrap_or_default();
|
||||
SvgShape { svg, x1, y1, x2, y2, fill, color, fill_color, stroke, angle, opacity, hardness, .. } => {
|
||||
let mut points_list = svg_render::svg_to_points(svg, *x1, *y1, *x2, *y2).unwrap_or_default();
|
||||
rotate_svg_points(&mut points_list, *x1, *y1, *x2, *y2, *angle);
|
||||
let alpha = (color[3] as f32 * opacity).round() as u8;
|
||||
let px_color = [color[0], color[1], color[2], alpha];
|
||||
let prev = ACTIVE_HARDNESS.with(|h| h.get());
|
||||
@@ -788,6 +789,35 @@ fn render_shape(layer: &mut Layer, shape: &VectorShape) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotate tessellated SVG polygons around the center of their shape bounds.
|
||||
///
|
||||
/// **Purpose:** Applies the `SvgShape::angle` field to SVG geometry before fill and stroke
|
||||
/// rasterization. **Logic & Workflow:** Computes the same bounds center used by vector transform
|
||||
/// handles, then rotates every tessellated point by the stored radian angle. A zero angle exits
|
||||
/// without touching the point buffers. **Arguments:** `polygons` contains canvas-space SVG paths;
|
||||
/// `x1`, `y1`, `x2`, and `y2` define the shape bounds; `angle` is expressed in radians.
|
||||
/// **Returns:** Nothing. **Side Effects / Dependencies:** Mutates all points in `polygons` in place
|
||||
/// and relies on the shared `rotate_point` geometry helper.
|
||||
fn rotate_svg_points(
|
||||
polygons: &mut [Vec<(f32, f32)>],
|
||||
x1: f32,
|
||||
y1: f32,
|
||||
x2: f32,
|
||||
y2: f32,
|
||||
angle: f32,
|
||||
) {
|
||||
if angle.abs() <= f32::EPSILON {
|
||||
return;
|
||||
}
|
||||
let cx = (x1 + x2) * 0.5;
|
||||
let cy = (y1 + y2) * 0.5;
|
||||
for polygon in polygons {
|
||||
for point in polygon {
|
||||
*point = rotate_point(point.0, point.1, cx, cy, angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normalized_rect(x1: f32, y1: f32, x2: f32, y2: f32) -> (f32, f32, f32, f32) {
|
||||
(x1.min(x2), y1.min(y2), x1.max(x2), y1.max(y2))
|
||||
}
|
||||
@@ -1126,8 +1156,29 @@ mod tests {
|
||||
assert!((rx - 100.0).abs() < 1e-4);
|
||||
assert!((ry - 150.0).abs() < 1e-4);
|
||||
}
|
||||
|
||||
/// Verify SVG tessellation applies its stored angle around the bounds center.
|
||||
///
|
||||
/// **Purpose:** Prevents SVG-backed shapes from remaining visually fixed at zero degrees.
|
||||
/// **Logic & Workflow:** Rotates two asymmetric points by 90 degrees in a 100x100 bounds and
|
||||
/// compares their resulting canvas coordinates. **Arguments/Returns:** Standard unit test with
|
||||
/// no inputs or return value. **Side Effects / Dependencies:** None.
|
||||
#[test]
|
||||
fn svg_points_follow_shape_angle() {
|
||||
let mut polygons = vec![vec![(100.0, 50.0), (50.0, 25.0)]];
|
||||
rotate_svg_points(
|
||||
&mut polygons,
|
||||
0.0,
|
||||
0.0,
|
||||
100.0,
|
||||
100.0,
|
||||
std::f32::consts::FRAC_PI_2,
|
||||
);
|
||||
assert!((polygons[0][0].0 - 50.0).abs() < 1e-4);
|
||||
assert!((polygons[0][0].1 - 100.0).abs() < 1e-4);
|
||||
assert!((polygons[0][1].0 - 75.0).abs() < 1e-4);
|
||||
assert!((polygons[0][1].1 - 50.0).abs() < 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -397,6 +397,7 @@ pub fn boolean_shapes(op: BooleanOp, a: &VectorShape, b: &VectorShape) -> Option
|
||||
};
|
||||
|
||||
Some(VectorShape::FreePath {
|
||||
name: String::new(),
|
||||
pts: result_pts,
|
||||
closed: true,
|
||||
stroke,
|
||||
|
||||
Reference in New Issue
Block a user