1use crate::style::Style;
4#[cfg(feature = "egc")]
5use alloc::sync::Arc;
6
7bitflags::bitflags! {
8 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
10 pub struct TileFlags: u8 {
11 const WIDE_CHAR = 0b0000_0001;
13 const WIDE_CHAR_SPACER = 0b0000_0010;
15 }
16}
17
18#[derive(Clone, Debug, PartialEq, Eq, Hash)]
25pub struct Tile {
26 pub(crate) glyph: char,
28 pub(crate) style: Style,
30 pub(crate) dx: i16,
34 pub(crate) dy: i16,
38 #[cfg(feature = "egc")]
42 pub(crate) flags: TileFlags,
43 #[cfg(feature = "egc")]
51 pub(crate) extra: Option<Arc<String>>,
52}
53
54impl Default for Tile {
55 fn default() -> Self {
56 Self {
57 glyph: ' ',
58 style: Style::default(),
59 dx: 0,
60 dy: 0,
61 #[cfg(feature = "egc")]
62 flags: TileFlags::empty(),
63 #[cfg(feature = "egc")]
64 extra: None,
65 }
66 }
67}
68
69impl Tile {
70 #[must_use]
74 pub const fn new(glyph: char, style: Style) -> Self {
75 Self {
76 glyph,
77 style,
78 dx: 0,
79 dy: 0,
80 #[cfg(feature = "egc")]
81 flags: TileFlags::empty(),
82 #[cfg(feature = "egc")]
83 extra: None,
84 }
85 }
86
87 #[must_use]
89 pub const fn glyph(&self) -> char {
90 self.glyph
91 }
92
93 #[must_use]
95 pub const fn style(&self) -> Style {
96 self.style
97 }
98
99 #[cfg(feature = "egc")]
103 #[must_use]
104 pub const fn flags(&self) -> TileFlags {
105 self.flags
106 }
107
108 #[cfg(feature = "egc")]
115 #[must_use]
116 pub fn extra(&self) -> Option<&str> {
117 self.extra.as_deref().map(String::as_str)
118 }
119
120 #[cfg(feature = "egc")]
129 #[must_use]
130 pub fn grapheme(&self) -> Option<&str> {
131 self.extra.as_deref().map(String::as_str)
132 }
133
134 #[must_use]
136 pub const fn with_glyph(mut self, glyph: char) -> Self {
137 self.glyph = glyph;
138 self
139 }
140
141 #[must_use]
143 pub const fn with_style(mut self, style: Style) -> Self {
144 self.style = style;
145 self
146 }
147
148 #[must_use]
150 pub const fn with_offset(mut self, dx: i16, dy: i16) -> Self {
151 self.dx = dx;
152 self.dy = dy;
153 self
154 }
155
156 #[cfg(feature = "egc")]
158 pub(crate) fn reset(&mut self) {
159 self.glyph = ' ';
160 self.style = Style::default();
161 self.dx = 0;
162 self.dy = 0;
163 self.flags = TileFlags::empty();
164 self.extra = None;
165 }
166}
167
168#[cfg(feature = "egc")]
173pub(crate) fn cap_grapheme(grapheme: &str) -> String {
174 const MAX_CODEPOINTS: usize = 8;
175 if grapheme.chars().count() <= MAX_CODEPOINTS {
177 return String::from(grapheme);
178 }
179 grapheme.chars().take(MAX_CODEPOINTS).collect()
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::color::Color;
186
187 #[test]
188 fn test_tile_defaults() {
189 let tile = Tile::default();
190 assert_eq!(tile.glyph(), ' ');
191 assert_eq!(tile.style(), Style::default());
192 assert_eq!(tile.dx, 0);
193 assert_eq!(tile.dy, 0);
194 #[cfg(feature = "egc")]
195 {
196 assert_eq!(tile.flags(), TileFlags::empty());
197 assert!(tile.extra().is_none());
198 }
199 }
200
201 #[test]
202 fn test_tile_builder() {
203 let style = Style::new().fg(Color::RED);
204 let tile = Tile::new('A', style);
205 assert_eq!(tile.glyph(), 'A');
206 assert_eq!(tile.style(), style);
207
208 let tile = tile.with_glyph('B');
209 assert_eq!(tile.glyph(), 'B');
210 }
211
212 #[test]
213 fn test_tile_with_offset() {
214 let tile = Tile::new('X', Style::default()).with_offset(-3, 5);
215 assert_eq!(tile.dx, -3);
216 assert_eq!(tile.dy, 5);
217 }
218
219 #[test]
220 fn test_tile_reset() {
221 let style = Style::new().fg(Color::RED);
222 let mut tile = Tile::new('X', style);
223 tile.reset();
224 assert_eq!(tile.glyph(), ' ');
225 assert_eq!(tile.style(), Style::default());
226 assert_eq!(tile.dx, 0);
227 assert_eq!(tile.dy, 0);
228 }
229
230 #[cfg(feature = "egc")]
231 #[test]
232 fn test_tile_grapheme_single() {
233 let tile = Tile::new('A', Style::default());
234 assert_eq!(tile.grapheme(), None); }
236
237 #[cfg(feature = "egc")]
238 #[test]
239 fn test_tile_grapheme_multi() {
240 let extra_str = Arc::new(String::from("e\u{0301}"));
242 let tile = Tile {
243 glyph: 'e',
244 style: Style::default(),
245 dx: 0,
246 dy: 0,
247 flags: TileFlags::empty(),
248 extra: Some(extra_str),
249 };
250 assert_eq!(tile.grapheme(), Some("e\u{0301}"));
251 }
252
253 #[cfg(feature = "egc")]
254 #[test]
255 fn test_tile_wide_flag() {
256 let mut tile = Tile::new('漢', Style::default());
257 tile.flags = TileFlags::WIDE_CHAR;
258 assert!(tile.flags().contains(TileFlags::WIDE_CHAR));
259 assert!(!tile.flags().contains(TileFlags::WIDE_CHAR_SPACER));
260 }
261}