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:
@@ -4586,6 +4586,24 @@ impl HcieApp {
|
||||
d, stroke_col, stroke, if *fill { fill_col } else { "none".to_string() }, if *fill { opacity } else { &0.0 }, opacity
|
||||
));
|
||||
}
|
||||
VectorShape::SvgShape { svg: inner_svg, x1, y1, x2, y2, stroke, color, fill_color, fill, opacity, .. } => {
|
||||
let stroke_col = format_color(color);
|
||||
let fill_col = format_color(fill_color);
|
||||
let x = x1.min(*x2);
|
||||
let y = y1.min(*y2);
|
||||
let w = (x2 - x1).abs();
|
||||
let h = (y2 - y1).abs();
|
||||
if let Some(di) = inner_svg.find(" d=\"") {
|
||||
let start = di + 4;
|
||||
if let Some(end) = inner_svg[start..].find('"') {
|
||||
let d = &inner_svg[start..start+end];
|
||||
svg.push_str(&format!(
|
||||
"<path d=\"{}\" transform=\"translate({},{}) scale({},{})\" stroke=\"{}\" stroke-width=\"{}\" fill=\"{}\" fill-opacity=\"{}\" stroke-opacity=\"{}\"/>\n",
|
||||
d, x, y, w, h, stroke_col, stroke, if *fill { fill_col } else { "none".to_string() }, if *fill { opacity } else { &0.0 }, opacity
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
svg.push_str("</svg>");
|
||||
|
||||
@@ -26,6 +26,23 @@ fn shape_label(shapes: &[VectorShape], i: usize) -> String {
|
||||
VectorShape::Crescent { .. } => format!("Crescent #{}", i + 1),
|
||||
VectorShape::Bolt { .. } => format!("Bolt #{}", i + 1),
|
||||
VectorShape::Arrow4 { .. } => format!("4-way Arrow #{}", i + 1),
|
||||
VectorShape::SvgShape { kind, .. } => {
|
||||
let label = match kind.as_str() {
|
||||
"arrow" => "Arrow",
|
||||
"star" => "Star",
|
||||
"rhombus" => "Rhombus",
|
||||
"cylinder" => "Cylinder",
|
||||
"heart" => "Heart",
|
||||
"bubble" => "Bubble",
|
||||
"gear" => "Gear",
|
||||
"cross" => "Cross",
|
||||
"crescent" => "Crescent",
|
||||
"bolt" => "Bolt",
|
||||
"arrow4" => "4-way Arrow",
|
||||
_ => kind,
|
||||
};
|
||||
format!("{} #{}", label, i + 1)
|
||||
}
|
||||
VectorShape::FreePath { pts, .. } => format!("Path ({} pts) #{}", pts.len(), i + 1),
|
||||
}
|
||||
}
|
||||
@@ -99,6 +116,23 @@ pub fn show_geometry(
|
||||
VectorShape::Crescent { .. } => format!("Crescent #{}", i + 1),
|
||||
VectorShape::Bolt { .. } => format!("Bolt #{}", i + 1),
|
||||
VectorShape::Arrow4 { .. } => format!("4-way Arrow #{}", i + 1),
|
||||
VectorShape::SvgShape { kind, .. } => {
|
||||
let label = match kind.as_str() {
|
||||
"arrow" => "Arrow",
|
||||
"star" => "Star",
|
||||
"rhombus" => "Rhombus",
|
||||
"cylinder" => "Cylinder",
|
||||
"heart" => "Heart",
|
||||
"bubble" => "Bubble",
|
||||
"gear" => "Gear",
|
||||
"cross" => "Cross",
|
||||
"crescent" => "Crescent",
|
||||
"bolt" => "Bolt",
|
||||
"arrow4" => "4-way Arrow",
|
||||
_ => kind,
|
||||
};
|
||||
format!("{} #{}", label, i + 1)
|
||||
}
|
||||
VectorShape::FreePath { pts, .. } => format!("Path ({} pts) #{}", pts.len(), i + 1),
|
||||
};
|
||||
let is_sel = state.selected_vector_shape == Some(i);
|
||||
|
||||
@@ -86,6 +86,9 @@ pub fn sync_shape_from_tool(doc: &mut AppDocument, state: &mut ToolState) {
|
||||
VectorShape::Polygon { .. } => {
|
||||
// polygon_sides via engine when available
|
||||
}
|
||||
VectorShape::SvgShape { .. } => {
|
||||
// shape-specific params are baked into the SVG
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +139,9 @@ pub fn sync_tool_from_shape(doc: &AppDocument, state: &mut ToolState) {
|
||||
} => {
|
||||
v.line_cap = *cap_start;
|
||||
}
|
||||
VectorShape::SvgShape { .. } => {
|
||||
// params baked into SVG, nothing to sync
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
||||
@@ -2216,34 +2216,28 @@ fn create_shape_from_drag(
|
||||
opacity,
|
||||
hardness,
|
||||
},
|
||||
Tool::VectorArrow => VectorShape::Arrow {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
thick: false,
|
||||
Tool::VectorArrow => {
|
||||
let mut p = std::collections::HashMap::new();
|
||||
p.insert("thick".to_string(), 0.0);
|
||||
match hcie_engine_api::create_vector_shape("arrow", x1, y1, x2, y2, &p) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorStar => VectorShape::Star {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
points: state.tool_configs.vector.star_points,
|
||||
inner_radius: state.tool_configs.vector.star_inner_radius,
|
||||
Tool::VectorStar => {
|
||||
let mut p = std::collections::HashMap::new();
|
||||
p.insert("points".to_string(), state.tool_configs.vector.star_points as f32);
|
||||
p.insert("inner_radius".to_string(), state.tool_configs.vector.star_inner_radius);
|
||||
match hcie_engine_api::create_vector_shape("star", x1, y1, x2, y2, &p) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorPolygon => VectorShape::Polygon {
|
||||
x1,
|
||||
@@ -2259,122 +2253,86 @@ fn create_shape_from_drag(
|
||||
hardness,
|
||||
sides: state.tool_configs.vector.polygon_sides,
|
||||
},
|
||||
Tool::VectorRhombus => VectorShape::Rhombus {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
Tool::VectorRhombus => {
|
||||
match hcie_engine_api::create_vector_shape("rhombus", x1, y1, x2, y2, &std::collections::HashMap::new()) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorCylinder => VectorShape::Cylinder {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
Tool::VectorCylinder => {
|
||||
match hcie_engine_api::create_vector_shape("cylinder", x1, y1, x2, y2, &std::collections::HashMap::new()) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorHeart => VectorShape::Heart {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
Tool::VectorHeart => {
|
||||
match hcie_engine_api::create_vector_shape("heart", x1, y1, x2, y2, &std::collections::HashMap::new()) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorBubble => VectorShape::Bubble {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
Tool::VectorBubble => {
|
||||
match hcie_engine_api::create_vector_shape("bubble", x1, y1, x2, y2, &std::collections::HashMap::new()) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorGear => VectorShape::Gear {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
Tool::VectorGear => {
|
||||
match hcie_engine_api::create_vector_shape("gear", x1, y1, x2, y2, &std::collections::HashMap::new()) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorCross => VectorShape::Cross {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
Tool::VectorCross => {
|
||||
match hcie_engine_api::create_vector_shape("cross", x1, y1, x2, y2, &std::collections::HashMap::new()) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorCrescent => VectorShape::Crescent {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
Tool::VectorCrescent => {
|
||||
match hcie_engine_api::create_vector_shape("crescent", x1, y1, x2, y2, &std::collections::HashMap::new()) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorBolt => VectorShape::Bolt {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
Tool::VectorBolt => {
|
||||
match hcie_engine_api::create_vector_shape("bolt", x1, y1, x2, y2, &std::collections::HashMap::new()) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Tool::VectorArrow4 => VectorShape::Arrow4 {
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
stroke,
|
||||
color,
|
||||
fill_color,
|
||||
fill,
|
||||
angle: 0.0,
|
||||
opacity,
|
||||
hardness,
|
||||
Tool::VectorArrow4 => {
|
||||
match hcie_engine_api::create_vector_shape("arrow4", x1, y1, x2, y2, &std::collections::HashMap::new()) {
|
||||
VectorShape::SvgShape { kind, svg, .. } => VectorShape::SvgShape {
|
||||
kind, svg, x1, y1, x2, y2, stroke, color, fill_color, fill,
|
||||
angle: 0.0, opacity, hardness,
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
_ => VectorShape::Line {
|
||||
x1,
|
||||
|
||||
@@ -386,6 +386,14 @@ fn get_shape_key_points(shape: &VectorShape) -> (f32, f32, f32, Vec<(f32, f32)>)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
VectorShape::SvgShape { .. } => {
|
||||
vec![
|
||||
rotate(left, top),
|
||||
rotate(right, top),
|
||||
rotate(right, bottom),
|
||||
rotate(left, bottom),
|
||||
]
|
||||
}
|
||||
_ => {
|
||||
vec![
|
||||
rotate(left, top),
|
||||
|
||||
Reference in New Issue
Block a user