From 2199add07d449027d4c95d0e06ea045b26f6a520 Mon Sep 17 00:00:00 2001 From: Brandon Reinhart Date: Sat, 11 Nov 2023 23:38:30 -0600 Subject: [PATCH] custom attachments (#20) * Add mutable string accessor macro. * Add an attachment loader wrapper. * temp patch spine_c to refcount obv temporary, see https://github.com/EsotericSoftware/spine-runtimes/pull/2399 * Convenience fn for creating region attachments. * return nulerror * clippy * fix doc string refs * move unsafe to interior As far as I can tell, this is safe to do if the attachment is valid, once esoteric updates spine with the ref count increment in spSlot_setAttachment * undo * mark unsafe again * clippy * Update src/attachment_loader.rs * Update slot.rs --------- Co-authored-by: jabu <46233424+jabuwu@users.noreply.github.com> --- src/attachment_loader.rs | 129 +++++++++++++++++++++++++++++++++++++++ src/c/spine_c.rs | 1 + src/c_interface.rs | 28 +++++++++ src/lib.rs | 2 + src/region_attachment.rs | 50 ++++++++++++--- 5 files changed, 201 insertions(+), 9 deletions(-) create mode 100644 src/attachment_loader.rs diff --git a/src/attachment_loader.rs b/src/attachment_loader.rs new file mode 100644 index 0000000..fb911dd --- /dev/null +++ b/src/attachment_loader.rs @@ -0,0 +1,129 @@ +use crate::{ + c::{ + spAtlasAttachmentLoader_create, spAttachmentLoader, spAttachmentLoader_createAttachment, + spAttachmentLoader_dispose, + }, + c_interface::{NewFromPtr, SyncPtr}, + Atlas, Attachment, AttachmentType, RegionProps, Skin, +}; + +/// Error types related to [`AttachmentLoader`](`crate::AttachmentLoader`). +#[derive(Debug)] +pub enum AttachmentLoaderError { + /// Creating an attachment failed. + /// Check [`error1`](`Self::error1`) and [`error2`](`Self::error2`) for more information. + CreateAttachmentFailed, + InvalidArgument { + field: &'static str, + }, +} + +#[derive(Debug)] +pub struct AttachmentLoader { + c_attachment_loader: SyncPtr, +} + +impl NewFromPtr for AttachmentLoader { + unsafe fn new_from_ptr(c_attachment_loader: *mut spAttachmentLoader) -> Self { + Self { + c_attachment_loader: SyncPtr(c_attachment_loader), + } + } +} + +impl AttachmentLoader { + /// The spine runtime offers a default [`AttachmentLoader`](`crate::AttachmentLoader`) that + /// loads attachments from an [`Atlas`](`crate::Atlas`). + pub fn new_atlas_loader(atlas: &Atlas) -> Self { + unsafe { + let atlas_attachment_loader = spAtlasAttachmentLoader_create(atlas.c_ptr()); + let attachment_loader = &mut (*atlas_attachment_loader).super_0; + Self::new_from_ptr(attachment_loader) + } + } + + /// Creates an [`Attachment`](`crate::Attachment`) of a specified type. + /// + /// # Errors + /// + /// Returns [`AttachmentLoaderError::CreateAttachmentFailed`] if creating the attachment failed. + /// Check [`error1`](`Self::error1`) and [`error2`](`Self::error2`) for more information. + /// Returns [`AttachmentLoaderError::InvalidArgument`] if `name` or `path` contain a null byte. + pub fn create_attachment( + &self, + skin: Option, + attachment_type: AttachmentType, + name: &str, + path: &str, + ) -> Result { + let c_name = std::ffi::CString::new(name) + .map_err(|_| AttachmentLoaderError::InvalidArgument { field: "name" })?; + let c_path = std::ffi::CString::new(path) + .map_err(|_| AttachmentLoaderError::InvalidArgument { field: "path" })?; + + unsafe { + let c_name = c_name.as_ptr(); + let c_path = c_path.as_ptr(); + let c_skin = skin.map_or(std::ptr::null_mut(), |skin| skin.c_ptr()); + let c_sequence = std::ptr::null_mut(); // What is this for? + + let attachment = spAttachmentLoader_createAttachment( + self.c_ptr(), + c_skin, + attachment_type as u32, + c_name, + c_path, + c_sequence, + ); + + if attachment.is_null() { + Err(AttachmentLoaderError::CreateAttachmentFailed) + } else { + Ok(Attachment::new_from_ptr(attachment)) + } + } + } + + /// Convenience function for creating a [`RegionAttachment`](`crate::RegionAttachment`). + /// + /// # Errors + /// + /// Returns [`AttachmentLoaderError::CreateAttachmentFailed`] if creating the attachment failed. + /// Check [`error1`](`Self::error1`) and [`error2`](`Self::error2`) for more information. + /// Returns [`AttachmentLoaderError::InvalidArgument`] if `name` or `path` contain a null byte. + pub fn create_region_attachment( + &self, + skin: Option, + name: &str, + path: &str, + props: &RegionProps, + ) -> Result { + let attachment = self.create_attachment(skin, AttachmentType::Region, name, path)?; + + let Some(mut region) = attachment.as_region() else { + return Err(AttachmentLoaderError::CreateAttachmentFailed); + }; + + region.update_from_props(props); + + Ok(attachment) + } + + c_accessor_string!(error1, error1); + c_accessor_string!(error2, error2); + c_ptr!(c_attachment_loader, spAttachmentLoader); +} + +impl Clone for AttachmentLoader { + fn clone(&self) -> Self { + unsafe { AttachmentLoader::new_from_ptr(self.c_ptr()) } + } +} + +impl Drop for AttachmentLoader { + fn drop(&mut self) { + unsafe { + spAttachmentLoader_dispose(self.c_ptr()); + } + } +} diff --git a/src/c/spine_c.rs b/src/c/spine_c.rs index 07d907e..cdfb4d3 100644 --- a/src/c/spine_c.rs +++ b/src/c/spine_c.rs @@ -24449,6 +24449,7 @@ pub unsafe extern "C" fn spSlot_setAttachment( if attachment == (*self_0).attachment { return; } + if isVertexAttachment(attachment) == 0 || isVertexAttachment((*self_0).attachment) == 0 || (*(attachment as *mut spVertexAttachment)).timelineAttachment diff --git a/src/c_interface.rs b/src/c_interface.rs index 2d760c2..0019d2e 100644 --- a/src/c_interface.rs +++ b/src/c_interface.rs @@ -462,6 +462,34 @@ macro_rules! c_accessor_string { }; } +macro_rules! c_accessor_string_mut { + ($(#[$($attrss:tt)*])* $rust:ident, $rust_set:ident, $c:ident) => { + $(#[$($attrss)*])* + #[must_use] + pub fn $rust(&self) -> &str { + unsafe { + if !self.c_ptr_ref().$c.is_null() { + crate::c_interface::from_c_str(std::ffi::CStr::from_ptr(self.c_ptr_ref().$c)) + } else { + "" + } + } + } + + /// # Errors + /// + /// Returns [`std::ffi::NulError`] if an interior nul byte is found. + $(#[$($attrss)*])* + pub fn $rust_set(&mut self, value: String) -> Result<(), std::ffi::NulError> { + let c_str = std::ffi::CString::new(value)?; + unsafe { + self.c_ptr_mut().$c = c_str.into_raw(); + } + Ok(()) + } + }; +} + macro_rules! c_accessor_string_optional { ($(#[$($attrss:tt)*])* $rust:ident, $c:ident) => { $(#[$($attrss)*])* diff --git a/src/lib.rs b/src/lib.rs index 00c0d9f..ec59fd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,7 @@ mod animation_state_data; #[path = "atlas.rs"] mod atlas_mod; mod attachment; +mod attachment_loader; mod bone; mod bounding_box_attachment; mod clipping_attachment; @@ -73,6 +74,7 @@ pub use animation_state::*; pub use animation_state_data::*; pub use atlas_mod::{atlas, Atlas}; pub use attachment::*; +pub use attachment_loader::*; pub use bone::*; pub use bounding_box_attachment::*; pub use clipping_attachment::*; diff --git a/src/region_attachment.rs b/src/region_attachment.rs index 94fe569..d12bdd8 100644 --- a/src/region_attachment.rs +++ b/src/region_attachment.rs @@ -6,11 +6,24 @@ use crate::{ c_interface::SyncPtr, slot::Slot, texture_region::TextureRegion, + Color, }; #[cfg(feature = "mint")] use mint::Vector2; +#[derive(Debug)] +pub struct RegionProps { + pub x: f32, + pub y: f32, + pub scale_x: f32, + pub scale_y: f32, + pub rotation: f32, + pub width: f32, + pub height: f32, + pub color: Color, +} + /// An attachment which draws a texture. /// /// [Spine API Reference](http://esotericsoftware.com/spine-api-reference#RegionAttachment) @@ -57,52 +70,71 @@ impl RegionAttachment { } } + pub fn update_from_props(&mut self, props: &RegionProps) { + self.set_x(props.x); + self.set_y(props.y); + self.set_scale_x(props.scale_x); + self.set_scale_y(props.scale_y); + self.set_rotation(props.rotation); + self.set_width(props.width); + self.set_height(props.height); + *self.color_mut() = props.color; + self.update_region(); + } + c_attachment_accessors!(); - c_accessor_string!(path, path); - c_accessor!( + c_accessor_string_mut!(path, set_path, path); + c_accessor_mut!( /// The local x translation. x, + set_x, x, f32 ); - c_accessor!( + c_accessor_mut!( /// The local y translation. y, + set_y, y, f32 ); - c_accessor!( + c_accessor_mut!( /// The local scaleX. scale_x, + set_scale_x, scaleX, f32 ); - c_accessor!( + c_accessor_mut!( /// The local scaleY. scale_y, + set_scale_y, scaleY, f32 ); // TODO: docs: in degrees? counter-clockwise? - c_accessor!( + c_accessor_mut!( /// The local rotation. rotation, + set_rotation, rotation, f32 ); - c_accessor!( + c_accessor_mut!( /// The width of the region attachment in Spine. width, + set_width, width, f32 ); - c_accessor!( + c_accessor_mut!( /// The height of the region attachment in Spine. height, + set_height, height, f32 ); - c_accessor_color!(color, color); + c_accessor_color_mut!(color, color_mut, color); c_accessor_passthrough!(uvs, uvs, [c_float; 8]); c_accessor_passthrough!(offset, offset, [c_float; 8]); c_accessor_renderer_object!();