diff --git a/pax-chassis-common/src/core_graphics_c_bridge.rs b/pax-chassis-common/src/core_graphics_c_bridge.rs index 049fa1269..cbaf8abb3 100644 --- a/pax-chassis-common/src/core_graphics_c_bridge.rs +++ b/pax-chassis-common/src/core_graphics_c_bridge.rs @@ -8,6 +8,7 @@ use std::mem::{transmute, ManuallyDrop}; use std::os::raw::c_char; use core_graphics::context::CGContext; +use pax_runtime::math::Point2; use piet_coregraphics::CoreGraphicsContext; use flexbuffers; @@ -95,7 +96,7 @@ pub extern "C" fn pax_interrupt( NativeInterrupt::Click(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); match prospective_hit { Some(topmost_node) => { let modifiers = args diff --git a/pax-chassis-web/interface/src/events/listeners.ts b/pax-chassis-web/interface/src/events/listeners.ts index a78ff3f08..8956f70d2 100644 --- a/pax-chassis-web/interface/src/events/listeners.ts +++ b/pax-chassis-web/interface/src/events/listeners.ts @@ -41,6 +41,7 @@ export function setupEventListeners(chassis: PaxChassisWeb, layer: any) { // @ts-ignore layer.addEventListener('click', (evt) => { + let clickEvent = { "Click": { "x": evt.clientX, @@ -72,11 +73,13 @@ export function setupEventListeners(chassis: PaxChassisWeb, layer: any) { }, true); // @ts-ignore layer.addEventListener('mousemove', (evt) => { + // @ts-ignore + let button = window.current_button || 'Left'; let event = { "MouseMove": { "x": evt.clientX, "y": evt.clientY, - "button": getMouseButton(evt), + "button": button, "modifiers": convertModifiers(evt) } }; @@ -97,6 +100,9 @@ export function setupEventListeners(chassis: PaxChassisWeb, layer: any) { }, {"passive": true, "capture": true}); // @ts-ignore layer.addEventListener('mousedown', (evt) => { + let button = getMouseButton(evt); + // @ts-ignore + window.current_button = button; let event = { "MouseDown": { "x": evt.clientX, @@ -231,4 +237,4 @@ export function setupEventListeners(chassis: PaxChassisWeb, layer: any) { }; chassis.interrupt(JSON.stringify(event), []); }, true); -} \ No newline at end of file +} diff --git a/pax-chassis-web/src/lib.rs b/pax-chassis-web/src/lib.rs index b4c4a5bdf..4fc0a61fe 100644 --- a/pax-chassis-web/src/lib.rs +++ b/pax-chassis-web/src/lib.rs @@ -5,6 +5,7 @@ use pax_runtime::api::ArgsButtonClick; use pax_runtime::api::ArgsCheckboxChange; use pax_runtime::api::ArgsTextboxChange; use pax_runtime::api::RenderContext; +use pax_runtime::math::Point2; use pax_runtime::ExpressionTable; use std::cell::RefCell; @@ -215,7 +216,7 @@ impl PaxChassisWeb { NativeInterrupt::Click(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let args_click = ArgsClick { mouse: MouseEventArgs { @@ -245,7 +246,7 @@ impl PaxChassisWeb { NativeInterrupt::Clap(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let args_clap = ArgsClap { x: args.x, @@ -258,7 +259,7 @@ impl PaxChassisWeb { let first_touch = args.touches.get(0).unwrap(); let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((first_touch.x, first_touch.y)); + .get_topmost_element_beneath_ray(Point2::new(first_touch.x, first_touch.y)); if let Some(topmost_node) = prospective_hit { let touches = args.touches.iter().map(|x| Touch::from(x)).collect(); let args_touch_start = ArgsTouchStart { touches }; @@ -273,7 +274,7 @@ impl PaxChassisWeb { let first_touch = args.touches.get(0).unwrap(); let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((first_touch.x, first_touch.y)); + .get_topmost_element_beneath_ray(Point2::new(first_touch.x, first_touch.y)); if let Some(topmost_node) = prospective_hit { let touches = args.touches.iter().map(|x| Touch::from(x)).collect(); let args_touch_move = ArgsTouchMove { touches }; @@ -288,7 +289,7 @@ impl PaxChassisWeb { let first_touch = args.touches.get(0).unwrap(); let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((first_touch.x, first_touch.y)); + .get_topmost_element_beneath_ray(Point2::new(first_touch.x, first_touch.y)); if let Some(topmost_node) = prospective_hit { let touches = args.touches.iter().map(|x| Touch::from(x)).collect(); let args_touch_end = ArgsTouchEnd { touches }; @@ -360,7 +361,7 @@ impl PaxChassisWeb { NativeInterrupt::DoubleClick(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let args_double_click = ArgsDoubleClick { mouse: MouseEventArgs { @@ -384,7 +385,7 @@ impl PaxChassisWeb { NativeInterrupt::MouseMove(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let args_mouse_move = ArgsMouseMove { mouse: MouseEventArgs { @@ -408,7 +409,7 @@ impl PaxChassisWeb { NativeInterrupt::Wheel(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let modifiers = args .modifiers @@ -428,7 +429,7 @@ impl PaxChassisWeb { NativeInterrupt::MouseDown(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let args_mouse_down = ArgsMouseDown { mouse: MouseEventArgs { @@ -452,7 +453,7 @@ impl PaxChassisWeb { NativeInterrupt::MouseUp(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let args_mouse_up = ArgsMouseUp { mouse: MouseEventArgs { @@ -472,7 +473,7 @@ impl PaxChassisWeb { NativeInterrupt::MouseOver(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let args_mouse_over = ArgsMouseOver { mouse: MouseEventArgs { @@ -496,7 +497,7 @@ impl PaxChassisWeb { NativeInterrupt::MouseOut(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let args_mouse_out = ArgsMouseOut { mouse: MouseEventArgs { @@ -520,7 +521,7 @@ impl PaxChassisWeb { NativeInterrupt::ContextMenu(args) => { let prospective_hit = engine .runtime_context - .get_topmost_element_beneath_ray((args.x, args.y)); + .get_topmost_element_beneath_ray(Point2::new(args.x, args.y)); if let Some(topmost_node) = prospective_hit { let args_context_menu = ArgsContextMenu { mouse: MouseEventArgs { diff --git a/pax-compiler/templates/cartridge_generation/cartridge.tera b/pax-compiler/templates/cartridge_generation/cartridge.tera index 45e8b3272..774c95ac7 100644 --- a/pax-compiler/templates/cartridge_generation/cartridge.tera +++ b/pax-compiler/templates/cartridge_generation/cartridge.tera @@ -511,4 +511,4 @@ impl DefinitionToInstanceTraverser { } None } -} \ No newline at end of file +} diff --git a/pax-compiler/templates/cartridge_generation/macros.tera b/pax-compiler/templates/cartridge_generation/macros.tera index 3c4786ea9..e3aabbe93 100644 --- a/pax-compiler/templates/cartridge_generation/macros.tera +++ b/pax-compiler/templates/cartridge_generation/macros.tera @@ -179,4 +179,4 @@ impl TypeFactory for {{active_type.type_id_escaped}}TypeFactory { } } -{%- endmacro -%} \ No newline at end of file +{%- endmacro -%} diff --git a/pax-engine/src/lib.rs b/pax-engine/src/lib.rs index 666ece346..fcd6e273a 100644 --- a/pax-engine/src/lib.rs +++ b/pax-engine/src/lib.rs @@ -2,6 +2,8 @@ pub extern crate pax_macro; pub use pax_macro::*; pub use pax_runtime::api; +pub use pax_runtime::engine::node_interface::*; +pub use pax_runtime::math; pub use pax_runtime::rendering; pub use pax_runtime::api::log; diff --git a/pax-runtime/src/api.rs b/pax-runtime/src/api.rs index 8d5380476..ee39264f9 100644 --- a/pax-runtime/src/api.rs +++ b/pax-runtime/src/api.rs @@ -11,6 +11,8 @@ use lazy_static::lazy_static; use mut_static::MutStatic; use piet::PaintBrush; +use crate::math::{Point2, Space}; +use crate::node_interface::NodeInterface; pub use crate::numeric::Numeric; use crate::{PropertyExpression, RuntimeContext}; use pax_manifest::constants::COMMON_PROPERTIES_TYPE; @@ -143,12 +145,47 @@ pub struct NodeContext<'a> { /// The bounds of this element in px pub bounds_self: (f64, f64), /// Borrow of the RuntimeContext, used at least for exposing raycasting to userland - pub runtime_context: &'a RuntimeContext, + pub(crate) runtime_context: &'a RuntimeContext, #[cfg(feature = "designtime")] pub designtime: Rc>, } +pub struct Window; + +impl Space for Window {} + +#[cfg(feature = "designtime")] +impl NodeContext<'_> { + pub fn raycast(&self, point: Point2) -> Vec { + let expanded_nodes = + self.runtime_context + .get_elements_beneath_ray(point.to_world(), false, vec![]); + expanded_nodes + .into_iter() + .map(Into::::into) + .collect() + } + + pub fn get_nodes_by_global_id(&self, type_id: &str, template_id: usize) -> Vec { + let expanded_nodes = self + .runtime_context + .get_expanded_nodes_by_global_ids(type_id, template_id); + expanded_nodes + .into_iter() + .map(Into::::into) + .collect() + } + + pub fn get_nodes_by_id(&self, id: &str) -> Vec { + let expanded_nodes = self.runtime_context.get_expanded_nodes_by_id(id); + expanded_nodes + .into_iter() + .map(Into::::into) + .collect() + } +} + // Unified events /// A Clap describes either a "click" (mousedown followed by mouseup), OR a diff --git a/pax-runtime/src/engine/expanded_node.rs b/pax-runtime/src/engine/expanded_node.rs index 73475df8a..6ffe1abcc 100644 --- a/pax-runtime/src/engine/expanded_node.rs +++ b/pax-runtime/src/engine/expanded_node.rs @@ -5,6 +5,7 @@ use crate::constants::{ MOUSE_OVER_HANDLERS, MOUSE_UP_HANDLERS, SCROLL_HANDLERS, TEXTBOX_CHANGE_HANDLERS, TOUCH_END_HANDLERS, TOUCH_MOVE_HANDLERS, TOUCH_START_HANDLERS, WHEEL_HANDLERS, }; +use crate::math::Point2; use crate::Globals; #[cfg(debug_assertions)] use core::fmt; @@ -12,8 +13,6 @@ use std::any::Any; use std::cell::RefCell; use std::rc::{Rc, Weak}; -use kurbo::Point; - use crate::api::{ ArgsButtonClick, ArgsCheckboxChange, ArgsClap, ArgsClick, ArgsContextMenu, ArgsDoubleClick, ArgsKeyDown, ArgsKeyPress, ArgsKeyUp, ArgsMouseDown, ArgsMouseMove, ArgsMouseOut, @@ -412,7 +411,7 @@ impl ExpandedNode { /// Determines whether the provided ray, orthogonal to the view plane, /// intersects this `ExpandedNode`. - pub fn ray_cast_test(&self, ray: &(f64, f64)) -> bool { + pub fn ray_cast_test(&self, ray: Point2) -> bool { // Don't vacuously hit for `invisible_to_raycasting` nodes if self.instance_node.base().flags().invisible_to_raycasting { return false; @@ -422,7 +421,7 @@ impl ExpandedNode { let computed_tab = &props.as_ref().unwrap().computed_tab; let inverted_transform = computed_tab.transform.inverse(); - let transformed_ray = inverted_transform * Point { x: ray.0, y: ray.1 }; + let transformed_ray = inverted_transform * ray; let relevant_bounds = computed_tab.bounds; diff --git a/pax-runtime/src/engine/mod.rs b/pax-runtime/src/engine/mod.rs index 1cfbc6332..81625dae5 100644 --- a/pax-runtime/src/engine/mod.rs +++ b/pax-runtime/src/engine/mod.rs @@ -8,17 +8,20 @@ use std::rc::Rc; use pax_message::{NativeMessage, OcclusionPatch}; use crate::api::{ - log, CommonProperties, Interpolatable, Layer, NodeContext, OcclusionLayerGen, RenderContext, + CommonProperties, Interpolatable, Layer, NodeContext, OcclusionLayerGen, RenderContext, TransitionManager, }; +use crate::math::Point2; use piet::InterpolationMode; use crate::declarative_macros::{handle_vtable_update, handle_vtable_update_optional}; use crate::{ - Affine, ComponentInstance, ExpressionContext, InstanceNode, RuntimeContext, + ComponentInstance, ExpressionContext, InstanceNode, RuntimeContext, RuntimePropertiesStackFrame, TransformAndBounds, }; +pub mod node_interface; + /// The atomic unit of rendering; also the container for each unique tuple of computed properties. /// Represents an expanded node, that is "expanded" in the context of computed properties and repeat expansion. /// For example, a Rectangle inside `for i in 0..3` and a `for j in 0..4` would have 12 expanded nodes representing the 12 virtual Rectangles in the @@ -253,12 +256,14 @@ impl PaxEngine { logger: crate::api::PlatformSpecificLogger, viewport_size: (f64, f64), ) -> Self { + use crate::math::Transform2; + crate::api::register_logger(logger); let globals = Globals { frames_elapsed: 0, viewport: TransformAndBounds { - transform: Affine::default(), + transform: Transform2::identity(), bounds: viewport_size, }, }; @@ -281,12 +286,14 @@ impl PaxEngine { viewport_size: (f64, f64), designtime: Rc>, ) -> Self { + use crate::math::Transform2; + crate::api::register_logger(logger); let globals = Globals { frames_elapsed: 0, viewport: TransformAndBounds { - transform: Affine::default(), + transform: Transform2::default(), bounds: viewport_size, }, designtime: designtime.clone(), @@ -388,7 +395,7 @@ impl PaxEngine { pub fn get_focused_element(&self) -> Option> { let (x, y) = self.runtime_context.globals().viewport.bounds; self.runtime_context - .get_topmost_element_beneath_ray((x / 2.0, y / 2.0)) + .get_topmost_element_beneath_ray(Point2::new(x / 2.0, y / 2.0)) } /// Called by chassis when viewport size changes, e.g. with native window resizes diff --git a/pax-runtime/src/engine/node_interface.rs b/pax-runtime/src/engine/node_interface.rs new file mode 100644 index 000000000..4cbb5669f --- /dev/null +++ b/pax-runtime/src/engine/node_interface.rs @@ -0,0 +1,73 @@ +use std::rc::Rc; + +use crate::{ + api::Window, + math::{Point2, Space, Transform2}, + ExpandedNode, +}; + +pub struct NodeInterface { + inner: Rc, +} + +impl From> for NodeInterface { + fn from(expanded_node: Rc) -> Self { + Self { + inner: expanded_node, + } + } +} + +pub struct NodeLocal; + +impl Space for NodeLocal {} + +impl NodeInterface { + pub fn global_id(&self) -> (String, usize) { + let base = self.inner.instance_node.base(); + (base.component_type_id.to_owned(), base.template_node_id) + } + + pub fn origin(&self) -> Option> { + let common_props = self.inner.get_common_properties(); + let common_props = common_props.borrow(); + let lp = self.inner.layout_properties.borrow(); + let tab = &lp.as_ref()?.computed_tab; + let p_anchor = Point2::new( + common_props + .anchor_x + .as_ref() + .map(|x| x.get().get_pixels(tab.bounds.0)) + .unwrap_or(0.0), + common_props + .anchor_y + .as_ref() + .map(|y| y.get().get_pixels(tab.bounds.1)) + .unwrap_or(0.0), + ); + let origin_window: Point2 = (tab.transform * p_anchor).to_world(); + Some(origin_window) + } + + pub fn transform(&self) -> Option> { + let up_lp = self.inner.layout_properties.borrow_mut(); + if let Some(lp) = up_lp.as_ref() { + Some(lp.computed_tab.transform.inverse().between_worlds()) + } else { + None + } + } + + pub fn bounding_points(&self) -> Option<[Point2; 4]> { + let lp = self.inner.layout_properties.borrow(); + if let Some(layout) = lp.as_ref() { + Some(layout.computed_tab.corners().map(Point2::to_world)) + } else { + None + } + } + + pub fn is_descendant_of(&self, node: &NodeInterface) -> bool { + self.inner.is_descendant_of(&node.inner.id_chain) + } +} diff --git a/pax-runtime/src/layout.rs b/pax-runtime/src/layout.rs index 0c182efa9..28903787c 100644 --- a/pax-runtime/src/layout.rs +++ b/pax-runtime/src/layout.rs @@ -1,6 +1,6 @@ use crate::api::{Axis, Size, Transform2D}; +use crate::math::{Generic, Transform2, Vector2}; use crate::{ExpandedNode, TransformAndBounds}; -use kurbo::Affine; /// For the `current_expanded_node` attached to `ptc`, calculates and returns a new [`crate::rendering::TransformAndBounds`] a.k.a. "tab". /// Intended as a helper method to be called during properties computation, for creating a new tab to attach to `ptc` for downstream calculations. @@ -119,7 +119,7 @@ pub trait ComputableTransform { &self, node_size: (f64, f64), container_bounds: (f64, f64), - ) -> Affine; + ) -> Transform2; } impl ComputableTransform for Transform2D { @@ -129,7 +129,7 @@ impl ComputableTransform for Transform2D { &self, node_size: (f64, f64), container_bounds: (f64, f64), - ) -> Affine { + ) -> Transform2 { //Three broad strokes: // a.) compute anchor // b.) decompose "vanilla" affine matrix @@ -137,7 +137,7 @@ impl ComputableTransform for Transform2D { // Compute anchor let anchor_transform = match &self.anchor { - Some(anchor) => Affine::translate(( + Some(anchor) => Transform2::::translate(Vector2::new( match anchor[0] { Size::Pixels(pix) => -pix.get_as_float(), Size::Percent(per) => -node_size.0 * (per / 100.0), @@ -154,7 +154,7 @@ impl ComputableTransform for Transform2D { }, )), //No anchor applied: treat as 0,0; identity matrix - None => Affine::default(), + None => Transform2::default(), }; //decompose vanilla affine matrix and pack into `Affine` @@ -199,12 +199,12 @@ impl ComputableTransform for Transform2D { let f = translate_y; let coeffs = [a, b, c, d, e, f]; - let transform = Affine::new(coeffs); + let transform = Transform2::new(coeffs); // Compute and combine previous_transform let previous_transform = match &self.previous { Some(previous) => (*previous).compute_transform2d_matrix(node_size, container_bounds), - None => Affine::default(), + None => Transform2::default(), }; transform * anchor_transform * previous_transform diff --git a/pax-runtime/src/lib.rs b/pax-runtime/src/lib.rs index 1291eab70..6f242ece5 100644 --- a/pax-runtime/src/lib.rs +++ b/pax-runtime/src/lib.rs @@ -1,4 +1,3 @@ -pub use kurbo::Affine; pub use piet::{Color, Error, StrokeStyle}; pub mod api; @@ -10,6 +9,7 @@ pub mod engine; pub mod expressions; pub mod form_event; pub mod layout; +pub mod math; pub mod numeric; pub mod properties; pub mod rendering; diff --git a/pax-runtime/src/math.rs b/pax-runtime/src/math.rs new file mode 100644 index 000000000..6f196eea7 --- /dev/null +++ b/pax-runtime/src/math.rs @@ -0,0 +1,31 @@ +use std::ops::Mul; + +use kurbo::Affine; + +mod point; +mod transform; +mod vector; + +pub use point::Point2; +pub use transform::Transform2; +pub use vector::Vector2; + +pub trait Space {} + +pub struct Generic; + +impl Space for Generic {} + +// TODO remove after Affine not used +impl Mul> for Affine { + type Output = Point2; + + #[inline] + fn mul(self, other: Point2) -> Point2 { + let coeffs = self.as_coeffs(); + Self::Output::new( + coeffs[0] * other.x + coeffs[2] * other.y + coeffs[4], + coeffs[1] * other.x + coeffs[3] * other.y + coeffs[5], + ) + } +} diff --git a/pax-runtime/src/math/point.rs b/pax-runtime/src/math/point.rs new file mode 100644 index 000000000..9caf065c4 --- /dev/null +++ b/pax-runtime/src/math/point.rs @@ -0,0 +1,86 @@ +use std::{ + marker::PhantomData, + ops::{Add, Sub}, +}; + +use super::{vector::Vector2, Generic, Space}; + +pub struct Point2 { + pub x: f64, + pub y: f64, + _panthom: PhantomData, +} + +// Implement Clone, Copy, PartialEq, etc manually, as +// to not require the Space to implement these. + +impl Clone for Point2 { + fn clone(&self) -> Self { + Self { + x: self.x, + y: self.y, + _panthom: PhantomData, + } + } +} + +impl Copy for Point2 {} + +impl PartialEq for Point2 { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +impl Default for Point2 { + fn default() -> Self { + Self::new(0.0, 0.0) + } +} + +impl Point2 { + pub fn new(x: f64, y: f64) -> Self { + Point2 { + x, + y, + _panthom: PhantomData, + } + } + + pub fn to_vector(self) -> Vector2 { + Vector2::new(self.x, self.y) + } + + pub fn to_world(self) -> Point2 { + Point2::new(self.x, self.y) + } + + pub fn midpoint_towards(self, other: Self) -> Self { + self.lerp_towards(other, 1.0 / 2.0) + } + + pub fn lerp_towards(self, other: Self, l: f64) -> Self { + let v = other - self; + self + l * v + } +} + +impl Sub for Point2 { + type Output = Vector2; + fn sub(self, rhs: Point2) -> Self::Output { + Self::Output::new(self.x - rhs.x, self.y - rhs.y) + } +} + +impl Add> for Point2 { + type Output = Point2; + fn add(self, rhs: Vector2) -> Self::Output { + Self::Output::new(self.x + rhs.x, self.y + rhs.y) + } +} +impl Add> for Vector2 { + type Output = Point2; + fn add(self, rhs: Point2) -> Self::Output { + Self::Output::new(self.x + rhs.x, self.y + rhs.y) + } +} diff --git a/pax-runtime/src/math/transform.rs b/pax-runtime/src/math/transform.rs new file mode 100644 index 000000000..93dfae459 --- /dev/null +++ b/pax-runtime/src/math/transform.rs @@ -0,0 +1,149 @@ +use super::{Generic, Point2, Space, Vector2}; +use std::{marker::PhantomData, ops::Mul}; + +//----------------------------------------------------------- +// Pax matrix/transform class heavily borrows from kurbos +// transform impl (copy/pasted initially with some modifications) +// curbo crate: https://www.michaelfbryan.com/arcs/kurbo/index.html +// original source code: https://www.michaelfbryan.com/arcs/src/kurbo/affine.rs.html#10 +// Kurbo is distributed under an MIT license. +//----------------------------------------------------------- + +pub struct Transform2 { + m: [f64; 6], + _panthom_from: PhantomData, + _panthom_to: PhantomData, +} + +// Implement Clone, Copy, PartialEq, etc manually, as +// to not require the Space to implement these. + +impl Clone for Transform2 { + fn clone(&self) -> Self { + Self { + m: self.m, + _panthom_from: PhantomData, + _panthom_to: PhantomData, + } + } +} + +impl std::fmt::Debug for Transform2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {} {}", self.m[0], self.m[1], self.m[2])?; + write!(f, "{} {} {}", self.m[3], self.m[4], self.m[5]) + } +} + +impl PartialEq for Transform2 { + fn eq(&self, other: &Self) -> bool { + self.m == other.m + } +} + +impl Copy for Transform2 {} + +impl Default for Transform2 { + fn default() -> Self { + Self::identity() + } +} + +impl Transform2 { + pub fn new(m: [f64; 6]) -> Self { + Self { + m, + _panthom_from: PhantomData, + _panthom_to: PhantomData, + } + } + + pub fn identity() -> Self { + Self::new([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]) + } + + pub fn scale(s: f64) -> Self { + Self::new([s, 0.0, 0.0, s, 0.0, 0.0]) + } + + pub fn rotate(th: f64) -> Self { + let (s, c) = th.sin_cos(); + Self::new([c, s, -s, c, 0.0, 0.0]) + } + + pub fn translate(p: Vector2) -> Self { + Self::new([1.0, 0.0, 0.0, 1.0, p.x, p.y]) + } + + pub fn determinant(self) -> f64 { + self.m[0] * self.m[3] - self.m[1] * self.m[2] + } + + pub fn coeffs(&self) -> [f64; 6] { + self.m + } + + pub fn get_translation(self) -> Vector2 { + (self * Point2::::default()).to_vector() + } + + pub fn between_worlds(self) -> Transform2 { + Transform2::new(self.m) + } + + /// Produces NaN values when the determinant is zero. + pub fn inverse(self) -> Transform2 { + let inv_det = self.determinant().recip(); + Transform2::::new([ + inv_det * self.m[3], + -inv_det * self.m[1], + -inv_det * self.m[2], + inv_det * self.m[0], + inv_det * (self.m[2] * self.m[5] - self.m[3] * self.m[4]), + inv_det * (self.m[1] * self.m[4] - self.m[0] * self.m[5]), + ]) + } +} + +impl Mul> for Transform2 { + type Output = Transform2; + + fn mul(self, rhs: Transform2) -> Self::Output { + Self::Output::new([ + self.m[0] * rhs.m[0] + self.m[2] * rhs.m[1], + self.m[1] * rhs.m[0] + self.m[3] * rhs.m[1], + self.m[0] * rhs.m[2] + self.m[2] * rhs.m[3], + self.m[1] * rhs.m[2] + self.m[3] * rhs.m[3], + self.m[0] * rhs.m[4] + self.m[2] * rhs.m[5] + self.m[4], + self.m[1] * rhs.m[4] + self.m[3] * rhs.m[5] + self.m[5], + ]) + } +} + +impl Mul> for Transform2 { + type Output = Point2; + + fn mul(self, other: Point2) -> Self::Output { + Self::Output::new( + self.m[0] * other.x + self.m[2] * other.y + self.m[4], + self.m[1] * other.x + self.m[3] * other.y + self.m[5], + ) + } +} + +impl Mul> for Transform2 { + type Output = Vector2; + + fn mul(self, other: Vector2) -> Self::Output { + Self::Output::new( + self.m[0] * other.x + self.m[2] * other.y, + self.m[1] * other.x + self.m[3] * other.y, + ) + } +} + +impl From> for kurbo::Affine { + fn from(value: Transform2) -> Self { + Self::new(value.m) + } +} diff --git a/pax-runtime/src/math/vector.rs b/pax-runtime/src/math/vector.rs new file mode 100644 index 000000000..bdc367fc0 --- /dev/null +++ b/pax-runtime/src/math/vector.rs @@ -0,0 +1,126 @@ +use std::{ + marker::PhantomData, + ops::{Add, Div, Mul, Neg, Sub}, +}; + +use super::{Generic, Point2, Space}; + +pub struct Vector2 { + pub x: f64, + pub y: f64, + _panthom: PhantomData, +} + +// Implement Clone, Copy, PartialEq, etc manually, as +// to not require the Space to implement these. + +impl Clone for Vector2 { + fn clone(&self) -> Self { + Self { + x: self.x, + y: self.y, + _panthom: PhantomData, + } + } +} + +impl Copy for Vector2 {} + +impl PartialEq for Vector2 { + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } +} + +impl Default for Vector2 { + fn default() -> Self { + Self::new(0.0, 0.0) + } +} + +impl Vector2 { + pub fn new(x: f64, y: f64) -> Self { + Self { + x, + y, + _panthom: PhantomData, + } + } + pub fn normal(&self) -> Self { + Self::new(-self.y, self.x) + } + + pub fn length_squared(&self) -> f64 { + self.x * self.x + self.y * self.y + } + + pub fn length(&self) -> f64 { + self.length_squared().sqrt() + } + + pub fn project_onto(self, axis: Vector2) -> f64 { + let dot_product = self * axis; + dot_product / axis.length_squared() + } + + pub fn to_point(self) -> Point2 { + Point2::new(self.x, self.y) + } + + pub fn to_world(self) -> Vector2 { + Vector2::new(self.x, self.y) + } +} + +impl Mul for Vector2 { + type Output = f64; + + fn mul(self, rhs: Vector2) -> Self::Output { + self.x * rhs.x + self.y * rhs.y + } +} + +impl Mul for Vector2 { + type Output = Self; + + fn mul(self, rhs: f64) -> Self::Output { + Vector2::new(self.x * rhs, self.y * rhs) + } +} +impl Mul> for f64 { + type Output = Vector2; + + fn mul(self, rhs: Vector2) -> Self::Output { + Vector2::new(rhs.x * self, rhs.y * self) + } +} + +impl Add for Vector2 { + type Output = Vector2; + + fn add(self, rhs: Vector2) -> Self::Output { + Self::Output::new(self.x + rhs.x, self.y + rhs.y) + } +} + +impl Neg for Vector2 { + type Output = Vector2; + + fn neg(self) -> Self::Output { + Self::Output::new(-self.x, -self.y) + } +} + +impl Sub for Vector2 { + type Output = Vector2; + fn sub(self, rhs: Vector2) -> Self::Output { + Self::Output::new(self.x - rhs.x, self.y - rhs.y) + } +} + +impl Div for Vector2 { + type Output = Vector2; + fn div(self, rhs: f64) -> Self::Output { + Self::Output::new(self.x / rhs, self.y / rhs) + } +} diff --git a/pax-runtime/src/properties.rs b/pax-runtime/src/properties.rs index f179e9638..75fe19e41 100644 --- a/pax-runtime/src/properties.rs +++ b/pax-runtime/src/properties.rs @@ -1,4 +1,4 @@ -use crate::api::log; +use crate::math::Point2; use crate::numeric::Numeric; use pax_message::NativeMessage; use std::cell::RefCell; @@ -81,7 +81,7 @@ impl RuntimeContext { /// not register a `hit`, nor will elements that suppress input events. pub fn get_elements_beneath_ray( &self, - ray: (f64, f64), + ray: Point2, limit_one: bool, mut accum: Vec>, ) -> Vec> { @@ -91,7 +91,7 @@ impl RuntimeContext { //Finally: check whether element itself satisfies hit_test(ray) for node in self.z_index_node_cache.iter().rev().skip(1) { - if node.ray_cast_test(&ray) { + if node.ray_cast_test(ray) { //We only care about the topmost node getting hit, and the element //pool is ordered by z-index so we can just resolve the whole //calculation when we find the first matching node @@ -104,7 +104,7 @@ impl RuntimeContext { if let Some(unwrapped_parent) = parent { if let Some(_) = unwrapped_parent.get_clipping_size() { ancestral_clipping_bounds_are_satisfied = - (*unwrapped_parent).ray_cast_test(&ray); + (*unwrapped_parent).ray_cast_test(ray); break; } parent = unwrapped_parent.parent_expanded_node.borrow().upgrade(); @@ -125,7 +125,7 @@ impl RuntimeContext { } /// Alias for `get_elements_beneath_ray` with `limit_one = true` - pub fn get_topmost_element_beneath_ray(&self, ray: (f64, f64)) -> Option> { + pub fn get_topmost_element_beneath_ray(&self, ray: Point2) -> Option> { let res = self.get_elements_beneath_ray(ray, true, vec![]); if res.len() == 0 { None diff --git a/pax-runtime/src/rendering.rs b/pax-runtime/src/rendering.rs index cfe1d5e32..18ba197de 100644 --- a/pax-runtime/src/rendering.rs +++ b/pax-runtime/src/rendering.rs @@ -1,12 +1,12 @@ use std::any::Any; use std::cell::RefCell; +use super::math::Point2; use std::iter; -use std::ops::{Add, Div, Mul, Sub}; use std::rc::Rc; use crate::api::{CommonProperties, RenderContext}; -pub use kurbo::Affine; +use crate::math::Transform2; use piet::{Color, StrokeStyle}; use crate::api::{ArgsScroll, Layer, Size}; @@ -33,104 +33,25 @@ pub struct InstantiationArgs { pub component_type_id: String, } -#[derive(Copy, Clone, Default, Debug)] -pub struct Point2D { - pub x: f64, - pub y: f64, -} - -impl Add for Point2D { - type Output = Point2D; - - fn add(self, rhs: Point2D) -> Self::Output { - Self::Output { - x: self.x + rhs.x, - y: self.y + rhs.y, - } - } -} - -impl Sub for Point2D { - type Output = Point2D; - fn sub(self, rhs: Point2D) -> Self::Output { - Self::Output { - x: self.x - rhs.x, - y: self.y - rhs.y, - } - } -} - -impl Div for Point2D { - type Output = Point2D; - fn div(self, rhs: f64) -> Self::Output { - Self::Output { - x: self.x / rhs, - y: self.y / rhs, - } - } -} - -impl Point2D { - fn subtract(self, other: Point2D) -> Self { - Self { - x: self.x - other.x, - y: self.y - other.y, - } - } - - fn dot(self, other: Point2D) -> f64 { - self.x * other.x + self.y * other.y - } - - fn normal(self) -> Self { - Self { - x: -self.y, - y: self.x, - } - } - - fn project_onto(self, axis: Point2D) -> f64 { - let dot_product = self.dot(axis); - dot_product / (axis.x.powi(2) + axis.y.powi(2)) - } -} - -impl Mul for Affine { - type Output = Point2D; - - #[inline] - fn mul(self, other: Point2D) -> Point2D { - let coeffs = self.as_coeffs(); - Point2D { - x: coeffs[0] * other.x + coeffs[2] * other.y + coeffs[4], - y: coeffs[1] * other.x + coeffs[3] * other.y + coeffs[5], - } - } -} - /// Stores the computed transform and the pre-transform bounding box (where the /// other corner is the origin). Useful for ray-casting, along with #[cfg_attr(debug_assertions, derive(Debug))] #[derive(Clone, PartialEq)] pub struct TransformAndBounds { - pub transform: Affine, + pub transform: Transform2, pub bounds: (f64, f64), // pub clipping_bounds: Option<(f64, f64)>, } impl TransformAndBounds { - pub fn corners(&self) -> [Point2D; 4] { + pub fn corners(&self) -> [Point2; 4] { let width = self.bounds.0; let height = self.bounds.1; - let top_left = self.transform * Point2D { x: 0.0, y: 0.0 }; - let top_right = self.transform * Point2D { x: width, y: 0.0 }; - let bottom_left = self.transform * Point2D { x: 0.0, y: height }; - let bottom_right = self.transform - * Point2D { - x: width, - y: height, - }; + let top_left = self.transform * Point2::new(0.0, 0.0); + let top_right = self.transform * Point2::new(width, 0.0); + let bottom_left = self.transform * Point2::new(0.0, height); + let bottom_right = self.transform * Point2::new(width, height); [top_left, top_right, bottom_right, bottom_left] } @@ -141,13 +62,15 @@ impl TransformAndBounds { let corners_other = other.corners(); for i in 0..2 { - let axis = corners_self[i].subtract(corners_self[(i + 1) % 4]).normal(); + let axis = (corners_self[i] - corners_self[(i + 1) % 4]).normal(); - let self_projections: Vec<_> = - corners_self.iter().map(|&p| p.project_onto(axis)).collect(); + let self_projections: Vec<_> = corners_self + .iter() + .map(|&p| p.to_vector().project_onto(axis)) + .collect(); let other_projections: Vec<_> = corners_other .iter() - .map(|&p| p.project_onto(axis)) + .map(|&p| p.to_vector().project_onto(axis)) .collect(); let (min_self, max_self) = min_max_projections(&self_projections); diff --git a/pax-std/pax-std-primitives/src/button.rs b/pax-std/pax-std-primitives/src/button.rs index a287afc10..c3a10b570 100644 --- a/pax-std/pax-std-primitives/src/button.rs +++ b/pax-std/pax-std-primitives/src/button.rs @@ -95,7 +95,7 @@ impl InstanceNode for ButtonInstance { patch_if_needed( &mut old_state.transform, &mut patch.transform, - computed_tab.transform.as_coeffs().to_vec(), + computed_tab.transform.coeffs().to_vec(), ), ]; if updates.into_iter().any(|v| v == true) { diff --git a/pax-std/pax-std-primitives/src/checkbox.rs b/pax-std/pax-std-primitives/src/checkbox.rs index 19811ad97..9fa81bb75 100644 --- a/pax-std/pax-std-primitives/src/checkbox.rs +++ b/pax-std/pax-std-primitives/src/checkbox.rs @@ -83,7 +83,7 @@ impl InstanceNode for CheckboxInstance { patch_if_needed( &mut old_state.transform, &mut patch.transform, - computed_tab.transform.as_coeffs().to_vec(), + computed_tab.transform.coeffs().to_vec(), ), ]; if updates.into_iter().any(|v| v == true) { diff --git a/pax-std/pax-std-primitives/src/ellipse.rs b/pax-std/pax-std-primitives/src/ellipse.rs index 900baef53..32ffcc135 100644 --- a/pax-std/pax-std-primitives/src/ellipse.rs +++ b/pax-std/pax-std-primitives/src/ellipse.rs @@ -48,7 +48,7 @@ impl InstanceNode for EllipseInstance { let accuracy = 0.1; let bez_path = ellipse.to_path(accuracy); - let transformed_bez_path = tab.transform * bez_path; + let transformed_bez_path = Into::::into(tab.transform) * bez_path; let duplicate_transformed_bez_path = transformed_bez_path.clone(); let color = if let Fill::Solid(properties_color) = properties.fill.get() { diff --git a/pax-std/pax-std-primitives/src/image.rs b/pax-std/pax-std-primitives/src/image.rs index 63c5abdd6..ab8a343b3 100644 --- a/pax-std/pax-std-primitives/src/image.rs +++ b/pax-std/pax-std-primitives/src/image.rs @@ -1,4 +1,4 @@ -use pax_runtime::api::RenderContext; +use pax_runtime::{api::RenderContext, math::Point2}; use pax_std::primitives::Image; use std::{cell::RefCell, collections::HashMap}; @@ -88,8 +88,8 @@ impl InstanceNode for ImageInstance { let height = bounding_dimens.1; let bounds = kurbo::Rect::new(0.0, 0.0, width, height); - let top_left = transform * kurbo::Point::new(bounds.min_x(), bounds.min_y()); - let bottom_right = transform * kurbo::Point::new(bounds.max_x(), bounds.max_y()); + let top_left = transform * Point2::new(bounds.min_x(), bounds.min_y()); + let bottom_right = transform * Point2::new(bounds.max_x(), bounds.max_y()); let transformed_bounds = kurbo::Rect::new(top_left.x, top_left.y, bottom_right.x, bottom_right.y); diff --git a/pax-std/pax-std-primitives/src/path.rs b/pax-std/pax-std-primitives/src/path.rs index ea37f79f9..ec24325c4 100644 --- a/pax-std/pax-std-primitives/src/path.rs +++ b/pax-std/pax-std-primitives/src/path.rs @@ -75,7 +75,10 @@ impl InstanceNode for PathInstance { } } - let transformed_bez_path = bez_path; + let computed_props = expanded_node.layout_properties.borrow(); + let tab = &computed_props.as_ref().unwrap().computed_tab; + + let transformed_bez_path = Into::::into(tab.transform) * bez_path; let duplicate_transformed_bez_path = transformed_bez_path.clone(); let color = properties.fill.get().to_piet_color(); diff --git a/pax-std/pax-std-primitives/src/rectangle.rs b/pax-std/pax-std-primitives/src/rectangle.rs index ef1125166..df9cf1283 100644 --- a/pax-std/pax-std-primitives/src/rectangle.rs +++ b/pax-std/pax-std-primitives/src/rectangle.rs @@ -80,7 +80,7 @@ impl InstanceNode for RectangleInstance { let rect = RoundedRect::new(0.0, 0.0, width, height, properties.corner_radii.get()); let bez_path = rect.to_path(0.1); - let transformed_bez_path = tab.transform * bez_path; + let transformed_bez_path = Into::::into(tab.transform) * bez_path; let duplicate_transformed_bez_path = transformed_bez_path.clone(); match properties.fill.get() { diff --git a/pax-std/pax-std-primitives/src/text.rs b/pax-std/pax-std-primitives/src/text.rs index 0e819641d..19a6071ad 100644 --- a/pax-std/pax-std-primitives/src/text.rs +++ b/pax-std/pax-std-primitives/src/text.rs @@ -105,7 +105,7 @@ impl InstanceNode for TextInstance { patch_if_needed( &mut old_state.transform, &mut patch.transform, - computed_tab.transform.as_coeffs().to_vec(), + computed_tab.transform.coeffs().to_vec(), ), ]; diff --git a/pax-std/pax-std-primitives/src/textbox.rs b/pax-std/pax-std-primitives/src/textbox.rs index e90f72710..a0eec2c76 100644 --- a/pax-std/pax-std-primitives/src/textbox.rs +++ b/pax-std/pax-std-primitives/src/textbox.rs @@ -81,7 +81,7 @@ impl InstanceNode for TextboxInstance { patch_if_needed( &mut old_state.transform, &mut patch.transform, - computed_tab.transform.as_coeffs().to_vec(), + computed_tab.transform.coeffs().to_vec(), ), ]; if updates.into_iter().any(|v| v == true) {