use std::rc::Rc;
use std::cell::RefCell;
use std::borrow::BorrowMut;
use wasm_bindgen::{JsValue, UnwrapThrowExt};
use web_sys::HtmlCanvasElement;
use crate::callbacks::Callbacks;
use crate::dom::Matrix;
use crate::traits::{CanvasObject, CanvasContext};
#[derive(Debug)]
pub struct Operations {
path: web_sys::Path2d,
}
impl Clone for Operations {
#[track_caller]
fn clone(&self) -> Self {
Self {
path: web_sys::Path2d::new_with_other(&self.path).unwrap_js(),
}
}
}
impl Operations {
#[inline]
#[track_caller]
pub fn new() -> Self {
Self {
path: web_sys::Path2d::new().unwrap_js(),
}
}
#[inline]
#[track_caller]
pub fn arc(self, center_x: f64, center_y: f64, radius: f64, start_angle: f64, end_angle: f64) -> Self {
self.path.arc(center_x, center_y, radius, start_angle, end_angle).unwrap_js();
self
}
#[inline]
#[track_caller]
pub fn arc_with_anticlockwise(self, center_x: f64, center_y: f64, radius: f64, start_angle: f64, end_angle: f64, anticlockwise: bool) -> Self {
self.path.arc_with_anticlockwise(center_x, center_y, radius, start_angle, end_angle, anticlockwise).unwrap_js();
self
}
#[inline]
#[track_caller]
pub fn arc_to(self, control_x1: f64, control_y1: f64, control_x2: f64, control_y2: f64, radius: f64) -> Self {
self.path.arc_to(control_x1, control_y1, control_x2, control_y2, radius).unwrap_js();
self
}
#[inline]
pub fn bezier_curve_to(self, control_x1: f64, control_y1: f64, control_x2: f64, control_y2: f64, end_x: f64, end_y: f64) -> Self {
self.path.bezier_curve_to(control_x1, control_y1, control_x2, control_y2, end_x, end_y);
self
}
#[inline]
pub fn quadratic_curve_to(self, control_x: f64, control_y: f64, end_x: f64, end_y: f64) -> Self {
self.path.quadratic_curve_to(control_x, control_y, end_x, end_y);
self
}
#[inline]
#[track_caller]
pub fn ellipse(self, center_x: f64, center_y: f64, radius_x: f64, radius_y: f64, rotation: f64, start_angle: f64, end_angle: f64) -> Self {
self.path.ellipse(center_x, center_y, radius_x, radius_y, rotation, start_angle, end_angle).unwrap_js();
self
}
#[inline]
#[track_caller]
pub fn ellipse_with_anticlockwise(
self,
center_x: f64,
center_y: f64,
radius_x: f64,
radius_y: f64,
rotation: f64,
start_angle: f64,
end_angle: f64,
anticlockwise: bool,
) -> Self {
self.path.ellipse_with_anticlockwise(center_x, center_y, radius_x, radius_y, rotation, start_angle, end_angle, anticlockwise).unwrap_js();
self
}
#[inline]
pub fn line_to(self, x: f64, y: f64) -> Self {
self.path.line_to(x, y);
self
}
#[inline]
pub fn move_to(self, x: f64, y: f64) -> Self {
self.path.move_to(x, y);
self
}
#[inline]
pub fn rect(self, x: f64, y: f64, width: f64, height: f64) -> Self {
self.path.rect(x, y, width, height);
self
}
#[inline]
pub fn close(self) -> Self {
self.path.close_path();
self
}
}
#[derive(Debug, Clone)]
pub struct Style(JsValue);
impl Style {
#[inline]
pub fn color(string: &str) -> Self {
Self(JsValue::from(string))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct Dirty<A> {
dirty: bool,
value: A,
}
impl<A> Dirty<A> {
fn new(value: A) -> Self {
Self {
dirty: false,
value,
}
}
}
#[derive(Debug, Clone)]
enum CanvasKind {
Container(Rc<RefCell<CanvasContainer>>),
Shape(Rc<RefCell<CanvasShape>>),
}
impl CanvasKind {
fn render(&self, context: &web_sys::CanvasRenderingContext2d, parent_transform: Dirty<Matrix>) {
match self {
Self::Container(x) => (**x).borrow_mut().render(context, parent_transform),
Self::Shape(x) => (**x).borrow_mut().render(context, parent_transform),
}
}
}
#[derive(Debug, Clone)]
pub struct Context {
context: web_sys::CanvasRenderingContext2d,
}
impl CanvasContext for Context {
#[track_caller]
fn new(canvas: &HtmlCanvasElement) -> Self {
Self {
context: canvas.get_context("2d").unwrap_js().unwrap_js(),
}
}
}
#[derive(Debug)]
pub struct CanvasObject {
kind: CanvasKind,
callbacks: Callbacks,
}
impl CanvasObject {
#[inline]
fn render(&self, context: &web_sys::CanvasRenderingContext2d, parent_transform: Dirty<Matrix>) {
self.kind.render(context, parent_transform)
}
}
#[derive(Debug)]
pub struct CanvasBuilder<A> {
object: Rc<RefCell<A>>,
callbacks: Callbacks,
}
impl<A> CanvasBuilder<A> where A: CanvasObject {
pub fn new(context: A::Context) -> Self {
Self {
object: Rc::new(RefCell::new(A::new(context))),
callbacks: Callbacks::new(),
}
}
}
impl<A> CanvasBuilder<A> where A: AsMut<CanvasShared> {
#[inline]
pub fn opacity(self, value: f64) -> Self {
{
let mut lock = (*self.object).borrow_mut();
let shared = lock.as_mut();
shared.opacity = value;
}
self
}
#[inline]
pub fn opacity_signal<B>(mut self, value: B) -> Self
where B: Signal<Item = f64> + 'static {
let this = self.object.clone();
self.callbacks.after_remove(for_each(value, move |value| {
let mut lock = this.borrow_mut();
let shared = lock.as_mut();
shared.opacity = value;
}));
}
}
impl CanvasBuilder<CanvasContainer> {
#[inline]
pub fn children<B: BorrowMut<CanvasObject>, C: IntoIterator<Item = B>>(mut self, children: C) -> Self {
{
let mut lock = (*self.object).borrow_mut();
for mut child in children {
let child = std::borrow::BorrowMut::borrow_mut(&mut child);
self.callbacks.after_insert.append(&mut child.callbacks.after_insert);
self.callbacks.after_remove.append(&mut child.callbacks.after_remove);
lock.children.push(child.kind.clone());
}
}
self
}
}
#[derive(Debug)]
pub struct CanvasShared {
opacity: f64,
transform: Dirty<Matrix>,
world_transform: Dirty<Matrix>,
mask: Option<Operations>,
}
impl CanvasShared {
fn new() -> Self {
Self {
opacity: 1.0,
transform: Dirty::new(Matrix::new()),
world_transform: Dirty::new(Matrix::new()),
mask: None,
}
}
#[track_caller]
fn render(&mut self, context: &web_sys::CanvasRenderingContext2d, parent_transform: Dirty<Matrix>) {
if parent_transform.dirty || self.transform.dirty {
self.world_transform.value = parent_transform.value.append(self.transform.value);
self.world_transform.dirty = true;
self.transform.dirty = false;
} else {
self.world_transform.dirty = false;
}
let Matrix { scale_x, scale_y, skew_x, skew_y, translate_x, translate_y } = self.world_transform.value;
context.set_transform(scale_x, skew_y, skew_x, scale_y, translate_x, translate_y).unwrap_js();
context.set_global_alpha(self.opacity);
if let Some(mask) = &self.mask {
context.clip_with_path_2d(&mask.path);
}
}
}
#[derive(Debug)]
struct CanvasContainer {
shared: CanvasShared,
children: Vec<CanvasKind>,
}
impl CanvasContainer {
fn new() -> Self {
Self {
shared: CanvasShared::new(),
children: vec![],
}
}
fn render(&mut self, context: &web_sys::CanvasRenderingContext2d, parent_transform: Dirty<Matrix>) {
self.shared.render(context, parent_transform);
for child in self.children.iter_mut() {
child.render(context, self.shared.world_transform);
}
}
}
impl AsMut<CanvasShared> for CanvasContainer {
#[inline]
fn as_mut(&mut self) -> &mut CanvasShared {
&mut self.shared
}
}
#[derive(Debug)]
pub struct CanvasShape {
shared: CanvasShared,
fill_style: Option<Style>,
stroke_style: Option<Style>,
operations: Option<Operations>,
}
impl CanvasShape {
fn new() -> Self {
Self {
shared: CanvasShared::new(),
fill_style: None,
stroke_style: None,
operations: None,
}
}
fn render(&mut self, context: &web_sys::CanvasRenderingContext2d, parent_transform: Dirty<Matrix>) {
self.shared.render(context, parent_transform);
if let Some(ops) = &self.operations {
if let Some(fill) = &self.fill_style {
context.set_fill_style(&fill.0);
context.fill_with_path_2d(&ops.path);
}
if let Some(stroke) = &self.stroke_style {
context.set_stroke_style(&stroke.0);
context.stroke_with_path(&ops.path);
}
}
}
}
impl AsMut<CanvasShared> for CanvasShape {
#[inline]
fn as_mut(&mut self) -> &mut CanvasShared {
&mut self.shared
}
}