use eframe::egui::{Pos2, Rect, pos2, vec2};
use image::RgbaImage;
use crate::raster::{self, IRect, Mask, Rgba8};
use crate::shapes::{self, ShapeKind};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Style {
pub outline: bool,
pub fill: bool,
pub size: f32,
pub line_color: Rgba8,
pub fill_color: Rgba8,
}
const HANDLES: [(i8, i8); 8] = [(-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)];
pub struct LiveShape {
pub kind: ShapeKind,
pub a: Pos2,
pub b: Pos2,
pub swapped: bool,
snapshot: RgbaImage,
fill_mask: Mask,
line_mask: Mask,
drawn: IRect,
touched: IRect,
rendered: Option<(ShapeKind, Pos2, Pos2, Style)>,
}
impl LiveShape {
pub fn new(kind: ShapeKind, at: Pos2, swapped: bool, snapshot: RgbaImage) -> Self {
let (w, h) = snapshot.dimensions();
Self {
kind,
a: at,
b: at,
swapped,
snapshot,
fill_mask: Mask::new(w, h),
line_mask: Mask::new(w, h),
drawn: IRect::EMPTY,
touched: IRect::EMPTY,
rendered: None,
}
}
pub fn is_line(&self) -> bool {
self.kind == ShapeKind::Line
}
pub fn is_empty(&self) -> bool {
let d = (self.b - self.a).abs();
d.x < 1.0 && d.y < 1.0
}
pub fn rect(&self) -> Rect {
Rect::from_two_pos(self.a, self.b)
}
pub fn snapshot(&self) -> &RgbaImage {
&self.snapshot
}
pub fn touched(&self) -> IRect {
self.touched
}
pub fn render(&mut self, img: &mut RgbaImage, style: Style) -> IRect {
let key = (self.kind, self.a, self.b, style);
if self.rendered == Some(key) {
return IRect::EMPTY;
}
let is_line = self.is_line();
let outline = style.outline || is_line;
let fill = style.fill && !is_line;
let radius = style.size * 0.5;
self.fill_mask.clear(self.drawn);
self.line_mask.clear(self.drawn);
let mut r = IRect::EMPTY;
if is_line {
r = self.line_mask.capsule(self.a, self.b, radius, 1.0);
} else {
for pts in self.kind.contours(self.a, self.b) {
if fill {
r = r.union(self.fill_mask.fill_polygon(&pts, 1.0));
}
if outline {
r = r.union(self.line_mask.stroke_path(&pts, true, radius));
}
}
}
let region = self.drawn.union(r);
raster::copy_rect(img, &self.snapshot, region);
if fill {
raster::blend_mask(img, &self.fill_mask, style.fill_color, 1.0, r);
}
if outline {
raster::blend_mask(img, &self.line_mask, style.line_color, 1.0, r);
}
self.drawn = r;
self.touched = self.touched.union(region);
self.rendered = Some(key);
region
}
pub fn rebase(&mut self, base: &RgbaImage, r: IRect) {
raster::copy_rect(&mut self.snapshot, base, r);
self.rendered = None;
}
pub fn erase(&self, img: &mut RgbaImage) -> IRect {
raster::copy_rect(img, &self.snapshot, self.touched);
self.touched
}
pub fn handles(&self) -> Vec<(usize, Pos2)> {
if self.is_line() {
return vec![(0, self.a), (1, self.b)];
}
let r = self.rect();
HANDLES
.iter()
.enumerate()
.map(|(i, &(dx, dy))| (i, r.center() + vec2(dx as f32 * r.width() * 0.5, dy as f32 * r.height() * 0.5)))
.collect()
}
pub fn handle_at(&self, p: Pos2, tol: f32) -> Option<usize> {
self.handles().into_iter().find(|(_, h)| (p - *h).abs().max_elem() <= tol).map(|(i, _)| i)
}
pub fn contains(&self, p: Pos2, tol: f32) -> bool {
if self.is_line() {
let ab = self.b - self.a;
let t = if ab.length_sq() > 0.0 { ((p - self.a).dot(ab) / ab.length_sq()).clamp(0.0, 1.0) } else { 0.0 };
return (p - (self.a + ab * t)).length() <= tol;
}
self.rect().expand(tol).contains(p)
}
pub fn drag_handle(&mut self, h: usize, (a, b): (Pos2, Pos2), p: Pos2, shift: bool) {
if self.is_line() {
if h == 0 {
self.a = shapes::constrain(ShapeKind::Line, b, p, shift);
} else {
self.b = shapes::constrain(ShapeKind::Line, a, p, shift);
}
return;
}
let r = Rect::from_two_pos(a, b);
let (dx, dy) = HANDLES[h];
let (mut x0, mut y0, mut x1, mut y1) = (r.min.x, r.min.y, r.max.x, r.max.y);
if dx != 0 && dy != 0 && shift {
let (ox, oy) = (if dx < 0 { x1 } else { x0 }, if dy < 0 { y1 } else { y0 });
let corner = shapes::constrain(ShapeKind::Rectangle, pos2(ox, oy), p, true);
(x0, y0, x1, y1) = (ox, oy, corner.x, corner.y);
} else {
if dx < 0 {
x0 = p.x;
} else if dx > 0 {
x1 = p.x;
}
if dy < 0 {
y0 = p.y;
} else if dy > 0 {
y1 = p.y;
}
}
self.a = pos2(x0, y0);
self.b = pos2(x1, y1);
}
pub fn translate(&mut self, (a, b): (Pos2, Pos2), d: eframe::egui::Vec2) {
self.a = a + d;
self.b = b + d;
}
}
pub fn handle_cursor(is_line: bool, h: usize) -> eframe::egui::CursorIcon {
use eframe::egui::CursorIcon;
if is_line {
return CursorIcon::Crosshair;
}
match h {
0 | 4 => CursorIcon::ResizeNwSe,
2 | 6 => CursorIcon::ResizeNeSw,
1 | 5 => CursorIcon::ResizeVertical,
_ => CursorIcon::ResizeHorizontal,
}
}