use crate::math::sin_cos;
use alloc::vec::Vec;
use crate::components::{Sprite, SpriteFit};
use crate::gfx::overlay::{OverlayTransform, UI_REFERENCE_SIZE};
use crate::gfx::render_types::{TextDrawCall, TextVertex};
use crate::render::overlay_maps::{ClipRects, OverlayLayers, TextureSlots};
pub fn covers_canvas(s: &Sprite) -> bool {
let [ref_w, ref_h] = UI_REFERENCE_SIZE;
s.screen.is_some()
&& s.x <= 0.0
&& s.y <= 0.0
&& s.x + s.width >= ref_w
&& s.y + s.height >= ref_h
}
#[cfg(test)]
pub(crate) fn build_sprite_calls(
sprites: &[Sprite],
default_atlas_slot: Option<usize>,
texture_slots: &TextureSlots,
viewport: [f32; 2],
clips: &ClipRects,
layers: &OverlayLayers,
) -> Vec<TextDrawCall> {
let mut out = crate::render::call_buffer::TextCallBuffer::default();
build_sprite_calls_into(
&mut out,
sprites,
default_atlas_slot,
texture_slots,
viewport,
clips,
layers,
);
out.take()
}
pub fn build_sprite_calls_into(
out: &mut crate::render::call_buffer::TextCallBuffer,
sprites: &[Sprite],
default_atlas_slot: Option<usize>,
texture_slots: &TextureSlots,
viewport: [f32; 2],
clips: &ClipRects,
layers: &OverlayLayers,
) {
let fill_slot = match default_atlas_slot {
Some(s) => s,
None => return,
};
let overlay = OverlayTransform::from_viewport(viewport);
let cover = OverlayTransform::cover_from_viewport(viewport);
let bottom = OverlayTransform::bottom_anchored_from_viewport(viewport);
let [vw, vh] = viewport;
for s in sprites {
if !s.visible || s.follow_cursor {
continue;
}
let [r, g, b, a] = s.tint;
let border_only = s.border_width > 0.0 && s.border_color[3] > 0.0;
if a <= 0.0 && !border_only {
continue;
}
let (x0, y0, x1, y1) = if s.screen.is_some() {
if s.fit == SpriteFit::Cover {
let (ax, ay) = cover.forward(s.x, s.y);
let (bx, by) = cover.forward(s.x + s.width, s.y + s.height);
(ax, ay, bx, by)
} else if s.fit == SpriteFit::Bottom {
let (ax, ay) = bottom.forward(s.x, s.y);
let (bx, by) = bottom.forward(s.x + s.width, s.y + s.height);
(ax, ay, bx, by)
} else if covers_canvas(s) && vw > 0.0 && vh > 0.0 {
(0.0, 0.0, vw, vh)
} else {
let (ax, ay) = overlay.forward(s.x, s.y);
let (bx, by) = overlay.forward(s.x + s.width, s.y + s.height);
(ax, ay, bx, by)
}
} else {
(s.x, s.y, s.x + s.width, s.y + s.height)
};
let texture_slot = s.texture.and_then(|t| texture_slots.get(&t).copied());
let (w, h) = ((x1 - x0).max(f32::EPSILON), (y1 - y0).max(f32::EPSILON));
let v = |x: f32, y: f32, alpha: f32| match texture_slot {
Some(_) => TextVertex {
pos: [x, y],
uv: [(x - x0) / w, (y - y0) / h],
color: [r, g, b],
mode: alpha,
},
None => TextVertex {
pos: [x, y],
uv: [-1.0, alpha],
color: [r, g, b],
mode: 0.0,
},
};
let scale = if s.width > 0.0 && s.height > 0.0 {
((x1 - x0) / s.width).min((y1 - y0) / s.height)
} else {
1.0
};
let radius = (s.corner_radius * scale).min(w / 2.0).min(h / 2.0);
let border = (s.border_width * scale).min(w / 2.0).min(h / 2.0);
let (mut vertices, mut indices) = out.geometry();
if border > 0.5 && s.border_color[3] > 0.0 {
let [br, bg, bb, ba] = s.border_color;
let border_v = |x: f32, y: f32, alpha: f32| TextVertex {
pos: [x, y],
uv: [-1.0, alpha],
color: [br, bg, bb],
mode: 0.0,
};
if a <= 0.0 {
ring_geometry(
&mut vertices,
&mut indices,
[x0, y0, x1, y1],
border,
ba,
border_v,
);
} else {
rect_geometry(
&mut vertices,
&mut indices,
[x0, y0, x1, y1],
radius,
ba,
border_v,
);
rect_geometry(
&mut vertices,
&mut indices,
[x0 + border, y0 + border, x1 - border, y1 - border],
(radius - border).max(0.0),
a,
v,
);
}
} else {
rect_geometry(&mut vertices, &mut indices, [x0, y0, x1, y1], radius, a, v);
}
out.calls.push(TextDrawCall {
vertices,
indices,
atlas_slot: texture_slot.unwrap_or(fill_slot),
clip_rect: clips
.get(&s.asset_id)
.map(|b| crate::render::text::band_to_window(&overlay, *b)),
layer: layers.get(&s.asset_id).copied().unwrap_or(0),
});
}
}
fn rect_geometry(
vertices: &mut Vec<TextVertex>,
indices: &mut Vec<u16>,
rect: [f32; 4],
radius: f32,
alpha: f32,
mut v: impl FnMut(f32, f32, f32) -> TextVertex,
) {
let [x0, y0, x1, y1] = rect;
if radius > 0.5 {
rounded_rect_geometry(vertices, indices, rect, radius, alpha, v);
} else {
let base = vertices.len() as u16;
vertices.extend_from_slice(&[
v(x0, y0, alpha),
v(x1, y0, alpha),
v(x1, y1, alpha),
v(x0, y1, alpha),
]);
indices.extend([0, 1, 2, 0, 2, 3].map(|i| base + i));
}
}
fn ring_geometry(
vertices: &mut Vec<TextVertex>,
indices: &mut Vec<u16>,
rect: [f32; 4],
width: f32,
alpha: f32,
mut v: impl FnMut(f32, f32, f32) -> TextVertex,
) {
let [x0, y0, x1, y1] = rect;
let strips = [
[x0, y0, x1, y0 + width],
[x0, y1 - width, x1, y1],
[x0, y0 + width, x0 + width, y1 - width],
[x1 - width, y0 + width, x1, y1 - width],
];
for strip in strips {
rect_geometry(vertices, indices, strip, 0.0, alpha, &mut v);
}
}
const CORNER_SEGMENTS: usize = 6;
const EDGE_FEATHER: f32 = 1.25;
fn rounded_rect_geometry(
vertices: &mut Vec<TextVertex>,
indices: &mut Vec<u16>,
rect: [f32; 4],
radius: f32,
alpha: f32,
mut v: impl FnMut(f32, f32, f32) -> TextVertex,
) {
use core::f32::consts::FRAC_PI_2;
let [x0, y0, x1, y1] = rect;
let mut arc = [(0.0_f32, 0.0_f32); CORNER_SEGMENTS + 1];
for (i, slot) in arc.iter_mut().enumerate() {
let (s, c) = sin_cos((i as f32 / CORNER_SEGMENTS as f32) * FRAC_PI_2);
*slot = (c, s);
}
let corners = [
(x0 + radius, y0 + radius, 2),
(x1 - radius, y0 + radius, 3),
(x1 - radius, y1 - radius, 0),
(x0 + radius, y1 - radius, 1),
];
let mut boundary = [(0.0_f32, 0.0_f32, 0.0_f32, 0.0_f32); 4 * (CORNER_SEGMENTS + 1)];
for (c, &(cx, cy, quadrant)) in corners.iter().enumerate() {
for (i, &(uc, us)) in arc.iter().enumerate() {
let (rc, rs) = match quadrant {
0 => (uc, us),
1 => (-us, uc),
2 => (-uc, -us),
_ => (us, -uc),
};
boundary[c * (CORNER_SEGMENTS + 1) + i] = (cx, cy, rc, rs);
}
}
let m = boundary.len();
let base = vertices.len() as u16;
let inner_r = (radius - EDGE_FEATHER).max(0.0);
vertices.reserve(2 * m);
for &(cx, cy, cos, sin) in &boundary {
vertices.push(v(cx + inner_r * cos, cy + inner_r * sin, alpha));
}
for &(cx, cy, cos, sin) in &boundary {
vertices.push(v(cx + radius * cos, cy + radius * sin, 0.0));
}
indices.reserve(3 * (m - 2) + 6 * m);
for i in 1..m - 1 {
indices.extend([base, base + i as u16, base + (i + 1) as u16]);
}
for i in 0..m {
let j = (i + 1) % m;
let (i, j, m) = (i as u16, j as u16, m as u16);
indices.extend([i, j, m + j, i, m + j, m + i].map(|k| base + k));
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ecs::TextureHandle;
use crate::ecs::asset_id::AssetId;
use alloc::vec;
fn no_clips() -> ClipRects {
ClipRects::new()
}
fn no_layers() -> OverlayLayers {
OverlayLayers::new()
}
fn no_slots() -> TextureSlots {
TextureSlots::new()
}
fn sprite(x: f32, y: f32, w: f32, h: f32, tint: [f32; 4]) -> Sprite {
Sprite {
asset_id: AssetId::default(),
x,
y,
width: w,
height: h,
texture: None,
tint,
follow_cursor: false,
visible: true,
screen: None,
fit: SpriteFit::Fit,
corner_radius: 0.0,
border_width: 0.0,
border_color: [0.0, 0.0, 0.0, 1.0],
}
}
#[test]
fn no_fonts_means_no_calls() {
let s = sprite(0.0, 0.0, 100.0, 100.0, [1.0, 0.0, 0.0, 1.0]);
assert!(
build_sprite_calls(
core::slice::from_ref(&s),
None,
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers()
)
.is_empty()
);
}
#[test]
fn visible_sprite_emits_quad_with_sentinel_uv() {
let s = sprite(10.0, 20.0, 100.0, 50.0, [0.5, 0.5, 0.5, 0.75]);
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls.len(), 1);
assert_eq!(calls[0].vertices.len(), 4);
assert_eq!(calls[0].indices, vec![0, 1, 2, 0, 2, 3]);
for v in &calls[0].vertices {
assert!(v.uv[0] < 0.0, "sentinel u should be negative");
assert!((v.uv[1] - 0.75).abs() < 1e-5, "alpha carried in v");
assert_eq!(v.color, [0.5, 0.5, 0.5]);
}
assert_eq!(calls[0].vertices[0].pos, [10.0, 20.0]);
assert_eq!(calls[0].vertices[2].pos, [110.0, 70.0]);
}
#[test]
fn transparent_fill_draws_when_a_border_is_set() {
let invisible = sprite(0.0, 0.0, 100.0, 50.0, [0.0, 0.0, 0.0, 0.0]);
assert!(
build_sprite_calls(
core::slice::from_ref(&invisible),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers()
)
.is_empty(),
"borderless transparent fill still skips"
);
let mut outline = sprite(0.0, 0.0, 100.0, 50.0, [0.0, 0.0, 0.0, 0.0]);
outline.border_width = 2.0;
outline.border_color = [0.2, 0.4, 0.9, 1.0];
let calls = build_sprite_calls(
core::slice::from_ref(&outline),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls.len(), 1, "the border ring draws");
let verts = &calls[0].vertices;
assert_eq!(verts.len(), 16);
assert!(
verts
.iter()
.all(|v| v.color == [0.2, 0.4, 0.9] && v.uv == [-1.0, 1.0])
);
let inside = |x: f32, y: f32| {
calls[0].indices.chunks(3).any(|t| {
let p: Vec<[f32; 2]> = t.iter().map(|&i| verts[i as usize].pos).collect();
point_in_triangle([x, y], p[0], p[1], p[2])
})
};
assert!(inside(50.0, 1.0), "the top strip covers the edge");
assert!(inside(1.0, 25.0), "the left strip covers the edge");
assert!(!inside(50.0, 25.0), "the interior is empty");
let mut panel = sprite(0.0, 0.0, 100.0, 50.0, [0.1, 0.1, 0.1, 1.0]);
panel.border_width = 2.0;
panel.border_color = [0.2, 0.4, 0.9, 1.0];
let calls = build_sprite_calls(
core::slice::from_ref(&panel),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls[0].vertices.len(), 8);
}
fn point_in_triangle(p: [f32; 2], a: [f32; 2], b: [f32; 2], c: [f32; 2]) -> bool {
let sign = |p: [f32; 2], q: [f32; 2], r: [f32; 2]| {
(p[0] - r[0]) * (q[1] - r[1]) - (q[0] - r[0]) * (p[1] - r[1])
};
let (d1, d2, d3) = (sign(p, a, b), sign(p, b, c), sign(p, c, a));
let neg = d1 < 0.0 || d2 < 0.0 || d3 < 0.0;
let pos = d1 > 0.0 || d2 > 0.0 || d3 > 0.0;
!(neg && pos)
}
#[test]
fn textured_sprite_emits_real_uvs_and_its_slot() {
let mut s = sprite(10.0, 20.0, 100.0, 50.0, [1.0, 0.9, 0.8, 0.75]);
s.texture = Some(TextureHandle(42));
let mut slots = no_slots();
slots.insert(TextureHandle(42), 3);
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&slots,
[0.0, 0.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls.len(), 1);
assert_eq!(calls[0].atlas_slot, 3);
let vs = &calls[0].vertices;
assert_eq!(vs[0].uv, [0.0, 0.0]);
assert_eq!(vs[1].uv, [1.0, 0.0]);
assert_eq!(vs[2].uv, [1.0, 1.0]);
assert_eq!(vs[3].uv, [0.0, 1.0]);
for v in vs {
assert_eq!(v.color, [1.0, 0.9, 0.8]);
assert!((v.mode - 0.75).abs() < 1e-5);
}
}
#[test]
fn rounded_sprite_tessellates_with_a_feathered_edge() {
let mut s = sprite(100.0, 100.0, 400.0, 200.0, [0.1, 0.2, 0.3, 0.9]);
s.corner_radius = 20.0;
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls.len(), 1);
let vs = &calls[0].vertices;
let ring = 4 * (CORNER_SEGMENTS + 1);
assert_eq!(vs.len(), 2 * ring);
for v in &vs[..ring] {
assert!(
(v.uv[1] - 0.9).abs() < 1e-5,
"inner ring carries the tint alpha"
);
}
for v in &vs[ring..] {
assert!(v.uv[1].abs() < 1e-5, "outer ring fades to transparent");
}
for v in vs {
assert!(v.pos[0] >= 100.0 - 1e-3 && v.pos[0] <= 500.0 + 1e-3);
assert!(v.pos[1] >= 100.0 - 1e-3 && v.pos[1] <= 300.0 + 1e-3);
}
let min_x = vs.iter().map(|v| v.pos[0]).fold(f32::MAX, f32::min);
assert!((min_x - 100.0).abs() < 1e-3);
assert!(!vs.iter().any(|v| v.pos == [100.0, 100.0]));
}
#[test]
fn bordered_sprite_emits_a_border_ring_and_an_inset_fill() {
let mut s = sprite(100.0, 100.0, 200.0, 120.0, [0.1, 0.2, 0.3, 1.0]);
s.border_width = 2.0;
s.border_color = [0.8, 0.4, 0.2, 1.0];
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls.len(), 1);
let vs = &calls[0].vertices;
assert!(
vs.iter().any(|v| v.color == [0.8, 0.4, 0.2]),
"border colour present"
);
assert!(
vs.iter().any(|v| v.color == [0.1, 0.2, 0.3]),
"fill colour present"
);
let outer_min_x = vs.iter().map(|v| v.pos[0]).fold(f32::MAX, f32::min);
let outer_max_x = vs.iter().map(|v| v.pos[0]).fold(f32::MIN, f32::max);
assert!(
(outer_min_x - 100.0).abs() < 1e-3,
"border at the left edge"
);
assert!(
(outer_max_x - 300.0).abs() < 1e-3,
"border at the right edge"
);
let fill_min_x = vs
.iter()
.filter(|v| v.color == [0.1, 0.2, 0.3])
.map(|v| v.pos[0])
.fold(f32::MAX, f32::min);
assert!(
(fill_min_x - 102.0).abs() < 1e-3,
"fill inset by the stroke width"
);
}
#[test]
fn zero_border_stays_a_single_layer() {
let mut s = sprite(0.0, 0.0, 100.0, 100.0, [0.2, 0.3, 0.4, 1.0]);
s.border_width = 0.0;
s.border_color = [1.0, 0.0, 0.0, 1.0];
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers(),
);
assert_eq!(
calls[0].vertices.len(),
4,
"one plain quad, no border layer"
);
assert!(calls[0].vertices.iter().all(|v| v.color == [0.2, 0.3, 0.4]));
}
#[test]
fn rounded_view_sprite_scales_its_radius_with_the_window() {
let mut s = sprite(100.0, 100.0, 400.0, 200.0, [0.1, 0.2, 0.3, 0.9]);
s.screen = Some(AssetId(7));
s.corner_radius = 20.0;
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[2.0 * UI_REFERENCE_SIZE[0], 2.0 * UI_REFERENCE_SIZE[1]],
&no_clips(),
&no_layers(),
);
let vs = &calls[0].vertices;
let min_x = vs.iter().map(|v| v.pos[0]).fold(f32::MAX, f32::min);
let top_left_arc_y = vs
.iter()
.filter(|v| (v.pos[0] - min_x).abs() < 1e-3)
.map(|v| v.pos[1])
.fold(f32::MAX, f32::min);
assert!((min_x - 200.0).abs() < 1e-3);
assert!((top_left_arc_y - (200.0 + 40.0)).abs() < 1e-3);
}
#[test]
fn textured_sprite_without_a_loaded_texture_falls_back_to_fill() {
let mut s = sprite(0.0, 0.0, 10.0, 10.0, [0.2, 0.3, 0.4, 1.0]);
s.texture = Some(TextureHandle(42));
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(5),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls[0].atlas_slot, 5);
assert!(calls[0].vertices[0].uv[0] < 0.0);
assert_eq!(calls[0].vertices[0].mode, 0.0);
}
#[test]
fn invisible_sprite_is_skipped() {
let mut s = sprite(0.0, 0.0, 100.0, 100.0, [1.0, 1.0, 1.0, 1.0]);
s.visible = false;
assert!(
build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers()
)
.is_empty()
);
}
#[test]
fn zero_alpha_sprite_is_skipped() {
let s = sprite(0.0, 0.0, 100.0, 100.0, [1.0, 1.0, 1.0, 0.0]);
assert!(
build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers()
)
.is_empty()
);
}
#[test]
fn view_owned_sprite_scales_to_window() {
let mut s = sprite(100.0, 100.0, 200.0, 100.0, [1.0, 1.0, 1.0, 1.0]);
s.screen = Some(AssetId(7));
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[2560.0, 1440.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls.len(), 1);
assert_eq!(calls[0].vertices[0].pos, [200.0, 200.0]);
assert_eq!(calls[0].vertices[2].pos, [600.0, 400.0]);
}
#[test]
fn view_owned_full_canvas_backdrop_fills_window() {
let mut s = sprite(0.0, 0.0, 1280.0, 720.0, [0.0, 0.0, 0.0, 0.5]);
s.screen = Some(AssetId(7));
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[2560.0, 1440.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls.len(), 1);
assert_eq!(calls[0].vertices[0].pos, [0.0, 0.0]);
assert_eq!(calls[0].vertices[2].pos, [2560.0, 1440.0]);
}
#[test]
fn cover_sprite_fills_the_window_and_crops_the_overflow() {
let mut s = sprite(0.0, 0.0, 1280.0, 720.0, [1.0, 1.0, 1.0, 1.0]);
s.screen = Some(AssetId(7));
s.fit = SpriteFit::Cover;
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[1024.0, 768.0],
&no_clips(),
&no_layers(),
);
let scale = 768.0 / 720.0;
let overflow = (1280.0 * scale - 1024.0) / 2.0;
let vs = &calls[0].vertices;
assert!(
(vs[0].pos[0] - -overflow).abs() < 1e-3,
"x0={}",
vs[0].pos[0]
);
assert!((vs[0].pos[1]).abs() < 1e-3, "y0={}", vs[0].pos[1]);
assert!(
(vs[2].pos[0] - (1024.0 + overflow)).abs() < 1e-3,
"x1={}",
vs[2].pos[0]
);
assert!((vs[2].pos[1] - 768.0).abs() < 1e-3, "y1={}", vs[2].pos[1]);
}
#[test]
fn cover_sprite_anchored_to_the_canvas_bottom_stays_flush() {
let mut s = sprite(400.0, 100.0, 480.0, 620.0, [1.0, 1.0, 1.0, 1.0]);
s.screen = Some(AssetId(7));
s.fit = SpriteFit::Cover;
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[1024.0, 768.0],
&no_clips(),
&no_layers(),
);
let bottom = calls[0].vertices[2].pos[1];
assert!((bottom - 768.0).abs() < 1e-3, "bottom={bottom}");
}
#[test]
fn view_less_sprite_keeps_literal_pixels() {
let s = sprite(10.0, 20.0, 100.0, 50.0, [0.5, 0.5, 0.5, 1.0]);
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[2560.0, 1440.0],
&no_clips(),
&no_layers(),
);
assert_eq!(calls[0].vertices[0].pos, [10.0, 20.0]);
assert_eq!(calls[0].vertices[2].pos, [110.0, 70.0]);
}
#[test]
fn clipped_element_carries_window_space_clip_rect() {
let mut s = sprite(100.0, 100.0, 50.0, 50.0, [1.0, 1.0, 1.0, 1.0]);
s.asset_id = AssetId(7);
s.screen = Some(AssetId(1));
let mut clips = no_clips();
clips.insert(AssetId(7), [200.0, 200.0, 200.0, 60.0]);
let calls = build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[2560.0, 1440.0],
&clips,
&no_layers(),
);
let clip = calls[0].clip_rect.expect("clipped sprite has a clip rect");
assert!((clip[0] - 400.0).abs() < 1e-3, "x={}", clip[0]);
assert!((clip[1] - 400.0).abs() < 1e-3, "y={}", clip[1]);
assert!((clip[2] - 400.0).abs() < 1e-3, "w={}", clip[2]);
assert!((clip[3] - 120.0).abs() < 1e-3, "h={}", clip[3]);
let mut other = sprite(0.0, 0.0, 10.0, 10.0, [1.0, 1.0, 1.0, 1.0]);
other.asset_id = AssetId(9);
other.screen = Some(AssetId(1));
let calls = build_sprite_calls(
core::slice::from_ref(&other),
Some(0),
&no_slots(),
[2560.0, 1440.0],
&clips,
&no_layers(),
);
assert!(calls[0].clip_rect.is_none());
}
#[test]
fn sprite_call_takes_its_layer_from_the_map() {
let mut mapped = sprite(0.0, 0.0, 10.0, 10.0, [1.0, 1.0, 1.0, 1.0]);
mapped.asset_id = AssetId(42);
let mut layers = OverlayLayers::new();
layers.insert(AssetId(42), 7);
let calls = build_sprite_calls(
core::slice::from_ref(&mapped),
Some(0),
&no_slots(),
[100.0, 100.0],
&no_clips(),
&layers,
);
assert_eq!(calls[0].layer, 7);
let mut unmapped = sprite(0.0, 0.0, 10.0, 10.0, [1.0, 1.0, 1.0, 1.0]);
unmapped.asset_id = AssetId(99);
let calls = build_sprite_calls(
core::slice::from_ref(&unmapped),
Some(0),
&no_slots(),
[100.0, 100.0],
&no_clips(),
&layers,
);
assert_eq!(calls[0].layer, 0, "an unmapped id is layer 0");
}
#[test]
fn follow_cursor_sprites_are_skipped() {
let mut s = sprite(0.0, 0.0, 10.0, 10.0, [1.0, 1.0, 1.0, 1.0]);
s.follow_cursor = true;
assert!(
build_sprite_calls(
core::slice::from_ref(&s),
Some(0),
&no_slots(),
[0.0, 0.0],
&no_clips(),
&no_layers()
)
.is_empty()
);
}
}