Files
hcie-rust-v3.05/hcie-vector/examples/vector_test.rs
T
phantom 2fb47520b3 feat(svg-editor): implement interactive SVG node editor with parsing and editing capabilities
feat(debug-logger): add DebugSignalLogger plugin for event logging and inspection

feat(plugins): introduce built-in plugins module and integrate debug logger

feat(plugin-integration): create integration layer for mapping Iced Messages to CoreEvents

feat(plugin-registry): establish PluginRegistry for managing plugins and event dispatching
2026-07-20 01:43:22 +03:00

862 lines
33 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#![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<f32> {
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<egui::TextureHandle>,
dirty: bool,
}
struct TiledCanvas {
pixels: Vec<u8>,
tiles: Vec<Tile>,
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<u8>,
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<egui::Pos2>,
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)))),
)
}