use cranpose_ui_graphics::{Rect, Size};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum PatchFill {
#[default]
Stretch,
Tile,
}
impl PatchFill {
pub fn is_tiled(self) -> bool {
matches!(self, PatchFill::Tile)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct NinePatchInsets {
pub left: f32,
pub top: f32,
pub right: f32,
pub bottom: f32,
}
impl NinePatchInsets {
pub fn new(left: f32, top: f32, right: f32, bottom: f32) -> Self {
Self {
left: sanitize(left),
top: sanitize(top),
right: sanitize(right),
bottom: sanitize(bottom),
}
}
pub fn uniform(inset: f32) -> Self {
Self::new(inset, inset, inset, inset)
}
pub fn scaled(self, factor: f32) -> Self {
if !factor.is_finite() || factor <= 0.0 {
return self;
}
Self::new(
self.left * factor,
self.top * factor,
self.right * factor,
self.bottom * factor,
)
}
pub fn fit(self, source: Size) -> bool {
source.width > self.left + self.right && source.height > self.top + self.bottom
}
}
fn sanitize(value: f32) -> f32 {
if value.is_finite() && value > 0.0 {
value
} else {
0.0
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PatchQuad {
pub source: Rect,
pub destination: Rect,
}
pub fn tile_quads(source: Rect, destination: Rect) -> Vec<PatchQuad> {
let mut quads = Vec::new();
push_tiles(&mut quads, source, destination);
quads
}
pub fn tile_count(source: Rect, destination: Rect) -> usize {
if !usable(source) || !usable(destination) {
return 0;
}
let columns = (destination.width / source.width).ceil().max(0.0) as usize;
let rows = (destination.height / source.height).ceil().max(0.0) as usize;
columns.saturating_mul(rows)
}
pub fn nine_patch_quads(
source: Rect,
destination: Rect,
insets: NinePatchInsets,
center: PatchFill,
edges: PatchFill,
) -> Vec<PatchQuad> {
if !usable(source) || !usable(destination) {
return Vec::new();
}
let source_size = Size::new(source.width, source.height);
let corners_fit = destination.width > insets.left + insets.right
&& destination.height > insets.top + insets.bottom;
if !insets.fit(source_size) || !corners_fit {
return vec![PatchQuad {
source,
destination,
}];
}
let source_columns = [
(source.x, insets.left),
(
source.x + insets.left,
source.width - insets.left - insets.right,
),
(source.x + source.width - insets.right, insets.right),
];
let source_rows = [
(source.y, insets.top),
(
source.y + insets.top,
source.height - insets.top - insets.bottom,
),
(source.y + source.height - insets.bottom, insets.bottom),
];
let destination_columns = [
(destination.x, insets.left),
(
destination.x + insets.left,
destination.width - insets.left - insets.right,
),
(
destination.x + destination.width - insets.right,
insets.right,
),
];
let destination_rows = [
(destination.y, insets.top),
(
destination.y + insets.top,
destination.height - insets.top - insets.bottom,
),
(
destination.y + destination.height - insets.bottom,
insets.bottom,
),
];
let mut quads = Vec::new();
for row in 0..3 {
for column in 0..3 {
let (source_x, source_width) = source_columns[column];
let (source_y, source_height) = source_rows[row];
let (destination_x, destination_width) = destination_columns[column];
let (destination_y, destination_height) = destination_rows[row];
if source_width <= 0.0
|| source_height <= 0.0
|| destination_width <= 0.0
|| destination_height <= 0.0
{
continue;
}
let patch_source = Rect {
x: source_x,
y: source_y,
width: source_width,
height: source_height,
};
let patch_destination = Rect {
x: destination_x,
y: destination_y,
width: destination_width,
height: destination_height,
};
let stretched = column == 1 || row == 1;
let fill = match (column, row) {
(1, 1) => center,
_ if stretched => edges,
_ => PatchFill::Stretch,
};
if fill.is_tiled() {
push_tiles(&mut quads, patch_source, patch_destination);
} else {
quads.push(PatchQuad {
source: patch_source,
destination: patch_destination,
});
}
}
}
quads
}
fn usable(rect: Rect) -> bool {
rect.x.is_finite()
&& rect.y.is_finite()
&& rect.width.is_finite()
&& rect.height.is_finite()
&& rect.width > 0.0
&& rect.height > 0.0
}
fn push_tiles(quads: &mut Vec<PatchQuad>, source: Rect, destination: Rect) {
if !usable(source) || !usable(destination) {
return;
}
let mut y = destination.y;
let bottom = destination.y + destination.height;
while y < bottom {
let height = source.height.min(bottom - y);
let mut x = destination.x;
let right = destination.x + destination.width;
while x < right {
let width = source.width.min(right - x);
quads.push(PatchQuad {
source: Rect {
x: source.x,
y: source.y,
width,
height,
},
destination: Rect {
x,
y,
width,
height,
},
});
x += source.width;
}
y += source.height;
}
}
#[cfg(test)]
#[path = "tests/nine_patch_tests.rs"]
mod tests;