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:
2026-07-20 01:43:22 +03:00
parent 6e0d179be9
commit 2fb47520b3
46 changed files with 4093 additions and 314 deletions
+16
View File
@@ -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
View File
@@ -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);
}
}
+1
View File
@@ -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,