rust_pixel 2.4.0

2d pixel-art game engine & rapid prototype tools support terminal, wgpu and web...
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
// RustPixel
// copyright zipxing@hotmail.com 2022~2026

//! Buffer is a basic rendering unit in RustPixel, represents a rectangle area.
//! A buffer comprises a cell vector with width * height elements.
//! A cell stores key data such as symbol, fg, bg.
//!
//! Almost all Unicode chars can be drawn in text mode, depending on the terminal apps
//! (use of iTerm2 in macOS is recommended). For example:
//! ```rust,ignore
//! my_buffer.set_str(0, 0, "Hello world 😃.",
//!     Style::default().fg(Color::Red).bg(Color::Reset))
//! ```
//!
//! Beware of the display width and height of Unicode chars.
//! Display width is a bit tricky, very much relying on the terminal apps (currently the development
//! work uses iTerm2 in macOS). Moreover, bold and italics fonts are also supported in text mode.
//!
//! In graphics mode,
//! 256 unicode chars mark the index of a symbol in a texture block
//! unicode: 0xE000 ~ 0xE0FF (Private Use Area)
//! maps to a 3 byte UTF8: 11101110 100000xx 10xxxxxx
//! an 8-digits index gets from the UTF8 code is used to mark the offset in its block
//!
//! Using Private Use Area avoids conflicts with standard Unicode characters,
//! allowing TUI mode to display mathematical symbols and other special characters.
//!
//! # Block System
//!
//! The tex field indicates the texture block index (0-255) in the 4096x4096 unified texture:
//! - **Block 0-159**: Sprite region (160 blocks, 256×256px each, 16×16 chars per block)
//! - **Block 160-169**: TUI region (10 blocks, 256×512px each, 16×16 chars per block)
//! - **Block 170-175**: Emoji region (6 blocks, 256×512px each, 8×16 emojis per block)
//! - **Block 176-239**: CJK region (64 blocks, 256×256px each, 8×8 chars per block)
//! - **Block 240-255**: Reserved for future use
//!
//! See `render::symbol_map` module for detailed block layout and symbol mapping.
//!
//! # Example
//! ```ignore
//! // Set a character using block 0 (Sprite region)
//! my_buffer.set_str_tex(0, 0, cellsym(0), Style::default().fg(Color::Red), 0);
//!
//! // For common ASCII characters, use the default block (automatically mapped)
//! my_buffer.set_str(0, 0, "Hello world.", Style::default().fg(Color::Red));
//! ```
//!
//! Note: When using symbol_map lookups (Emoji, TUI, CJK), the block index is automatically
//! determined by `get_cell_info()` based on the character's region.
//!
#[allow(unused_imports)]
use crate::{
    render::cell::{cellsym, cellsym_block, is_prerendered_emoji, Cell},
    render::style::{Color, Style},
    render::symbol_map::ascii_to_petscii,
    util::{Rect, PointU16},
    util::shape::{circle, line, prepare_line},
};
use bitflags::bitflags;
use log::info;
use serde::{Deserialize, Serialize};
use std::cmp::min;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

/// Common line-drawing symbols (in text mode)
pub const SYMBOL_LINE: [&str; 37] = [
    "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
    "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
];

