#![warn(missing_docs)]
use agb_fixnum::Vector2D;
use crate::{dma, fixnum::Rect, memory_mapped::MemoryMapped};
use super::{DISPLAY_CONTROL, HEIGHT, WIDTH, tiled::BackgroundId};
pub struct Windows {
wins: [MovableWindow; 2],
out: Window,
obj: Window,
}
const REG_HORIZONTAL_BASE: *mut u16 = 0x0400_0040 as *mut _;
const REG_VERTICAL_BASE: *mut u16 = 0x0400_0044 as *mut _;
const REG_WINDOW_CONTROL_BASE: *mut u16 = 0x0400_0048 as *mut _;
pub enum WinIn {
Win0,
Win1,
}
impl Windows {
pub(crate) fn new() -> Self {
Self {
wins: [MovableWindow::new(0), MovableWindow::new(1)],
out: Window::new(),
obj: Window::new(),
}
}
#[inline(always)]
pub fn win_out(&mut self) -> &mut Window {
self.out.enable()
}
#[inline(always)]
pub fn win_in(&mut self, id: WinIn) -> &mut MovableWindow {
self.wins[id as usize].enable()
}
#[inline(always)]
pub fn win_obj(&mut self) -> &mut Window {
self.obj.enable()
}
pub(crate) fn commit(&self) {
for win in &self.wins {
win.commit();
}
self.out.commit(2);
self.obj.commit(3);
let mut display_control_register = DISPLAY_CONTROL.get();
display_control_register.set_obj_window_display(self.obj.is_enabled());
display_control_register.set_window0_display(self.wins[0].is_enabled());
display_control_register.set_window1_display(self.wins[1].is_enabled());
DISPLAY_CONTROL.set(display_control_register);
}
}
pub struct Window {
window_bits: u8,
}
pub struct MovableWindow {
inner: Window,
rect: Rect<u8>,
id: usize,
}
impl Window {
fn new() -> Window {
Self { window_bits: 0 }
}
fn enable(&mut self) -> &mut Self {
self.set_bit(7, true);
self
}
fn is_enabled(&self) -> bool {
(self.window_bits >> 7) != 0
}
fn set_bit(&mut self, bit: usize, value: bool) {
self.window_bits &= u8::MAX ^ (1 << bit);
self.window_bits |= (value as u8) << bit;
}
#[inline(always)]
pub fn enable_blending(&mut self) -> &mut Self {
self.set_bit(5, true);
self
}
#[inline(always)]
pub fn enable_background(&mut self, back: impl Into<BackgroundId>) -> &mut Self {
self.set_bit(back.into().0 as usize, true);
self
}
#[inline(always)]
pub fn enable_objects(&mut self) -> &mut Self {
self.set_bit(4, true);
self
}
fn commit(&self, id: usize) {
let base_reg = id / 2;
let offset_in_reg = (id % 2) * 8;
unsafe {
let reg = MemoryMapped::new(REG_WINDOW_CONTROL_BASE.add(base_reg) as usize);
reg.set_bits(self.window_bits as u16, 8, offset_in_reg as u16);
}
}
}
impl MovableWindow {
fn new(id: usize) -> Self {
Self {
inner: Window::new(),
rect: Rect::new((0, 0).into(), (0, 0).into()),
id,
}
}
fn enable(&mut self) -> &mut Self {
self.inner.enable();
self
}
fn is_enabled(&self) -> bool {
self.inner.is_enabled()
}
#[inline(always)]
pub fn enable_blending(&mut self) -> &mut Self {
self.inner.enable_blending();
self
}
#[inline(always)]
pub fn enable_background(&mut self, back: impl Into<BackgroundId>) -> &mut Self {
self.inner.enable_background(back);
self
}
#[inline(always)]
pub fn enable_objects(&mut self) -> &mut Self {
self.inner.enable_objects();
self
}
fn commit(&self) {
self.inner.commit(self.id);
let left_right =
((self.rect.position.x as u16) << 8) | (self.rect.position.x + self.rect.size.x) as u16;
let top_bottom =
((self.rect.position.y as u16) << 8) | (self.rect.position.y + self.rect.size.y) as u16;
unsafe {
REG_HORIZONTAL_BASE.add(self.id).write_volatile(left_right);
REG_VERTICAL_BASE.add(self.id).write_volatile(top_bottom);
}
}
#[inline(always)]
fn set_pos_u8(&mut self, rect: Rect<u8>) -> &mut Self {
self.rect = rect;
self
}
#[inline(always)]
pub fn set_pos(&mut self, rect: Rect<i32>) -> &mut Self {
let new_rect = Rect::new(
(
rect.position.x.clamp(0, WIDTH) as u8,
rect.position.y.clamp(0, HEIGHT) as u8,
)
.into(),
(rect.size.x as u8, rect.size.y as u8).into(),
);
self.set_pos_u8(new_rect)
}
#[must_use]
pub fn horizontal_pos_dma(&self) -> dma::DmaControllable<Vector2D<u8>> {
unsafe { dma::DmaControllable::new(REG_HORIZONTAL_BASE.add(self.id).cast()) }
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
Gba,
display::{
AffineMatrix, Priority,
tiled::{
AffineBackground, AffineBackgroundSize, AffineBackgroundWrapBehaviour,
RegularBackground, RegularBackgroundSize, VRAM_MANAGER,
},
},
fixnum::{Num, num, vec2},
include_background_gfx,
test_runner::assert_image_output,
};
include_background_gfx!(mod background,
LOGO => deduplicate "gfx/test_logo.aseprite",
LOGO_256 => 256 "gfx/test_logo.aseprite",
);
#[test_case]
fn can_draw_window_box(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
let bg_id = bg.show(&mut frame);
frame
.windows()
.win_in(WinIn::Win0)
.enable_background(bg_id)
.set_pos(Rect::new(vec2(40, 40), vec2(100, 100)));
frame.commit();
assert_image_output("gfx/test_output/window/regular_box.png");
}
#[test_case]
fn can_draw_inverse_window_box(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
let bg_id = bg.show(&mut frame);
frame
.windows()
.win_in(WinIn::Win0)
.set_pos(Rect::new(vec2(40, 40), vec2(100, 100)));
frame
.windows()
.win_in(WinIn::Win1)
.set_pos(Rect::new(vec2(130, 70), vec2(50, 50)));
frame.windows().win_out().enable_background(bg_id);
frame.commit();
assert_image_output("gfx/test_output/window/regular_box_outside.png");
}
#[test_case]
fn can_do_affine_background_windows(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = AffineBackground::new(
Priority::P0,
AffineBackgroundSize::Background32x32,
AffineBackgroundWrapBehaviour::Wrap,
);
for i in 0..32 {
for j in 0..32 {
bg.set_tile(
(i, j),
&background::LOGO_256.tiles,
3 * 30 + 3 + (i + j) as u16 % 5,
);
}
}
bg.set_transform(AffineMatrix::<Num<i32, 8>>::from_rotation(num!(0.125)));
let mut frame = gfx.frame();
let bg_id = bg.show(&mut frame);
frame
.windows()
.win_in(WinIn::Win0)
.set_pos(Rect::new(vec2(40, 40), vec2(100, 100)));
frame
.windows()
.win_in(WinIn::Win1)
.set_pos(Rect::new(vec2(130, 70), vec2(50, 50)));
frame.windows().win_out().enable_background(bg_id);
frame.commit();
assert_image_output("gfx/test_output/window/affine_background_outside.png");
}
}