use kurbo::BezPath;
use pax_runtime::api::{borrow, borrow_mut, use_RefCell};
use pax_runtime::api::{Color, Layer, RenderContext, Stroke};
use pax_runtime::{
BaseInstance, ExpandedNode, InstanceFlags, InstanceNode, InstantiationArgs, RuntimeContext,
};
use crate::common::Point;
use pax_engine::*;
use_RefCell!();
use std::collections::HashMap;
use std::iter;
use std::rc::Rc;
#[pax]
#[engine_import_path("pax_engine")]
#[primitive("pax_std::drawing::path::PathInstance")]
pub struct Path {
pub elements: Property<Vec<PathElement>>,
pub stroke: Property<Stroke>,
pub fill: Property<Color>,
}
impl Path {
pub fn start(x: Size, y: Size) -> Vec<PathElement> {
let mut start: Vec<PathElement> = Vec::new();
start.push(PathElement::Point(x, y));
start
}
pub fn line_to(mut path: Vec<PathElement>, x: Size, y: Size) -> Vec<PathElement> {
path.push(PathElement::Line);
path.push(PathElement::Point(x, y));
path
}
pub fn curve_to(
mut path: Vec<PathElement>,
h_x: Size,
h_y: Size,
x: Size,
y: Size,
) -> Vec<PathElement> {
path.push(PathElement::Curve(h_x, h_y));
path.push(PathElement::Point(x, y));
path
}
}
pub struct PathInstance {
base: BaseInstance,
}
impl InstanceNode for PathInstance {
fn instantiate(args: InstantiationArgs) -> Rc<Self>
where
Self: Sized,
{
Rc::new(Self {
base: BaseInstance::new(
args,
InstanceFlags {
invisible_to_slot: false,
invisible_to_raycasting: true,
layer: Layer::Canvas,
is_component: false,
},
),
})
}
fn handle_mount(
self: Rc<Self>,
expanded_node: &Rc<ExpandedNode>,
context: &Rc<RuntimeContext>,
) {
let env = expanded_node
.stack
.push(HashMap::new(), &*borrow!(expanded_node.properties));
expanded_node.with_properties_unwrapped(|properties: &mut Path| {
env.insert_stack_local_store(PathContext {
elements: properties.elements.clone(),
});
let children = borrow!(self.base().get_instance_children());
let children_with_envs = children.iter().cloned().zip(iter::repeat(env));
let new_children = expanded_node.generate_children(
children_with_envs,
context,
&expanded_node.parent_frame,
);
*borrow_mut!(expanded_node.expanded_slot_children) = Some(new_children.clone());
expanded_node.children.set(new_children);
});
}
fn update(self: Rc<Self>, expanded_node: &Rc<ExpandedNode>, _context: &Rc<RuntimeContext>) {
expanded_node.compute_flattened_slot_children();
}
fn render(
&self,
expanded_node: &ExpandedNode,
_rtc: &Rc<RuntimeContext>,
rc: &mut dyn RenderContext,
) {
let layer_id = format!("{}", expanded_node.occlusion.get().occlusion_layer_id);
expanded_node.with_properties_unwrapped(|properties: &mut Path| {
let mut bez_path = BezPath::new();
let bounds = expanded_node.transform_and_bounds.get().bounds;
let elems = properties.elements.get();
let mut itr_elems = elems.iter();
if let Some(elem) = itr_elems.next() {
if let &PathElement::Point(x, y) = elem {
bez_path.move_to(Point { x, y }.to_kurbo_point(bounds));
} else {
log::warn!("path must start with point");
return;
}
}
while let Some(elem) = itr_elems.next() {
match elem {
&PathElement::Point(x, y) => {
bez_path.move_to(Point { x, y }.to_kurbo_point(bounds));
}
&PathElement::Line => {
let Some(&PathElement::Point(x, y)) = itr_elems.next() else {
log::warn!("line expects to be followed by a point");
return;
};
bez_path.line_to(Point { x, y }.to_kurbo_point(bounds));
}
&PathElement::Curve(h_x, h_y) => {
let Some(&PathElement::Point(x, y)) = itr_elems.next() else {
log::warn!("curve expects to be followed by a point");
return;
};
bez_path.quad_to(
Point { x: h_x, y: h_y }.to_kurbo_point(bounds),
Point { x, y }.to_kurbo_point(bounds),
);
}
&PathElement::Close => {
bez_path.close_path();
}
PathElement::Empty => (), }
}
let tab = expanded_node.transform_and_bounds.get();
let transform = Into::<kurbo::Affine>::into(tab.transform);
let mut clip_path = BezPath::new();
let (width, height) = tab.bounds;
clip_path.move_to((0.0, 0.0));
clip_path.line_to((width, 0.0));
clip_path.line_to((width, height));
clip_path.line_to((0.0, height));
clip_path.line_to((0.0, 0.0));
clip_path.close_path();
let transformed_clip_path = transform * clip_path;
let transformed_bez_path = transform * bez_path;
let duplicate_transformed_bez_path = transformed_bez_path.clone();
let color = properties.fill.get().to_piet_color();
rc.save(&layer_id);
rc.clip(&layer_id, transformed_clip_path.clone());
rc.fill(&layer_id, transformed_bez_path, &color.into());
if properties
.stroke
.get()
.width
.get()
.expect_pixels()
.to_float()
> f64::EPSILON
{
rc.stroke(
&layer_id,
duplicate_transformed_bez_path,
&properties.stroke.get().color.get().to_piet_color().into(),
properties
.stroke
.get()
.width
.get()
.expect_pixels()
.to_float(),
);
}
rc.restore(&layer_id);
});
}
fn base(&self) -> &BaseInstance {
&self.base
}
fn resolve_debug(
&self,
f: &mut std::fmt::Formatter,
_expanded_node: Option<&ExpandedNode>,
) -> std::fmt::Result {
f.debug_struct("Path").finish()
}
}
use pax_engine::{
api::{NodeContext, Size, Store},
pax, Property,
};
pub struct PathContext {
pub elements: Property<Vec<PathElement>>,
}
impl Store for PathContext {}
#[pax]
#[engine_import_path("pax_engine")]
#[inlined( @settings { @mount: on_mount @pre_render: pre_render @unmount: on_unmount })]
pub struct PathPoint {
pub x: Property<Size>,
pub y: Property<Size>,
pub on_change: Property<bool>,
}
impl PathPoint {
pub fn on_mount(&mut self, ctx: &NodeContext) {
let path_elems = ctx
.peek_local_store(|path_ctx: &mut PathContext| path_ctx.elements.clone())
.expect("path point can only exist in <Path> tag");
let x = self.x.clone();
let y = self.y.clone();
let id = ctx.slot_index.clone();
let deps = [x.untyped(), y.untyped(), id.untyped()];
self.on_change.replace_with(Property::computed(
move || {
path_elems.update(|elems| {
let id = id.get().unwrap();
while elems.len() < id + 1 {
elems.push(PathElement::Close)
}
elems[id] = PathElement::point(x.get(), y.get());
});
false
},
&deps,
));
}
pub fn on_unmount(&mut self, ctx: &NodeContext) {
let path_elems = ctx
.peek_local_store(|path_ctx: &mut PathContext| path_ctx.elements.clone())
.expect("path point can only exist in <Path> tag");
let id = ctx.slot_index.get().unwrap();
path_elems.update(|elems| {
if id < elems.len() {
elems.remove(id);
}
});
}
pub fn pre_render(&mut self, _ctx: &NodeContext) {
self.on_change.get();
}
}
#[pax]
#[engine_import_path("pax_engine")]
#[inlined( @settings { @mount: on_mount @pre_render: pre_render @unmount: on_unmount })]
pub struct PathLine {
pub on_change: Property<bool>,
}
impl PathLine {
pub fn on_mount(&mut self, ctx: &NodeContext) {
let path_elems = ctx
.peek_local_store(|path_ctx: &mut PathContext| path_ctx.elements.clone())
.expect("path line can only exist in <Path> tag");
let id = ctx.slot_index.clone();
let deps = [id.untyped()];
self.on_change.replace_with(Property::computed(
move || {
path_elems.update(|elems| {
let id = id.get().unwrap();
while elems.len() < id + 1 {
elems.push(PathElement::Close)
}
elems[id] = PathElement::line();
});
false
},
&deps,
));
}
pub fn on_unmount(&mut self, ctx: &NodeContext) {
let path_elems = ctx
.peek_local_store(|path_ctx: &mut PathContext| path_ctx.elements.clone())
.expect("path point can only exist in <Path> tag");
let id = ctx.slot_index.get().unwrap();
path_elems.update(|elems| {
if id < elems.len() {
elems.remove(id);
}
});
}
pub fn pre_render(&mut self, _ctx: &NodeContext) {
self.on_change.get();
}
}
#[pax]
#[engine_import_path("pax_engine")]
#[inlined( @settings { @mount: on_mount @pre_render: pre_render @unmount: on_unmount })]
pub struct PathClose {
pub on_change: Property<bool>,
}
impl PathClose {
pub fn on_mount(&mut self, ctx: &NodeContext) {
let path_elems = ctx
.peek_local_store(|path_ctx: &mut PathContext| path_ctx.elements.clone())
.expect("path line can only exist in <Path> tag");
let id = ctx.slot_index.clone();
let deps = [id.untyped()];
self.on_change.replace_with(Property::computed(
move || {
path_elems.update(|elems| {
let id = id.get().unwrap();
while elems.len() < id + 1 {
elems.push(PathElement::Close)
}
elems[id] = PathElement::close();
});
false
},
&deps,
));
}
pub fn on_unmount(&mut self, ctx: &NodeContext) {
let path_elems = ctx
.peek_local_store(|path_ctx: &mut PathContext| path_ctx.elements.clone())
.expect("path point can only exist in <Path> tag");
let id = ctx.slot_index.clone();
path_elems.update(|elems| {
let id = id.get().unwrap();
if id < elems.len() {
elems.remove(id);
}
});
}
pub fn pre_render(&mut self, _ctx: &NodeContext) {
self.on_change.get();
}
}
#[pax]
#[engine_import_path("pax_engine")]
#[inlined( @settings { @mount: on_mount @pre_render: pre_render @unmount: on_unmount })]
pub struct PathCurve {
pub x: Property<Size>,
pub y: Property<Size>,
pub on_change: Property<bool>,
}
impl PathCurve {
pub fn on_mount(&mut self, ctx: &NodeContext) {
let path_elems = ctx
.peek_local_store(|path_ctx: &mut PathContext| path_ctx.elements.clone())
.expect("path point can only exist in <Path> tag");
let x = self.x.clone();
let y = self.y.clone();
let id = ctx.slot_index.clone();
let deps = [x.untyped(), y.untyped(), id.untyped()];
self.on_change.replace_with(Property::computed(
move || {
path_elems.update(|elems| {
let id = id.get().unwrap();
while elems.len() < id + 1 {
elems.push(PathElement::Close)
}
elems[id] = PathElement::curve(x.get(), y.get());
});
false
},
&deps,
));
}
pub fn on_unmount(&mut self, ctx: &NodeContext) {
let path_elems = ctx
.peek_local_store(|path_ctx: &mut PathContext| path_ctx.elements.clone())
.expect("path point can only exist in <Path> tag");
let id = ctx.slot_index.get().unwrap();
path_elems.update(|elems| {
if id < elems.len() {
elems.remove(id);
}
});
}
pub fn pre_render(&mut self, _ctx: &NodeContext) {
self.on_change.get();
}
}
#[pax]
#[engine_import_path("pax_engine")]
#[has_helpers]
pub enum PathElement {
#[default]
Empty,
Point(Size, Size),
Line,
Curve(Size, Size),
Close,
}
#[helpers]
impl PathElement {
pub fn line() -> Self {
Self::Line
}
pub fn close() -> Self {
Self::Close
}
pub fn point(x: Size, y: Size) -> Self {
Self::Point(x, y)
}
pub fn curve(x: Size, y: Size) -> Self {
Self::Curve(x, y)
}
}