1use bitvec::prelude::*;
7use compact_str::CompactString;
8use presentar_core::Color;
9use unicode_width::UnicodeWidthStr;
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
13pub struct Modifiers(u8);
14
15impl Modifiers {
16 pub const NONE: Self = Self(0);
18 pub const BOLD: Self = Self(1 << 0);
20 pub const ITALIC: Self = Self(1 << 1);
22 pub const UNDERLINE: Self = Self(1 << 2);
24 pub const STRIKETHROUGH: Self = Self(1 << 3);
26 pub const DIM: Self = Self(1 << 4);
28 pub const BLINK: Self = Self(1 << 5);
30 pub const REVERSE: Self = Self(1 << 6);
32 pub const HIDDEN: Self = Self(1 << 7);
34
35 #[must_use]
37 pub const fn empty() -> Self {
38 Self::NONE
39 }
40
41 #[must_use]
43 pub const fn is_empty(self) -> bool {
44 self.0 == 0
45 }
46
47 #[must_use]
49 pub const fn contains(self, other: Self) -> bool {
50 (self.0 & other.0) == other.0
51 }
52
53 #[must_use]
55 pub const fn with(self, other: Self) -> Self {
56 Self(self.0 | other.0)
57 }
58
59 #[must_use]
61 pub const fn without(self, other: Self) -> Self {
62 Self(self.0 & !other.0)
63 }
64
65 #[must_use]
67 pub const fn bits(self) -> u8 {
68 self.0
69 }
70
71 #[must_use]
73 pub const fn from_bits(bits: u8) -> Self {
74 Self(bits)
75 }
76}
77
78impl std::ops::BitOr for Modifiers {
79 type Output = Self;
80
81 fn bitor(self, rhs: Self) -> Self::Output {
82 Self(self.0 | rhs.0)
83 }
84}
85
86impl std::ops::BitOrAssign for Modifiers {
87 fn bitor_assign(&mut self, rhs: Self) {
88 self.0 |= rhs.0;
89 }
90}
91
92impl std::ops::BitAnd for Modifiers {
93 type Output = Self;
94
95 fn bitand(self, rhs: Self) -> Self::Output {
96 Self(self.0 & rhs.0)
97 }
98}
99
100#[derive(Clone, Debug, PartialEq)]
105pub struct Cell {
106 pub symbol: CompactString,
108 pub fg: Color,
110 pub bg: Color,
112 pub modifiers: Modifiers,
114 width: u8,
116}
117
118impl Default for Cell {
119 fn default() -> Self {
120 Self {
121 symbol: CompactString::const_new(" "),
122 fg: Color::WHITE,
123 bg: Color::TRANSPARENT,
125 modifiers: Modifiers::NONE,
126 width: 1,
127 }
128 }
129}
130
131impl Cell {
132 #[must_use]
134 pub fn new(symbol: &str, fg: Color, bg: Color, modifiers: Modifiers) -> Self {
135 let width = UnicodeWidthStr::width(symbol).min(255) as u8;
136 Self {
137 symbol: CompactString::new(symbol),
138 fg,
139 bg,
140 modifiers,
141 width: width.max(1),
142 }
143 }
144
145 pub fn update(&mut self, symbol: &str, fg: Color, bg: Color, modifiers: Modifiers) {
147 self.symbol.clear();
148 self.symbol.push_str(symbol);
149 self.fg = fg;
150 self.bg = bg;
151 self.modifiers = modifiers;
152 self.width = UnicodeWidthStr::width(symbol).clamp(1, 255) as u8;
153 }
154
155 pub fn make_continuation(&mut self) {
157 self.symbol.clear();
158 self.width = 0;
159 }
160
161 #[must_use]
163 pub const fn is_continuation(&self) -> bool {
164 self.width == 0
165 }
166
167 #[must_use]
169 pub const fn width(&self) -> u8 {
170 self.width
171 }
172
173 pub fn reset(&mut self) {
175 self.symbol.clear();
176 self.symbol.push(' ');
177 self.fg = Color::WHITE;
178 self.bg = Color::TRANSPARENT;
179 self.modifiers = Modifiers::NONE;
180 self.width = 1;
181 }
182}
183
184#[derive(Debug)]
189pub struct CellBuffer {
190 cells: Vec<Cell>,
192 width: u16,
194 height: u16,
196 dirty: BitVec,
198}
199
200impl CellBuffer {
201 #[must_use]
203 pub fn new(width: u16, height: u16) -> Self {
204 let size = (width as usize) * (height as usize);
205 Self {
206 cells: vec![Cell::default(); size],
207 width,
208 height,
209 dirty: bitvec![0; size],
210 }
211 }
212
213 #[must_use]
215 pub const fn width(&self) -> u16 {
216 self.width
217 }
218
219 #[must_use]
221 pub const fn height(&self) -> u16 {
222 self.height
223 }
224
225 #[must_use]
227 pub fn len(&self) -> usize {
228 self.cells.len()
229 }
230
231 #[must_use]
233 pub fn is_empty(&self) -> bool {
234 self.cells.is_empty()
235 }
236
237 #[must_use]
239 pub fn index(&self, x: u16, y: u16) -> usize {
240 debug_assert!(x < self.width, "x coordinate out of bounds");
241 debug_assert!(y < self.height, "y coordinate out of bounds");
242 (y as usize) * (self.width as usize) + (x as usize)
243 }
244
245 #[must_use]
247 pub fn coords(&self, idx: usize) -> (u16, u16) {
248 debug_assert!(idx < self.cells.len(), "index out of bounds");
249 let x = (idx % (self.width as usize)) as u16;
250 let y = (idx / (self.width as usize)) as u16;
251 debug_assert!(
252 x < self.width && y < self.height,
253 "coords must be in bounds"
254 );
255 (x, y)
256 }
257
258 #[must_use]
260 pub fn get(&self, x: u16, y: u16) -> Option<&Cell> {
261 if x < self.width && y < self.height {
262 Some(&self.cells[self.index(x, y)])
263 } else {
264 None
265 }
266 }
267
268 pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> {
270 if x < self.width && y < self.height {
271 let idx = self.index(x, y);
272 Some(&mut self.cells[idx])
273 } else {
274 None
275 }
276 }
277
278 pub fn set(&mut self, x: u16, y: u16, cell: Cell) {
280 if x < self.width && y < self.height {
281 let idx = self.index(x, y);
282 self.cells[idx] = cell;
283 self.dirty.set(idx, true);
284 }
285 }
286
287 pub fn update(
289 &mut self,
290 x: u16,
291 y: u16,
292 symbol: &str,
293 fg: Color,
294 bg: Color,
295 modifiers: Modifiers,
296 ) {
297 if x < self.width && y < self.height {
298 let idx = self.index(x, y);
299 self.cells[idx].update(symbol, fg, bg, modifiers);
300 self.dirty.set(idx, true);
301 }
302 }
303
304 pub fn mark_dirty(&mut self, x: u16, y: u16) {
306 if x < self.width && y < self.height {
307 let idx = self.index(x, y);
308 self.dirty.set(idx, true);
309 }
310 }
311
312 pub fn mark_all_dirty(&mut self) {
314 self.dirty.fill(true);
315 }
316
317 pub fn clear_dirty(&mut self) {
319 self.dirty.fill(false);
320 }
321
322 #[must_use]
324 pub fn dirty_count(&self) -> usize {
325 self.dirty.count_ones()
326 }
327
328 pub fn iter_dirty(&self) -> impl Iterator<Item = usize> + '_ {
330 self.dirty.iter_ones()
331 }
332
333 #[must_use]
335 pub fn cells(&self) -> &[Cell] {
336 &self.cells
337 }
338
339 pub fn cells_mut(&mut self) -> &mut [Cell] {
341 &mut self.cells
342 }
343
344 pub fn resize(&mut self, width: u16, height: u16) {
346 let size = (width as usize) * (height as usize);
347 self.width = width;
348 self.height = height;
349 self.cells.clear();
350 self.cells.resize(size, Cell::default());
351 self.dirty = bitvec![0; size];
352 self.mark_all_dirty();
353 }
354
355 pub fn clear(&mut self) {
357 for cell in &mut self.cells {
358 cell.reset();
359 }
360 self.mark_all_dirty();
361 }
362
363 pub fn fill_rect(&mut self, x: u16, y: u16, width: u16, height: u16, fg: Color, bg: Color) {
365 let x_end = (x + width).min(self.width);
366 let y_end = (y + height).min(self.height);
367
368 for cy in y..y_end {
369 for cx in x..x_end {
370 self.update(cx, cy, " ", fg, bg, Modifiers::NONE);
371 }
372 }
373 }
374
375 pub fn set_char(&mut self, x: u16, y: u16, ch: char) {
377 if let Some(cell) = self.get_mut(x, y) {
378 let mut buf = [0u8; 4];
379 let s = ch.encode_utf8(&mut buf);
380 cell.symbol = CompactString::from(&*s);
381 self.mark_dirty(x, y);
382 }
383 }
384
385 pub fn write_str(&mut self, x: u16, y: u16, s: &str) {
387 let mut cx = x;
388 for ch in s.chars() {
389 self.set_char(cx, y, ch);
390 cx = cx.saturating_add(1);
391 if cx >= self.width {
392 break;
393 }
394 }
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn test_modifiers_empty() {
404 let m = Modifiers::empty();
405 assert!(m.is_empty());
406 assert_eq!(m.bits(), 0);
407 }
408
409 #[test]
410 fn test_modifiers_with() {
411 let m = Modifiers::NONE.with(Modifiers::BOLD);
412 assert!(m.contains(Modifiers::BOLD));
413 assert!(!m.contains(Modifiers::ITALIC));
414 }
415
416 #[test]
417 fn test_modifiers_without() {
418 let m = Modifiers::BOLD.with(Modifiers::ITALIC);
419 let m2 = m.without(Modifiers::BOLD);
420 assert!(!m2.contains(Modifiers::BOLD));
421 assert!(m2.contains(Modifiers::ITALIC));
422 }
423
424 #[test]
425 fn test_modifiers_bitor() {
426 let m = Modifiers::BOLD | Modifiers::ITALIC;
427 assert!(m.contains(Modifiers::BOLD));
428 assert!(m.contains(Modifiers::ITALIC));
429 }
430
431 #[test]
432 fn test_modifiers_bitor_assign() {
433 let mut m = Modifiers::BOLD;
434 m |= Modifiers::ITALIC;
435 assert!(m.contains(Modifiers::BOLD));
436 assert!(m.contains(Modifiers::ITALIC));
437 }
438
439 #[test]
440 fn test_modifiers_bitand() {
441 let m1 = Modifiers::BOLD | Modifiers::ITALIC;
442 let m2 = Modifiers::BOLD | Modifiers::UNDERLINE;
443 let m3 = m1 & m2;
444 assert!(m3.contains(Modifiers::BOLD));
445 assert!(!m3.contains(Modifiers::ITALIC));
446 }
447
448 #[test]
449 fn test_modifiers_from_bits() {
450 let m = Modifiers::from_bits(0b0000_0011);
451 assert!(m.contains(Modifiers::BOLD));
452 assert!(m.contains(Modifiers::ITALIC));
453 }
454
455 #[test]
456 fn test_cell_default() {
457 let cell = Cell::default();
458 assert_eq!(cell.symbol.as_str(), " ");
459 assert_eq!(cell.fg, Color::WHITE);
460 assert_eq!(cell.bg, Color::TRANSPARENT);
461 assert_eq!(cell.modifiers, Modifiers::NONE);
462 assert_eq!(cell.width(), 1);
463 }
464
465 #[test]
466 fn test_cell_new() {
467 let cell = Cell::new("A", Color::RED, Color::BLUE, Modifiers::BOLD);
468 assert_eq!(cell.symbol.as_str(), "A");
469 assert_eq!(cell.fg, Color::RED);
470 assert_eq!(cell.bg, Color::BLUE);
471 assert!(cell.modifiers.contains(Modifiers::BOLD));
472 assert_eq!(cell.width(), 1);
473 }
474
475 #[test]
476 fn test_cell_wide_char() {
477 let cell = Cell::new("日", Color::WHITE, Color::BLACK, Modifiers::NONE);
478 assert_eq!(cell.width(), 2);
479 }
480
481 #[test]
482 fn test_cell_update() {
483 let mut cell = Cell::default();
484 cell.update("X", Color::GREEN, Color::YELLOW, Modifiers::ITALIC);
485 assert_eq!(cell.symbol.as_str(), "X");
486 assert_eq!(cell.fg, Color::GREEN);
487 assert_eq!(cell.bg, Color::YELLOW);
488 assert!(cell.modifiers.contains(Modifiers::ITALIC));
489 }
490
491 #[test]
492 fn test_cell_continuation() {
493 let mut cell = Cell::new("日", Color::WHITE, Color::BLACK, Modifiers::NONE);
494 cell.make_continuation();
495 assert!(cell.is_continuation());
496 assert_eq!(cell.width(), 0);
497 }
498
499 #[test]
500 fn test_cell_reset() {
501 let mut cell = Cell::new("X", Color::RED, Color::BLUE, Modifiers::BOLD);
502 cell.reset();
503 assert_eq!(cell.symbol.as_str(), " ");
504 assert_eq!(cell.fg, Color::WHITE);
505 assert_eq!(cell.bg, Color::TRANSPARENT);
506 assert!(cell.modifiers.is_empty());
507 }
508
509 #[test]
510 fn test_buffer_creation() {
511 let buf = CellBuffer::new(80, 24);
512 assert_eq!(buf.width(), 80);
513 assert_eq!(buf.height(), 24);
514 assert_eq!(buf.len(), 1920);
515 assert!(!buf.is_empty());
516 }
517
518 #[test]
519 fn test_buffer_empty() {
520 let buf = CellBuffer::new(0, 0);
521 assert!(buf.is_empty());
522 }
523
524 #[test]
525 fn test_buffer_index() {
526 let buf = CellBuffer::new(10, 5);
527 assert_eq!(buf.index(0, 0), 0);
528 assert_eq!(buf.index(5, 0), 5);
529 assert_eq!(buf.index(0, 1), 10);
530 assert_eq!(buf.index(5, 2), 25);
531 }
532
533 #[test]
534 fn test_buffer_coords() {
535 let buf = CellBuffer::new(10, 5);
536 assert_eq!(buf.coords(0), (0, 0));
537 assert_eq!(buf.coords(5), (5, 0));
538 assert_eq!(buf.coords(10), (0, 1));
539 assert_eq!(buf.coords(25), (5, 2));
540 }
541
542 #[test]
543 fn test_buffer_get() {
544 let buf = CellBuffer::new(10, 5);
545 assert!(buf.get(0, 0).is_some());
546 assert!(buf.get(9, 4).is_some());
547 assert!(buf.get(10, 0).is_none());
548 assert!(buf.get(0, 5).is_none());
549 }
550
551 #[test]
552 fn test_buffer_get_mut() {
553 let mut buf = CellBuffer::new(10, 5);
554 assert!(buf.get_mut(0, 0).is_some());
555 assert!(buf.get_mut(10, 0).is_none());
556 }
557
558 #[test]
559 fn test_buffer_set() {
560 let mut buf = CellBuffer::new(10, 5);
561 let cell = Cell::new("X", Color::RED, Color::BLUE, Modifiers::NONE);
562 buf.set(5, 2, cell);
563
564 let retrieved = buf.get(5, 2).unwrap();
565 assert_eq!(retrieved.symbol.as_str(), "X");
566 assert!(buf.dirty_count() > 0);
567 }
568
569 #[test]
570 fn test_buffer_set_out_of_bounds() {
571 let mut buf = CellBuffer::new(10, 5);
572 let cell = Cell::new("X", Color::RED, Color::BLUE, Modifiers::NONE);
573 buf.set(100, 100, cell); }
575
576 #[test]
577 fn test_buffer_update() {
578 let mut buf = CellBuffer::new(10, 5);
579 buf.update(3, 3, "Y", Color::GREEN, Color::BLACK, Modifiers::BOLD);
580
581 let cell = buf.get(3, 3).unwrap();
582 assert_eq!(cell.symbol.as_str(), "Y");
583 assert_eq!(cell.fg, Color::GREEN);
584 }
585
586 #[test]
587 fn test_buffer_dirty_tracking() {
588 let mut buf = CellBuffer::new(10, 5);
589 assert_eq!(buf.dirty_count(), 0);
590
591 buf.mark_dirty(0, 0);
592 assert_eq!(buf.dirty_count(), 1);
593
594 buf.mark_all_dirty();
595 assert_eq!(buf.dirty_count(), 50);
596
597 buf.clear_dirty();
598 assert_eq!(buf.dirty_count(), 0);
599 }
600
601 #[test]
602 fn test_buffer_iter_dirty() {
603 let mut buf = CellBuffer::new(10, 5);
604 buf.mark_dirty(1, 1);
605 buf.mark_dirty(3, 3);
606
607 let dirty: Vec<usize> = buf.iter_dirty().collect();
608 assert_eq!(dirty.len(), 2);
609 assert!(dirty.contains(&buf.index(1, 1)));
610 assert!(dirty.contains(&buf.index(3, 3)));
611 }
612
613 #[test]
614 fn test_buffer_resize() {
615 let mut buf = CellBuffer::new(10, 5);
616 buf.update(0, 0, "X", Color::RED, Color::BLACK, Modifiers::NONE);
617
618 buf.resize(20, 10);
619 assert_eq!(buf.width(), 20);
620 assert_eq!(buf.height(), 10);
621 assert_eq!(buf.len(), 200);
622 assert_eq!(buf.get(0, 0).unwrap().symbol.as_str(), " ");
624 assert_eq!(buf.dirty_count(), 200);
626 }
627
628 #[test]
629 fn test_buffer_clear() {
630 let mut buf = CellBuffer::new(10, 5);
631 buf.update(0, 0, "X", Color::RED, Color::BLACK, Modifiers::BOLD);
632 buf.clear_dirty();
633
634 buf.clear();
635 let cell = buf.get(0, 0).unwrap();
636 assert_eq!(cell.symbol.as_str(), " ");
637 assert!(cell.modifiers.is_empty());
638 assert_eq!(buf.dirty_count(), 50);
639 }
640
641 #[test]
642 fn test_buffer_fill_rect() {
643 let mut buf = CellBuffer::new(10, 10);
644 buf.fill_rect(2, 2, 3, 3, Color::WHITE, Color::RED);
645
646 assert_eq!(buf.get(3, 3).unwrap().bg, Color::RED);
648 assert_eq!(buf.get(0, 0).unwrap().bg, Color::TRANSPARENT);
650 }
651
652 #[test]
653 fn test_buffer_fill_rect_clipped() {
654 let mut buf = CellBuffer::new(10, 10);
655 buf.fill_rect(8, 8, 5, 5, Color::WHITE, Color::BLUE);
656
657 assert_eq!(buf.get(9, 9).unwrap().bg, Color::BLUE);
659 }
660
661 #[test]
662 fn test_buffer_cells_access() {
663 let mut buf = CellBuffer::new(10, 5);
664 assert_eq!(buf.cells().len(), 50);
665 assert_eq!(buf.cells_mut().len(), 50);
666 }
667
668 #[test]
669 fn test_cell_empty_string() {
670 let cell = Cell::new("", Color::WHITE, Color::BLACK, Modifiers::NONE);
671 assert_eq!(cell.width(), 1);
673 }
674
675 #[test]
676 fn test_modifiers_all_flags() {
677 let all = Modifiers::BOLD
678 | Modifiers::ITALIC
679 | Modifiers::UNDERLINE
680 | Modifiers::STRIKETHROUGH
681 | Modifiers::DIM
682 | Modifiers::BLINK
683 | Modifiers::REVERSE
684 | Modifiers::HIDDEN;
685
686 assert!(all.contains(Modifiers::BOLD));
687 assert!(all.contains(Modifiers::ITALIC));
688 assert!(all.contains(Modifiers::UNDERLINE));
689 assert!(all.contains(Modifiers::STRIKETHROUGH));
690 assert!(all.contains(Modifiers::DIM));
691 assert!(all.contains(Modifiers::BLINK));
692 assert!(all.contains(Modifiers::REVERSE));
693 assert!(all.contains(Modifiers::HIDDEN));
694 }
695}