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
impl Display
Sourcepub fn new(width: u32, height: u32) -> Display
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?
More examples
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}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}Sourcepub fn width(&self) -> u32
pub fn width(&self) -> u32
The number of columns.
Examples found in repository?
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
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}Sourcepub fn height(&self) -> u32
pub fn height(&self) -> u32
The number of rows.
Examples found in repository?
More examples
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}Sourcepub fn set_pixel(
&mut self,
x: isize,
y: isize,
character: char,
colour: Colour,
background: Colour,
)
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?
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
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 }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}Sourcepub fn pixel(&self, x: isize, y: isize) -> Option<&Pixel>
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());Sourcepub fn pixel_mut(&mut self, x: isize, y: isize) -> Option<&mut Pixel>
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());Sourcepub fn clear(&mut self)
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.
Sourcepub fn render(&self) -> String
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'));Sourcepub fn write_to<W>(&self, writer: &mut W) -> Result<(), Error>where
W: Write,
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());Sourcepub fn print(&self) -> Result<(), Error>
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.
Sourcepub fn reserve_space(&self) -> Result<(), Error>
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()?;