mabel_aseprite/
tags.rs

1use std::num::NonZeroU32;
2
3use crate::{reader::AseReader, user_data::UserData, AsepriteParseError, Result};
4
5/// A tag is a grouping of one or more frames.
6///
7/// Tag ranges may overlap each other. Tag names are _not_ guaranteed to be
8/// unique.
9#[derive(Debug, Clone)]
10pub struct Tag {
11    name: String,
12    from_frame: u16,
13    to_frame: u16,
14    repeat: u16,
15    animation_direction: AnimationDirection,
16    pub(crate) user_data: Option<UserData>,
17}
18
19impl Tag {
20    /// Tag name. May not be unique among all tags.
21    pub fn name(&self) -> &str {
22        &self.name
23    }
24
25    /// First frame included in the tag.
26    pub fn from_frame(&self) -> u32 {
27        self.from_frame as u32
28    }
29
30    /// Last frame included in the tag.
31    pub fn to_frame(&self) -> u32 {
32        self.to_frame as u32
33    }
34
35    /// See [AnimationDirection] for details.
36    pub fn animation_direction(&self) -> AnimationDirection {
37        self.animation_direction
38    }
39
40    /// Repeat count included in the tag.
41    ///
42    /// `None` if unspecified/not enabled (UI shows infinity symbol).
43    pub fn repeat(&self) -> Option<NonZeroU32> {
44        NonZeroU32::new(self.repeat as u32)
45    }
46
47    /// Returns the user data for the tag, if any exists.
48    pub fn user_data(&self) -> Option<&UserData> {
49        self.user_data.as_ref()
50    }
51
52    pub(crate) fn set_user_data(&mut self, user_data: UserData) {
53        self.user_data = Some(user_data);
54    }
55}
56
57/// Describes how the tag's frames should be animated.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum AnimationDirection {
60    /// Start at `from_frame` and count up to `to_frame`.
61    Forward,
62    /// Start at `to_frame` and count down to `from_frame`.
63    Reverse,
64    /// Start at `from_frame`, count up to `to_frame`, then back down to `from_frame`.
65    PingPong,
66}
67
68pub(crate) fn parse_chunk(data: &[u8]) -> Result<Vec<Tag>> {
69    let mut reader = AseReader::new(data);
70
71    let num_tags = reader.word()?;
72    reader.skip_reserved(8)?;
73
74    let mut result = Vec::with_capacity(num_tags as usize);
75
76    for _tag in 0..num_tags {
77        let from_frame = reader.word()?;
78        let to_frame = reader.word()?;
79        let anim_dir = reader.byte()?;
80        let repeat = reader.word()?;
81        reader.skip_reserved(6)?;
82        let _color = reader.dword()?;
83        let name = reader.string()?;
84        let animation_direction = parse_animation_direction(anim_dir)?;
85        result.push(Tag {
86            name,
87            from_frame,
88            to_frame,
89            animation_direction,
90            repeat,
91            user_data: None,
92        });
93    }
94
95    Ok(result)
96}
97
98fn parse_animation_direction(id: u8) -> Result<AnimationDirection> {
99    match id {
100        0 => Ok(AnimationDirection::Forward),
101        1 => Ok(AnimationDirection::Reverse),
102        2 => Ok(AnimationDirection::PingPong),
103        _ => Err(AsepriteParseError::InvalidInput(format!(
104            "Unknown animation direction: {}",
105            id
106        ))),
107    }
108}