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
use bevy::prelude::*;

use crate::ninepatch::*;

/// State of the current `NinePatch`
#[derive(Debug, Clone, Component)]
pub struct NinePatchData<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> {
    /// Handle of the texture
    pub texture: Handle<Image>,
    /// Handle to the `NinePatchBuilder`
    pub nine_patch: Handle<NinePatchBuilder<T>>,
    /// Is the element already loaded and displayed
    pub loaded: bool,
    /// Entity that should be used for the content
    pub content: Option<std::collections::HashMap<T, Entity>>,
}

impl<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> Default for NinePatchData<T> {
    fn default() -> Self {
        NinePatchData {
            texture: Default::default(),
            nine_patch: Default::default(),
            loaded: false,
            content: Default::default(),
        }
    }
}

impl<T: Clone + Send + Sync + Default + Eq + std::hash::Hash + 'static> NinePatchData<T> {
    /// Create a NinePathData with content when there is only one content
    pub fn with_single_content(
        texture: Handle<Image>,
        nine_patch: Handle<NinePatchBuilder<T>>,
        content: Entity,
    ) -> NinePatchData<T> {
        let mut content_map = std::collections::HashMap::with_capacity(1);
        content_map.insert(T::default(), content);
        NinePatchData {
            texture,
            nine_patch,
            loaded: false,
            content: Some(content_map),
        }
    }
}

#[derive(Bundle)]
/// Component Bundle to place the NinePatch
pub struct NinePatchBundle<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> {
    /// Style of this UI node
    pub style: Style,
    /// State of the `NinePatch`
    pub nine_patch_data: NinePatchData<T>,
    /// UI node
    pub node: Node,
    /// Transform
    pub transform: Transform,
    /// Global transform - should be set automatically by bevy's systems
    pub global_transform: GlobalTransform,
}

impl<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> Default for NinePatchBundle<T> {
    fn default() -> Self {
        NinePatchBundle {
            style: Default::default(),
            nine_patch_data: Default::default(),
            node: Default::default(),
            transform: Default::default(),
            global_transform: Default::default(),
        }
    }
}

/// Plugin that will add the system and the resource for nine patch
#[derive(Debug, Clone, Copy)]
pub struct NinePatchPlugin<T: Clone + Send + Sync + 'static = ()> {
    marker: std::marker::PhantomData<T>,
}

impl<T: Clone + Send + Sync + 'static> Default for NinePatchPlugin<T> {
    fn default() -> Self {
        NinePatchPlugin {
            marker: Default::default(),
        }
    }
}
impl<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> Plugin for NinePatchPlugin<T> {
    fn build(&self, app: &mut App) {
        app.add_asset::<NinePatchBuilder<T>>()
            .add_system(create_ninepatches::<T>);
    }
}

#[allow(clippy::type_complexity)]
fn create_ninepatches<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static>(
    mut commands: Commands,
    mut nine_patches: ResMut<Assets<NinePatchBuilder<T>>>,
    mut textures: ResMut<Assets<Image>>,
    mut patches_query: Query<(Entity, &mut NinePatchData<T>, &Style)>,
) {
    for (entity, mut data, style) in patches_query.iter_mut() {
        if !data.loaded {
            if let Some(nine_patch) = nine_patches.get_mut(&data.nine_patch) {
                if textures.get(&data.texture).is_none() {
                    // texture is not available yet, will try next loop
                    continue;
                }
                let np = nine_patch.apply(&data.texture, &mut textures);
                np.add_with_parent(&mut commands, entity, style, &data.content);
                data.loaded = true;
            }
        }
    }
}