feat: introduce SVG-based complex vector shapes

- Added `SvgShape` variant to `VectorShape` in `hcie-protocol`, allowing for SVG rendering of complex shapes.
- Updated serialization to skip old shape variants for backward compatibility.
- Implemented SVG generation functions in `svg_templates.rs` for various shapes (arrow, star, rhombus, etc.).
- Created `svg_render.rs` to parse SVG strings and tessellate paths using `usvg`.
- Modified rendering logic in `hcie-vector` to handle `SvgShape` and integrate with existing shape rendering.
- Updated shape creation API in `hcie-engine-api` to support SVG shapes.
- Enhanced property editing and shape synchronization in both `hcie-egui-app` and `hcie-iced-app` to accommodate new SVG shapes.
- Added comprehensive documentation for new features and changes.
This commit is contained in:
2026-07-19 02:44:59 +03:00
parent e90cb2d31d
commit 5632c916ee
17 changed files with 1124 additions and 300 deletions
+50
View File
@@ -352,6 +352,7 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
#[serde(skip_serializing)]
Arrow {
x1: f32,
y1: f32,
@@ -371,6 +372,7 @@ pub enum VectorShape {
#[serde(default)]
thick: bool,
},
#[serde(skip_serializing)]
Star {
x1: f32,
y1: f32,
@@ -411,6 +413,7 @@ pub enum VectorShape {
#[serde(default)]
sides: u32,
},
#[serde(skip_serializing)]
Rhombus {
x1: f32,
y1: f32,
@@ -428,6 +431,7 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
#[serde(skip_serializing)]
Cylinder {
x1: f32,
y1: f32,
@@ -445,6 +449,7 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
#[serde(skip_serializing)]
Heart {
x1: f32,
y1: f32,
@@ -462,6 +467,7 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
#[serde(skip_serializing)]
Bubble {
x1: f32,
y1: f32,
@@ -479,6 +485,7 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
#[serde(skip_serializing)]
Gear {
x1: f32,
y1: f32,
@@ -496,6 +503,7 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
#[serde(skip_serializing)]
Cross {
x1: f32,
y1: f32,
@@ -513,6 +521,7 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
#[serde(skip_serializing)]
Crescent {
x1: f32,
y1: f32,
@@ -530,6 +539,7 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
#[serde(skip_serializing)]
Bolt {
x1: f32,
y1: f32,
@@ -547,6 +557,7 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
#[serde(skip_serializing)]
Arrow4 {
x1: f32,
y1: f32,
@@ -564,6 +575,25 @@ pub enum VectorShape {
#[serde(default = "default_hardness")]
hardness: f32,
},
SvgShape {
x1: f32,
y1: f32,
x2: f32,
y2: f32,
kind: String,
svg: String,
stroke: f32,
color: [u8; 4],
#[serde(default = "default_fill_color")]
fill_color: [u8; 4],
fill: bool,
#[serde(default)]
angle: f32,
#[serde(default = "default_opacity")]
opacity: f32,
#[serde(default = "default_hardness")]
hardness: f32,
},
FreePath {
pts: Vec<[f32; 2]>,
closed: bool,
@@ -1986,6 +2016,7 @@ impl VectorShape {
| VectorShape::Cross { x1, y1, x2, y2, .. }
| VectorShape::Crescent { x1, y1, x2, y2, .. }
| VectorShape::Arrow4 { x1, y1, x2, y2, .. }
| VectorShape::SvgShape { x1, y1, x2, y2, .. }
| VectorShape::Bolt { x1, y1, x2, y2, .. } => (*x1, *y1, *x2, *y2),
VectorShape::FreePath { pts, .. } => {
let x1 = pts.iter().map(|p| p[0]).fold(f32::INFINITY, f32::min);
@@ -2024,6 +2055,7 @@ impl VectorShape {
| VectorShape::Crescent { color, .. }
| VectorShape::Arrow4 { color, .. }
| VectorShape::Bolt { color, .. }
| VectorShape::SvgShape { color, .. }
| VectorShape::FreePath { color, .. } => *color,
}
}
@@ -2045,6 +2077,7 @@ impl VectorShape {
| VectorShape::Crescent { color, .. }
| VectorShape::Arrow4 { color, .. }
| VectorShape::Bolt { color, .. }
| VectorShape::SvgShape { color, .. }
| VectorShape::FreePath { color, .. } => *color = new_color,
}
}
@@ -2066,6 +2099,7 @@ impl VectorShape {
| VectorShape::Crescent { opacity, .. }
| VectorShape::Arrow4 { opacity, .. }
| VectorShape::Bolt { opacity, .. }
| VectorShape::SvgShape { opacity, .. }
| VectorShape::FreePath { opacity, .. } => *opacity,
}
}
@@ -2088,6 +2122,7 @@ impl VectorShape {
| VectorShape::Crescent { opacity, .. }
| VectorShape::Arrow4 { opacity, .. }
| VectorShape::Bolt { opacity, .. }
| VectorShape::SvgShape { opacity, .. }
| VectorShape::FreePath { opacity, .. } => *opacity = clamped,
}
}
@@ -2109,6 +2144,7 @@ impl VectorShape {
| VectorShape::Crescent { hardness, .. }
| VectorShape::Arrow4 { hardness, .. }
| VectorShape::Bolt { hardness, .. }
| VectorShape::SvgShape { hardness, .. }
| VectorShape::FreePath { hardness, .. } => *hardness,
}
}
@@ -2131,6 +2167,7 @@ impl VectorShape {
| VectorShape::Crescent { hardness, .. }
| VectorShape::Arrow4 { hardness, .. }
| VectorShape::Bolt { hardness, .. }
| VectorShape::SvgShape { hardness, .. }
| VectorShape::FreePath { hardness, .. } => *hardness = clamped,
}
}
@@ -2152,6 +2189,7 @@ impl VectorShape {
| VectorShape::Crescent { stroke, .. }
| VectorShape::Arrow4 { stroke, .. }
| VectorShape::Bolt { stroke, .. }
| VectorShape::SvgShape { stroke, .. }
| VectorShape::FreePath { stroke, .. } => *stroke,
}
}
@@ -2174,6 +2212,7 @@ impl VectorShape {
| VectorShape::Crescent { stroke, .. }
| VectorShape::Arrow4 { stroke, .. }
| VectorShape::Bolt { stroke, .. }
| VectorShape::SvgShape { stroke, .. }
| VectorShape::FreePath { stroke, .. } => *stroke = clamped,
}
}
@@ -2387,6 +2426,7 @@ impl VectorShape {
| VectorShape::Cross { angle, .. }
| VectorShape::Crescent { angle, .. }
| VectorShape::Arrow4 { angle, .. }
| VectorShape::SvgShape { angle, .. }
| VectorShape::Bolt { angle, .. } => *angle,
VectorShape::FreePath { .. } => 0.0,
}
@@ -2408,6 +2448,7 @@ impl VectorShape {
| VectorShape::Cross { angle, .. }
| VectorShape::Crescent { angle, .. }
| VectorShape::Arrow4 { angle, .. }
| VectorShape::SvgShape { angle, .. }
| VectorShape::Bolt { angle, .. } => {
*angle += delta;
}
@@ -2431,6 +2472,7 @@ impl VectorShape {
| VectorShape::Cross { angle, .. }
| VectorShape::Crescent { angle, .. }
| VectorShape::Arrow4 { angle, .. }
| VectorShape::SvgShape { angle, .. }
| VectorShape::Bolt { angle, .. } => {
*angle = new_angle;
}
@@ -2454,6 +2496,7 @@ impl VectorShape {
| VectorShape::Cross { angle, .. }
| VectorShape::Crescent { angle, .. }
| VectorShape::Arrow4 { angle, .. }
| VectorShape::SvgShape { angle, .. }
| VectorShape::Bolt { angle, .. } => Some(angle),
VectorShape::FreePath { .. } => None,
}
@@ -2474,6 +2517,7 @@ impl VectorShape {
| VectorShape::Cross { fill, .. }
| VectorShape::Crescent { fill, .. }
| VectorShape::Arrow4 { fill, .. }
| VectorShape::SvgShape { fill, .. }
| VectorShape::Bolt { fill, .. } => *fill,
_ => false,
}
@@ -2494,6 +2538,7 @@ impl VectorShape {
| VectorShape::Cross { fill_color, .. }
| VectorShape::Crescent { fill_color, .. }
| VectorShape::Arrow4 { fill_color, .. }
| VectorShape::SvgShape { fill_color, .. }
| VectorShape::Bolt { fill_color, .. } => Some(*fill_color),
_ => None,
}
@@ -2514,6 +2559,7 @@ impl VectorShape {
| VectorShape::Cross { fill, .. }
| VectorShape::Crescent { fill, .. }
| VectorShape::Arrow4 { fill, .. }
| VectorShape::SvgShape { fill, .. }
| VectorShape::Bolt { fill, .. } => Some(*fill),
_ => None,
}
@@ -2534,6 +2580,7 @@ impl VectorShape {
| VectorShape::Cross { fill, .. }
| VectorShape::Crescent { fill, .. }
| VectorShape::Arrow4 { fill, .. }
| VectorShape::SvgShape { fill, .. }
| VectorShape::Bolt { fill, .. } => Some(fill),
_ => None,
}
@@ -2554,6 +2601,7 @@ impl VectorShape {
| VectorShape::Cross { fill_color, .. }
| VectorShape::Crescent { fill_color, .. }
| VectorShape::Arrow4 { fill_color, .. }
| VectorShape::SvgShape { fill_color, .. }
| VectorShape::Bolt { fill_color, .. } => Some(fill_color),
_ => None,
}
@@ -2576,6 +2624,7 @@ impl VectorShape {
| VectorShape::Crescent { stroke, .. }
| VectorShape::Arrow4 { stroke, .. }
| VectorShape::Bolt { stroke, .. }
| VectorShape::SvgShape { stroke, .. }
| VectorShape::FreePath { stroke, .. } => Some(stroke),
}
}
@@ -2659,6 +2708,7 @@ impl VectorShape {
| VectorShape::Cross { x1, y1, x2, y2, .. }
| VectorShape::Crescent { x1, y1, x2, y2, .. }
| VectorShape::Arrow4 { x1, y1, x2, y2, .. }
| VectorShape::SvgShape { x1, y1, x2, y2, .. }
| VectorShape::Bolt { x1, y1, x2, y2, .. } => (x1, y1, x2, y2),
VectorShape::FreePath { .. } => unreachable!("coords_mut not valid for FreePath"),
}