use bevy::{camera::Viewport, prelude::*, window::PrimaryWindow};
#[derive(Component, Clone, Copy, Debug, Reflect)]
pub struct LetterboxSettings {
pub aspect: f32,
}
#[derive(Component)]
pub(crate) struct LetterboxBars;
pub(crate) fn sync_letterbox(
mut commands: Commands,
windows: Query<&Window, With<PrimaryWindow>>,
mut boxed: Query<(&LetterboxSettings, &mut Camera, &mut Projection)>,
bars: Query<Entity, With<LetterboxBars>>,
) {
if boxed.is_empty() {
for entity in &bars {
commands.entity(entity).despawn();
}
return;
}
let Ok(window) = windows.single() else {
return;
};
let win = UVec2::new(window.physical_width(), window.physical_height());
if win.x == 0 || win.y == 0 {
return;
}
if bars.is_empty() {
commands.spawn((
Name::new("letterbox bars"),
LetterboxBars,
Camera2d,
Camera {
order: -100,
clear_color: ClearColorConfig::Custom(Color::BLACK),
..Default::default()
},
));
}
for (settings, mut camera, mut projection) in &mut boxed {
let (position, size) = viewport_rect(win, settings.aspect);
camera.viewport = Some(Viewport {
physical_position: position,
physical_size: size,
..Default::default()
});
if let Projection::Perspective(p) = &mut *projection {
p.aspect_ratio = settings.aspect;
}
}
}
pub(crate) fn viewport_rect(win: UVec2, aspect: f32) -> (UVec2, UVec2) {
let aspect = aspect.max(0.01);
let win_aspect = win.x as f32 / win.y as f32;
if win_aspect > aspect {
let width = ((win.y as f32 * aspect).round() as u32).clamp(1, win.x);
(UVec2::new((win.x - width) / 2, 0), UVec2::new(width, win.y))
} else {
let height = ((win.x as f32 / aspect).round() as u32).clamp(1, win.y);
(UVec2::new(0, (win.y - height) / 2), UVec2::new(win.x, height))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn letterbox_rect_math_both_orientations() {
let (pos, size) = viewport_rect(UVec2::new(1920, 1080), 2.39);
assert_eq!(size.x, 1920);
assert_eq!(size.y, 803);
assert_eq!(pos, UVec2::new(0, 138));
let (pos, size) = viewport_rect(UVec2::new(1080, 1920), 2.39);
assert_eq!(size, UVec2::new(1080, 452));
assert_eq!(pos.x, 0);
let (pos, size) = viewport_rect(UVec2::new(3440, 1440), 1.78);
assert_eq!(size.y, 1440);
assert_eq!(size.x, 2563);
assert!(pos.x > 0 && pos.y == 0);
let (_, size) = viewport_rect(UVec2::new(100, 100), 2.39);
assert!(size.x <= 100 && size.y <= 100);
}
}