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)]
399#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn test_modifiers_empty() {
405 let m = Modifiers::empty();
406 assert!(m.is_empty());
407 assert_eq!(m.bits(), 0);
408 }
409
410 #[test]
411 fn test_modifiers_with() {
412 let m = Modifiers::NONE.with(Modifiers::BOLD);
413 assert!(m.contains(Modifiers::BOLD));
414 assert!(!m.contains(Modifiers::ITALIC));
415 }
416
417 #[test]
418 fn test_modifiers_without() {
419 let m = Modifiers::BOLD.with(Modifiers::ITALIC);
420 let m2 = m.without(Modifiers::BOLD);
421 assert!(!m2.contains(Modifiers::BOLD));
422 assert!(m2.contains(Modifiers::ITALIC));
423 }
424
425 #[test]
426 fn test_modifiers_bitor() {
427 let m = Modifiers::BOLD | Modifiers::ITALIC;
428 assert!(m.contains(Modifiers::BOLD));
429 assert!(m.contains(Modifiers::ITALIC));
430 }
431
432 #[test]
433 fn test_modifiers_bitor_assign() {
434 let mut m = Modifiers::BOLD;
435 m |= Modifiers::ITALIC;
436 assert!(m.contains(Modifiers::BOLD));
437 assert!(m.contains(Modifiers::ITALIC));
438 }
439
440 #[test]
441 fn test_modifiers_bitand() {
442 let m1 = Modifiers::BOLD | Modifiers::ITALIC;
443 let m2 = Modifiers::BOLD | Modifiers::UNDERLINE;
444 let m3 = m1 & m2;
445 assert!(m3.contains(Modifiers::BOLD));
446 assert!(!m3.contains(Modifiers::ITALIC));
447 }
448
449 #[test]
450 fn test_modifiers_from_bits() {
451 let m = Modifiers::from_bits(0b0000_0011);
452 assert!(m.contains(Modifiers::BOLD));
453 assert!(m.contains(Modifiers::ITALIC));
454 }
455
456 #[test]
457 fn test_cell_default() {
458 let cell = Cell::default();
459 assert_eq!(cell.symbol.as_str(), " ");
460 assert_eq!(cell.fg, Color::WHITE);
461 assert_eq!(cell.bg, Color::TRANSPARENT);
462 assert_eq!(cell.modifiers, Modifiers::NONE);
463 assert_eq!(cell.width(), 1);
464 }
465
466 #[test]
467 fn test_cell_new() {
468 let cell = Cell::new("A", Color::RED, Color::BLUE, Modifiers::BOLD);
469 assert_eq!(cell.symbol.as_str(), "A");
470 assert_eq!(cell.fg, Color::RED);
471 assert_eq!(cell.bg, Color::BLUE);
472 assert!(cell.modifiers.contains(Modifiers::BOLD));
473 assert_eq!(cell.width(), 1);
474 }
475
476 #[test]
477 fn test_cell_wide_char() {
478 let cell = Cell::new("日", Color::WHITE, Color::BLACK, Modifiers::NONE);
479 assert_eq!(cell.width(), 2);
480 }
481
482 #[test]
483 fn test_cell_update() {
484 let mut cell = Cell::default();
485 cell.update("X", Color::GREEN, Color::YELLOW, Modifiers::ITALIC);
486 assert_eq!(cell.symbol.as_str(), "X");
487 assert_eq!(cell.fg, Color::GREEN);
488 assert_eq!(cell.bg, Color::YELLOW);
489 assert!(cell.modifiers.contains(Modifiers::ITALIC));
490 }
491
492 #[test]
493 fn test_cell_continuation() {
494 let mut cell = Cell::new("日", Color::WHITE, Color::BLACK, Modifiers::NONE);
495 cell.make_continuation();
496 assert!(cell.is_continuation());
497 assert_eq!(cell.width(), 0);
498 }
499
500 #[test]
501 fn test_cell_reset() {
502 let mut cell = Cell::new("X", Color::RED, Color::BLUE, Modifiers::BOLD);
503 cell.reset();
504 assert_eq!(cell.symbol.as_str(), " ");
505 assert_eq!(cell.fg, Color::WHITE);
506 assert_eq!(cell.bg, Color::TRANSPARENT);
507 assert!(cell.modifiers.is_empty());
508 }
509
510 #[test]
511 fn test_buffer_creation() {
512 let buf = CellBuffer::new(80, 24);
513 assert_eq!(buf.width(), 80);
514 assert_eq!(buf.height(), 24);
515 assert_eq!(buf.len(), 1920);
516 assert!(!buf.is_empty());
517 }
518
519 #[test]
520 fn test_buffer_empty() {
521 let buf = CellBuffer::new(0, 0);
522 assert!(buf.is_empty());
523 }
524
525 #[test]
526 fn test_buffer_index() {
527 let buf = CellBuffer::new(10, 5);
528 assert_eq!(buf.index(0, 0), 0);
529 assert_eq!(buf.index(5, 0), 5);
530 assert_eq!(buf.index(0, 1), 10);
531 assert_eq!(buf.index(5, 2), 25);
532 }
533
534 #[test]
535 fn test_buffer_coords() {
536 let buf = CellBuffer::new(10, 5);
537 assert_eq!(buf.coords(0), (0, 0));
538 assert_eq!(buf.coords(5), (5, 0));
539 assert_eq!(buf.coords(10), (0, 1));
540 assert_eq!(buf.coords(25), (5, 2));
541 }
542
543 #[test]
544 fn test_buffer_get() {
545 let buf = CellBuffer::new(10, 5);
546 assert!(buf.get(0, 0).is_some());
547 assert!(buf.get(9, 4).is_some());
548 assert!(buf.get(10, 0).is_none());
549 assert!(buf.get(0, 5).is_none());
550 }
551
552 #[test]
553 fn test_buffer_get_mut() {
554 let mut buf = CellBuffer::new(10, 5);
555 assert!(buf.get_mut(0, 0).is_some());
556 assert!(buf.get_mut(10, 0).is_none());
557 }
558
559 #[test]
560 fn test_buffer_set() {
561 let mut buf = CellBuffer::new(10, 5);
562 let cell = Cell::new("X", Color::RED, Color::BLUE, Modifiers::NONE);
563 buf.set(5, 2, cell);
564
565 let retrieved = buf.get(5, 2).unwrap();
566 assert_eq!(retrieved.symbol.as_str(), "X");
567 assert!(buf.dirty_count() > 0);
568 }
569
570 #[test]
571 fn test_buffer_set_out_of_bounds() {
572 let mut buf = CellBuffer::new(10, 5);
573 let cell = Cell::new("X", Color::RED, Color::BLUE, Modifiers::NONE);
574 buf.set(100, 100, cell); }
576
577 #[test]
578 fn test_buffer_update() {
579 let mut buf = CellBuffer::new(10, 5);
580 buf.update(3, 3, "Y", Color::GREEN, Color::BLACK, Modifiers::BOLD);
581
582 let cell = buf.get(3, 3).unwrap();
583 assert_eq!(cell.symbol.as_str(), "Y");
584 assert_eq!(cell.fg, Color::GREEN);
585 }
586
587 #[test]
588 fn test_buffer_dirty_tracking() {
589 let mut buf = CellBuffer::new(10, 5);
590 assert_eq!(buf.dirty_count(), 0);
591
592 buf.mark_dirty(0, 0);
593 assert_eq!(buf.dirty_count(), 1);
594
595 buf.mark_all_dirty();
596 assert_eq!(buf.dirty_count(), 50);
597
598 buf.clear_dirty();
599 assert_eq!(buf.dirty_count(), 0);
600 }
601
602 #[test]
603 fn test_buffer_iter_dirty() {
604 let mut buf = CellBuffer::new(10, 5);
605 buf.mark_dirty(1, 1);
606 buf.mark_dirty(3, 3);
607
608 let dirty: Vec<usize> = buf.iter_dirty().collect();
609 assert_eq!(dirty.len(), 2);
610 assert!(dirty.contains(&buf.index(1, 1)));
611 assert!(dirty.contains(&buf.index(3, 3)));
612 }
613
614 #[test]
615 fn test_buffer_resize() {
616 let mut buf = CellBuffer::new(10, 5);
617 buf.update(0, 0, "X", Color::RED, Color::BLACK, Modifiers::NONE);
618
619 buf.resize(20, 10);
620 assert_eq!(buf.width(), 20);
621 assert_eq!(buf.height(), 10);
622 assert_eq!(buf.len(), 200);
623 assert_eq!(buf.get(0, 0).unwrap().symbol.as_str(), " ");
625 assert_eq!(buf.dirty_count(), 200);
627 }
628
629 #[test]
630 fn test_buffer_clear() {
631 let mut buf = CellBuffer::new(10, 5);
632 buf.update(0, 0, "X", Color::RED, Color::BLACK, Modifiers::BOLD);
633 buf.clear_dirty();
634
635 buf.clear();
636 let cell = buf.get(0, 0).unwrap();
637 assert_eq!(cell.symbol.as_str(), " ");
638 assert!(cell.modifiers.is_empty());
639 assert_eq!(buf.dirty_count(), 50);
640 }
641
642 #[test]
643 fn test_buffer_fill_rect() {
644 let mut buf = CellBuffer::new(10, 10);
645 buf.fill_rect(2, 2, 3, 3, Color::WHITE, Color::RED);
646
647 assert_eq!(buf.get(3, 3).unwrap().bg, Color::RED);
649 assert_eq!(buf.get(0, 0).unwrap().bg, Color::TRANSPARENT);
651 }
652
653 #[test]
654 fn test_buffer_fill_rect_clipped() {
655 let mut buf = CellBuffer::new(10, 10);
656 buf.fill_rect(8, 8, 5, 5, Color::WHITE, Color::BLUE);
657
658 assert_eq!(buf.get(9, 9).unwrap().bg, Color::BLUE);
660 }
661
662 #[test]
663 fn test_buffer_cells_access() {
664 let mut buf = CellBuffer::new(10, 5);
665 assert_eq!(buf.cells().len(), 50);
666 assert_eq!(buf.cells_mut().len(), 50);
667 }
668
669 #[test]
670 fn test_cell_empty_string() {
671 let cell = Cell::new("", Color::WHITE, Color::BLACK, Modifiers::NONE);
672 assert_eq!(cell.width(), 1);
674 }
675
676 #[test]
677 fn test_modifiers_all_flags() {
678 let all = Modifiers::BOLD
679 | Modifiers::ITALIC
680 | Modifiers::UNDERLINE
681 | Modifiers::STRIKETHROUGH
682 | Modifiers::DIM
683 | Modifiers::BLINK
684 | Modifiers::REVERSE
685 | Modifiers::HIDDEN;
686
687 assert!(all.contains(Modifiers::BOLD));
688 assert!(all.contains(Modifiers::ITALIC));
689 assert!(all.contains(Modifiers::UNDERLINE));
690 assert!(all.contains(Modifiers::STRIKETHROUGH));
691 assert!(all.contains(Modifiers::DIM));
692 assert!(all.contains(Modifiers::BLINK));
693 assert!(all.contains(Modifiers::REVERSE));
694 assert!(all.contains(Modifiers::HIDDEN));
695 }
696}