use crate::style::Style;
#[cfg(feature = "egc")]
use alloc::string::String;
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct TileFlags: u8 {
const WIDE_CHAR = 0b0000_0001;
const WIDE_CHAR_SPACER = 0b0000_0010;
const EMPTY = 0b0000_0100;
const HAS_EXTRA = 0b0000_1000;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Tile {
pub(crate) glyph: char,
pub(crate) style: Style,
pub(crate) dx: i16,
pub(crate) dy: i16,
pub(crate) flags: TileFlags,
}
impl Default for Tile {
fn default() -> Self {
Self {
glyph: ' ',
style: Style::default(),
dx: 0,
dy: 0,
flags: TileFlags::EMPTY,
}
}
}
impl Tile {
#[must_use]
pub const fn new(glyph: char, style: Style) -> Self {
Self {
glyph,
style,
dx: 0,
dy: 0,
flags: TileFlags::empty(),
}
}
#[must_use]
pub const fn glyph(&self) -> char {
self.glyph
}
#[must_use]
pub const fn style(&self) -> Style {
self.style
}
#[must_use]
pub const fn dx(&self) -> i16 {
self.dx
}
#[must_use]
pub const fn dy(&self) -> i16 {
self.dy
}
#[must_use]
pub const fn flags(&self) -> TileFlags {
self.flags
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.flags.contains(TileFlags::EMPTY)
}
#[must_use]
pub const fn with_glyph(mut self, glyph: char) -> Self {
self.glyph = glyph;
self.flags = self.flags.difference(TileFlags::EMPTY);
self
}
#[must_use]
pub const fn with_style(mut self, style: Style) -> Self {
self.style = style;
self.flags = self.flags.difference(TileFlags::EMPTY);
self
}
#[must_use]
pub const fn with_offset(mut self, dx: i16, dy: i16) -> Self {
self.dx = dx;
self.dy = dy;
self.flags = self.flags.difference(TileFlags::EMPTY);
self
}
#[cfg(feature = "egc")]
pub(crate) fn reset(&mut self) {
self.glyph = ' ';
self.style = Style::default();
self.dx = 0;
self.dy = 0;
self.flags = TileFlags::EMPTY;
}
}
#[cfg(feature = "egc")]
pub(crate) fn cap_grapheme(grapheme: &str) -> String {
const MAX_CODEPOINTS: usize = 8;
if grapheme.chars().count() <= MAX_CODEPOINTS {
return String::from(grapheme);
}
grapheme.chars().take(MAX_CODEPOINTS).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color::Color;
#[test]
fn test_tile_size_is_stable_and_small() {
assert_eq!(size_of::<Tile>(), 20);
}
#[test]
fn test_tile_defaults() {
let tile = Tile::default();
assert_eq!(tile.glyph(), ' ');
assert_eq!(tile.style(), Style::default());
assert_eq!(tile.dx, 0);
assert_eq!(tile.dy, 0);
assert!(tile.is_empty());
assert_eq!(tile.flags(), TileFlags::EMPTY);
}
#[test]
fn test_tile_empty_semantics() {
assert!(Tile::default().is_empty());
assert!(!Tile::new(' ', Style::default()).is_empty());
assert!(!Tile::default().with_glyph(' ').is_empty());
assert!(!Tile::default().with_style(Style::default()).is_empty());
assert!(!Tile::default().with_offset(1, 1).is_empty());
}
#[test]
fn test_tile_builder() {
let style = Style::new().fg(Color::RED);
let tile = Tile::new('A', style);
assert_eq!(tile.glyph(), 'A');
assert_eq!(tile.style(), style);
let tile = tile.with_glyph('B');
assert_eq!(tile.glyph(), 'B');
}
#[test]
fn test_tile_with_offset() {
let tile = Tile::new('X', Style::default()).with_offset(-3, 5);
assert_eq!(tile.dx, -3);
assert_eq!(tile.dy, 5);
}
#[test]
fn test_tile_reset() {
let style = Style::new().fg(Color::RED);
let mut tile = Tile::new('X', style);
assert!(!tile.is_empty());
tile.reset();
assert_eq!(tile.glyph(), ' ');
assert_eq!(tile.style(), Style::default());
assert_eq!(tile.dx, 0);
assert_eq!(tile.dy, 0);
assert!(tile.is_empty());
}
#[cfg(feature = "egc")]
#[test]
fn test_tile_wide_flag() {
let mut tile = Tile::new('漢', Style::default());
tile.flags = TileFlags::WIDE_CHAR;
assert!(tile.flags().contains(TileFlags::WIDE_CHAR));
assert!(!tile.flags().contains(TileFlags::WIDE_CHAR_SPACER));
}
}