1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#![deny(unsafe_code)]
//! Plugin for defining and loading [TextureAtlas] assets.
//!
//! The definitions are added as resources (see [GenericAtlasDefinitions] or
//! [TypedAtlasDefinition]), generic over some enumeration index `T`.
//!
//! The plugin then loads the needed images as assets before creating the individual
//! [TextureAtlas].
//!
//! Finally a resource [AtlasTextures<T>] is created (for each `T`) through which the
//! [TextureAtlas] handles can be retrieved by the enumeration index `T`.
//!
//! The plugin also provides an event [AtlasTexturesEvent<T>] upon completion or failure.

use bevy::{prelude::*, sprite::TextureAtlas, utils::HashMap};
use derive_more::IsVariant;
use std::marker::PhantomData;

pub use self::definitions::*;
pub use self::systems::*;

mod definitions;
mod systems;

/// Plugin for loading and creating [TextureAtlas] from a simple definition, and providing the
/// results in a [AtlasTextures<T>] resource.
///
/// See [GenericAtlasDefinitions].
pub struct AtlasTexturePlugin<T>(PhantomData<T>);

impl<T> Plugin for AtlasTexturePlugin<T>
where
    T: Send + Sync + Eq + std::str::FromStr + std::hash::Hash + strum::VariantNames + 'static,
    <T as std::str::FromStr>::Err: std::fmt::Debug,
{
    fn build(&self, app: &mut App) {
        app.add_system(process_atlas_definitions::<T>)
            .add_asset::<GenericAtlasDefinitions>()
            .add_event::<AtlasTexturesEvent<T>>();
    }
}

impl<T> Default for AtlasTexturePlugin<T> {
    fn default() -> Self {
        Self(Default::default())
    }
}

/// Resulting resource after creating all [TextureAtlas] for some enumeration index `T`.
///
/// Example:
/// ```
/// # use bevy::prelude::*;
/// # use bevy_atlas_loader::*;
/// #
/// #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
/// #[derive(strum::EnumVariantNames, strum::EnumString)]
/// enum MyAtlasTextures {
///     Pacman,
/// }
///
/// fn setup_game(mut commands: Commands, atlases: Res<AtlasTextures<MyAtlasTextures>>) {
///     commands
///         .spawn_bundle(SpriteSheetBundle {
///             sprite: TextureAtlasSprite {
///                 index: 0,
///                 custom_size: Some(Vec2::new(32.0, 32.0)),
///                 ..Default::default()
///             },
///             texture_atlas: atlases.handle(MyAtlasTextures::Pacman),
///             ..Default::default()
///         });
/// }
#[derive(Debug)]
pub struct AtlasTextures<T>(HashMap<T, CreatedAtlas>)
where
    T: Eq + std::hash::Hash;

#[derive(Debug, Default, Clone)]
struct CreatedAtlas {
    handle: Handle<TextureAtlas>,
    len: usize,
}

/// Event sent whenever the plugin has (re)created the defined [AtlasTextures<T>] for some `T`
/// (or failed in doing so!).
#[derive(Debug, Clone, Copy)]
pub struct AtlasTexturesEvent<T>(ResourceStatus, PhantomData<T>);

impl<T> AtlasTexturesEvent<T> {
    pub fn status(&self) -> ResourceStatus {
        self.0
    }
}

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, IsVariant)]
pub enum ResourceStatus {
    Created,
    Failed,
}

impl<T> AtlasTextures<T>
where
    T: Eq + std::hash::Hash,
{
    /// Returns a cloned [TextureAtlas] handle for a specific `T`.
    pub fn handle<B: std::borrow::Borrow<T>>(&self, index: B) -> Handle<TextureAtlas> {
        self.0[index.borrow()].handle.clone_weak()
    }

    /// Returns the total number of [TextureAtlas] index' for a specific `T`.
    ///
    /// Saves you from a lookup into `Asset<TextureAtlas>`.
    #[allow(clippy::len_without_is_empty)]
    pub fn len<B: std::borrow::Borrow<T>>(&self, index: B) -> usize {
        self.0[index.borrow()].len
    }
}

impl<T> AtlasTexturesEvent<T> {
    pub fn state(&self) -> ResourceStatus {
        self.0
    }
}