~hime/protochat

9596ed28fc9bf6e5c9b55dbf2a90bd8b1daf1b87 — drbawb 11 months ago 76aa9e6
change buffer scrolling to be line oriented

Vastly simplify the buffer handling. The page approach had many problems
when scrolling a page and then making the terminal wider (i.e: reducing
the number of span lines.)

This approach simply tracks the current "buffer line" the user wishes
to view and always keeps that at the bottom of the viewport; then it
fills in the rest of the viewport with as many spans as possible.

This has the added advantage of drawing exactly as many spans as is
necessary to fill the viewport; as opposed to the previous approach
which required drawing two full screens worth of spans on every repaint.
1 files changed, 42 insertions(+), 84 deletions(-)

M linetest/src/shell/ui/buffer.rs
M linetest/src/shell/ui/buffer.rs => linetest/src/shell/ui/buffer.rs +42 -84
@@ 10,17 10,26 @@ use std::io;
use super::{COLOR_TITLE_BG, COLOR_TITLE_FG};
use textwrap;


pub fn span(line: &str, cols: u16) -> Vec<String> {
    let wrapped_str = textwrap::fill(line, cols as usize);
    let lines = wrapped_str
        .lines()
        .map(|line| { line.to_owned() })
        .collect::<Vec<_>>();
    
    lines
struct Span {
    lines: Vec<String>,
}

impl Span {
    pub fn new(buf: &str, cols: u16) -> Self {
        let wrapped_str = textwrap::fill(buf, cols as usize);

        let lines = wrapped_str
            .lines()
            .map(|line| { line.to_owned() })
            .collect::<Vec<_>>();

        Self { lines: lines }
    }

    pub fn line_iter(&self) -> impl Iterator<Item = &String> {
        self.lines.iter().rev()
    }
}
/// The `Buffer` stores a list of lines which need to be displayed 
/// in a fixed-width context. Lines can be added to the buffer and
/// then displayed at any arbitrary resolution later.


@@ 29,11 38,8 @@ pub struct Buffer {
    rows: u16,

    lines: Vec<String>,
    
    page_idx: usize,
    curr_page: Vec<String>,
    next_page: Vec<String>,
    offset_lines: usize,
    viewport_spans: Vec<Span>,
    current_line_ptr: usize,

    needs_repaint: bool,
}


@@ 45,12 51,8 @@ impl Buffer {
            rows: rows,

            lines: vec![],


            curr_page: vec![],
            next_page: vec![],
            page_idx: 0,
            offset_lines: 0,
            viewport_spans: vec![],
            current_line_ptr: 0,

            needs_repaint: true,
        }


@@ 67,47 69,18 @@ impl Buffer {
        self.needs_repaint = true;
    }

    pub fn draw_page_n(&mut self, page_num: usize) -> Vec<String> {
        self.lines
            .iter()
            .rev()
            .flat_map(|line| { span(line, self.cols) })
            .skip(self.rows as usize * page_num)
            .take(self.rows as usize)
            .collect::<Vec<_>>()
    }

    pub fn scroll_up(&mut self) {
        let offset_len = 3;
        let new_offset = cmp::min(self.next_page.len(), self.offset_lines + offset_len);

        if new_offset == self.rows as usize {
            self.page_idx += 1;
            self.offset_lines = 0;
            self.needs_repaint = true;
        } else if new_offset > self.rows as usize && self.next_page.len() > 0 {
            // go to next page
            self.page_idx += 1;
            self.offset_lines = 0;
            self.needs_repaint = true;
        } else if new_offset > self.offset_lines {
            self.offset_lines = new_offset;
        if (self.current_line_ptr + 1) <= self.lines.len() {
            self.current_line_ptr += 1;
            self.needs_repaint = true;
        }
    }

    pub fn scroll_down(&mut self) {
        let offset_len = std::cmp::min(3, self.offset_lines);

        if offset_len >= self.offset_lines && self.page_idx > 0 {
            self.page_idx -= 1;
            self.offset_lines = self.rows as usize - offset_len;
            self.needs_repaint = true;
        } else if offset_len > 0 {
            self.offset_lines -= offset_len;
        if self.current_line_ptr > 0 {
            self.current_line_ptr -= 1;
            self.needs_repaint = true;
        }

    }

    /// The caller *must* set the cursor to the row where this buffer should be rendered.


@@ 117,48 90,33 @@ impl Buffer {
        if !self.needs_repaint { return Ok(()); }
        self.needs_repaint = false;

        // draw the current neighboring pages
        self.curr_page = self.draw_page_n(self.page_idx);
        self.next_page = self.draw_page_n(self.page_idx + 1);

        // move to bottom-most row.
        stdout
        .queue(cursor::MoveDown(self.rows - 1))?
        .queue(cursor::MoveToColumn(0))?;

        let mut rows_drawn = self.rows;

        // draw (rows - offset) lines from front buffer
        for line in self.curr_page.iter().skip(self.offset_lines) {
            stdout
            .queue(Clear(ClearType::CurrentLine))?
            .queue(Print(line))?
            .queue(cursor::MoveUp(1))?
            .queue(cursor::MoveToColumn(0))?;
        // resize the spans starting from current line
        self.viewport_spans = self.lines
        .iter()
        .rev()
        .skip(self.current_line_ptr)
        .map(|line| Span::new(line, self.cols))
        .collect::<Vec<_>>();

            rows_drawn -= 1;
        }
        let viewport_lines = self.viewport_spans
            .iter()
            .flat_map(|span| span.line_iter())
            .take(self.rows as usize);

        // draw remaining (offset) lines from back buffer
        for line in self.next_page.iter().take(self.offset_lines) {
        for line in viewport_lines {
            stdout
            .queue(Clear(ClearType::CurrentLine))?
            .queue(Print(line))?
            .queue(cursor::MoveUp(1))?
            .queue(cursor::MoveToColumn(0))?;

            rows_drawn -= 1;
                .queue(Clear(ClearType::CurrentLine))?
                .queue(Print(line))?
                .queue(cursor::MoveUp(1))?
                .queue(cursor::MoveToColumn(0))?;
        }

        // clear rest of buffer ...
        while rows_drawn > 0 {
            stdout
            .queue(Clear(ClearType::CurrentLine))?
            .queue(cursor::MoveUp(1))?
            .queue(cursor::MoveToColumn(0))?;

            rows_drawn -= 1;
        }

        // while rows_left > 0 {
        //     // grab the next buffer line