Skip to main content

Display

Struct Display 

Source
pub struct Display { /* private fields */ }
Expand description

The terminal buffer: a grid of Pixels that you draw into, then print.

Nothing reaches the terminal until you call Display::print (or get the output with Display::render / Display::write_to). Coordinates are (x, y) with (0, 0) at the top left, x growing to the right and y growing down. Drawing outside the display is silently clipped, so nothing here panics on out-of-range coordinates.

use terminal_graphics::{Colour, Display};

let mut display = Display::new(10, 3);
display.set_pixel(1, 1, '#', Colour::Red, Colour::Black);

assert_eq!(display.pixel(1, 1).unwrap().character(), '#');
assert!(display.render().contains('#'));

Implementations§

Source§

impl Display

Source

pub fn new(width: u32, height: u32) -> Display

Creates a display width columns wide and height rows high, with every pixel set to the default (a black space on a white background).

A size of 0 in either direction gives a display with no pixels, so all drawing into it is clipped.

use terminal_graphics::Display;

let display = Display::new(10, 3);
assert_eq!(display.size(), (10, 3));
Examples found in repository?
examples/paint.rs (line 69)
67    fn new(width: u32, height: u32) -> Paint {
68        let mut paint = Paint {
69            display: Display::new(width, height),
70            brush: 0,
71            last: None,
72        };
73        paint.clear();
74        paint
75    }
More examples
Hide additional examples
examples/quickstart.rs (line 9)
4fn main() -> std::io::Result<()> {
5    let mut terminal = Terminal::start()?;
6
7    // `draw` needs a display at most one row shorter than the window
8    let (width, height) = terminal.size();
9    let mut display = Display::new(width, height.saturating_sub(1));
10
11    loop {
12        terminal.draw(&display)?;
13
14        // wait up to 50 ms for input; `None` means a frame passed with no input
15        match terminal.poll(Duration::from_millis(50))? {
16            Some(Event::Key(key)) if key.is_char('q') || key.is_ctrl('c') => return Ok(()),
17            Some(Event::Mouse(mouse)) if matches!(mouse.kind, MouseKind::Down(_)) => {
18                if let Some((x, y)) = mouse.cell_in(&display) {
19                    display.set_pixel(x, y, 'X', Colour::BrightYellow, Colour::Blue);
20                }
21            }
22            _ => {}
23        }
24    }
25}
examples/screensaver.rs (line 179)
174fn main() -> io::Result<()> {
175    let mut terminal = Terminal::start()?;
176
177    // `draw` needs a display at most one row shorter than the window
178    let (width, height) = terminal.size();
179    let mut display = Display::new(width, height.saturating_sub(1));
180
181    // any number will do as a seed; the clock makes each run different
182    let seed = SystemTime::now()
183        .duration_since(UNIX_EPOCH)
184        .map_or(1, |since| since.subsec_nanos())
185        ^ std::process::id();
186    let mut saver = Screensaver::new(display.width(), display.height(), seed);
187
188    let mut next_frame = Instant::now() + FRAME;
189    loop {
190        saver.render(&mut display);
191        terminal.draw(&display)?;
192
193        // spend the time until the next frame handling input
194        loop {
195            let wait = next_frame.saturating_duration_since(Instant::now());
196            match terminal.poll(wait)? {
197                Some(event) => {
198                    if saver.handle(&event) == Action::Quit {
199                        return Ok(());
200                    }
201                }
202                None => break,
203            }
204        }
205
206        saver.step();
207        // if we fell behind, do not try to catch up with a burst of frames
208        next_frame = (next_frame + FRAME).max(Instant::now());
209    }
210}
Source

pub fn size(&self) -> (u32, u32)

The display size as (width, height), in cells.

Source

pub fn width(&self) -> u32

The number of columns.