// border's bitflags
bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
    pub struct Borders: u32 {
        const NONE   = 0b0000_0001;
        const TOP    = 0b0000_0010;
        const RIGHT  = 0b0000_0100;
        const BOTTOM = 0b0000_1000;
        const LEFT   = 0b0001_0000;
        const ALL    = Self::TOP.bits() | Self::RIGHT.bits() | Self::BOTTOM.bits() | Self::LEFT.bits();
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BorderType {
    Plain,
    Rounded,
    Double,
    Thick,
}

/// Buffer mode: TUI uses standard Unicode, Sprite uses PUA encoding.
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub enum BufferMode {
    /// TUI mode: symbols are standard Unicode (ASCII, Box Drawing, Emoji, CJK)
    #[default]
    Tui,
    /// Sprite mode: symbols must be PUA encoded (U+E000-U+E3FF)
    Sprite,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Buffer {
    pub area: Rect,
    pub content: Vec<Cell>,
    /// Buffer rendering mode.
    /// - Tui: symbols are standard Unicode, rendered with TUI texture
    /// - Sprite: symbols are PUA encoded, rendered with Sprite texture
    #[serde(default)]
    pub mode: BufferMode,
}

impl Default for Buffer {
    fn default() -> Self {
        Buffer {
            area: Rect::default(),
            content: Vec::new(),
            mode: BufferMode::Tui,
        }
    }
}

impl Buffer {
    /// Create an empty buffer in TUI mode (default).
    pub fn empty(area: Rect) -> Buffer {
        let cell: Cell = Default::default();
        Buffer::filled(area, &cell)
    }

    /// Create an empty buffer in Sprite mode.
    pub fn empty_sprite(area: Rect) -> Buffer {
        let cell: Cell = Default::default();
        Buffer::filled_with_mode(area, &cell, BufferMode::Sprite)
    }

    /// Create a filled buffer in TUI mode (default).
    pub fn filled(area: Rect, cell: &Cell) -> Buffer {
        Buffer::filled_with_mode(area, cell, BufferMode::Tui)
    }

    /// Create a filled buffer with specified mode.
    pub fn filled_with_mode(area: Rect, cell: &Cell, mode: BufferMode) -> Buffer {
        let size = area.area() as usize;
        let mut content = Vec::with_capacity(size);
        for _ in 0..size {
            content.push(cell.clone());
        }
        Buffer { area, content, mode }
    }

    /// Check if this buffer is in TUI mode.
    pub fn is_tui(&self) -> bool {
        self.mode == BufferMode::Tui
    }

    /// Check if this buffer is in Sprite mode.
    pub fn is_sprite(&self) -> bool {
        self.mode == BufferMode::Sprite
    }

    /// Set the buffer mode.
    pub fn set_mode(&mut self, mode: BufferMode) {
        self.mode = mode;
    }

    pub fn with_lines<S>(lines: Vec<S>) -> Buffer
    where
        S: AsRef<str>,
    {
        let height = lines.len() as u16;
        let width = lines
            .iter()
            .map(|i| i.as_ref().width() as u16)
            .max()
            .unwrap_or_default();
        let mut buffer = Buffer::empty(Rect {
            x: 0,
            y: 0,
            width,
            height,
        });
        for (y, line) in lines.iter().enumerate() {
            buffer.set_string(0, y as u16, line, Style::default());
        }
        buffer
    }

    pub fn content(&self) -> &[Cell] {
        &self.content
    }

    ///// Convert buffer to RGBA image data for OpenGL shader texture.
    /////
    ///// Each cell is encoded as 4 bytes:
    ///// - Byte 0: symbol_index (0-255, index within block)
    ///// - Byte 1: block_index (0-255, texture block)
    ///// - Byte 2: foreground color
    ///// - Byte 3: background color
    /////
    ///// Used by graphics adapters to pass buffer data to GPU shaders.
    //pub fn get_rgba_image(&self) -> Vec<u8> {
    //    let mut dat = vec![];
    //    for c in &self.content {
    //        // Get (symbol_index, block_index, fg, bg, modifier)
    //        let ci = c.get_cell_info();
    //        dat.push(ci.0); // symbol_index
    //        dat.push(ci.1); // block_index
    //        dat.push(u8::from(ci.2)); // fg
    //        dat.push(u8::from(ci.3)); // bg
    //    }
    //    dat
    //}

    ///// Convert RGBA image data back to buffer (from OpenGL shader output).
    /////
    ///// Each cell is decoded from 4 bytes:
    ///// - Byte 0: symbol_index
    ///// - Byte 1: block_index
    ///// - Byte 2: foreground color
    ///// - Byte 3: background color
    /////
    ///// Symbol is set using PUA encoding: cellsym_block(block, idx)
    //pub fn set_rgba_image(&mut self, dat: &[u8], w: u16, h: u16) {
    //    use crate::render::cell::cellsym_block;
    //    let mut idx = 0;
    //    for i in 0..h {
    //        for j in 0..w {
    //            let sym_idx = dat[idx];
    //            let block = dat[idx + 1];
    //            self.content[(i * w + j) as usize]
    //                .set_symbol(&cellsym_block(block, sym_idx))
    //                .set_fg(Color::Indexed(dat[idx + 2]))
    //                .set_bg(Color::Indexed(dat[idx + 3]));
    //            idx += 4;
    //        }
    //    }
    //}

    pub fn area(&self) -> &Rect {
        &self.area
    }

    pub fn get(&self, x: u16, y: u16) -> &Cell {
        let i = self.index_of(x, y);
        &self.content[i]
    }

    pub fn get_mut(&mut self, x: u16, y: u16) -> &mut Cell {
        let i = self.index_of(x, y);
        &mut self.content[i]
    }

    //global offset
    pub fn index_of(&self, x: u16, y: u16) -> usize {
        debug_assert!(
            x >= self.area.left()
                && x < self.area.right()
                && y >= self.area.top()
                && y < self.area.bottom(),
            "Trying to access position outside the buffer: x={}, y={}, area={:?}",
            x,
            y,
            self.area
        );
        ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
    }

    pub fn pos_of(&self, i: usize) -> (u16, u16) {
        debug_assert!(
            i < self.content.len(),
            "Trying to get the coords of a cell outside the buffer: i={} len={}",
            i,
            self.content.len()
        );
        (
            self.area.x + i as u16 % self.area.width,
            self.area.y + i as u16 / self.area.width,
        )
    }

    //relative pos in game sprite, easier to set content
    pub fn dstr<S>(&mut self, string: S)
    where
        S: AsRef<str>,
    {
        self.set_str(0, 0, string, Style::default());
    }

    /// Set string at relative position (relative to buffer's area).
    ///
    /// For graphics mode (Sprite buffer), pass PUA-encoded symbols via cellsym/cellsym_block.
    /// For TUI mode, pass standard Unicode strings.
    pub fn set_str<S>(&mut self, x: u16, y: u16, string: S, style: Style)
    where
        S: AsRef<str>,
    {
        self.set_stringn(
            x + self.area.x,
            y + self.area.y,
            string,
            style,
        );
    }

    /// Set string at absolute position (global screen coordinates).
    pub fn set_string<S>(&mut self, x: u16, y: u16, string: S, style: Style)
    where
        S: AsRef<str>,
    {
        self.set_stringn(x, y, string, style);
    }

    /// Core method to set string content.
    ///
    /// Simply sets the symbol and style for each character. No tex/block conversion.
    /// For graphics mode, caller should use cellsym_block(block, idx) to construct
    /// the correct PUA-encoded symbol.
    ///
    /// # Arguments
    ///
    /// * `x`, `y` - Absolute coordinates
    /// * `string` - Text to render (Unicode or PUA-encoded)
    /// * `width` - Maximum width in characters (usize::MAX for no limit)
    /// * `style` - Text style (colors, modifiers)
    ///
    /// # Returns
    ///
    /// Final (x, y) position after rendering the string.
    pub fn set_stringn<S>(
        &mut self,
        x: u16,
        y: u16,
        string: S,
        // width: usize,  // legacy tui-rs param, callers always pass usize::MAX, never effective
        style: Style,
    )
    where
        S: AsRef<str>,
    {
        // Bounds check: skip if starting position is outside buffer
        if x < self.area.left() || x >= self.area.right()
            || y < self.area.top() || y >= self.area.bottom() {
            return;
        }

        let mut index = self.index_of(x, y);
        let mut x_offset = x as usize;
        let graphemes = UnicodeSegmentation::graphemes(string.as_ref(), true);
        let max_offset = self.area.right() as usize;
        // let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize));
        for s in graphemes {
            let width = s.width();
            if width == 0 {
                continue;
            }
            if width > max_offset.saturating_sub(x_offset) {
                break;
            }

            let is_emoji = is_prerendered_emoji(s);

            // 直接设置 symbol,不做任何转换
            // - TUI模式:传入 Unicode
            // - Sprite模式:调用方应传入 PUA 编码的 symbol (通过 cellsym_block)
            self.content[index].set_symbol(s);
            self.content[index].set_style(style);

            // emoji 强制占2格,CJK等宽字符由 unicode-width 正确返回2
            let effective_width = if is_emoji { 2 } else { width };

            // 多宽字符后续 cell 清空
            for i in index + 1..index + effective_width {
                if i < self.content.len() {
                    self.content[i].reset();
                }
            }

            index += effective_width;
            x_offset += effective_width;
        }
        // return (x_offset as u16, y)  // legacy: no caller uses the return value
    }

    pub fn set_style(&mut self, area: Rect, style: Style) {
        for y in area.top()..area.bottom() {
            for x in area.left()..area.right() {
                self.get_mut(x, y).set_style(style);
            }
        }
    }

    pub fn resize(&mut self, area: Rect) {
        let length = area.area() as usize;
        if self.content.len() > length {
            self.content.truncate(length);
        } else {
            self.content.resize(length, Default::default());
        }
        self.area = area;
    }

    pub fn reset(&mut self) {
        for c in &mut self.content {
            c.reset();
        }
    }

    /// Clear a specific rectangular area in the buffer
    /// More efficient than calling reset() on individual cells
    pub fn clear_area(&mut self, area: Rect) {
        let x_start = area.x.max(self.area.x);
        let y_start = area.y.max(self.area.y);
        let x_end = (area.x + area.width).min(self.area.x + self.area.width);
        let y_end = (area.y + area.height).min(self.area.y + self.area.height);

        for y in y_start..y_end {
            for x in x_start..x_end {
                let idx = self.index_of(x, y);
                self.content[idx].reset();
            }
        }
    }

    pub fn set_fg(&mut self, color: Color) {
        for c in &mut self.content {
            c.set_fg(color);
        }
    }

    // ========== Content-drawing convenience methods ==========

    /// Set string content at (x,y) with fg/bg color.
    /// Coordinates are relative to buffer's area.
    pub fn set_color_str<S>(&mut self, x: u16, y: u16, string: S, fg: Color, bg: Color)
    where
        S: AsRef<str>,
    {
        self.set_str(x, y, string, Style::default().fg(fg).bg(bg));
    }

    /// Set PETSCII-style string at (x,y) with fg/bg color.
    ///
    /// Converts ASCII characters to PETSCII symbols by looking up sprite extras.
    /// Use this for rendering text with retro C64-style font in graphics mode.
    ///
    /// # Example
    /// ```ignore
    /// buf.set_petscii_str(0, 0, "HELLO WORLD", Color::White, Color::Reset);
    /// ```
    pub fn set_petscii_str<S>(&mut self, x: u16, y: u16, string: S, fg: Color, bg: Color)
    where
        S: AsRef<str>,
    {
        self.set_str(x, y, ascii_to_petscii(string.as_ref()), Style::default().fg(fg).bg(bg));
    }

    /// Set string content at (0,0) with default style.
    pub fn set_default_str<S>(&mut self, string: S)
    where
        S: AsRef<str>,
    {
        self.set_str(0, 0, string, Style::default());
    }

    /// Set graphic mode symbol (texture:texture_id, index:sym) at (x,y) with fg color.
    pub fn set_graph_sym(&mut self, x: u16, y: u16, texture_id: u8, sym: u8, fg: Color) {
        self.set_str(
            x,
            y,
            cellsym_block(texture_id, sym),
            Style::default().fg(fg).bg(Color::Reset),
        );
    }

    // ========== Shape drawing methods ==========

    pub fn draw_circle(
        &mut self,
        x0: u16,
        y0: u16,
        radius: u16,
        sym: &str,
        fg_color: u8,
        bg_color: u8,
    ) {
        for p in circle(x0, y0, radius) {
            if (p.0 as u16) < self.area.width && (p.1 as u16) < self.area.height {
                self.set_str(
                    p.0 as u16,
                    p.1 as u16,
                    sym,
                    Style::default()
                        .fg(Color::Indexed(fg_color))
                        .bg(Color::Indexed(bg_color)),
                );
            }
        }
    }

    pub fn draw_line(
        &mut self,
        p0: PointU16,
        p1: PointU16,
        sym: Option<Vec<Option<u8>>>,
        fg_color: u8,
        bg_color: u8,
    ) {
        let (x0, y0, x1, y1) = prepare_line(p0.x, p0.y, p1.x, p1.y);
        // start, end, v, h, s, bs...
        let mut syms: Vec<Option<u8>> = vec![None, None, Some(119), Some(116), Some(77), Some(78)];
        if let Some(s) = sym {
            syms = s;
        }
        for p in line(x0, y0, x1, y1) {
            let x = p.0 as u16;
            let y = p.1 as u16;
            let sym = syms[p.2 as usize];
            if let Some(s) = sym {
                if x < self.area.width && y < self.area.height {
                    self.set_str(
                        x,
                        y,
                        cellsym_block(bg_color, s),
                        Style::default()
                            .fg(Color::Indexed(fg_color))
                            .bg(Color::Reset),
                    );
                }
            }
        }
    }

    // ========== Border drawing ==========

    /// Set a border symbol at the given position.
    /// x, y are relative coordinates (0-based within buffer).
    /// In Sprite mode: looks up PETSCII table first (C64-style border),
    ///   then falls back to reverse_lookup for the general symbol map.
    /// In TUI mode: uses the Unicode character directly.
    fn set_border_sym(&mut self, x: u16, y: u16, sym: &str, style: Style) {
        use crate::render::symbol_map::get_layered_symbol_map;

        if self.mode == BufferMode::Sprite {
            if let Some(map) = get_layered_symbol_map() {
                // Priority 1: PETSCII table (C64 sprite for box-drawing chars)
                if let Some(pua) = map.petscii_lookup(sym) {
                    let abs_x = x + self.area.x;
                    let abs_y = y + self.area.y;
                    let index = self.index_of(abs_x, abs_y);
                    if index < self.content.len() {
                        self.content[index].set_symbol(pua);
                        self.content[index].set_style(style);
                    }
                    return;
                }
                // Priority 2: general reverse lookup
                if let Some((block, idx)) = map.reverse_lookup(sym) {
                    use crate::render::cell::cellsym_block;
                    let abs_x = x + self.area.x;
                    let abs_y = y + self.area.y;
                    let index = self.index_of(abs_x, abs_y);
                    if index < self.content.len() {
                        self.content[index].set_symbol(&cellsym_block(block, idx));
                        self.content[index].set_style(style);
                    }
                }
            }
        } else {
            // TUI mode: use Unicode directly
            self.set_str(x, y, sym, style);
        }
    }

    pub fn set_border(&mut self, borders: Borders, border_type: BorderType, style: Style) {
        let lineidx: [usize; 11] = match border_type {
            BorderType::Plain => [0, 3, 6, 10, 14, 18, 22, 25, 28, 31, 34],
            BorderType::Rounded => [0, 3, 7, 11, 15, 19, 22, 25, 28, 31, 34],
            BorderType::Double => [1, 4, 8, 12, 16, 20, 23, 26, 29, 33, 35],
            BorderType::Thick => [2, 5, 9, 13, 17, 21, 24, 27, 30, 34, 36],
        };
        if borders.intersects(Borders::LEFT) {
            for y in 0..self.area.height {
                self.set_border_sym(0, y, SYMBOL_LINE[lineidx[0]], style);
            }
        }
        if borders.intersects(Borders::TOP) {
            for x in 0..self.area.width {
                self.set_border_sym(x, 0, SYMBOL_LINE[lineidx[1]], style);
            }
        }
        if borders.intersects(Borders::RIGHT) {
            let x = self.area.width - 1;
            for y in 0..self.area.height {
                self.set_border_sym(x, y, SYMBOL_LINE[lineidx[0]], style);
            }
        }
        if borders.intersects(Borders::BOTTOM) {
            let y = self.area.height - 1;
            for x in 0..self.area.width {
                self.set_border_sym(x, y, SYMBOL_LINE[lineidx[1]], style);
            }
        }
        if borders.contains(Borders::RIGHT | Borders::BOTTOM) {
            self.set_border_sym(
                self.area.width - 1,
                self.area.height - 1,
                SYMBOL_LINE[lineidx[4]],
                style,
            );
        }
        if borders.contains(Borders::RIGHT | Borders::TOP) {
            self.set_border_sym(
                self.area.width - 1,
                0,
                SYMBOL_LINE[lineidx[2]],
                style,
            );
        }
        if borders.contains(Borders::LEFT | Borders::BOTTOM) {
            self.set_border_sym(
                0,
                self.area.height - 1,
                SYMBOL_LINE[lineidx[5]],
                style,
            );
        }
        if borders.contains(Borders::LEFT | Borders::TOP) {
            self.set_border_sym(0, 0, SYMBOL_LINE[lineidx[3]], style);
        }
    }

    #[allow(unused_variables)]
    pub fn copy_cell(&mut self, pos_self: usize, other: &Buffer, alpha: u8, pos_other: usize) {
        // self.content[pos_self].symbol = other.content[pos_other].symbol.clone();
        // self.content[pos_self].bg = other.content[pos_other].bg;
        self.content[pos_self] = other.content[pos_other].clone();
        #[cfg(graphics_mode)]
        {
            let fc = other.content[pos_other].fg.get_rgba();
            if other.content[pos_other].bg != Color::Reset {
                let bc = other.content[pos_other].bg.get_rgba();
                self.content[pos_self].bg = Color::Rgba(bc.0, bc.1, bc.2, alpha);
            }
            self.content[pos_self].fg = Color::Rgba(fc.0, fc.1, fc.2, alpha);
        }
    }

    pub fn blit(
        &mut self,
        dstx: u16,
        dsty: u16,
        other: &Buffer,
        other_part: Rect,
        alpha: u8,
    ) -> Result<(u16, u16), String> {
        //make sure dstx and dsty are correct
        if dstx >= self.area.width || dsty >= self.area.height {
            return Err(String::from("buffer blit:dstx, dsty too large"));
        }
        //make sure other_part is correct
        let oa = Rect::new(0, 0, other.area.width, other.area.height);
        if !other_part.intersects(oa) {
            info!(
                "buffer blit:error oa = {:?} other_part = {:?}",
                oa, other_part
            );
            return Err(String::from("buffer blit:error other_part"));
        }
        let bw = min(other_part.width, self.area.width - dstx);
        let bh = min(other_part.height, self.area.height - dsty);
        // info!("blit....(bw={} bh={})", bw, bh);

        for i in 0..bh {
            for j in 0..bw {
                let pos_self = (self.area.width * (dsty + i) + dstx + j) as usize;
                let pos_other =
                    // (other.area.width * other_part.y + other_part.x + i * bw + j) as usize;
                    (other.area.width * other_part.y + other_part.x + i * other.area.width + j) as usize;
                // info!("blit...ps{:?} po{:?}", pos_self, pos_other);
                self.copy_cell(pos_self, other, alpha, pos_other);
            }
        }

        Ok((bw, bh))
    }

    pub fn merge(&mut self, other: &Buffer, alpha: u8, fast: bool) {
        let area = self.area.union(other.area);
        let cell: Cell = Default::default();
        self.content.resize(area.area() as usize, cell.clone());
        if !fast {
            let size = self.area.area() as usize;
            for i in (0..size).rev() {
                let (x, y) = self.pos_of(i);
                // New index in content
                let k = ((y - area.y) * area.width + x - area.x) as usize;
                if i != k {
                    self.content[k] = self.content[i].clone();
                    self.content[i] = cell.clone();
                }
            }
        }
        let size = other.area.area() as usize;
        for i in 0..size {
            let (x, y) = other.pos_of(i);
            let k = ((y - area.y) * area.width + x - area.x) as usize;
            // add transparent support...
            if !other.content[i].is_blank() {
                self.copy_cell(k, other, alpha, i);
            }
        }
        self.area = area;
    }

    /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from
    /// self to other.
    pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> {
        let previous_buffer = &self.content;
        let next_buffer = &other.content;
        let width = self.area.width;

        let mut updates: Vec<(u16, u16, &Cell)> = vec![];
        // Cells invalidated by drawing/replacing preceeding multi-width characters:
        let mut invalidated: usize = 0;
        // Cells from the current buffer to skip due to preceeding multi-width characters taking their
        // place (the skipped cells should be blank anyway):
        let mut to_skip: usize = 0;
        for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() {
            if (current != previous || invalidated > 0) && to_skip == 0 {
                let x = i as u16 % width;
                let y = i as u16 / width;
                updates.push((x, y, &next_buffer[i]));
            }

            to_skip = current.symbol.width().saturating_sub(1);

            let affected_width = std::cmp::max(current.symbol.width(), previous.symbol.width());
            invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1);
        }
        updates
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // fn cell(s: &str) -> Cell {
    //     let mut cell = Cell::default();
    //     cell.set_symbol(s);
    //     cell
    // }

    #[test]
    fn it_translates_to_and_from_coordinates() {
        let rect = Rect::new(200, 100, 50, 80);
        let buf = Buffer::empty(rect);

        // First cell is at the upper left corner.
        assert_eq!(buf.pos_of(0), (200, 100));
        assert_eq!(buf.index_of(200, 100), 0);

        // Last cell is in the lower right.
        assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179));
        assert_eq!(buf.index_of(249, 179), buf.content.len() - 1);
    }
}