Files
hcie-rust-v3.05/hcie-vector/examples/vector_test.rs
T

846 lines
32 KiB
Rust
Raw Normal View History

2026-07-09 02:59:53 +03:00
#![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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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)))),
)
}