Examples found in repository?
examples/paint.rs (line 80)
78    fn clear(&mut self) {
79        for y in 0..self.canvas_height() {
80            for x in 0..self.display.width() as isize {
81                self.display
82                    .set_pixel(x, y, ' ', Colour::White, Colour::Black);
83            }
84        }
85        self.draw_status();
86    }
87
88    /// The last row of the display is the status line, not canvas.
89    fn canvas_height(&self) -> isize {
90        self.display.height() as isize - 1
91    }
92
93    /// One swatch per colour (the chosen one marked `#`), then the controls.
94    fn draw_status(&mut self) {
95        let y = self.canvas_height();
96        for x in 0..self.display.width() as isize {
97            self.display
98                .set_pixel(x, y, ' ', Colour::White, Colour::Black);
99        }
100        for (index, colour) in PALETTE.iter().enumerate() {
101            let mark = if index == self.brush { '#' } else { ' ' };
102            self.display
103                .set_pixel(index as isize, y, mark, Colour::Black, *colour);
104        }
105        let help = "1-8 or scroll: colour   drag: paint   right drag: erase   c: clear   q: quit";
106        Text::new(
107            PALETTE.len() as isize + 1,
108            y,
109            help,
110            Colour::White,
111            Colour::Black,
112        )
113        .draw(&mut self.display);
114    }
115
116    /// Paints (or erases, with black) from where the pointer last was to `(x, y)`.
117    fn stroke(&mut self, x: isize, y: isize, colour: Colour) {
118        let width = self.display.width() as isize;
119        let height = self.canvas_height();
120
121        // Keep the line walk short and free of overflow however wild the
122        // coordinates are: one cell outside the canvas is as good as a mile.
123        let clamp = |(x, y): (isize, isize)| (x.clamp(-1, width), y.clamp(-1, height));
124        let from = self.last.unwrap_or((x, y));
125        for (cx, cy) in line(clamp(from), clamp((x, y))) {
126            if (0..width).contains(&cx) && (0..height).contains(&cy) {
127                self.display.set_pixel(cx, cy, ' ', Colour::White, colour);
128            }
129        }
130        self.last = Some((x, y));
131    }
More examples
Hide additional examples
examples/screensaver.rs (line 186)
174fn main() -> io::Result<()> {
175    let mut terminal = Terminal::start()?;
176
177    // `draw` needs a display at most one row shorter than the window
178    let (width, height) = terminal.size();
179    let mut display = Display::new(width, height.saturating_sub(1));
180
181    // any number will do as a seed; the clock makes each run different
182    let seed = SystemTime::now()
183        .duration_since(UNIX_EPOCH)
184        .map_or(1, |since| since.subsec_nanos())
185        ^ std::process::id();
186    let mut saver = Screensaver::new(display.width(), display.height(), seed);
187
188    let mut next_frame = Instant::now() + FRAME;
189    loop {
190        saver.render(&mut display);
191        terminal.draw(&display)?;
192
193        // spend the time until the next frame handling input
194        loop {
195            let wait = next_frame.saturating_duration_since(Instant::now());
196            match terminal.poll(wait)? {
197                Some(event) => {
198                    if saver.handle(&event) == Action::Quit {
199                        return Ok(());
200                    }
201                }
202                None => break,
203            }
204        }
205
206        saver.step();
207        // if we fell behind, do not try to catch up with a burst of frames
208        next_frame = (next_frame + FRAME).max(Instant::now());
209    }
210}
Source

pub fn height(&self) -> u32

The number of rows.

Examples found in repository?
examples/paint.rs (line 90)
89    fn canvas_height(&self) -> isize {
90        self.display.height() as isize - 1
91    }
More examples
Hide additional examples
examples/screensaver.rs (line 186)
174fn main() -> io::Result<()> {
175    let mut terminal = Terminal::start()?;
176
177    // `draw` needs a display at most one row shorter than the window
178    let (width, height) = terminal.size();
179    let mut display = Display::new(width, height.saturating_sub(1));
180
181    // any number will do as a seed; the clock makes each run different
182    let seed = SystemTime::now()
183        .duration_since(UNIX_EPOCH)
184        .map_or(1, |since| since.subsec_nanos())
185        ^ std::process::id();
186    let mut saver = Screensaver::new(display.width(), display.height(), seed);
187
188    let mut next_frame = Instant::now() + FRAME;
189    loop {
190        saver.render(&mut display);
191        terminal.draw(&display)?;
192
193        // spend the time until the next frame handling input
194        loop {
195            let wait = next_frame.saturating_duration_since(Instant::now());
196            match terminal.poll(wait)? {
197                Some(event) => {
198                    if saver.handle(&event) == Action::Quit {
199                        return Ok(());
200                    }
201                }
202                None => break,
203            }
204        }
205
206        saver.step();
207        // if we fell behind, do not try to catch up with a burst of frames
208        next_frame = (next_frame + FRAME).max(Instant::now());
209    }
210}
Source

