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
//! Double buffering to avoid flickering

use crate::style::{DiffStyle, Style};
use crate::term::{Term, Tile};
use std::io;
use unicode_width::UnicodeWidthChar;

#[derive(Debug, Clone)]
/// Double buffer
pub struct DoubleBuffer {
    front: Term,
    /// A Term for the next
    pub back: Term,
}

impl DoubleBuffer {
    /// Create DoubleBuffer with given height and width
    pub fn new(height: usize, width: usize) -> Self {
        Self {
            front: Term::new(height, width),
            back: Term::new(height, width),
        }
    }

    /// Create DoubleBuffer with current terminal size
    pub fn with_terminal_size() -> io::Result<Self> {
        Ok(Self {
            front: Term::with_terminal_size()?,
            back: Term::with_terminal_size()?,
        })
    }

    /// Draw a back
    pub fn present<T: io::Write>(&mut self, out: &mut T) -> io::Result<()> {
        if (self.front.height(), self.front.width()) != (self.back.height(), self.back.width()) {
            self.back.draw(out)?;
        } else {
            write!(out, "{}", termion::cursor::Hide)?;
            let mut current_style = Style::default();
            write!(out, "{}", current_style)?;

            for (row, (f, b)) in self
                .front
                .buf()
                .iter()
                .zip(self.back.buf().iter())
                .enumerate()
            {
                let mut current_col = None;
                for (col, (f, b)) in f.iter().zip(b.iter()).enumerate() {
                    match b {
                        Tile::Char(ch, style) => {
                            if b != f {
                                let w = ch.width().unwrap_or_default();
                                if current_col != Some(col) {
                                    write!(
                                        out,
                                        "{}",
                                        termion::cursor::Goto(col as u16 + 1, row as u16 + 1)
                                    )?;
                                }
                                write!(
                                    out,
                                    "{}{}",
                                    DiffStyle {
                                        from: current_style,
                                        to: *style
                                    },
                                    ch
                                )?;

                                current_style = *style;
                                current_col = Some(col + w);
                            }
                        }
                        Tile::Empty => {
                            if current_col != Some(col) {
                                write!(
                                    out,
                                    "{}",
                                    termion::cursor::Goto(col as u16 + 1, row as u16 + 1)
                                )?;
                            }
                            write!(
                                out,
                                "{}{}",
                                DiffStyle {
                                    from: current_style,
                                    to: Style::default()
                                },
                                ' '
                            )?;

                            current_style = Style::default();
                            current_col = Some(col + 1);
                        }
                        Tile::Occupied => {}
                    }
                }
            }
        }

        if let Some(cursor) = self.back.cursor {
            write!(
                out,
                "{}{}{}",
                termion::cursor::Goto(cursor.col as u16 + 1, cursor.row as u16 + 1),
                cursor.shape,
                termion::cursor::Show
            )?;
        } else {
            write!(out, "{}", termion::cursor::Hide)?;
        }
        std::mem::swap(&mut self.front, &mut self.back);
        Ok(())
    }
}