use crate::animation_builder::{CABasicAnimationBuilder, KeyPath};
use crate::color::Color;
use objc2::rc::Retained;
use objc2_core_foundation::{CFRetained, CGFloat, CGPoint, CGRect, CGSize};
use objc2_core_graphics::{CGColor, CGPath};
use objc2_foundation::NSString;
use objc2_quartz_core::{CABasicAnimation, CAShapeLayer, CATransform3D};
struct PendingAnimation {
name: String,
animation: Retained<CABasicAnimation>,
}
#[derive(Default)]
pub struct CAShapeLayerBuilder {
bounds: Option<CGRect>,
position: Option<CGPoint>,
path: Option<CFRetained<CGPath>>,
fill_color: Option<CFRetained<CGColor>>,
stroke_color: Option<CFRetained<CGColor>>,
line_width: Option<CGFloat>,
transform: Option<CATransform3D>,
hidden: Option<bool>,
opacity: Option<f32>,
shadow_color: Option<CFRetained<CGColor>>,
shadow_offset: Option<(f64, f64)>,
shadow_radius: Option<f64>,
shadow_opacity: Option<f32>,
scale: Option<f64>,
rotation: Option<f64>,
translation: Option<(f64, f64)>,
animations: Vec<PendingAnimation>,
}
impl CAShapeLayerBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn bounds(mut self, bounds: CGRect) -> Self {
self.bounds = Some(bounds);
self
}
pub fn position(mut self, position: CGPoint) -> Self {
self.position = Some(position);
self
}
pub fn path(mut self, path: CFRetained<CGPath>) -> Self {
self.path = Some(path);
self
}
pub fn circle(mut self, diameter: CGFloat) -> Self {
let rect = CGRect::new(CGPoint::ZERO, CGSize::new(diameter, diameter));
let path = unsafe { CGPath::with_ellipse_in_rect(rect, std::ptr::null()) };
self.path = Some(path);
self.bounds = Some(rect);
self
}
pub fn ellipse(mut self, width: CGFloat, height: CGFloat) -> Self {
let rect = CGRect::new(CGPoint::ZERO, CGSize::new(width, height));
let path = unsafe { CGPath::with_ellipse_in_rect(rect, std::ptr::null()) };
self.path = Some(path);
self.bounds = Some(rect);
self
}
pub fn fill_color(mut self, color: impl Into<CFRetained<CGColor>>) -> Self {
self.fill_color = Some(color.into());
self
}
pub fn fill_rgba(mut self, r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) -> Self {
self.fill_color = Some(Color::rgba(r, g, b, a).into());
self
}
pub fn stroke_color(mut self, color: impl Into<CFRetained<CGColor>>) -> Self {
self.stroke_color = Some(color.into());
self
}
pub fn stroke_rgba(mut self, r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) -> Self {
self.stroke_color = Some(Color::rgba(r, g, b, a).into());
self
}
pub fn line_width(mut self, width: CGFloat) -> Self {
self.line_width = Some(width);
self
}
pub fn transform(mut self, transform: CATransform3D) -> Self {
self.transform = Some(transform);
self
}
pub fn hidden(mut self, hidden: bool) -> Self {
self.hidden = Some(hidden);
self
}
pub fn opacity(mut self, opacity: f32) -> Self {
self.opacity = Some(opacity);
self
}
pub fn shadow_color(mut self, color: impl Into<CFRetained<CGColor>>) -> Self {
self.shadow_color = Some(color.into());
self
}
pub fn shadow_offset(mut self, dx: f64, dy: f64) -> Self {
self.shadow_offset = Some((dx, dy));
self
}
pub fn shadow_radius(mut self, radius: f64) -> Self {
self.shadow_radius = Some(radius);
self
}
pub fn shadow_opacity(mut self, opacity: f32) -> Self {
self.shadow_opacity = Some(opacity);
self
}
pub fn scale(mut self, scale: f64) -> Self {
self.scale = Some(scale);
self
}
pub fn rotation(mut self, radians: f64) -> Self {
self.rotation = Some(radians);
self
}
pub fn translate(mut self, dx: f64, dy: f64) -> Self {
self.translation = Some((dx, dy));
self
}
pub fn animate<F>(mut self, name: impl Into<String>, key_path: KeyPath, configure: F) -> Self
where
F: FnOnce(CABasicAnimationBuilder) -> CABasicAnimationBuilder,
{
let builder = CABasicAnimationBuilder::new(key_path);
let animation = configure(builder).build();
self.animations.push(PendingAnimation {
name: name.into(),
animation,
});
self
}
pub fn build(self) -> Retained<CAShapeLayer> {
let layer = CAShapeLayer::new();
if let Some(bounds) = self.bounds {
layer.setBounds(bounds);
}
if let Some(position) = self.position {
layer.setPosition(position);
}
if let Some(ref path) = self.path {
layer.setPath(Some(&**path));
}
if let Some(ref color) = self.fill_color {
layer.setFillColor(Some(&**color));
}
if let Some(ref color) = self.stroke_color {
layer.setStrokeColor(Some(&**color));
}
if let Some(width) = self.line_width {
layer.setLineWidth(width);
}
if let Some(transform) = self.transform {
layer.setTransform(transform);
} else if self.scale.is_some() || self.rotation.is_some() || self.translation.is_some() {
let mut transform = CATransform3D::new_scale(1.0, 1.0, 1.0);
if let Some(s) = self.scale {
transform = CATransform3D::new_scale(s, s, 1.0);
}
if let Some(r) = self.rotation {
let rotation_transform = CATransform3D::new_rotation(r, 0.0, 0.0, 1.0);
transform = transform.concat(rotation_transform);
}
if let Some((dx, dy)) = self.translation {
let translation_transform = CATransform3D::new_translation(dx, dy, 0.0);
transform = transform.concat(translation_transform);
}
layer.setTransform(transform);
}
if let Some(hidden) = self.hidden {
layer.setHidden(hidden);
}
if let Some(opacity) = self.opacity {
layer.setOpacity(opacity);
}
if let Some(ref color) = self.shadow_color {
layer.setShadowColor(Some(&**color));
}
if let Some((dx, dy)) = self.shadow_offset {
layer.setShadowOffset(CGSize::new(dx, dy));
}
if let Some(radius) = self.shadow_radius {
layer.setShadowRadius(radius);
}
if let Some(opacity) = self.shadow_opacity {
layer.setShadowOpacity(opacity);
}
for pending in self.animations {
let key = NSString::from_str(&pending.name);
layer.addAnimation_forKey(&pending.animation, Some(&key));
}
layer
}
}