#![allow(unreachable_patterns, unused_assignments, unused_must_use)] #![allow(deprecated)] use eframe::egui; use eframe::egui::Color32; use egui::ColorImage; use hcie_protocol::{Layer, LayerData, VectorShape, LineCap}; use hcie_vector::render_vector_shapes; const TILE_SIZE: u32 = 512; #[derive(Debug, Clone, Copy, PartialEq)] enum CanvasPreset { S512, S1024, S2048, S4096 } impl CanvasPreset { fn size(self) -> (u32, u32) { match self { Self::S512 => (512, 512), Self::S1024 => (1024, 1024), Self::S2048 => (2048, 2048), Self::S4096 => (4096, 4096), } } fn name(self) -> &'static str { match self { Self::S512 => "512×512", Self::S1024 => "1024×1024", Self::S2048 => "2048×2048", Self::S4096 => "4096×4096", } } } #[derive(Debug, Clone, Copy, PartialEq)] enum FpsCap { Unlimited, Fps30, Fps60, Fps165 } impl FpsCap { fn value(self) -> Option { match self { Self::Fps30 => Some(30.0), Self::Fps60 => Some(60.0), Self::Fps165 => Some(165.0), Self::Unlimited => None, } } fn name(self) -> &'static str { match self { Self::Fps30 => "30", Self::Fps60 => "60", Self::Fps165 => "165", Self::Unlimited => "∞", } } } struct Tile { texture: Option, dirty: bool, } struct TiledCanvas { pixels: Vec, tiles: Vec, w: u32, h: u32, cols: u32, dirty_count: usize, } impl TiledCanvas { fn new(w: u32, h: u32) -> Self { let cols = w.div_ceil(TILE_SIZE); let rows = h.div_ceil(TILE_SIZE); let total = (cols * rows) as usize; Self { pixels: vec![255; (w * h * 4) as usize], tiles: (0..total).map(|_| Tile { texture: None, dirty: true }).collect(), w, h, cols, dirty_count: total, } } fn resize(&mut self, w: u32, h: u32) { *self = Self::new(w, h); } fn clear(&mut self) { self.pixels.fill(255); self.mark_all_dirty(); } fn mark_dirty_rect(&mut self, cx: f32, cy: f32, radius: f32) { let tx0 = ((cx - radius).max(0.0) / TILE_SIZE as f32) as u32; let tx1 = (((cx + radius).min(self.w as f32 - 1.0) / TILE_SIZE as f32) as u32) .min(self.cols.saturating_sub(1)); let ty0 = ((cy - radius).max(0.0) / TILE_SIZE as f32) as u32; let ty1 = (((cy + radius).min(self.h as f32 - 1.0) / TILE_SIZE as f32) as u32) .min(self.h.div_ceil(TILE_SIZE).saturating_sub(1)); for ty in ty0..=ty1 { for tx in tx0..=tx1 { let idx = (ty * self.cols + tx) as usize; if !self.tiles[idx].dirty { self.tiles[idx].dirty = true; self.dirty_count += 1; } } } } fn mark_all_dirty(&mut self) { for t in &mut self.tiles { t.dirty = true; } self.dirty_count = self.tiles.len(); } fn upload_dirty(&mut self, ctx: &egui::Context) { if self.dirty_count == 0 { return; } let rows = self.h.div_ceil(TILE_SIZE); for ty in 0..rows { let tile_h = if ty == rows - 1 { self.h - ty * TILE_SIZE } else { TILE_SIZE } as usize; for tx in 0..self.cols { let idx = (ty * self.cols + tx) as usize; if !self.tiles[idx].dirty { continue; } self.tiles[idx].dirty = false; self.dirty_count = self.dirty_count.saturating_sub(1); let tile_w = if tx == self.cols - 1 { self.w - tx * TILE_SIZE } else { TILE_SIZE } as usize; let px = (tx * TILE_SIZE) as usize; let py = (ty * TILE_SIZE) as usize; let cap = tile_w * tile_h * 4; let mut data = vec![0u8; cap]; for row in 0..tile_h { let src = ((py + row) * self.w as usize + px) * 4; let dst = row * tile_w * 4; data[dst..dst + tile_w * 4] .copy_from_slice(&self.pixels[src..src + tile_w * 4]); } let img = ColorImage::from_rgba_unmultiplied([tile_w, tile_h], &data); let key = format!("t{}_{}", tx, ty); if let Some(tex) = &mut self.tiles[idx].texture { tex.set(img, egui::TextureOptions::NEAREST); } else { self.tiles[idx].texture = Some(ctx.load_texture(key, img, egui::TextureOptions::NEAREST)); } } } } } #[derive(Debug, Clone, Copy, PartialEq)] enum ShapeKind { Line, Rect, Circle, Arrow, Star, Polygon, Rhombus, Cylinder, Heart, Bubble, Gear, Cross, Crescent, Bolt, Arrow4, FreePath, } impl ShapeKind { fn all() -> &'static [ShapeKind] { &[ ShapeKind::Line, ShapeKind::Rect, ShapeKind::Circle, ShapeKind::Arrow, ShapeKind::Star, ShapeKind::Polygon, ShapeKind::Rhombus, ShapeKind::Cylinder, ShapeKind::Heart, ShapeKind::Bubble, ShapeKind::Gear, ShapeKind::Cross, ShapeKind::Crescent, ShapeKind::Bolt, ShapeKind::Arrow4, ShapeKind::FreePath, ] } fn name(self) -> &'static str { match self { ShapeKind::Line => "Line", ShapeKind::Rect => "Rect", ShapeKind::Circle => "Circle", ShapeKind::Arrow => "Arrow", ShapeKind::Star => "Star", ShapeKind::Polygon => "Polygon", ShapeKind::Rhombus => "Rhombus", ShapeKind::Cylinder => "Cylinder", ShapeKind::Heart => "Heart", ShapeKind::Bubble => "Bubble", ShapeKind::Gear => "Gear", ShapeKind::Cross => "Cross", ShapeKind::Crescent => "Crescent", ShapeKind::Bolt => "Bolt", ShapeKind::Arrow4 => "Arrow4", ShapeKind::FreePath => "FreePath", } } } #[allow(dead_code)] struct SliderCaps { stroke: bool, angle: bool, opacity: bool, hardness: bool, fill: bool, fill_color: bool, rect_radius: bool, star_points: bool, star_inner_radius: bool, polygon_sides: bool, arrow_thick: bool, line_cap: bool, } impl SliderCaps { fn for_shape(kind: ShapeKind) -> Self { match kind { ShapeKind::Line => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: false, fill_color: false, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: true, }, ShapeKind::Rect => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: true, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Circle => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Arrow => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: true, line_cap: false, }, ShapeKind::Star => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: true, star_inner_radius: true, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Polygon => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: true, arrow_thick: false, line_cap: false, }, ShapeKind::Rhombus => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Cylinder => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Heart => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Bubble => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Gear => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Cross => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Crescent => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Bolt => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::Arrow4 => SliderCaps { stroke: true, angle: true, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, ShapeKind::FreePath => SliderCaps { stroke: true, angle: false, opacity: true, hardness: true, fill: true, fill_color: true, rect_radius: false, star_points: false, star_inner_radius: false, polygon_sides: false, arrow_thick: false, line_cap: false, }, } } } struct App { canvas: TiledCanvas, canvas_preset: CanvasPreset, base_pixels: Vec, shape_kind: ShapeKind, stroke: f32, color: [u8; 4], fill_color: [u8; 4], fill: bool, angle: f32, opacity: f32, hardness: f32, star_points: u32, star_inner_radius: f32, polygon_sides: u32, rect_radius: f32, arrow_thick: bool, line_cap: LineCap, drag_start: Option, shape_count: u64, fps_cap: FpsCap, last_frame: std::time::Instant, fps: f32, } impl App { fn new(_cc: &eframe::CreationContext<'_>) -> Self { let preset = CanvasPreset::S1024; let (w, h) = preset.size(); let canvas = TiledCanvas::new(w, h); let base_pixels = canvas.pixels.clone(); let now = std::time::Instant::now(); Self { canvas, canvas_preset: preset, base_pixels, shape_kind: ShapeKind::Rect, stroke: 3.0, color: [0, 0, 0, 255], fill_color: [255, 128, 0, 200], fill: true, angle: 0.0, opacity: 1.0, hardness: 1.0, star_points: 5, star_inner_radius: 0.4, polygon_sides: 6, rect_radius: 0.0, arrow_thick: false, line_cap: LineCap::Round, drag_start: None, shape_count: 0, fps_cap: FpsCap::Fps60, last_frame: now, fps: 0.0, } } fn set_canvas_size(&mut self, p: CanvasPreset) { if self.canvas_preset == p { return; } self.canvas_preset = p; let (w, h) = p.size(); self.canvas.resize(w, h); self.base_pixels = self.canvas.pixels.clone(); self.shape_count = 0; } fn make_shape(&self, x1: f32, y1: f32, x2: f32, y2: f32) -> VectorShape { match self.shape_kind { ShapeKind::Line => VectorShape::Line { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, angle: self.angle, cap_start: self.line_cap, cap_end: self.line_cap, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Rect => VectorShape::Rect { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, radius: self.rect_radius, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Circle => VectorShape::Circle { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Arrow => VectorShape::Arrow { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, thick: self.arrow_thick, }, ShapeKind::Star => VectorShape::Star { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, points: self.star_points, inner_radius: self.star_inner_radius, }, ShapeKind::Polygon => VectorShape::Polygon { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, sides: self.polygon_sides, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Rhombus => VectorShape::Rhombus { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Cylinder => VectorShape::Cylinder { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Heart => VectorShape::Heart { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Bubble => VectorShape::Bubble { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Gear => VectorShape::Gear { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Cross => VectorShape::Cross { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Crescent => VectorShape::Crescent { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Bolt => VectorShape::Bolt { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::Arrow4 => VectorShape::Arrow4 { name: String::new(), x1, y1, x2, y2, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, angle: self.angle, opacity: self.opacity, hardness: self.hardness, }, ShapeKind::FreePath => { let pts = vec![ [x1, y1], [(x1 + x2) / 2.0, y1 - 50.0], [x2, y2], [(x1 + x2) / 2.0, y2 + 50.0], ]; VectorShape::FreePath { name: String::new(), pts, closed: true, stroke: self.stroke, color: self.color, fill_color: self.fill_color, fill: self.fill, opacity: self.opacity, hardness: self.hardness, } } } } } impl eframe::App for App { fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { let ctx = ui.ctx().clone(); let now = std::time::Instant::now(); if let Some(limit) = self.fps_cap.value() { let interval = std::time::Duration::from_secs_f32(1.0 / limit); let elapsed = now - self.last_frame; if elapsed < interval { std::thread::sleep(interval - elapsed); } } let after_sleep = std::time::Instant::now(); let dt = (after_sleep - self.last_frame).as_secs_f32(); self.last_frame = after_sleep; if dt > 0.0 { self.fps = self.fps * 0.9 + (1.0 / dt) * 0.1; } self.canvas.upload_dirty(&ctx); let caps = SliderCaps::for_shape(self.shape_kind); egui::Panel::left("panel").show(&ctx, |ui| { ui.heading("Vector Shape Test"); let limit_label = match self.fps_cap { FpsCap::Fps30 => "30", FpsCap::Fps60 => "60", FpsCap::Fps165 => "165", FpsCap::Unlimited => "∞", }; ui.label(format!( "FPS: {:.0}/{} │ dirty: {} │ shapes: {}", self.fps, limit_label, self.canvas.dirty_count, self.shape_count )); ui.label(format!( "Canvas: {}×{} │ Tiles: {}×{}", self.canvas.w, self.canvas.h, self.canvas.cols, self.canvas.h.div_ceil(TILE_SIZE), )); ui.separator(); ui.label("Canvas Size"); egui::ComboBox::from_id_salt("canvas_preset") .selected_text(self.canvas_preset.name()) .show_ui(ui, |ui| { for p in &[CanvasPreset::S512, CanvasPreset::S1024, CanvasPreset::S2048, CanvasPreset::S4096] { if ui.selectable_label(self.canvas_preset == *p, p.name()).clicked() { self.set_canvas_size(*p); } } }); ui.label("FPS Cap"); egui::ComboBox::from_id_salt("fps_cap") .selected_text(self.fps_cap.name()) .show_ui(ui, |ui| { for c in &[FpsCap::Fps30, FpsCap::Fps60, FpsCap::Fps165, FpsCap::Unlimited] { if ui.selectable_label(self.fps_cap == *c, c.name()).clicked() { self.fps_cap = *c; } } }); ui.separator(); ui.label("Shape"); egui::ComboBox::from_id_salt("shape_kind") .selected_text(self.shape_kind.name()) .show_ui(ui, |ui| { for &s in ShapeKind::all() { ui.selectable_value(&mut self.shape_kind, s, s.name()); } }); ui.add_enabled(caps.stroke, egui::Slider::new(&mut self.stroke, 1.0..=50.0).text("Stroke")); ui.add_enabled(caps.angle, egui::Slider::new(&mut self.angle, 0.0..=360.0).text("Angle (°)")); ui.add_enabled(caps.opacity, egui::Slider::new(&mut self.opacity, 0.0..=1.0).text("Opacity")); ui.add_enabled(caps.hardness, egui::Slider::new(&mut self.hardness, 0.0..=1.0).text("Hardness")); ui.add_enabled(caps.rect_radius, egui::Slider::new(&mut self.rect_radius, 0.0..=200.0).text("Corner Radius")); ui.add_enabled(caps.star_points, egui::Slider::new(&mut self.star_points, 3..=20).text("Star Points")); ui.add_enabled(caps.star_inner_radius, egui::Slider::new(&mut self.star_inner_radius, 0.1..=0.9).text("Inner Radius")); ui.add_enabled(caps.polygon_sides, egui::Slider::new(&mut self.polygon_sides, 3..=12).text("Polygon Sides")); ui.add_enabled(caps.arrow_thick, egui::Checkbox::new(&mut self.arrow_thick, "Thick Arrow")); if caps.line_cap { ui.label("Line Cap"); egui::ComboBox::from_id_salt("line_cap") .selected_text(self.line_cap.label()) .show_ui(ui, |ui| { for &cap in LineCap::ALL { ui.selectable_value(&mut self.line_cap, cap, cap.label()); } }); } let mut rgba = [ self.color[0] as f32 / 255., self.color[1] as f32 / 255., self.color[2] as f32 / 255., self.color[3] as f32 / 255., ]; ui.horizontal(|ui| { ui.label("Stroke Color"); if ui.color_edit_button_rgba_unmultiplied(&mut rgba).changed() { self.color = [ (rgba[0] * 255.) as u8, (rgba[1] * 255.) as u8, (rgba[2] * 255.) as u8, (rgba[3] * 255.) as u8, ]; } }); let mut fill_rgba = [ self.fill_color[0] as f32 / 255., self.fill_color[1] as f32 / 255., self.fill_color[2] as f32 / 255., self.fill_color[3] as f32 / 255., ]; ui.add_enabled(caps.fill, egui::Slider::new(&mut fill_rgba[0], 0.0..=1.0).text("Fill R")); ui.add_enabled(caps.fill, egui::Slider::new(&mut fill_rgba[1], 0.0..=1.0).text("Fill G")); ui.add_enabled(caps.fill, egui::Slider::new(&mut fill_rgba[2], 0.0..=1.0).text("Fill B")); ui.add_enabled(caps.fill, egui::Slider::new(&mut fill_rgba[3], 0.0..=1.0).text("Fill A")); self.fill_color = [ (fill_rgba[0] * 255.) as u8, (fill_rgba[1] * 255.) as u8, (fill_rgba[2] * 255.) as u8, (fill_rgba[3] * 255.) as u8, ]; ui.add_enabled(caps.fill, egui::Checkbox::new(&mut self.fill, "Fill")); ui.separator(); if ui.button("Clear Canvas").clicked() { self.canvas.clear(); self.base_pixels = self.canvas.pixels.clone(); self.shape_count = 0; } }); egui::CentralPanel::default().show(&ctx, |ui| { let available = ui.available_size_before_wrap(); let view_w = available.x.max(64.0); let view_h = available.y.max(64.0); let scale = (view_w / self.canvas.w as f32).min(view_h / self.canvas.h as f32); let disp_w = self.canvas.w as f32 * scale; let disp_h = self.canvas.h as f32 * scale; let (rect, resp) = ui.allocate_exact_size(egui::vec2(disp_w, disp_h), egui::Sense::click_and_drag()); if self.canvas.tiles.first().and_then(|t| t.texture.as_ref()).is_some() { let rows = self.canvas.h.div_ceil(TILE_SIZE); for ty in 0..rows { for tx in 0..self.canvas.cols { let idx = (ty * self.canvas.cols + tx) as usize; if let Some(tex) = &self.canvas.tiles[idx].texture { let tile_w = if tx == self.canvas.cols - 1 { self.canvas.w - tx * TILE_SIZE } else { TILE_SIZE } as f32; let tile_h = if ty == rows - 1 { self.canvas.h - ty * TILE_SIZE } else { TILE_SIZE } as f32; let src_x = (tx * TILE_SIZE) as f32 * scale; let src_y = (ty * TILE_SIZE) as f32 * scale; let r = egui::Rect::from_min_size( rect.min + egui::vec2(src_x, src_y), egui::vec2(tile_w * scale, tile_h * scale), ); ui.painter().image( tex.id(), r, egui::Rect::from_min_max(egui::pos2(0., 0.), egui::pos2(1., 1.)), Color32::WHITE, ); } } } } if resp.drag_started() { self.base_pixels = self.canvas.pixels.clone(); self.drag_start = resp.interact_pointer_pos().or(resp.hover_pos()).or(Some(rect.min)); } if let Some(start) = self.drag_start { if resp.drag_stopped() { if let Some(end_pos) = resp.hover_pos() { let sx = (start.x - rect.min.x) / scale; let sy = (start.y - rect.min.y) / scale; let ex = (end_pos.x - rect.min.x) / scale; let ey = (end_pos.y - rect.min.y) / scale; if sx >= 0.0 && sy >= 0.0 && sx < self.canvas.w as f32 && sy >= 0.0 && sy < self.canvas.h as f32 && ex >= 0.0 && ey >= 0.0 && ex < self.canvas.w as f32 && ey >= 0.0 && ey < self.canvas.h as f32 { self.canvas.pixels.copy_from_slice(&self.base_pixels); let shape = self.make_shape(sx, sy, ex, ey); let mut layer = Layer::from_rgba("test", self.canvas.w, self.canvas.h, self.canvas.pixels.clone()); layer.data = LayerData::Vector { shapes: vec![shape] }; render_vector_shapes(&mut layer); self.canvas.pixels.copy_from_slice(&layer.pixels); self.base_pixels = self.canvas.pixels.clone(); let max_x = sx.max(ex); let min_x = sx.min(ex); let max_y = sy.max(ey); let min_y = sy.min(ey); let radius = self.stroke + 20.0; self.canvas.mark_dirty_rect(min_x, min_y, radius); self.canvas.mark_dirty_rect(max_x, max_y, radius); self.canvas.mark_dirty_rect((min_x + max_x) / 2.0, (min_y + max_y) / 2.0, (max_x - min_x).max(max_y - min_y) / 2.0 + radius); self.canvas.upload_dirty(&ctx); self.shape_count += 1; } } self.drag_start = None; } else { let start = start; if let Some(cur) = resp.hover_pos() { let sx = (start.x - rect.min.x) / scale; let sy = (start.y - rect.min.y) / scale; let ex = (cur.x - rect.min.x) / scale; let ey = (cur.y - rect.min.y) / scale; self.canvas.pixels.copy_from_slice(&self.base_pixels); let shape = self.make_shape(sx, sy, ex, ey); let mut layer = Layer::from_rgba("preview", self.canvas.w, self.canvas.h, self.canvas.pixels.clone()); layer.data = LayerData::Vector { shapes: vec![shape] }; render_vector_shapes(&mut layer); self.canvas.pixels.copy_from_slice(&layer.pixels); self.canvas.mark_all_dirty(); self.canvas.upload_dirty(&ctx); } } } }); ctx.request_repaint(); } } fn main() -> eframe::Result<()> { eframe::run_native( "HCIE Vector Engine — Shape Test", eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([1400., 900.]), vsync: true, ..Default::default() }, Box::new(|cc| Ok(Box::new(App::new(cc)))), ) }