feat(iced): add accurate wireframe previews for all 15 vector shapes
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user