osu_file_parser/osu_file/events/storyboard/
sprites.rs

1use std::path::{Path, PathBuf};
2
3use either::Either;
4use nom::branch::alt;
5use nom::bytes::complete::tag;
6use nom::combinator::{cut, fail};
7use nom::error::context;
8use nom::sequence::{preceded, tuple};
9use nom::Parser;
10
11use crate::events::EventWithCommands;
12use crate::osu_file::{
13    FilePath, Position, Version, VersionedDefault, VersionedFromStr, VersionedToString,
14};
15use crate::parsers::{
16    comma, comma_field, comma_field_type, comma_field_versioned_type, consume_rest_versioned_type,
17    nothing,
18};
19use crate::{Integer, VersionedFrom, VersionedTryFrom};
20
21use super::cmds::*;
22use super::error::*;
23
24#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum Layer {
27    Background,
28    Fail,
29    Pass,
30    Foreground,
31    Overlay,
32}
33
34impl VersionedFromStr for Layer {
35    type Err = ParseLayerError;
36
37    fn from_str(s: &str, _: Version) -> std::result::Result<Option<Self>, Self::Err> {
38        match s {
39            "Background" => Ok(Some(Layer::Background)),
40            "Fail" => Ok(Some(Layer::Fail)),
41            "Pass" => Ok(Some(Layer::Pass)),
42            "Foreground" => Ok(Some(Layer::Foreground)),
43            "Overlay" => Ok(Some(Layer::Overlay)),
44            _ => Err(ParseLayerError::UnknownVariant),
45        }
46    }
47}
48
49impl VersionedToString for Layer {
50    fn to_string(&self, _: Version) -> Option<String> {
51        let layer = match self {
52            Layer::Background => "Background",
53            Layer::Fail => "Fail",
54            Layer::Pass => "Pass",
55            Layer::Foreground => "Foreground",
56            Layer::Overlay => "Overlay",
57        };
58
59        Some(layer.to_string())
60    }
61}
62
63#[derive(Clone, Debug, Hash, PartialEq, Eq)]
64pub struct Object {
65    pub layer: Layer,
66    pub origin: Origin,
67    pub position: Position,
68    pub object_type: ObjectType,
69    pub commands: Vec<Command>,
70}
71
72impl VersionedToString for Object {
73    fn to_string(&self, version: Version) -> Option<String> {
74        self.to_string_variables(version, &[])
75    }
76}
77
78impl EventWithCommands for Object {
79    fn commands(&self) -> &[Command] {
80        &self.commands
81    }
82
83    fn commands_mut(&mut self) -> &mut Vec<Command> {
84        &mut self.commands
85    }
86
87    fn to_string_cmd(&self, version: Version) -> Option<String> {
88        let pos_str = format!("{},{}", self.position.x, self.position.y);
89
90        let obj = match &self.object_type {
91            ObjectType::Sprite(sprite) => format!(
92                "Sprite,{},{},{},{}",
93                self.layer.to_string(version).unwrap(),
94                self.origin.to_string(version).unwrap(),
95                sprite.filepath.to_string(version).unwrap(),
96                pos_str
97            ),
98            ObjectType::Animation(anim) => {
99                format!(
100                    "Animation,{},{},{},{},{},{},{}",
101                    self.layer.to_string(version).unwrap(),
102                    self.origin.to_string(version).unwrap(),
103                    anim.filepath.to_string(version).unwrap(),
104                    pos_str,
105                    anim.frame_count,
106                    anim.frame_delay,
107                    anim.loop_type.to_string(version).unwrap()
108                )
109            }
110        };
111
112        Some(obj)
113    }
114}
115
116// it will reject commands since push_cmd is used for that case
117impl VersionedFromStr for Object {
118    type Err = ParseObjectError;
119
120    fn from_str(s: &str, version: Version) -> Result<Option<Self>, Self::Err> {
121        let layer = || {
122            preceded(
123                context(ParseObjectError::MissingLayer.into(), comma()),
124                context(
125                    ParseObjectError::InvalidLayer.into(),
126                    comma_field_versioned_type(version),
127                ),
128            )
129        };
130        let origin = || {
131            preceded(
132                context(ParseObjectError::MissingOrigin.into(), comma()),
133                context(
134                    ParseObjectError::InvalidOrigin.into(),
135                    comma_field_versioned_type(version),
136                ),
137            )
138        };
139        let file_path = || {
140            preceded(
141                context(ParseObjectError::MissingFilePath.into(), comma()),
142                comma_field(),
143            )
144            .map(|p| p.into())
145        };
146        let position = || {
147            tuple((
148                preceded(
149                    context(ParseObjectError::MissingPositionX.into(), comma()),
150                    context(
151                        ParseObjectError::InvalidPositionX.into(),
152                        comma_field_type(),
153                    ),
154                ),
155                preceded(
156                    context(ParseObjectError::MissingPositionY.into(), comma()),
157                    context(
158                        ParseObjectError::InvalidPositionY.into(),
159                        comma_field_type(),
160                    ),
161                ),
162            ))
163            .map(|(x, y)| Position { x, y })
164        };
165        let frame_count = preceded(
166            context(ParseObjectError::MissingFrameCount.into(), comma()),
167            context(
168                ParseObjectError::InvalidFrameCount.into(),
169                comma_field_type(),
170            ),
171        );
172        let frame_delay = preceded(
173            context(ParseObjectError::MissingFrameDelay.into(), comma()),
174            context(
175                ParseObjectError::InvalidFrameDelay.into(),
176                comma_field_type(),
177            ),
178        );
179        let loop_type = alt((
180            preceded(
181                context(ParseObjectError::MissingLoopType.into(), comma()),
182                context(
183                    ParseObjectError::InvalidLoopType.into(),
184                    consume_rest_versioned_type(version),
185                ),
186            ),
187            nothing().map(|_| LoopType::default(version).unwrap()),
188        ));
189
190        let (_, object) = alt((
191            preceded(
192                tag("Sprite"),
193                cut(tuple((layer(), origin(), file_path(), position()))).map(
194                    |(layer, origin, filepath, position)| Object {
195                        layer,
196                        origin,
197                        position,
198                        object_type: ObjectType::Sprite(Sprite { filepath }),
199                        commands: Vec::new(),
200                    },
201                ),
202            ),
203            preceded(
204                tag("Animation"),
205                cut(tuple((
206                    layer(),
207                    origin(),
208                    file_path(),
209                    position(),
210                    frame_count,
211                    frame_delay,
212                    loop_type,
213                ))),
214            )
215            .map(
216                |(layer, origin, filepath, position, frame_count, frame_delay, loop_type)| Object {
217                    layer,
218                    origin,
219                    position,
220                    object_type: ObjectType::Animation(Animation {
221                        frame_count,
222                        frame_delay,
223                        loop_type,
224                        filepath,
225                    }),
226                    commands: Vec::new(),
227                },
228            ),
229            context(ParseObjectError::UnknownObjectType.into(), fail),
230        ))(s)?;
231
232        Ok(Some(object))
233    }
234}
235
236#[derive(Clone, Debug, Hash, PartialEq, Eq)]
237pub struct Animation {
238    pub frame_count: u32,
239    pub frame_delay: rust_decimal::Decimal,
240    pub loop_type: LoopType,
241    pub filepath: FilePath,
242}
243
244impl Animation {
245    pub fn frame_file_names(&self) -> Vec<PathBuf> {
246        let mut file_names = Vec::with_capacity(self.frame_count as usize);
247
248        let filepath = self.filepath.get();
249        let mut file_name = filepath.file_name().unwrap().to_string_lossy().to_string();
250        let file_extension = match filepath.extension() {
251            Some(extension) => {
252                let extension = format!(".{}", extension.to_string_lossy());
253                file_name = file_name.strip_suffix(&extension).unwrap().to_string();
254                extension
255            }
256            None => String::new(),
257        };
258
259        for i in 0..self.frame_count {
260            file_names.push(format!("{file_name}{i}{file_extension}").into());
261        }
262
263        file_names
264    }
265}
266
267#[derive(Clone, Debug, Hash, PartialEq, Eq)]
268pub struct Sprite {
269    pub filepath: FilePath,
270}
271
272impl Sprite {
273    pub fn new(filepath: &Path) -> Result<Self, FilePathNotRelative> {
274        if filepath.is_absolute() {
275            Err(FilePathNotRelative)
276        } else {
277            Ok(Self {
278                filepath: filepath.into(),
279            })
280        }
281    }
282}
283
284#[derive(Clone, Debug, Hash, PartialEq, Eq)]
285#[non_exhaustive]
286pub enum ObjectType {
287    Sprite(Sprite),
288    Animation(Animation),
289}
290
291#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
292pub struct Origin {
293    /// Origin type.
294    /// - `Left` variant would be the valid enum variants.
295    /// - `Right` variant is for other variants that is used but isn't documented.
296    pub type_: Either<OriginType, Integer>,
297    pub shorthand: bool,
298}
299
300#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
301#[non_exhaustive]
302pub enum OriginType {
303    TopLeft,
304    Centre,
305    CentreLeft,
306    TopRight,
307    BottomCentre,
308    TopCentre,
309    Custom,
310    CentreRight,
311    BottomLeft,
312    BottomRight,
313}
314
315impl From<OriginType> for Origin {
316    fn from(type_: OriginType) -> Self {
317        Self {
318            type_: Either::Left(type_),
319            shorthand: true,
320        }
321    }
322}
323
324impl VersionedToString for Origin {
325    fn to_string(&self, version: Version) -> Option<String> {
326        let origin = if self.shorthand {
327            let origin = match self.type_ {
328                Either::Left(type_) => {
329                    <Integer as VersionedFrom<OriginType>>::from(type_, version).unwrap()
330                }
331                Either::Right(type_) => type_,
332            };
333
334            origin.to_string()
335        } else {
336            match self.type_ {
337                Either::Left(type_) => match type_ {
338                    OriginType::TopLeft => "TopLeft",
339                    OriginType::Centre => "Centre",
340                    OriginType::CentreLeft => "CentreLeft",
341                    OriginType::TopRight => "TopRight",
342                    OriginType::BottomCentre => "BottomCentre",
343                    OriginType::TopCentre => "TopCentre",
344                    OriginType::Custom => "Custom",
345                    OriginType::CentreRight => "CentreRight",
346                    OriginType::BottomLeft => "BottomLeft",
347                    OriginType::BottomRight => "BottomRight",
348                }
349                .to_string(),
350                Either::Right(type_) => type_.to_string(),
351            }
352        };
353
354        Some(origin)
355    }
356}
357
358impl VersionedFromStr for Origin {
359    type Err = ParseOriginError;
360
361    fn from_str(s: &str, version: Version) -> std::result::Result<Option<Self>, Self::Err> {
362        match s {
363            "TopLeft" => Ok(Some(Origin {
364                shorthand: false,
365                type_: Either::Left(OriginType::TopLeft),
366            })),
367            "Centre" => Ok(Some(Origin {
368                shorthand: false,
369                type_: Either::Left(OriginType::Centre),
370            })),
371            "CentreLeft" => Ok(Some(Origin {
372                shorthand: false,
373                type_: Either::Left(OriginType::CentreLeft),
374            })),
375            "TopRight" => Ok(Some(Origin {
376                shorthand: false,
377                type_: Either::Left(OriginType::TopRight),
378            })),
379            "BottomCentre" => Ok(Some(Origin {
380                shorthand: false,
381                type_: Either::Left(OriginType::BottomCentre),
382            })),
383            "TopCentre" => Ok(Some(Origin {
384                shorthand: false,
385                type_: Either::Left(OriginType::TopCentre),
386            })),
387            "Custom" => Ok(Some(Origin {
388                shorthand: false,
389                type_: Either::Left(OriginType::Custom),
390            })),
391            "CentreRight" => Ok(Some(Origin {
392                shorthand: false,
393                type_: Either::Left(OriginType::CentreRight),
394            })),
395            "BottomLeft" => Ok(Some(Origin {
396                shorthand: false,
397                type_: Either::Left(OriginType::BottomLeft),
398            })),
399            "BottomRight" => Ok(Some(Origin {
400                shorthand: false,
401                type_: Either::Left(OriginType::BottomRight),
402            })),
403            _ => {
404                let s = s.parse()?;
405                let type_ = match <OriginType as VersionedTryFrom<Integer>>::try_from(s, version) {
406                    Ok(type_) => Either::Left(type_.unwrap()),
407                    Err(err) => match err {
408                        OriginTryFromIntError::UnknownVariant => Either::Right(s),
409                    },
410                };
411
412                Ok(Some(Origin {
413                    type_,
414                    shorthand: true,
415                }))
416            }
417        }
418    }
419}
420
421impl VersionedTryFrom<Integer> for OriginType {
422    type Error = OriginTryFromIntError;
423
424    fn try_from(value: Integer, _: Version) -> Result<Option<Self>, Self::Error> {
425        match value {
426            0 => Ok(Some(OriginType::TopLeft)),
427            1 => Ok(Some(OriginType::Centre)),
428            2 => Ok(Some(OriginType::CentreLeft)),
429            3 => Ok(Some(OriginType::TopRight)),
430            4 => Ok(Some(OriginType::BottomCentre)),
431            5 => Ok(Some(OriginType::TopCentre)),
432            6 => Ok(Some(OriginType::Custom)),
433            7 => Ok(Some(OriginType::CentreRight)),
434            8 => Ok(Some(OriginType::BottomLeft)),
435            9 => Ok(Some(OriginType::BottomRight)),
436            _ => Err(OriginTryFromIntError::UnknownVariant),
437        }
438    }
439}
440
441impl VersionedFrom<OriginType> for Integer {
442    fn from(value: OriginType, _: Version) -> Option<Self> {
443        match value {
444            OriginType::TopLeft => Some(0),
445            OriginType::Centre => Some(1),
446            OriginType::CentreLeft => Some(2),
447            OriginType::TopRight => Some(3),
448            OriginType::BottomCentre => Some(4),
449            OriginType::TopCentre => Some(5),
450            OriginType::Custom => Some(6),
451            OriginType::CentreRight => Some(7),
452            OriginType::BottomLeft => Some(8),
453            OriginType::BottomRight => Some(9),
454        }
455    }
456}
457
458#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
459#[non_exhaustive]
460pub enum LoopType {
461    LoopForever,
462    LoopOnce,
463}
464
465impl VersionedToString for LoopType {
466    fn to_string(&self, _: Version) -> Option<String> {
467        let loop_type = match self {
468            LoopType::LoopForever => "LoopForever",
469            LoopType::LoopOnce => "LoopOnce",
470        };
471
472        Some(loop_type.to_string())
473    }
474}
475
476impl VersionedFromStr for LoopType {
477    type Err = ParseLoopTypeError;
478
479    fn from_str(s: &str, _: Version) -> std::result::Result<Option<Self>, Self::Err> {
480        match s {
481            "LoopForever" => Ok(Some(LoopType::LoopForever)),
482            "LoopOnce" => Ok(Some(LoopType::LoopOnce)),
483            _ => Err(ParseLoopTypeError::UnknownVariant),
484        }
485    }
486}
487
488impl VersionedDefault for LoopType {
489    fn default(_: Version) -> Option<Self> {
490        Some(Self::LoopForever)
491    }
492}