use canary_script::Panel; use crate::{Color, Vec2}; use bitflags::bitflags; pub enum Corner { TopRight, BottomRight, BottomLeft, TopLeft, } bitflags! { pub struct CornerFlags: u8 { const TOP_RIGHT = 0x01; const BOTTOM_RIGHT = 0x02; const BOTTOM_LEFT = 0x04; const TOP_LEFT = 0x08; const TOP = 0x09; const RIGHT = 0x03; const BOTTOM = 0x06; const LEFT = 0x0C; const ALL = 0x0F; } } #[derive(Copy, Clone)] pub struct ColoredTriangle { pub v1: Vec2, pub v2: Vec2, pub v3: Vec2, pub color: Color, } #[derive(Copy, Clone)] pub struct Rect { pub bl: Vec2, pub tr: Vec2, } impl Rect { pub fn from_xy_size(xy: Vec2, size: Vec2) -> Self { Self { bl: xy, tr: xy + size, } } pub fn from_circle_bounds(center: Vec2, radius: f32) -> Self { Self { bl: center - radius, tr: center + radius, } } pub fn from_triangle_bounds(tri: &ColoredTriangle) -> Self { Self { bl: tri.v1.min(tri.v2).min(tri.v3), tr: tri.v1.max(tri.v2).max(tri.v3), } } pub fn inset(&self, d: f32) -> Self { Self { bl: self.bl + d, tr: self.tr - d, } } pub fn tl(&self) -> Vec2 { Vec2::new(self.bl.x, self.tr.y) } pub fn br(&self) -> Vec2 { Vec2::new(self.tr.x, self.bl.y) } pub fn offset(&self, offset: Vec2) -> Self { Self { bl: self.bl + offset, tr: self.tr + offset, } } pub fn is_valid(&self) -> bool { self.bl.cmplt(self.tr).all() } pub fn intersects_rect(&self, other: &Self) -> bool { self.bl.cmple(other.tr).all() && self.tr.cmpge(other.bl).all() } pub fn intersection(&self, other: &Self) -> Option { let clipped = Self { bl: self.bl.max(other.bl), tr: self.tr.min(other.tr), }; if clipped.is_valid() { Some(clipped) } else { None } } pub fn contains_rect(&self, other: &Self) -> bool { self.bl.x < other.bl.x && self.bl.y < other.bl.y && self.tr.x > other.tr.x && self.tr.y > other.tr.y } pub fn contains_point(&self, xy: Vec2) -> bool { self.bl.x < xy.x && self.bl.y < xy.y && self.tr.x > xy.x && self.tr.y > xy.y } pub fn width(&self) -> f32 { self.tr.x - self.bl.x } pub fn height(&self) -> f32 { self.tr.y - self.bl.y } } pub struct DrawContext { panel: Panel, offset: Option, clip_rect: Option, opacity: Option, } impl DrawContext { pub fn new(panel: Panel) -> Self { Self { panel, offset: None, clip_rect: None, opacity: None, } } pub fn draw_triangle(&self, v1: Vec2, v2: Vec2, v3: Vec2, color: Color) { if let Some(clip_rect) = self.clip_rect.as_ref() { let tri = ColoredTriangle { v1, v2, v3, color }; let bb = Rect::from_triangle_bounds(&tri); if clip_rect.contains_rect(&bb) { self.draw_triangle_noclip(tri.v1, tri.v2, tri.v3, tri.color); } } else { self.draw_triangle_noclip(v1, v2, v3, color); } } fn draw_triangle_noclip(&self, mut v1: Vec2, mut v2: Vec2, mut v3: Vec2, mut color: Color) { if let Some(offset) = self.offset { v1 += offset; v2 += offset; v3 += offset; } if let Some(opacity) = self.opacity.as_ref() { color.a *= opacity; } self.panel.draw_triangle(v1.into(), v2.into(), v3.into(), color); } pub fn draw_circle(&self, center: Vec2, radius: f32, color: Color) { use std::f32::consts::PI; let delta = PI / 16.0; let limit = PI * 2.0 + delta; let mut last_spoke = Vec2::new(radius + center.x, center.y); let mut theta = delta; while theta < limit { let new_spoke = Vec2::from_angle(theta) * radius + center; self.draw_triangle(center, last_spoke, new_spoke, color); last_spoke = new_spoke; theta += delta; } } pub fn draw_quarter_circle(&self, corner: Corner, center: Vec2, radius: f32, color: Color) { use std::f32::consts::{FRAC_PI_2, PI}; let spoke_num = 16.0; let delta = PI / 4.0 / spoke_num; let (mut theta, limit) = match corner { Corner::TopRight => (0.0, FRAC_PI_2), Corner::BottomRight => (FRAC_PI_2 * 3.0, PI * 2.0), Corner::BottomLeft => (PI, FRAC_PI_2 * 3.0), Corner::TopLeft => (FRAC_PI_2, PI), }; let mut last_spoke = Vec2::from_angle(theta) * radius + center; while theta < limit { theta += delta; let new_spoke = Vec2::from_angle(theta) * radius + center; self.draw_triangle(center, last_spoke, new_spoke, color); last_spoke = new_spoke; } } pub fn draw_ring(&self, center: Vec2, radius: f32, thickness: f32, color: Color) { use std::f32::consts::PI; let delta = PI / 64.0; let limit = PI * 2.0 + delta; let mut last_spoke = glam::Vec2::new(radius + center.x, center.y); let mut last_theta = 0.0; let mut theta = delta; while theta < limit { let angle = Vec2::from_angle(theta); let new_spoke = angle * radius + center; let new_spoke2 = angle * (radius + thickness) + center; let last_spoke2 = Vec2::from_angle(last_theta) * (radius + thickness) + center; self.draw_triangle(new_spoke2, last_spoke, new_spoke, color); self.draw_triangle(new_spoke2, last_spoke2, last_spoke, color); last_spoke = new_spoke; last_theta = theta; theta += delta; } } pub fn draw_rect(&self, rect: Rect, color: Color) { let rect = if let Some(clip_rect) = self.clip_rect.as_ref() { if let Some(clipped) = clip_rect.intersection(&rect) { clipped } else { return; } } else { rect }; let v1 = rect.bl; let v2 = Vec2::new(rect.bl.x, rect.tr.y); let v3 = Vec2::new(rect.tr.x, rect.bl.y); let v4 = rect.tr; self.draw_triangle_noclip(v1, v2, v3, color); self.draw_triangle_noclip(v2, v3, v4, color); } pub fn draw_rounded_rect(&self, rect: Rect, radius: f32, color: Color) { self.draw_partially_rounded_rect(CornerFlags::ALL, rect, radius, color); } pub fn draw_partially_rounded_rect( &self, corners: CornerFlags, rect: Rect, radius: f32, color: Color, ) { if corners.is_empty() { self.draw_rect(rect, color); return; } let mut inner_rect = rect; let inset = rect.inset(radius); if corners.intersects(CornerFlags::BOTTOM) { inner_rect.bl.y += radius; let mut bottom_edge = Rect { bl: rect.bl, tr: Vec2::new(rect.tr.x, rect.bl.y + radius), }; if corners.contains(CornerFlags::BOTTOM_LEFT) { bottom_edge.bl.x += radius; self.draw_quarter_circle(Corner::BottomLeft, inset.bl, radius, color); } if corners.contains(CornerFlags::BOTTOM_RIGHT) { bottom_edge.tr.x -= radius; self.draw_quarter_circle(Corner::BottomRight, inset.br(), radius, color); } self.draw_rect(bottom_edge, color); } if corners.intersects(CornerFlags::TOP) { inner_rect.tr.y -= radius; let mut top_edge = Rect { bl: Vec2::new(rect.bl.x, rect.tr.y - radius), tr: rect.tr, }; if corners.contains(CornerFlags::TOP_LEFT) { top_edge.bl.x += radius; self.draw_quarter_circle(Corner::TopLeft, inset.tl(), radius, color); } if corners.contains(CornerFlags::TOP_RIGHT) { top_edge.tr.x -= radius; self.draw_quarter_circle(Corner::TopRight, inset.tr, radius, color); } self.draw_rect(top_edge, color); } self.draw_rect(inner_rect, color); } pub fn get_clip_rect(&self) -> &Option { &self.clip_rect } pub fn with_offset(&self, offset: Vec2) -> Self { Self { offset: self.offset.map(|old| old + offset).or(Some(offset)), clip_rect: self.clip_rect.map(|r| r.offset(-offset)), ..*self } } pub fn with_clip_rect(&self, mut clip_rect: Rect) -> Option { if let Some(old) = self.clip_rect { if let Some(clipped) = old.intersection(&clip_rect) { clip_rect = clipped; } else { return None; } } Some(Self { clip_rect: Some(clip_rect), ..*self }) } pub fn with_opacity(&self, mut opacity: f32) -> Self { if let Some(old) = self.opacity { opacity *= old; } Self { opacity: Some(opacity), ..*self } } }