feat(iced): add accurate wireframe previews for all 15 vector shapes

This commit is contained in:
2026-07-16 05:36:37 +03:00
parent d9cae597f9
commit 418f4adc15
@@ -747,6 +747,319 @@ impl canvas::Program<Message> for OverlayProgram {
}); });
frame.stroke(&poly_path, stroke_style); frame.stroke(&poly_path, stroke_style);
} }
hcie_engine_api::Tool::VectorArrow => {
// Arrow: triangle head at the end, shaft line from start to end.
let p0 = Point::new(origin_x + x0 * self.zoom, origin_y + y0 * self.zoom);
let p1 = Point::new(origin_x + x1 * self.zoom, origin_y + y1 * self.zoom);
let shaft_path = Path::line(p0, p1);
frame.stroke(&shaft_path, stroke_style);
// Triangle head at the end point.
let dx = x1 - x0;
let dy = y1 - y0;
let len = (dx * dx + dy * dy).sqrt().max(1.0);
let nx = dx / len;
let ny = dy / len;
let head_len = len * 0.25;
let head_w = len * 0.18;
let hx = origin_x + x1 * self.zoom;
let hy = origin_y + y1 * self.zoom;
let base_x = origin_x + (x1 - nx * head_len) * self.zoom;
let base_y = origin_y + (y1 - ny * head_len) * self.zoom;
let tip = Point::new(hx, hy);
let left = Point::new(base_x - ny * head_w, base_y + nx * head_w);
let right = Point::new(base_x + ny * head_w, base_y - nx * head_w);
let head_path = Path::new(|b| {
b.move_to(tip);
b.line_to(left);
b.line_to(right);
b.close();
});
frame.stroke(&head_path, stroke_style);
}
hcie_engine_api::Tool::VectorRhombus => {
// Diamond shape: top, right, bottom, left of bounding box.
let cx = origin_x + ((x0 + x1) / 2.0) * self.zoom;
let cy = origin_y + ((y0 + y1) / 2.0) * self.zoom;
let hw = ((x1 - x0).abs() / 2.0).max(1.0) * self.zoom;
let hh = ((y1 - y0).abs() / 2.0).max(1.0) * self.zoom;
let rhombus_path = Path::new(|b| {
b.move_to(Point::new(cx, cy - hh)); // top
b.line_to(Point::new(cx + hw, cy)); // right
b.line_to(Point::new(cx, cy + hh)); // bottom
b.line_to(Point::new(cx - hw, cy)); // left
b.close();
});
frame.stroke(&rhombus_path, stroke_style);
}
hcie_engine_api::Tool::VectorCylinder => {
// Cylinder: ellipse at top and bottom, vertical lines connecting them.
let left = origin_x + x0.min(x1) * self.zoom;
let right = origin_x + x0.max(x1) * self.zoom;
let top = origin_y + y0.min(y1) * self.zoom;
let bottom = origin_y + y0.max(y1) * self.zoom;
let hw = (right - left) / 2.0;
let ellipse_rx = hw;
let ellipse_ry = hw * 0.3;
let segments = 36;
// Top ellipse
let top_cy = top + ellipse_ry;
let top_ellipse = Path::new(|b| {
for i in 0..=segments {
let t = i as f32 / segments as f32 * std::f32::consts::TAU;
let px = left + hw + ellipse_rx * t.cos();
let py = top_cy + ellipse_ry * t.sin();
if i == 0 {
b.move_to(Point::new(px, py));
} else {
b.line_to(Point::new(px, py));
}
}
b.close();
});
frame.stroke(&top_ellipse, stroke_style);
// Bottom ellipse
let bot_cy = bottom - ellipse_ry;
let bot_ellipse = Path::new(|b| {
for i in 0..=segments {
let t = i as f32 / segments as f32 * std::f32::consts::TAU;
let px = left + hw + ellipse_rx * t.cos();
let py = bot_cy + ellipse_ry * t.sin();
if i == 0 {
b.move_to(Point::new(px, py));
} else {
b.line_to(Point::new(px, py));
}
}
b.close();
});
frame.stroke(&bot_ellipse, stroke_style);
// Left vertical line
let left_line = Path::line(
Point::new(left, top_cy),
Point::new(left, bot_cy),
);
frame.stroke(&left_line, stroke_style);
// Right vertical line
let right_line = Path::line(
Point::new(right, top_cy),
Point::new(right, bot_cy),
);
frame.stroke(&right_line, stroke_style);
}
hcie_engine_api::Tool::VectorHeart => {
// Heart: two arcs at top meeting at bottom point.
let cx = origin_x + ((x0 + x1) / 2.0) * self.zoom;
let top = origin_y + y0.min(y1) * self.zoom;
let bottom = origin_y + y0.max(y1) * self.zoom;
let hw = ((x1 - x0).abs() / 2.0).max(1.0) * self.zoom;
let hh = bottom - top;
let cp_off = hw * 0.55;
let heart_path = Path::new(|b| {
b.move_to(Point::new(cx, top + hh * 0.35));
// Right curve
b.bezier_curve_to(
Point::new(cx + cp_off, top - hh * 0.15),
Point::new(cx + hw, top + hh * 0.1),
Point::new(cx, bottom),
);
// Left curve
b.bezier_curve_to(
Point::new(cx - hw, top + hh * 0.1),
Point::new(cx - cp_off, top - hh * 0.15),
Point::new(cx, top + hh * 0.35),
);
b.close();
});
frame.stroke(&heart_path, stroke_style);
}
hcie_engine_api::Tool::VectorBubble => {
// Bubble: ellipse with a small triangle tail at bottom-left.
let cx = origin_x + ((x0 + x1) / 2.0) * self.zoom;
let cy = origin_y + ((y0 + y1) / 2.0) * self.zoom;
let rx = ((x1 - x0).abs() / 2.0).max(1.0) * self.zoom;
let ry = ((y1 - y0).abs() / 2.0).max(1.0) * self.zoom;
let segments = 48;
let bubble_path = Path::new(|b| {
for i in 0..=segments {
let t = i as f32 / segments as f32 * std::f32::consts::TAU;
let px = cx + rx * t.cos();
let py = cy + ry * t.sin();
if i == 0 {
b.move_to(Point::new(px, py));
} else {
b.line_to(Point::new(px, py));
}
}
b.close();
});
frame.stroke(&bubble_path, stroke_style);
// Triangle tail at bottom-left.
let tail_base_x = cx - rx * 0.35;
let tail_base_y = cy + ry * 0.7;
let tail_tip_x = cx - rx * 0.6;
let tail_tip_y = cy + ry * 1.15;
let tail_path = Path::new(|b| {
b.move_to(Point::new(tail_base_x - rx * 0.15, tail_base_y));
b.line_to(Point::new(tail_tip_x, tail_tip_y));
b.line_to(Point::new(tail_base_x + rx * 0.15, tail_base_y));
b.close();
});
frame.stroke(&tail_path, stroke_style);
}
hcie_engine_api::Tool::VectorGear => {
// Gear: circle with teeth (small rectangles around perimeter).
let cx = origin_x + ((x0 + x1) / 2.0) * self.zoom;
let cy = origin_y + ((y0 + y1) / 2.0) * self.zoom;
let r_out = ((x1 - x0).abs() / 2.0).max(1.0) * self.zoom;
let r_in = r_out * 0.78;
let teeth = 8;
let segments = 48;
let gear_path = Path::new(|b| {
for i in 0..teeth {
let a0 = (i as f32) * std::f32::consts::TAU / teeth as f32;
let a1 = (i as f32 + 0.35) * std::f32::consts::TAU / teeth as f32;
let a2 = (i as f32 + 0.65) * std::f32::consts::TAU / teeth as f32;
let a3 = ((i + 1) as f32) * std::f32::consts::TAU / teeth as f32;
let p0 = Point::new(cx + r_in * a0.cos(), cy + r_in * a0.sin());
let p1 = Point::new(cx + r_out * a1.cos(), cy + r_out * a1.sin());
let p2 = Point::new(cx + r_out * a2.cos(), cy + r_out * a2.sin());
let p3 = Point::new(cx + r_in * a3.cos(), cy + r_in * a3.sin());
if i == 0 {
b.move_to(p0);
} else {
b.line_to(p0);
}
b.line_to(p1);
b.line_to(p2);
b.line_to(p3);
}
b.close();
});
frame.stroke(&gear_path, stroke_style);
}
hcie_engine_api::Tool::VectorCross => {
// Cross: plus sign shape (horizontal + vertical bars).
let cx = origin_x + ((x0 + x1) / 2.0) * self.zoom;
let cy = origin_y + ((y0 + y1) / 2.0) * self.zoom;
let hw = ((x1 - x0).abs() / 2.0).max(1.0) * self.zoom;
let hh = ((y1 - y0).abs() / 2.0).max(1.0) * self.zoom;
let arm = hw * 0.3;
let cross_path = Path::new(|b| {
// Horizontal bar
b.move_to(Point::new(cx - hw, cy - arm));
b.line_to(Point::new(cx + hw, cy - arm));
b.line_to(Point::new(cx + hw, cy + arm));
b.line_to(Point::new(cx - hw, cy + arm));
b.close();
});
frame.stroke(&cross_path, stroke_style);
let cross_path2 = Path::new(|b| {
// Vertical bar
b.move_to(Point::new(cx - arm, cy - hh));
b.line_to(Point::new(cx + arm, cy - hh));
b.line_to(Point::new(cx + arm, cy + hh));
b.line_to(Point::new(cx - arm, cy + hh));
b.close();
});
frame.stroke(&cross_path2, stroke_style);
}
hcie_engine_api::Tool::VectorCrescent => {
// Crescent: outer and inner arcs forming a moon shape.
let cx = origin_x + ((x0 + x1) / 2.0) * self.zoom;
let cy = origin_y + ((y0 + y1) / 2.0) * self.zoom;
let r = ((x1 - x0).abs() / 2.0).max(1.0) * self.zoom;
let segments = 48;
let inner_offset = r * 0.35;
let crescent_path = Path::new(|b| {
// Outer arc (full circle)
for i in 0..=segments {
let t = i as f32 / segments as f32 * std::f32::consts::TAU;
let px = cx + r * t.cos();
let py = cy + r * t.sin();
if i == 0 {
b.move_to(Point::new(px, py));
} else {
b.line_to(Point::new(px, py));
}
}
b.close();
});
frame.stroke(&crescent_path, stroke_style);
// Inner arc (smaller, offset to create crescent)
let inner_path = Path::new(|b| {
for i in 0..=segments {
let t = i as f32 / segments as f32 * std::f32::consts::TAU;
let px = cx + inner_offset + (r * 0.7) * t.cos();
let py = cy + (r * 0.7) * t.sin();
if i == 0 {
b.move_to(Point::new(px, py));
} else {
b.line_to(Point::new(px, py));
}
}
b.close();
});
frame.stroke(&inner_path, stroke_style);
}
hcie_engine_api::Tool::VectorBolt => {
// Bolt: zigzag lightning bolt shape.
let left = origin_x + x0.min(x1) * self.zoom;
let right = origin_x + x0.max(x1) * self.zoom;
let top = origin_y + y0.min(y1) * self.zoom;
let bottom = origin_y + y0.max(y1) * self.zoom;
let hw = (right - left) / 2.0;
let bolt_path = Path::new(|b| {
b.move_to(Point::new(left + hw * 0.4, top));
b.line_to(Point::new(left + hw * 0.8, top));
b.line_to(Point::new(left + hw * 0.55, top + (bottom - top) * 0.42));
b.line_to(Point::new(left + hw * 0.85, top + (bottom - top) * 0.42));
b.line_to(Point::new(left + hw * 0.25, bottom));
b.line_to(Point::new(left + hw * 0.45, top + (bottom - top) * 0.55));
b.line_to(Point::new(left + hw * 0.15, top + (bottom - top) * 0.55));
b.close();
});
frame.stroke(&bolt_path, stroke_style);
}
hcie_engine_api::Tool::VectorArrow4 => {
// 4-way arrow: up/down/left/right from center.
let cx = origin_x + ((x0 + x1) / 2.0) * self.zoom;
let cy = origin_y + ((y0 + y1) / 2.0) * self.zoom;
let hw = ((x1 - x0).abs() / 2.0).max(1.0) * self.zoom;
let hh = ((y1 - y0).abs() / 2.0).max(1.0) * self.zoom;
let arm = hw * 0.25;
let arrow4_path = Path::new(|b| {
// Up arrow
b.move_to(Point::new(cx - arm, cy));
b.line_to(Point::new(cx - arm, cy - hh + arm));
b.line_to(Point::new(cx - arm * 2.0, cy - hh + arm));
b.line_to(Point::new(cx, cy - hh));
b.line_to(Point::new(cx + arm * 2.0, cy - hh + arm));
b.line_to(Point::new(cx + arm, cy - hh + arm));
b.line_to(Point::new(cx + arm, cy));
// Right arrow
b.line_to(Point::new(cx + hw - arm, cy));
b.line_to(Point::new(cx + hw - arm, cy - arm * 2.0));
b.line_to(Point::new(cx + hw, cy));
b.line_to(Point::new(cx + hw - arm, cy + arm * 2.0));
b.line_to(Point::new(cx + hw - arm, cy));
// Down arrow
b.line_to(Point::new(cx + arm, cy));
b.line_to(Point::new(cx + arm, cy + hh - arm));
b.line_to(Point::new(cx + arm * 2.0, cy + hh - arm));
b.line_to(Point::new(cx, cy + hh));
b.line_to(Point::new(cx - arm * 2.0, cy + hh - arm));
b.line_to(Point::new(cx - arm, cy + hh - arm));
b.line_to(Point::new(cx - arm, cy));
// Left arrow
b.line_to(Point::new(cx - hw + arm, cy));
b.line_to(Point::new(cx - hw + arm, cy - arm * 2.0));
b.line_to(Point::new(cx - hw, cy));
b.line_to(Point::new(cx - hw + arm, cy + arm * 2.0));
b.line_to(Point::new(cx - hw + arm, cy));
b.close();
});
frame.stroke(&arrow4_path, stroke_style);
}
_ => { _ => {
// Rectangular shapes (Rect, Arrow, Rhombus, etc.) fall // Rectangular shapes (Rect, Arrow, Rhombus, etc.) fall
// back to a bounding-box outline for the preview. // back to a bounding-box outline for the preview.