use bevy::{
prelude::*,
reflect::TypeUuid,
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
ui::FocusPolicy,
};
#[derive(Debug, Clone)]
pub struct Patch<T: Clone + Send + Sync + 'static> {
pub original_size: IVec2,
pub target_size: Size,
pub content: Option<T>,
}
#[derive(Debug)]
pub struct NinePatchBuilder<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static = ()> {
pub patches: Vec<Vec<Patch<T>>>,
pub(crate) patch_textures: Option<Vec<Handle<Image>>>,
pub(crate) original_texture: Option<Handle<Image>>,
}
impl<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> TypeUuid for NinePatchBuilder<T> {
const TYPE_UUID: bevy::reflect::Uuid =
bevy::reflect::Uuid::from_u128(0xee097b8ab9a747e3ad5c09e4a9c9ccb0);
}
impl<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> NinePatchBuilder<T> {
pub fn from_patches(patches: Vec<Vec<Patch<T>>>) -> Self {
Self {
patches,
patch_textures: None,
original_texture: None,
}
}
}
impl<T: Clone + Send + Sync + Default + Eq + std::hash::Hash + 'static> NinePatchBuilder<T> {
pub fn by_margins(
top_margin: u32,
bottom_margin: u32,
left_margin: u32,
right_margin: u32,
) -> Self {
Self::by_margins_with_content(
top_margin,
bottom_margin,
left_margin,
right_margin,
T::default(),
)
}
}
impl<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> NinePatchBuilder<T> {
pub fn by_margins_with_content(
top_margin: u32,
bottom_margin: u32,
left_margin: u32,
right_margin: u32,
content: T,
) -> Self {
let top = vec![
Patch {
original_size: IVec2::new(left_margin as i32, top_margin as i32),
target_size: Size::new(Val::Undefined, Val::Undefined),
content: None,
},
Patch {
original_size: IVec2::new(
-(left_margin as i32) - right_margin as i32,
top_margin as i32,
),
target_size: Size::new(Val::Auto, Val::Undefined),
content: None,
},
Patch {
original_size: IVec2::new(right_margin as i32, top_margin as i32),
target_size: Size::new(Val::Undefined, Val::Undefined),
content: None,
},
];
let middle = vec![
Patch {
original_size: IVec2::new(
left_margin as i32,
-(top_margin as i32) - bottom_margin as i32,
),
target_size: Size::new(Val::Undefined, Val::Auto),
content: None,
},
Patch {
original_size: IVec2::new(
-(left_margin as i32) - right_margin as i32,
-(top_margin as i32) - bottom_margin as i32,
),
target_size: Size::new(Val::Auto, Val::Auto),
content: Some(content),
},
Patch {
original_size: IVec2::new(
right_margin as i32,
-(top_margin as i32) - bottom_margin as i32,
),
target_size: Size::new(Val::Undefined, Val::Auto),
content: None,
},
];
let bottom = vec![
Patch {
original_size: IVec2::new(left_margin as i32, bottom_margin as i32),
target_size: Size::new(Val::Undefined, Val::Undefined),
content: None,
},
Patch {
original_size: IVec2::new(
-(left_margin as i32) - right_margin as i32,
bottom_margin as i32,
),
target_size: Size::new(Val::Auto, Val::Undefined),
content: None,
},
Patch {
original_size: IVec2::new(right_margin as i32, bottom_margin as i32),
target_size: Size::new(Val::Undefined, Val::Undefined),
content: None,
},
];
Self {
patches: vec![top, middle, bottom],
patch_textures: None,
original_texture: None,
}
}
}
fn to_width(patch: IVec2, total: Extent3d) -> u32 {
if patch.x > 0 {
patch.x as u32
} else {
(total.width as i32 + patch.x) as u32
}
}
fn to_height(patch: IVec2, total: Extent3d) -> u32 {
if patch.y > 0 {
patch.y as u32
} else {
(total.height as i32 + patch.y) as u32
}
}
impl<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> NinePatchBuilder<T> {
pub fn apply(
&mut self,
texture_handle: &Handle<Image>,
textures: &mut Assets<Image>,
) -> NinePatch<T> {
let (texture_size, texture_data) = {
let t = textures
.get(texture_handle)
.expect("could not get texture from handle");
(t.texture_descriptor.size, &t.data)
};
let mut textures_to_add = vec![];
if self.patch_textures.is_none() || self.original_texture.as_ref() != Some(texture_handle) {
let mut patch_textures = vec![];
let mut accu_y = 0;
for row in &self.patches {
let mut accu_x = 0;
for column_item in row {
let start_x = accu_x;
let end_x = accu_x + to_width(column_item.original_size, texture_size);
let start_y = accu_y;
let end_y = accu_y + to_height(column_item.original_size, texture_size);
let mut patch_texture_data = vec![];
for j in start_y as usize..end_y as usize {
let start_line = (start_x as usize + j * texture_size.width as usize) * 4;
let end_line = (end_x as usize + j * texture_size.width as usize) * 4;
patch_texture_data.extend_from_slice(&texture_data[start_line..end_line]);
}
let patch_texture = Image::new(
Extent3d {
width: to_width(column_item.original_size, texture_size),
height: to_height(column_item.original_size, texture_size),
depth_or_array_layers: texture_size.depth_or_array_layers,
},
TextureDimension::D2,
patch_texture_data,
TextureFormat::Rgba8UnormSrgb,
);
textures_to_add.push(patch_texture);
accu_x += to_width(column_item.original_size, texture_size);
}
accu_y += to_height(row[0].original_size, texture_size);
}
textures_to_add.into_iter().for_each(|patch_texture| {
let patch_texture_handle = textures.add(patch_texture);
patch_textures.push(patch_texture_handle);
});
self.patch_textures = Some(patch_textures);
self.original_texture = Some(texture_handle.clone());
}
NinePatch {
patches: self.patches.clone(),
texture_size,
splitted_texture: self.patch_textures.as_ref().cloned().unwrap(),
}
}
}
#[derive(Clone, Debug, Component)]
pub struct NinePatchContent<T: Send + Sync + 'static> {
pub content: T,
pub loaded: bool,
pub parent: Entity,
}
#[derive(Debug)]
pub struct NinePatch<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> {
patches: Vec<Vec<Patch<T>>>,
texture_size: Extent3d,
splitted_texture: Vec<Handle<Image>>,
}
impl<T: Clone + Send + Sync + Eq + std::hash::Hash + 'static> NinePatch<T> {
pub(crate) fn add_with_parent(
&self,
commands: &mut Commands,
parent: Entity,
style: &Style,
contents: &Option<std::collections::HashMap<T, Entity>>,
) {
commands.entity(parent).insert(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
align_content: AlignContent::Stretch,
..*style
},
background_color: BackgroundColor(Color::NONE),
focus_policy: FocusPolicy::Pass,
..Default::default()
});
let mut rows = vec![];
let mut n = 0;
for row in self.patches.iter() {
let (size_height, growth) = row
.get(0)
.map(|p| match p.target_size.height {
Val::Undefined => (
Val::Px(to_height(p.original_size, self.texture_size) as f32),
0.,
),
Val::Px(i) => (Val::Px(i), 0.),
Val::Auto => (Val::Auto, 1.),
Val::Percent(x) => (Val::Auto, x / 100.),
})
.unwrap_or((Val::Undefined, 0.));
let id = commands
.spawn(NodeBundle {
style: Style {
size: Size::new(Val::Percent(100.), size_height),
flex_direction: FlexDirection::Row,
align_content: AlignContent::Stretch,
flex_grow: growth,
flex_shrink: growth,
margin: UiRect::all(Val::Px(0.)),
..Default::default()
},
background_color: BackgroundColor(Color::NONE),
focus_policy: FocusPolicy::Pass,
..Default::default()
})
.id();
rows.push(id);
commands.entity(id).with_children(|row_parent| {
for column_item in row.iter() {
let (size_width, growth) = match column_item.target_size.width {
Val::Undefined => (
Val::Px(to_width(column_item.original_size, self.texture_size) as f32),
0.,
),
Val::Px(i) => (Val::Px(i), 0.),
Val::Auto => (Val::Auto, 1.),
Val::Percent(x) => (Val::Auto, x / 100.),
};
let size_height = match column_item.target_size.height {
Val::Undefined => {
Val::Px(to_height(column_item.original_size, self.texture_size) as f32)
}
Val::Percent(_) => Val::Auto,
other => other,
};
let mut child = row_parent.spawn(ImageBundle {
image: UiImage(self.splitted_texture[n].clone_weak()),
style: Style {
size: Size::new(size_width, size_height),
margin: UiRect::all(Val::Px(0.)),
flex_grow: growth,
flex_shrink: growth,
..Default::default()
},
focus_policy: FocusPolicy::Pass,
..Default::default()
});
if let Some(content_part) = column_item.content.as_ref() {
child.insert(NinePatchContent {
content: content_part.clone(),
loaded: false,
parent,
});
if let Some(content_entity) =
contents.as_ref().and_then(|m| m.get(content_part))
{
child.push_children(&[*content_entity]);
}
}
n += 1;
}
});
}
commands.entity(parent).push_children(&rows);
}
}