Skip to main content

uncurses/buffer/
window.rs

1//! Owned windows — a [`Buffer`] plus the placement metadata needed to
2//! blit it into a larger surface.
3//!
4//! A [`Window`] holds its own cells and knows where on a target it
5//! should land ([`Window::position`]). It is the unit of composition
6//! when an app stitches several independent panes into one frame:
7//! every window writes into its own buffer in window-local
8//! coordinates, then [`Window::present`] copies its cells to a target
9//! at the configured position.
10
11use crate::cell::Cell;
12use crate::layout::{Position, Rect};
13
14use super::{Bounded, Buffer, Surface, SurfaceMut};
15
16/// A self-contained pane: a [`Buffer`] plus its position on a target
17/// surface.
18#[derive(Debug, Clone)]
19pub struct Window {
20    buffer: Buffer,
21    position: Position,
22}
23
24impl Window {
25    /// New `width × height` window at the origin.
26    pub fn new(width: u16, height: u16) -> Self {
27        Self {
28            buffer: Buffer::new(width, height),
29            position: Position::new(0, 0),
30        }
31    }
32
33    /// Where the window's top-left lands on a target surface.
34    pub fn position(&self) -> Position {
35        self.position
36    }
37
38    /// Move the window. Affects subsequent [`Self::present`] calls.
39    pub fn set_position(&mut self, pos: impl Into<Position>) {
40        self.position = pos.into();
41    }
42
43    /// Resize the underlying buffer in place.
44    pub fn resize(&mut self, width: u16, height: u16) {
45        self.buffer.resize(width, height);
46    }
47
48    /// Read-only handle to the underlying buffer.
49    pub fn buffer(&self) -> &Buffer {
50        &self.buffer
51    }
52
53    /// Mutable handle to the underlying buffer.
54    pub fn buffer_mut(&mut self) -> &mut Buffer {
55        &mut self.buffer
56    }
57
58    /// Blit this window's cells onto `target`, mapping the window's
59    /// top-left to [`Self::position`] in target coordinates.
60    pub fn present<T: SurfaceMut + ?Sized>(&self, target: &mut T) {
61        self.buffer.draw(target, self.position);
62    }
63}
64
65impl Bounded for Window {
66    fn bounds(&self) -> Rect {
67        self.buffer.bounds()
68    }
69}
70
71impl Surface for Window {
72    fn cell(&self, pos: Position) -> Option<&Cell> {
73        self.buffer.cell(pos)
74    }
75}
76
77impl SurfaceMut for Window {
78    fn set_cell(&mut self, pos: Position, cell: &Cell) {
79        self.buffer.set_cell(pos, cell);
80    }
81
82    fn cell_mut(&mut self, pos: Position) -> Option<&mut Cell> {
83        self.buffer.cell_mut(pos)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::cell::Cell;
91
92    #[test]
93    fn write_then_present_offsets_by_position() {
94        let mut src = Window::new(3, 2);
95        src.set_position((5, 1));
96        src.set_cell(Position::new(0, 0), &Cell::narrow("A"));
97        src.set_cell(Position::new(2, 1), &Cell::narrow("B"));
98
99        let mut dst = Buffer::new(10, 4);
100        src.present(&mut dst);
101
102        assert_eq!(dst.cell(Position::new(5, 1)).unwrap().content(), "A");
103        assert_eq!(dst.cell(Position::new(7, 2)).unwrap().content(), "B");
104        // Outside the window's footprint stays blank.
105        assert!(dst.cell(Position::new(0, 0)).unwrap().is_blank());
106    }
107
108    #[test]
109    fn present_clips_to_target_bounds() {
110        let mut src = Window::new(4, 4);
111        src.set_position((8, 2));
112        src.fill(&Cell::narrow("X"));
113        // Target is 10x3 — only the top-left 2x1 of src lands.
114        let mut dst = Buffer::new(10, 3);
115        src.present(&mut dst);
116        assert_eq!(dst.cell(Position::new(8, 2)).unwrap().content(), "X");
117        assert_eq!(dst.cell(Position::new(9, 2)).unwrap().content(), "X");
118        // Below target bottom — never written.
119        assert!(dst.cell(Position::new(8, 0)).unwrap().is_blank());
120    }
121}