use waterui_core::layout::{Point, Rect, Size};
use vello::kurbo::{self, Shape};
use super::conversions::{point_to_kurbo, rect_to_kurbo};
pub struct Path {
inner: kurbo::BezPath,
}
impl Path {
#[must_use]
pub fn new() -> Self {
Self {
inner: kurbo::BezPath::new(),
}
}
pub fn move_to(&mut self, point: Point) {
self.inner.move_to(point_to_kurbo(point));
}
pub fn line_to(&mut self, point: Point) {
self.inner.line_to(point_to_kurbo(point));
}
pub fn quadratic_to(&mut self, control_point: Point, end: Point) {
self.inner
.quad_to(point_to_kurbo(control_point), point_to_kurbo(end));
}
pub fn bezier_to(&mut self, control_point1: Point, control_point2: Point, end: Point) {
self.inner.curve_to(
point_to_kurbo(control_point1),
point_to_kurbo(control_point2),
point_to_kurbo(end),
);
}
pub fn arc(
&mut self,
center: Point,
radius: f32,
start_angle: f32,
end_angle: f32,
anticlockwise: bool,
) {
let center_kurbo = point_to_kurbo(center);
let radius_f64 = f64::from(radius);
let (start, sweep) = if anticlockwise {
let sweep = f64::from(start_angle - end_angle);
(f64::from(start_angle), sweep)
} else {
let sweep = f64::from(end_angle - start_angle);
(f64::from(start_angle), sweep)
};
let arc = kurbo::Arc::new(center_kurbo, (radius_f64, radius_f64), start, sweep, 0.0);
let bez_path = arc.to_path(0.1);
for el in bez_path.elements() {
self.inner.push(*el);
}
}
pub fn arc_to(&mut self, point1: Point, point2: Point, radius: f32) {
let current = self.inner.elements().last().and_then(|el| match el {
kurbo::PathEl::MoveTo(p)
| kurbo::PathEl::LineTo(p)
| kurbo::PathEl::CurveTo(_, _, p)
| kurbo::PathEl::QuadTo(_, p) => Some(*p),
kurbo::PathEl::ClosePath => None,
});
if let Some(current_pt) = current {
let p0 = current_pt;
let p1 = point_to_kurbo(point1);
let p2 = point_to_kurbo(point2);
let r = f64::from(radius);
let v0 = kurbo::Vec2::new(p1.x - p0.x, p1.y - p0.y);
let v1 = kurbo::Vec2::new(p2.x - p1.x, p2.y - p1.y);
let len0 = v0.hypot();
let len1 = v1.hypot();
if len0 > 0.0 && len1 > 0.0 {
let v0_norm = v0 / len0;
let v1_norm = v1 / len1;
let cos_angle = v0_norm.dot(v1_norm).clamp(-1.0, 1.0);
let angle = cos_angle.acos();
if angle > 0.01 {
let tan_half = (angle / 2.0).tan();
let dist = r / tan_half;
let start_pt = p1 - v0_norm * dist;
let end_pt = p1 + v1_norm * dist;
self.inner.line_to(start_pt);
let bisector = (v0_norm + v1_norm).normalize();
let center_dist = r / (angle / 2.0).sin();
let center = p1 + bisector * center_dist;
let start_angle = (start_pt.y - center.y).atan2(start_pt.x - center.x);
let end_angle = (end_pt.y - center.y).atan2(end_pt.x - center.x);
let mut sweep = end_angle - start_angle;
if sweep > core::f64::consts::PI {
sweep -= 2.0 * core::f64::consts::PI;
} else if sweep < -core::f64::consts::PI {
sweep += 2.0 * core::f64::consts::PI;
}
let arc = kurbo::Arc::new(center, (r, r), start_angle, sweep, 0.0);
let arc_path = arc.to_path(0.1);
for el in arc_path.elements() {
self.inner.push(*el);
}
}
}
}
}
pub fn ellipse(
&mut self,
center: Point,
radii: Size,
rotation: f32,
start_angle: f32,
end_angle: f32,
anticlockwise: bool,
) {
let center_kurbo = point_to_kurbo(center);
let radii_tuple = (f64::from(radii.width), f64::from(radii.height));
let (start, sweep) = if anticlockwise {
let sweep = f64::from(start_angle - end_angle);
(f64::from(start_angle), sweep)
} else {
let sweep = f64::from(end_angle - start_angle);
(f64::from(start_angle), sweep)
};
let arc = kurbo::Arc::new(center_kurbo, radii_tuple, start, sweep, f64::from(rotation));
let arc_path = arc.to_path(0.1);
for el in arc_path.elements() {
self.inner.push(*el);
}
}
pub fn rect(&mut self, rect: Rect) {
let kurbo_rect = rect_to_kurbo(rect);
let x = kurbo_rect.x0;
let y = kurbo_rect.y0;
let width = kurbo_rect.width();
let height = kurbo_rect.height();
self.inner.move_to((x, y));
self.inner.line_to((x + width, y));
self.inner.line_to((x + width, y + height));
self.inner.line_to((x, y + height));
self.inner.close_path();
}
pub fn close(&mut self) {
self.inner.close_path();
}
#[must_use]
pub(crate) const fn inner(&self) -> &kurbo::BezPath {
&self.inner
}
}
impl Default for Path {
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Debug for Path {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Path")
.field("elements", &self.inner.elements().len())
.finish()
}
}