pub fn set_pixel( &mut self, x: isize, y: isize, character: char, colour: Colour, background: Colour, )

Sets the character and both colours of the pixel at (x, y). Coordinates outside the display are ignored.

use terminal_graphics::{Colour, Display};

let mut display = Display::new(4, 2);
display.set_pixel(3, 1, 'x', Colour::Green, Colour::Blue);

// (4, 0) is outside a 4 by 2 display, so this has no effect
display.set_pixel(4, 0, 'y', Colour::Red, Colour::Red);
assert!(!display.render().contains('y'));
Examples found in repository?
examples/paint.rs (line 82)
78    fn clear(&mut self) {
79        for y in 0..self.canvas_height() {
80            for x in 0..self.display.width() as isize {
81                self.display
82                    .set_pixel(x, y, ' ', Colour::White, Colour::Black);
83            }
84        }
85        self.draw_status();
86    }
87
88    /// The last row of the display is the status line, not canvas.
89    fn canvas_height(&self) -> isize {
90        self.display.height() as isize - 1
91    }
92
93    /// One swatch per colour (the chosen one marked `#`), then the controls.
94    fn draw_status(&mut self) {
95        let y = self.canvas_height();
96        for x in 0..self.display.width() as isize {
97            self.display
98                .set_pixel(x, y, ' ', Colour::White, Colour::Black);
99        }
100        for (index, colour) in PALETTE.iter().enumerate() {
101            let mark = if index == self.brush { '#' } else { ' ' };
102            self.display
103                .set_pixel(index as isize, y, mark, Colour::Black, *colour);
104        }
105        let help = "1-8 or scroll: colour   drag: paint   right drag: erase   c: clear   q: quit";
106        Text::new(
107            PALETTE.len() as isize + 1,
108            y,
109            help,
110            Colour::White,
111            Colour::Black,
112        )
113        .draw(&mut self.display);
114    }
115
116    /// Paints (or erases, with black) from where the pointer last was to `(x, y)`.
117    fn stroke(&mut self, x: isize, y: isize, colour: Colour) {
118        let width = self.display.width() as isize;
119        let height = self.canvas_height();
120
121        // Keep the line walk short and free of overflow however wild the
122        // coordinates are: one cell outside the canvas is as good as a mile.
123        let clamp = |(x, y): (isize, isize)| (x.clamp(-1, width), y.clamp(-1, height));
124        let from = self.last.unwrap_or((x, y));
125        for (cx, cy) in line(clamp(from), clamp((x, y))) {
126            if (0..width).contains(&cx) && (0..height).contains(&cy) {
127                self.display.set_pixel(cx, cy, ' ', Colour::White, colour);
128            }
129        }
130        self.last = Some((x, y));
131    }
More examples
Hide additional examples
examples/screensaver.rs (line 155)
144    fn render(&self, display: &mut Display) {
145        for y in 0..self.height {
146            for x in 0..self.width {
147                let (colour, frames_left) = self.trail[(y * self.width + x) as usize];
148                let block = match frames_left {
149                    0 => ' ',
150                    n if n == TRAIL_LENGTH => '█',
151                    n if n * 3 >= TRAIL_LENGTH * 2 => '▓',
152                    n if n * 3 >= TRAIL_LENGTH => '▒',
153                    _ => '░',
154                };
155                display.set_pixel(x, y, block, colour, Colour::Black);
156            }
157        }
158    }
examples/quickstart.rs (line 19)
4fn main() -> std::io::Result<()> {
5    let mut terminal = Terminal::start()?;
6
7    // `draw` needs a display at most one row shorter than the window
8    let (width, height) = terminal.size();
9    let mut display = Display::new(width, height.saturating_sub(1));
10
11    loop {
12        terminal.draw(&display)?;
13
14        // wait up to 50 ms for input; `None` means a frame passed with no input
15        match terminal.poll(Duration::from_millis(50))? {
16            Some(Event::Key(key)) if key.is_char('q') || key.is_ctrl('c') => return Ok(()),
17            Some(Event::Mouse(mouse)) if matches!(mouse.kind, MouseKind::Down(_)) => {
18                if let Some((x, y)) = mouse.cell_in(&display) {
19                    display.set_pixel(x, y, 'X', Colour::BrightYellow, Colour::Blue);
20                }
21            }
22            _ => {}
23        }
24    }
25}
Source

