Skip to main content

uncurses/buffer/
view.rs

1//! Sub-rectangle borrows over any surface.
2//!
3//! A [`View`] is a thin handle that narrows access to a fixed
4//! sub-rect of an existing [`Surface`] / [`SurfaceMut`]. Reads and
5//! writes outside the view's [`Bounded::bounds`] are silently
6//! ignored. Coordinates passed to [`Surface::cell`] /
7//! [`SurfaceMut::set_cell`] are in the *inner* surface's coordinate
8//! space — a view does **not** translate addresses, it just clips
9//! them.
10
11use crate::cell::Cell;
12use crate::layout::{Position, Rect};
13
14use super::{Bounded, Surface, SurfaceMut};
15
16/// Borrowed sub-rect over an existing surface.
17#[derive(Debug)]
18pub struct View<'a, T: ?Sized> {
19    inner: &'a mut T,
20    bounds: Rect,
21}
22
23impl<'a, T: Bounded + ?Sized> View<'a, T> {
24    /// New view bound to the intersection of `bounds` and
25    /// `inner.bounds()`. If they are disjoint, the resulting view is
26    /// empty.
27    pub fn new(inner: &'a mut T, bounds: impl Into<Rect>) -> Self {
28        let bounds = inner.bounds().intersection(bounds.into());
29        Self { inner, bounds }
30    }
31
32    /// Reborrow the inner surface mutably. Useful when the view
33    /// itself is borrowed but the caller needs to escape the clip.
34    pub fn inner_mut(&mut self) -> &mut T {
35        self.inner
36    }
37}
38
39impl<T: Bounded + ?Sized> Bounded for View<'_, T> {
40    fn bounds(&self) -> Rect {
41        self.bounds
42    }
43}
44
45impl<T: Surface + ?Sized> Surface for View<'_, T> {
46    fn cell(&self, pos: Position) -> Option<&Cell> {
47        if self.bounds.contains(pos) {
48            self.inner.cell(pos)
49        } else {
50            None
51        }
52    }
53}
54
55impl<T: SurfaceMut + ?Sized> SurfaceMut for View<'_, T> {
56    fn set_cell(&mut self, pos: Position, cell: &Cell) {
57        if self.bounds.contains(pos) {
58            self.inner.set_cell(pos, cell);
59        }
60    }
61
62    fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell> {
63        if self.bounds.contains(pos) {
64            self.inner.cell_mut(pos)
65        } else {
66            None
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::buffer::Buffer;
75    use crate::cell::Cell;
76
77    #[test]
78    fn view_clips_writes_outside_bounds() {
79        let mut buf = Buffer::new(10, 5);
80        {
81            let mut v = View::new(&mut buf, Rect::new(2, 1, 3, 2));
82            v.set_cell(Position::new(2, 1), &Cell::narrow("A")); // inside
83            v.set_cell(Position::new(0, 0), &Cell::narrow("B")); // outside view
84            v.set_cell(Position::new(5, 1), &Cell::narrow("C")); // right of view
85        }
86        assert_eq!(buf.cell(Position::new(2, 1)).unwrap().content(), "A");
87        assert!(buf.cell(Position::new(0, 0)).unwrap().is_blank());
88        assert!(buf.cell(Position::new(5, 1)).unwrap().is_blank());
89    }
90
91    #[test]
92    fn view_clips_reads_outside_bounds() {
93        let mut buf = Buffer::new(10, 5);
94        buf.set_cell(Position::new(2, 1), &Cell::narrow("A"));
95        buf.set_cell(Position::new(0, 0), &Cell::narrow("B"));
96        let mut buf2 = buf.clone();
97        let v = View::new(&mut buf2, Rect::new(2, 1, 3, 2));
98        assert_eq!(v.cell(Position::new(2, 1)).unwrap().content(), "A");
99        assert!(v.cell(Position::new(0, 0)).is_none());
100    }
101
102    #[test]
103    fn view_clips_to_inner_bounds() {
104        let mut buf = Buffer::new(5, 3);
105        let v = View::new(&mut buf, Rect::new(3, 1, 10, 10));
106        // Clipped down to fit inside the 5x3 buffer.
107        assert_eq!(v.bounds(), Rect::new(3, 1, 2, 2));
108    }
109}