use dotzuki_engine::render::{Rgba, Painter, TilePos, TileRect};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BorderTiles {
pub top_left: u8,
pub top: u8,
pub top_right: u8,
pub left: u8,
pub right: u8,
pub bottom_left: u8,
pub bottom: u8,
pub bottom_right: u8,
pub fill: u8,
}
impl Default for BorderTiles {
fn default() -> Self {
Self {
top_left: 0x79, top: 0x7A, top_right: 0x7B, left: 0x7C, right: 0x7C, bottom_left: 0x7D, bottom: 0x7A, bottom_right: 0x7E, fill: 0x7F, }
}
}
#[derive(Debug, Clone)]
pub struct Border {
pub rect: TileRect,
pub tiles: Option<BorderTiles>,
pub color: Rgba,
}
impl Border {
#[inline]
pub fn new(rect: TileRect, color: Rgba) -> Self {
Self {
rect,
tiles: Some(BorderTiles::default()),
color,
}
}
#[inline]
pub fn with_tiles(rect: TileRect, tiles: BorderTiles, color: Rgba) -> Self {
Self {
rect,
tiles: Some(tiles),
color,
}
}
#[inline]
pub fn fill(rect: TileRect, color: Rgba) -> Self {
Self {
rect,
tiles: None,
color,
}
}
#[inline]
pub fn has_style(&self) -> bool {
self.tiles.is_some()
}
pub fn render(&self, painter: &mut dyn Painter) {
let rect = self.rect;
match &self.tiles {
Some(tiles) => {
painter.draw_gb_tile(
TilePos::new(rect.tx, rect.ty),
tiles.top_left,
" ",
self.color,
);
if rect.tw > 1 {
painter.draw_gb_tile(
TilePos::new(rect.tx + rect.tw - 1, rect.ty),
tiles.top_right,
" ",
self.color,
);
}
if rect.th > 1 {
painter.draw_gb_tile(
TilePos::new(rect.tx, rect.ty + rect.th - 1),
tiles.bottom_left,
" ",
self.color,
);
}
if rect.tw > 1 && rect.th > 1 {
painter.draw_gb_tile(
TilePos::new(rect.tx + rect.tw - 1, rect.ty + rect.th - 1),
tiles.bottom_right,
" ",
self.color,
);
}
for x in 1..rect.tw.saturating_sub(1) {
painter.draw_gb_tile(
TilePos::new(rect.tx + x, rect.ty),
tiles.top,
" ",
self.color,
);
}
if rect.th > 1 {
for x in 1..rect.tw.saturating_sub(1) {
painter.draw_gb_tile(
TilePos::new(rect.tx + x, rect.ty + rect.th - 1),
tiles.bottom,
" ",
self.color,
);
}
}
for y in 1..rect.th.saturating_sub(1) {
painter.draw_gb_tile(
TilePos::new(rect.tx, rect.ty + y),
tiles.left,
" ",
self.color,
);
}
if rect.tw > 1 {
for y in 1..rect.th.saturating_sub(1) {
painter.draw_gb_tile(
TilePos::new(rect.tx + rect.tw - 1, rect.ty + y),
tiles.right,
" ",
self.color,
);
}
}
for y in 1..rect.th.saturating_sub(1) {
for x in 1..rect.tw.saturating_sub(1) {
painter.draw_gb_tile(
TilePos::new(rect.tx + x, rect.ty + y),
tiles.fill,
" ",
self.color,
);
}
}
}
None => {
painter.draw_pixel_rect(
rect.tx * 8,
rect.ty * 8,
rect.tw * 8,
rect.th * 8,
self.color,
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use dotzuki_engine::render::TileRect;
#[test]
fn default_tiles_match_textbox_constants() {
let t = BorderTiles::default();
assert_eq!(t.top_left, 0x79);
assert_eq!(t.top, 0x7A);
assert_eq!(t.top_right, 0x7B);
assert_eq!(t.left, 0x7C);
assert_eq!(t.right, 0x7C);
assert_eq!(t.bottom_left, 0x7D);
assert_eq!(t.bottom, 0x7A);
assert_eq!(t.bottom_right, 0x7E);
assert_eq!(t.fill, 0x7F);
}
#[test]
fn custom_tiles_support() {
let t = BorderTiles {
top_left: 0x01,
top: 0x02,
top_right: 0x03,
left: 0x04,
right: 0x05,
bottom_left: 0x06,
bottom: 0x07,
bottom_right: 0x08,
fill: 0x09,
};
assert_eq!(t.top_left, 0x01);
assert_eq!(t.right, 0x05);
assert_eq!(t.fill, 0x09);
}
#[test]
fn border_tiles_copy_and_eq() {
let a = BorderTiles::default();
let b = a;
assert_eq!(a, b);
let mut c = a;
c.top_left = 0x00;
assert_ne!(a, c);
}
#[test]
fn new_border_has_default_tiles() {
let rect = TileRect::new(0, 0, 10, 5);
let b = Border::new(rect, Rgba::INK_BLACK);
assert!(b.has_style());
assert_eq!(b.tiles, Some(BorderTiles::default()));
assert_eq!(b.rect, rect);
}
#[test]
fn with_tiles_sets_custom_tiles() {
let rect = TileRect::new(2, 3, 8, 4);
let tiles = BorderTiles {
top_left: 0x10,
..BorderTiles::default()
};
let b = Border::with_tiles(rect, tiles, Rgba::INK_DARK_GRAY);
assert!(b.has_style());
assert_eq!(b.tiles.unwrap().top_left, 0x10);
}
#[test]
fn fill_border_has_no_style() {
let rect = TileRect::new(0, 0, 20, 18);
let b = Border::fill(rect, Rgba::INK_WHITE);
assert!(!b.has_style());
assert_eq!(b.tiles, None);
assert_eq!(b.color, Rgba::INK_WHITE);
}
#[test]
fn has_style_reflects_tiles_presence() {
let rect = TileRect::new(0, 0, 4, 4);
let styled = Border::new(rect, Rgba::INK_BLACK);
assert!(styled.has_style());
let fill_only = Border::fill(rect, Rgba::INK_WHITE);
assert!(!fill_only.has_style());
}
#[test]
fn minimal_1x1_border() {
let rect = TileRect::new(5, 5, 1, 1);
let b = Border::new(rect, Rgba::INK_BLACK);
assert!(b.has_style());
assert_eq!(b.rect.tw, 1);
assert_eq!(b.rect.th, 1);
}
#[test]
fn thin_horizontal_border() {
let rect = TileRect::new(0, 0, 10, 1);
let b = Border::new(rect, Rgba::INK_BLACK);
assert_eq!(b.rect.tw, 10);
assert_eq!(b.rect.th, 1);
}
#[test]
fn thin_vertical_border() {
let rect = TileRect::new(0, 0, 1, 5);
let b = Border::new(rect, Rgba::INK_BLACK);
assert_eq!(b.rect.tw, 1);
assert_eq!(b.rect.th, 5);
}
}