Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom attachments #20

Merged
merged 13 commits into from
Nov 12, 2023
129 changes: 129 additions & 0 deletions src/attachment_loader.rs
Original file line number Diff line number Diff line change
@@ -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<spAttachmentLoader>,
}

impl NewFromPtr<spAttachmentLoader> 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<Skin>,
attachment_type: AttachmentType,
name: &str,
path: &str,
) -> Result<Attachment, AttachmentLoaderError> {
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<Skin>,
name: &str,
path: &str,
props: &RegionProps,
) -> Result<Attachment, AttachmentLoaderError> {
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());
}
}
}
1 change: 1 addition & 0 deletions src/c/spine_c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions src/c_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)*])*
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::*;
Expand Down
50 changes: 41 additions & 9 deletions src/region_attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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!();
Expand Down