From 418f4adc15e208f61d04fd4dfe21a9edc94b2238 Mon Sep 17 00:00:00 2001 From: Halit Can Date: Thu, 16 Jul 2026 05:36:37 +0300 Subject: [PATCH] feat(iced): add accurate wireframe previews for all 15 vector shapes --- .../crates/hcie-iced-gui/src/canvas/mod.rs | 313 ++++++++++++++++++ 1 file changed, 313 insertions(+) diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/canvas/mod.rs b/hcie-iced-app/crates/hcie-iced-gui/src/canvas/mod.rs index 36acc01..3a7f227 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/canvas/mod.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/canvas/mod.rs @@ -747,6 +747,319 @@ impl canvas::Program for OverlayProgram { }); 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 // back to a bounding-box outline for the preview.