pub fn pixel(&self, x: isize, y: isize) -> Option<&Pixel>

The pixel at (x, y), or None if that position is outside the display. Use it to read what has been drawn.

use terminal_graphics::{Colour, Display};

let mut display = Display::new(3, 1);
display.set_pixel(2, 0, '#', Colour::Red, Colour::Black);

assert_eq!(display.pixel(2, 0).unwrap().character(), '#');
assert!(display.pixel(3, 0).is_none());
Source

pub fn pixel_mut(&mut self, x: isize, y: isize) -> Option<&mut Pixel>

A mutable reference to the pixel at (x, y), or None if that position is outside the display. Use it to change just one property of a pixel, such as its background.

use terminal_graphics::{Colour, Display};

let mut display = Display::new(3, 3);
if let Some(pixel) = display.pixel_mut(2, 2) {
    pixel.set_character('@');
    pixel.set_colour(Colour::Yellow);
}

assert!(display.pixel_mut(3, 0).is_none());
Source

pub fn clear(&mut self)

Resets every pixel to the default (a black space on a white background). The size of the display does not change.

Source

pub fn render(&self) -> String

Renders the display to a string of ANSI escape codes, without printing it.

The string starts by moving the cursor up by the display’s height, so printing it again overwrites the previous frame in place. A display with no rows renders to an empty string.

Use this for tests, or to send the output somewhere other than stdout.

use terminal_graphics::{Colour, Display};

let mut display = Display::new(2, 1);
display.set_pixel(0, 0, 'A', Colour::Red, Colour::Black);

let output = display.render();
assert!(output.starts_with("\x1b[1F"));
assert!(output.contains('A'));
Source

pub fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>
where W: Write,

Writes the rendered display, as produced by Display::render, to any writer: a file, a Vec<u8>, a socket, and so on. Does not flush.

use terminal_graphics::Display;

let display = Display::new(3, 1);
let mut bytes: Vec<u8> = Vec::new();
display.write_to(&mut bytes).unwrap();
assert_eq!(bytes, display.render().into_bytes());
Source

pub fn print(&self) -> Result<(), Error>

Prints the display to stdout and flushes it. Printing the same display again redraws it in place.

Returns the error if stdout cannot be written to, for example a closed pipe. Call Display::reserve_space once before the first frame.

Source

pub fn reserve_space(&self) -> Result<(), Error>

Prints one blank line per display row, making room for the display.

Each frame starts by moving the cursor up over the previous one, so there must be at least height lines above the cursor to draw on. Call this once before the first Display::print; without it the first frame overwrites whatever is above the cursor. Do not call it between frames.

use terminal_graphics::Display;

let display = Display::new(40, 10);
display.reserve_space()?;
display.print()?;

Trait Implementations§

Source§

impl Clone for Display

Source§

fn clone(&self) -> Display

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Display

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Eq for Display

Source§

impl PartialEq for Display

Source§

fn eq(&self, other: &Display) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Display

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.