use crate::corety::{AzString, OptionF32};
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[repr(C)]
pub struct ShapePoint {
pub x: f32,
pub y: f32,
}
impl_option!(
ShapePoint,
OptionShapePoint,
[Debug, Copy, Clone, PartialEq, PartialOrd]
);
impl ShapePoint {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub const fn zero() -> Self {
Self { x: 0.0, y: 0.0 }
}
}
impl Eq for ShapePoint {}
impl Ord for ShapePoint {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match self.x.partial_cmp(&other.x) {
Some(core::cmp::Ordering::Equal) => self
.y
.partial_cmp(&other.y)
.unwrap_or(core::cmp::Ordering::Equal),
other => other.unwrap_or(core::cmp::Ordering::Equal),
}
}
}
impl core::hash::Hash for ShapePoint {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.x.to_bits().hash(state);
self.y.to_bits().hash(state);
}
}
impl_vec!(ShapePoint, ShapePointVec, ShapePointVecDestructor, ShapePointVecDestructorType, ShapePointVecSlice, OptionShapePoint);
impl_vec_debug!(ShapePoint, ShapePointVec);
impl_vec_partialord!(ShapePoint, ShapePointVec);
impl_vec_ord!(ShapePoint, ShapePointVec);
impl_vec_clone!(ShapePoint, ShapePointVec, ShapePointVecDestructor);
impl_vec_partialeq!(ShapePoint, ShapePointVec);
impl_vec_eq!(ShapePoint, ShapePointVec);
impl_vec_hash!(ShapePoint, ShapePointVec);
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct ShapeCircle {
pub center: ShapePoint,
pub radius: f32,
}
impl Eq for ShapeCircle {}
impl core::hash::Hash for ShapeCircle {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.center.hash(state);
self.radius.to_bits().hash(state);
}
}
impl PartialOrd for ShapeCircle {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ShapeCircle {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match self.center.cmp(&other.center) {
core::cmp::Ordering::Equal => self
.radius
.partial_cmp(&other.radius)
.unwrap_or(core::cmp::Ordering::Equal),
other => other,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct ShapeEllipse {
pub center: ShapePoint,
pub radius_x: f32,
pub radius_y: f32,
}
impl Eq for ShapeEllipse {}
impl core::hash::Hash for ShapeEllipse {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.center.hash(state);
self.radius_x.to_bits().hash(state);
self.radius_y.to_bits().hash(state);
}
}
impl PartialOrd for ShapeEllipse {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ShapeEllipse {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match self.center.cmp(&other.center) {
core::cmp::Ordering::Equal => match self.radius_x.partial_cmp(&other.radius_x) {
Some(core::cmp::Ordering::Equal) | None => self
.radius_y
.partial_cmp(&other.radius_y)
.unwrap_or(core::cmp::Ordering::Equal),
Some(other) => other,
},
other => other,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub struct ShapePolygon {
pub points: ShapePointVec,
}
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct ShapeInset {
pub inset_top: f32,
pub inset_right: f32,
pub inset_bottom: f32,
pub inset_left: f32,
pub border_radius: OptionF32,
}
impl Eq for ShapeInset {}
impl core::hash::Hash for ShapeInset {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.inset_top.to_bits().hash(state);
self.inset_right.to_bits().hash(state);
self.inset_bottom.to_bits().hash(state);
self.inset_left.to_bits().hash(state);
self.border_radius.hash(state);
}
}
impl PartialOrd for ShapeInset {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ShapeInset {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match self.inset_top.partial_cmp(&other.inset_top) {
Some(core::cmp::Ordering::Equal) | None => {
match self.inset_right.partial_cmp(&other.inset_right) {
Some(core::cmp::Ordering::Equal) | None => {
match self.inset_bottom.partial_cmp(&other.inset_bottom) {
Some(core::cmp::Ordering::Equal) | None => {
match self.inset_left.partial_cmp(&other.inset_left) {
Some(core::cmp::Ordering::Equal) | None => {
self.border_radius.cmp(&other.border_radius)
}
Some(other) => other,
}
}
Some(other) => other,
}
}
Some(other) => other,
}
}
Some(other) => other,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[repr(C)]
pub struct ShapePath {
pub data: AzString,
}
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum CssShape {
Circle(ShapeCircle),
Ellipse(ShapeEllipse),
Polygon(ShapePolygon),
Inset(ShapeInset),
Path(ShapePath),
}
impl Eq for CssShape {}
impl core::hash::Hash for CssShape {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
CssShape::Circle(c) => c.hash(state),
CssShape::Ellipse(e) => e.hash(state),
CssShape::Polygon(p) => p.hash(state),
CssShape::Inset(i) => i.hash(state),
CssShape::Path(p) => p.hash(state),
}
}
}
impl PartialOrd for CssShape {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for CssShape {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match (self, other) {
(CssShape::Circle(a), CssShape::Circle(b)) => a.cmp(b),
(CssShape::Ellipse(a), CssShape::Ellipse(b)) => a.cmp(b),
(CssShape::Polygon(a), CssShape::Polygon(b)) => a.cmp(b),
(CssShape::Inset(a), CssShape::Inset(b)) => a.cmp(b),
(CssShape::Path(a), CssShape::Path(b)) => a.cmp(b),
(CssShape::Circle(_), _) => core::cmp::Ordering::Less,
(_, CssShape::Circle(_)) => core::cmp::Ordering::Greater,
(CssShape::Ellipse(_), _) => core::cmp::Ordering::Less,
(_, CssShape::Ellipse(_)) => core::cmp::Ordering::Greater,
(CssShape::Polygon(_), _) => core::cmp::Ordering::Less,
(_, CssShape::Polygon(_)) => core::cmp::Ordering::Greater,
(CssShape::Inset(_), CssShape::Path(_)) => core::cmp::Ordering::Less,
(CssShape::Path(_), CssShape::Inset(_)) => core::cmp::Ordering::Greater,
}
}
}
impl CssShape {
pub fn circle(center: ShapePoint, radius: f32) -> Self {
CssShape::Circle(ShapeCircle { center, radius })
}
pub fn ellipse(center: ShapePoint, radius_x: f32, radius_y: f32) -> Self {
CssShape::Ellipse(ShapeEllipse {
center,
radius_x,
radius_y,
})
}
pub fn polygon(points: ShapePointVec) -> Self {
CssShape::Polygon(ShapePolygon { points })
}
pub fn inset(top: f32, right: f32, bottom: f32, left: f32) -> Self {
CssShape::Inset(ShapeInset {
inset_top: top,
inset_right: right,
inset_bottom: bottom,
inset_left: left,
border_radius: OptionF32::None,
})
}
pub fn inset_rounded(top: f32, right: f32, bottom: f32, left: f32, radius: f32) -> Self {
CssShape::Inset(ShapeInset {
inset_top: top,
inset_right: right,
inset_bottom: bottom,
inset_left: left,
border_radius: OptionF32::Some(radius),
})
}
}
impl_option!(
CssShape,
OptionCssShape,
copy = false,
[Debug, Clone, PartialEq]
);
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct LineSegment {
pub start_x: f32,
pub width: f32,
pub priority: i32,
}
impl_option!(
LineSegment,
OptionLineSegment,
[Debug, Copy, Clone, PartialEq]
);
impl LineSegment {
pub const fn new(start_x: f32, width: f32) -> Self {
Self {
start_x,
width,
priority: 0,
}
}
#[inline]
pub fn end_x(&self) -> f32 {
self.start_x + self.width
}
pub fn overlaps(&self, other: &Self) -> bool {
self.start_x < other.end_x() && other.start_x < self.end_x()
}
pub fn intersection(&self, other: &Self) -> Option<Self> {
let start = self.start_x.max(other.start_x);
let end = self.end_x().min(other.end_x());
if start < end {
Some(Self {
start_x: start,
width: end - start,
priority: self.priority.max(other.priority),
})
} else {
None
}
}
}
impl_vec!(LineSegment, LineSegmentVec, LineSegmentVecDestructor, LineSegmentVecDestructorType, LineSegmentVecSlice, OptionLineSegment);
impl_vec_debug!(LineSegment, LineSegmentVec);
impl_vec_clone!(LineSegment, LineSegmentVec, LineSegmentVecDestructor);
impl_vec_partialeq!(LineSegment, LineSegmentVec);
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct ShapeRect {
pub origin: ShapePoint,
pub width: f32,
pub height: f32,
}
impl ShapeRect {
pub const fn new(origin: ShapePoint, width: f32, height: f32) -> Self {
Self {
origin,
width,
height,
}
}
pub const fn zero() -> Self {
Self {
origin: ShapePoint::zero(),
width: 0.0,
height: 0.0,
}
}
}
impl_option!(ShapeRect, OptionShapeRect, [Debug, Copy, Clone, PartialEq]);
impl CssShape {
pub fn bounding_box(&self) -> ShapeRect {
match self {
CssShape::Circle(ShapeCircle { center, radius }) => ShapeRect {
origin: ShapePoint::new(center.x - radius, center.y - radius),
width: radius * 2.0,
height: radius * 2.0,
},
CssShape::Ellipse(ShapeEllipse {
center,
radius_x,
radius_y,
}) => ShapeRect {
origin: ShapePoint::new(center.x - radius_x, center.y - radius_y),
width: radius_x * 2.0,
height: radius_y * 2.0,
},
CssShape::Polygon(ShapePolygon { points }) => {
if points.as_ref().is_empty() {
return ShapeRect::zero();
}
let first = points.as_ref()[0];
let mut min_x = first.x;
let mut min_y = first.y;
let mut max_x = first.x;
let mut max_y = first.y;
for point in points.as_ref().iter().skip(1) {
min_x = min_x.min(point.x);
min_y = min_y.min(point.y);
max_x = max_x.max(point.x);
max_y = max_y.max(point.y);
}
ShapeRect {
origin: ShapePoint::new(min_x, min_y),
width: max_x - min_x,
height: max_y - min_y,
}
}
CssShape::Inset(ShapeInset {
inset_top,
inset_right,
inset_bottom,
inset_left,
..
}) => {
ShapeRect {
origin: ShapePoint::new(*inset_left, *inset_top),
width: 0.0, height: 0.0,
}
}
CssShape::Path(_) => {
ShapeRect::zero()
}
}
}
pub fn compute_line_segments(
&self,
y: f32,
margin: f32,
reference_box: OptionShapeRect,
) -> LineSegmentVec {
use alloc::vec::Vec;
let segments: Vec<LineSegment> = match self {
CssShape::Circle(ShapeCircle { center, radius }) => {
let dy = y - center.y;
let r_with_margin = radius - margin;
if dy.abs() > r_with_margin {
Vec::new() } else {
let half_width = (r_with_margin.powi(2) - dy.powi(2)).sqrt();
alloc::vec![LineSegment {
start_x: center.x - half_width,
width: 2.0 * half_width,
priority: 0,
}]
}
}
CssShape::Ellipse(ShapeEllipse {
center,
radius_x,
radius_y,
}) => {
let dy = y - center.y;
let ry_with_margin = radius_y - margin;
if dy.abs() > ry_with_margin {
Vec::new() } else {
let ratio = dy / ry_with_margin;
let factor = (1.0 - ratio.powi(2)).sqrt();
let half_width = (radius_x - margin) * factor;
alloc::vec![LineSegment {
start_x: center.x - half_width,
width: 2.0 * half_width,
priority: 0,
}]
}
}
CssShape::Polygon(ShapePolygon { points }) => {
compute_polygon_line_segments(points.as_ref(), y, margin)
}
CssShape::Inset(ShapeInset {
inset_top: top,
inset_right: right,
inset_bottom: bottom,
inset_left: left,
border_radius,
}) => {
let ref_box = match reference_box {
OptionShapeRect::Some(r) => r,
OptionShapeRect::None => ShapeRect::zero(),
};
let inset_top = ref_box.origin.y + top + margin;
let inset_bottom = ref_box.origin.y + ref_box.height - bottom - margin;
let inset_left = ref_box.origin.x + left + margin;
let inset_right = ref_box.origin.x + ref_box.width - right - margin;
if y < inset_top || y > inset_bottom {
Vec::new()
} else {
alloc::vec![LineSegment {
start_x: inset_left,
width: inset_right - inset_left,
priority: 0,
}]
}
}
CssShape::Path(_) => {
Vec::new()
}
};
segments.into()
}
}
fn compute_polygon_line_segments(
points: &[ShapePoint],
y: f32,
margin: f32,
) -> alloc::vec::Vec<LineSegment> {
use alloc::vec::Vec;
if points.len() < 3 {
return Vec::new();
}
let mut intersections = Vec::new();
for i in 0..points.len() {
let p1 = points[i];
let p2 = points[(i + 1) % points.len()];
let min_y = p1.y.min(p2.y);
let max_y = p1.y.max(p2.y);
if y >= min_y && y < max_y {
let t = (y - p1.y) / (p2.y - p1.y);
let x = p1.x + t * (p2.x - p1.x);
intersections.push(x);
}
}
intersections.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
let mut segments = Vec::new();
for chunk in intersections.chunks(2) {
if chunk.len() == 2 {
let start = chunk[0] + margin;
let end = chunk[1] - margin;
if start < end {
segments.push(LineSegment {
start_x: start,
width: end - start,
priority: 0,
});
}
}
}
segments
}