From 14aedaf25212cd50924566821ad37645a4cacf28 Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 13 Jul 2022 10:38:26 +0200 Subject: [PATCH] Add stack of drawable areas This lets the user restrict the drawable area to a sub-area of the buffer. This lets the user draw without caring about the absolute position, and guarantees that no glyphs or glyph parts appear outside of the drawable area. --- src/buffer.rs | 108 +++++++++++++++++++++++++++++++++++++++++++++----- src/frame.rs | 8 ++++ 2 files changed, 105 insertions(+), 11 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 7031f74..ef4fe4f 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Neg, Range, Sub, SubAssign}; use crossterm::style::ContentStyle; @@ -64,6 +64,12 @@ impl Pos { } } +impl From for Pos { + fn from(s: Size) -> Self { + Self::new(s.width.into(), s.height.into()) + } +} + impl Add for Pos { type Output = Self; @@ -72,6 +78,14 @@ impl Add for Pos { } } +impl Add for Pos { + type Output = Self; + + fn add(self, rhs: Size) -> Self { + Self::new(self.x + rhs.width as i32, self.y + rhs.height as i32) + } +} + impl AddAssign for Pos { fn add_assign(&mut self, rhs: Self) { self.x += rhs.x; @@ -79,6 +93,13 @@ impl AddAssign for Pos { } } +impl AddAssign for Pos { + fn add_assign(&mut self, rhs: Size) { + self.x += rhs.width as i32; + self.y += rhs.height as i32; + } +} + impl Sub for Pos { type Output = Self; @@ -87,6 +108,14 @@ impl Sub for Pos { } } +impl Sub for Pos { + type Output = Self; + + fn sub(self, rhs: Size) -> Self { + Self::new(self.x - rhs.width as i32, self.y - rhs.height as i32) + } +} + impl SubAssign for Pos { fn sub_assign(&mut self, rhs: Self) { self.x -= rhs.x; @@ -94,6 +123,13 @@ impl SubAssign for Pos { } } +impl SubAssign for Pos { + fn sub_assign(&mut self, rhs: Size) { + self.x -= rhs.width as i32; + self.y -= rhs.height as i32; + } +} + impl Neg for Pos { type Output = Self; @@ -125,9 +161,17 @@ impl Default for Cell { pub struct Buffer { size: Size, data: Vec, + /// A stack of rectangular drawing areas. + /// + /// When rendering to the buffer with a nonempty stack, it behaves as if it + /// was the size of the topmost stack element, and characters are translated + /// by the position of the topmost stack element. No characters can be + /// placed outside the area described by the topmost stack element. + stack: Vec<(Pos, Size)>, } impl Buffer { + /// Ignores the stack. fn index(&self, x: u16, y: u16) -> usize { assert!(x < self.size.width); assert!(y < self.size.height); @@ -139,6 +183,7 @@ impl Buffer { y * width + x } + /// Ignores the stack. pub(crate) fn at(&self, x: u16, y: u16) -> &Cell { assert!(x < self.size.width); assert!(y < self.size.height); @@ -146,6 +191,7 @@ impl Buffer { &self.data[i] } + /// Ignores the stack. fn at_mut(&mut self, x: u16, y: u16) -> &mut Cell { assert!(x < self.size.width); assert!(y < self.size.height); @@ -153,16 +199,41 @@ impl Buffer { &mut self.data[i] } + pub fn push(&mut self, pos: Pos, size: Size) { + let cur_pos = self.stack.last().map(|(p, _)| *p).unwrap_or(Pos::ZERO); + self.stack.push((cur_pos + pos, size)); + } + + pub fn pop(&mut self) { + self.stack.pop(); + } + + fn drawable_area(&self) -> (Pos, Size) { + self.stack.last().copied().unwrap_or((Pos::ZERO, self.size)) + } + + /// Size of the currently drawable area. pub fn size(&self) -> Size { - self.size + self.drawable_area().1 + } + + /// Min (inclusive) and max (not inclusive) coordinates of the currently + /// drawable area. + fn legal_ranges(&self) -> (Range, Range) { + let (top_left, size) = self.drawable_area(); + let min_x = top_left.x.max(0); + let min_y = top_left.y.max(0); + let max_x = (top_left.x + size.width as i32).min(self.size.width as i32); + let max_y = (top_left.y + size.height as i32).min(self.size.height as i32); + (min_x..max_x, min_y..max_y) } /// Resize the buffer and reset its contents. /// /// The buffer's contents are reset even if the buffer is already the - /// correct size. + /// correct size. The stack is reset as well. pub fn resize(&mut self, size: Size) { - if size == self.size() { + if size == self.size { self.data.fill_with(Cell::default); } else { let width: usize = size.width.into(); @@ -173,13 +244,16 @@ impl Buffer { self.data.clear(); self.data.resize_with(len, Cell::default); } + + self.stack.clear(); } - /// Reset the contents of the buffer. + /// Reset the contents and stack of the buffer. /// /// `buf.reset()` is equivalent to `buf.resize(buf.size())`. pub fn reset(&mut self) { self.data.fill_with(Cell::default); + self.stack.clear(); } /// Remove the grapheme at the specified coordinates from the buffer. @@ -187,6 +261,8 @@ impl Buffer { /// Removes the entire grapheme, not just the cell at the coordinates. /// Preserves the style of the affected cells. Works even if the coordinates /// don't point to the beginning of the grapheme. + /// + /// Ignores the stack. fn erase(&mut self, x: u16, y: u16) { let cell = self.at(x, y); let width: u16 = cell.width.into(); @@ -200,8 +276,10 @@ impl Buffer { } pub fn write(&mut self, widthdb: &mut WidthDB, tab_width: u8, pos: Pos, styled: &Styled) { + let pos = self.drawable_area().0 + pos; + let (xrange, yrange) = self.legal_ranges(); // If we're not even visible, there's nothing to do - if pos.y < 0 || pos.y >= self.size.height as i32 { + if !yrange.contains(&pos.y) { return; } let y = pos.y as u16; @@ -215,22 +293,30 @@ impl Buffer { let width = wrap::tab_width_at_column(tab_width, col); col += width as usize; for dx in 0..width { - self.write_grapheme(x + dx as i32, y, width, " ", style); + self.write_grapheme(&xrange, x + dx as i32, y, width, " ", style); } } else { let width = widthdb.grapheme_width(g); col += width as usize; if width > 0 { - self.write_grapheme(x, y, width, g, style); + self.write_grapheme(&xrange, x, y, width, g, style); } } } } /// Assumes that `pos.y` is in range. - fn write_grapheme(&mut self, x: i32, y: u16, width: u8, grapheme: &str, style: ContentStyle) { - let min_x = 0; - let max_x = self.size.width as i32 - 1; // Last possible cell + fn write_grapheme( + &mut self, + xrange: &Range, + x: i32, + y: u16, + width: u8, + grapheme: &str, + style: ContentStyle, + ) { + let min_x = xrange.start; + let max_x = xrange.end - 1; // Last possible cell let start_x = x; let end_x = x + width as i32 - 1; // Coordinate of last cell diff --git a/src/frame.rs b/src/frame.rs index bec8cb6..75fc091 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -26,6 +26,14 @@ impl Default for Frame { } impl Frame { + pub fn push(&mut self, pos: Pos, size: Size) { + self.buffer.push(pos, size); + } + + pub fn pop(&mut self) { + self.buffer.pop(); + } + pub fn size(&self) -> Size { self.buffer.size() }