Struct TextCanvas

Source
pub struct TextCanvas {
    pub output: Surface,
    pub screen: Surface,
    pub buffer: PixelBuffer,
    pub color_buffer: ColorBuffer,
    pub text_buffer: TextBuffer,
    pub is_inverted: bool,
    /* private fields */
}
Expand description

Draw to the terminal like an HTML Canvas.

§Examples

use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

assert_eq!(
    canvas.repr(),
    "Canvas(output=(15×5), screen=(30×20)))"
);

assert_eq!(
    (canvas.w(), canvas.h(), canvas.cx(), canvas.cy()),
    (29, 19, 15, 10),
);

canvas.stroke_line(0, 0, canvas.w(), canvas.h());
canvas.draw_text("hello, world", 1, 2);
assert_eq!(
    canvas.to_string(),
    "\
⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀hello,⠢world⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄
"
);

Fields§

§output: Surface

Properties of the output surface, whose size is given as parameter to the constructor. One unit in width and in height represents exactly one character on the terminal.

§screen: Surface

Properties of the virtual output surface. This surface benefits from the increase in resolution. One unit in width and in height represents one Braille dot. There are 2 dots per character in width, and 4 per character in height. So this surface is 2× wider and 4× higher than the output surface.

§buffer: PixelBuffer

The in-memory pixel buffer. This maps 1-to-1 to the virtual screen. Each pixel is this buffer is either on or off.

§color_buffer: ColorBuffer

The in-memory color buffer. This maps 1-to-1 to the output buffer (and so to the physical screen). This contains color data for characters. Screen dots, being part of one output character, cannot be colored individually. Color is thus less precise than screen pixels. Note that a call to set_color() is valid for both pixels and text, but text embeds color in its own buffer, and does not use this buffer at all. Note also that this buffer is empty until the first call to set_color(). The first call to set_color() initializes the buffer and sets is_colorized() to true.

§text_buffer: TextBuffer

The in-memory text buffer. This maps 1-to-1 to the output buffer (and so to the physical screen). This contains regular text characters. Text is drawn on top of the pixel buffer on a separate layer. Drawing text does not affect pixels. Pixels and text do not share the same color buffer either. Color info is embedded in the text buffer with each character directly. Note also that this buffer is empty until the first call to draw_text(). The first call to draw_text() initializes the buffer and sets is_textual() to True.

§is_inverted: bool

Inverted drawing mode. In inverted mode, functions which usually turn pixels on, will turn them off, and vice-versa.

Implementations§

Source§

impl TextCanvas

Source

pub fn new(width: i32, height: i32) -> Self

Create new TextCanvas.

§Panics

If width and height of canvas are < 1×1.

Also, if the pixel resolution of width or height is larger than 65_535 (realistically, should not happen). This is because we let the user interact with the canvas with i32s. i32 is a more likely user-type (maths, etc.), and allows for negative values. Even though they are never displayed, allowing negative values makes for a nicer API. We handle the complexity for the user.

Examples found in repository?
examples/matrix.rs (line 202)
195fn main() {
196    debug_assert!(CHARS.chars().count() > STREAM_LENGTH as usize);
197
198    if std::env::args().any(|arg| arg == "-i" || arg == "--with-intro") {
199        do_intro();
200    }
201
202    let mut canvas = TextCanvas::new(NB_STREAMS, STREAM_LENGTH);
203    let mut rng = Rng::new();
204
205    let mut droplets: Vec<Droplet> = (0..(NB_STREAMS * 11 / 10))
206        .map(|_| Droplet::new(&mut rng))
207        .collect();
208
209    GameLoop::loop_fixed(time::Duration::from_millis(30), &mut || {
210        canvas.clear();
211
212        for droplet in &mut droplets {
213            droplet.fall();
214            if droplet.has_fallen_out_of_screen() {
215                droplet.recycle(&mut rng);
216            }
217            droplet.maybe_glitch(&mut rng);
218            droplet.draw_onto(&mut canvas);
219        }
220
221        Some(canvas.to_string())
222    });
223}
224
225fn do_intro() {
226    let sleep = |duration| std::thread::sleep(time::Duration::from_millis(duration));
227
228    let mut game_loop = GameLoop::new();
229    game_loop.set_up();
230
231    let mut canvas = TextCanvas::new(NB_STREAMS, STREAM_LENGTH);
232    let mut rng = Rng::new_from_seed(42);
233
234    canvas.set_color(Color::new().bright_green());
235
236    // Wake up, Neo...
237    for (x, c) in "Wake up, Neo...".chars().enumerate() {
238        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
239        game_loop.update(&canvas.to_string());
240        sleep(if c == ',' {
241            300
242        } else if c == ' ' {
243            100
244        } else {
245            50
246        });
247    }
248    sleep(2000);
249
250    // The Matrix has you...
251    canvas.clear();
252    for (x, c) in "The Matrix has you...".chars().enumerate() {
253        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
254        game_loop.update(&canvas.to_string());
255
256        sleep(if x < 3 {
257            400
258        } else {
259            u64::from(rng.urand_between(150, 300))
260        });
261    }
262    sleep(2000);
263
264    // Follow the white rabbit.
265    canvas.clear();
266    for (x, c) in "Follow the white rabbit.".chars().enumerate() {
267        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
268        game_loop.update(&canvas.to_string());
269
270        sleep(if x < 4 { 100 } else { 50 });
271    }
272    sleep(3000);
273
274    // Knock, knock, Neo.
275    canvas.clear();
276    game_loop.update(&canvas.to_string());
277    sleep(70);
278    canvas.draw_text("Knock, knock, Neo.", 3, 1);
279    game_loop.update(&canvas.to_string());
280
281    // Don't tear down.
282    // game_loop.tear_down();
283
284    sleep(4000);
285}
More examples
Hide additional examples
examples/sines.rs (line 56)
55fn with_fixed_time_step() {
56    let mut canvas = TextCanvas::new(80, 16);
57
58    let mut big_sine = TextCanvas::new(80, 10);
59    let mut medium_sine = TextCanvas::new(80, 6);
60    let mut small_sine = TextCanvas::new(80, 4);
61
62    let f = |x: f64| x.sin();
63
64    let mut i = 0;
65    #[rustfmt::skip]
66    GameLoop::loop_fixed(time::Duration::from_millis(30), &mut || {
67        if i == 108 {
68            // Break out of the loop.
69            return None;
70        }
71
72        let big_offset = f64::from(i) / 350.0 * 37.0;
73        let medium_offset = f64::from(i) / 250.0 * 37.0;
74        let small_offset = f64::from(i) / 150.0 * 37.0;
75
76        canvas.clear();
77
78        big_sine.clear();
79        medium_sine.clear();
80        small_sine.clear();
81
82        Plot::function(&mut big_sine, -10.0 + big_offset, 10.0 + big_offset, &f);
83        Plot::function(&mut medium_sine, -10.0 + medium_offset, 10.0 + medium_offset, &f);
84        Plot::function(&mut small_sine, -10.0 + small_offset, 10.0 + small_offset, &f);
85
86        canvas.merge_canvas(&big_sine, 0, 12);
87        canvas.merge_canvas(&medium_sine, 0, 20);
88        canvas.merge_canvas(&small_sine, 0, 25);
89
90        canvas.draw_text(&format!("  i: {i}"), 0, 0);
91        canvas.draw_text("Fixed", 37, 0);
92
93        i += 1;
94
95        Some(canvas.to_string())
96    });
97}
98
99/// Will loop as fast as possible.
100fn with_variable_time_step() {
101    let mut canvas = TextCanvas::new(80, 16);
102
103    let mut big_sine = TextCanvas::new(80, 10);
104    let mut medium_sine = TextCanvas::new(80, 6);
105    let mut small_sine = TextCanvas::new(80, 4);
106
107    let f = |x: f64| x.sin();
108
109    let mut accumulator = 0.0;
110    #[rustfmt::skip]
111    GameLoop::loop_variable(&mut |delta_time| {
112        if accumulator >= 333.33 {
113            // Break out of the loop.
114            return None;
115        }
116
117        let big_offset = accumulator / 350.0 * 12.0;
118        let medium_offset = accumulator / 250.0 * 12.0;
119        let small_offset = accumulator / 150.0 * 12.0;
120
121        canvas.clear();
122
123        big_sine.clear();
124        medium_sine.clear();
125        small_sine.clear();
126
127        Plot::function(&mut big_sine, -10.0 + big_offset, 10.0 + big_offset, &f);
128        Plot::function(&mut medium_sine, -10.0 + medium_offset, 10.0 + medium_offset, &f);
129        Plot::function(&mut small_sine, -10.0 + small_offset, 10.0 + small_offset, &f);
130
131        canvas.merge_canvas(&big_sine, 0, 12);
132        canvas.merge_canvas(&medium_sine, 0, 20);
133        canvas.merge_canvas(&small_sine, 0, 25);
134
135        canvas.draw_text(&format!(" Δt: {delta_time:.9}"), 0, 0);
136        canvas.draw_text(&format!("acc: {accumulator:>11.7}"), 0, 1);
137        canvas.draw_text("Variable", 36, 0);
138
139        accumulator += 100.0 * delta_time;
140
141        Some(canvas.to_string())
142    });
143}
examples/cube.rs (line 53)
52fn main() {
53    let mut canvas = TextCanvas::new(CANVAS_WIDTH as i32, CANVAS_HEIGHT as i32);
54
55    let camera = Camera::new();
56    let mut cube = Cube::new();
57
58    GameLoop::loop_variable(&mut |delta_time| {
59        canvas.clear();
60
61        let rotate = 1.0 * delta_time;
62        cube.rotate(rotate, rotate, rotate);
63
64        let mut model = camera.project_cube(&cube);
65        for face in &mut model {
66            let mut first: Option<Vec2D> = None;
67            let mut previous: Option<Vec2D> = None;
68            for vertex in face {
69                let [x, y, z] = *vertex;
70
71                let [x, y] = world_to_screen([x, y, z]);
72                *vertex = [x, y, 0.0]; // `z` gets ditched.
73
74                if let Some(previous) = previous {
75                    canvas.stroke_line(
76                        previous[0].trunc() as i32,
77                        previous[1].trunc() as i32,
78                        x.trunc() as i32,
79                        y.trunc() as i32,
80                    );
81                }
82
83                if first.is_none() {
84                    first = Some([x, y]);
85                }
86
87                previous = Some([x, y]);
88            }
89
90            // Close.
91            if let (Some(first), Some(previous)) = (first, previous) {
92                canvas.stroke_line(
93                    previous[0].trunc() as i32,
94                    previous[1].trunc() as i32,
95                    first[0].trunc() as i32,
96                    first[1].trunc() as i32,
97                );
98            }
99        }
100
101        // Don't eat up the whole CPU.
102        std::thread::sleep(std::time::Duration::from_millis(7));
103
104        Some(canvas.to_string())
105    });
106}
Source

pub fn new_auto() -> Result<Self, TextCanvasError>

Create new TextCanvas by reading size from environment.

§Errors

If either or both WIDTH and HEIGHT variables cannot be read from the environment.

Source

pub fn get_default_size() -> (i32, i32)

Default canvas size.

This value is used by TextCanvas::default(), but it may be useful to query it separately.

Source

pub fn get_auto_size() -> Result<(i32, i32), TextCanvasError>

Read canvas size from WIDTH and HEIGHT env variables.

This value is used by TextCanvas::new_auto(), but it may be useful to query it separately.

§Errors

If either or both WIDTH and HEIGHT variables cannot be read from the environment.

Source

pub fn repr(&self) -> String

High-level string representation of the canvas.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

assert_eq!(
    canvas.repr(),
    "Canvas(output=(15×5), screen=(30×20)))"
);
Source

pub fn w(&self) -> i32

Shortcut for width of pixel screen (index of last column).

Source

pub fn uw(&self) -> usize

Shortcut for width of pixel screen (index of last column).

Source

pub fn fw(&self) -> f64

Shortcut for width of pixel screen (index of last column).

Source

pub fn h(&self) -> i32

Shortcut for height of pixel screen (index of last row).

Source

pub fn uh(&self) -> usize

Shortcut for height of pixel screen (index of last row).

Source

pub fn fh(&self) -> f64

Shortcut for height of pixel screen (index of last row).

Source

pub fn cx(&self) -> i32

Shortcut for center-X of pixel screen.

Source

pub fn ucx(&self) -> usize

Shortcut for center-X of pixel screen.

Source

pub fn fcx(&self) -> f64

Shortcut for center-X of pixel screen.

Source

pub fn cy(&self) -> i32

Shortcut for center-Y of pixel screen.

Source

pub fn ucy(&self) -> usize

Shortcut for center-Y of pixel screen.

Source

pub fn fcy(&self) -> f64

Shortcut for center-Y of pixel screen.

Source

pub fn clear(&mut self)

Turn all pixels off and remove color and text.

Note: This method does not drop the color and text buffers, it only clears them. No memory is freed, and all references remain valid (buffers are cleared in-place, not replaced).

Note: clear() is not affected by inverted mode, it works on a lower level.

Examples found in repository?
examples/matrix.rs (line 210)
195fn main() {
196    debug_assert!(CHARS.chars().count() > STREAM_LENGTH as usize);
197
198    if std::env::args().any(|arg| arg == "-i" || arg == "--with-intro") {
199        do_intro();
200    }
201
202    let mut canvas = TextCanvas::new(NB_STREAMS, STREAM_LENGTH);
203    let mut rng = Rng::new();
204
205    let mut droplets: Vec<Droplet> = (0..(NB_STREAMS * 11 / 10))
206        .map(|_| Droplet::new(&mut rng))
207        .collect();
208
209    GameLoop::loop_fixed(time::Duration::from_millis(30), &mut || {
210        canvas.clear();
211
212        for droplet in &mut droplets {
213            droplet.fall();
214            if droplet.has_fallen_out_of_screen() {
215                droplet.recycle(&mut rng);
216            }
217            droplet.maybe_glitch(&mut rng);
218            droplet.draw_onto(&mut canvas);
219        }
220
221        Some(canvas.to_string())
222    });
223}
224
225fn do_intro() {
226    let sleep = |duration| std::thread::sleep(time::Duration::from_millis(duration));
227
228    let mut game_loop = GameLoop::new();
229    game_loop.set_up();
230
231    let mut canvas = TextCanvas::new(NB_STREAMS, STREAM_LENGTH);
232    let mut rng = Rng::new_from_seed(42);
233
234    canvas.set_color(Color::new().bright_green());
235
236    // Wake up, Neo...
237    for (x, c) in "Wake up, Neo...".chars().enumerate() {
238        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
239        game_loop.update(&canvas.to_string());
240        sleep(if c == ',' {
241            300
242        } else if c == ' ' {
243            100
244        } else {
245            50
246        });
247    }
248    sleep(2000);
249
250    // The Matrix has you...
251    canvas.clear();
252    for (x, c) in "The Matrix has you...".chars().enumerate() {
253        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
254        game_loop.update(&canvas.to_string());
255
256        sleep(if x < 3 {
257            400
258        } else {
259            u64::from(rng.urand_between(150, 300))
260        });
261    }
262    sleep(2000);
263
264    // Follow the white rabbit.
265    canvas.clear();
266    for (x, c) in "Follow the white rabbit.".chars().enumerate() {
267        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
268        game_loop.update(&canvas.to_string());
269
270        sleep(if x < 4 { 100 } else { 50 });
271    }
272    sleep(3000);
273
274    // Knock, knock, Neo.
275    canvas.clear();
276    game_loop.update(&canvas.to_string());
277    sleep(70);
278    canvas.draw_text("Knock, knock, Neo.", 3, 1);
279    game_loop.update(&canvas.to_string());
280
281    // Don't tear down.
282    // game_loop.tear_down();
283
284    sleep(4000);
285}
More examples
Hide additional examples
examples/sines.rs (line 76)
55fn with_fixed_time_step() {
56    let mut canvas = TextCanvas::new(80, 16);
57
58    let mut big_sine = TextCanvas::new(80, 10);
59    let mut medium_sine = TextCanvas::new(80, 6);
60    let mut small_sine = TextCanvas::new(80, 4);
61
62    let f = |x: f64| x.sin();
63
64    let mut i = 0;
65    #[rustfmt::skip]
66    GameLoop::loop_fixed(time::Duration::from_millis(30), &mut || {
67        if i == 108 {
68            // Break out of the loop.
69            return None;
70        }
71
72        let big_offset = f64::from(i) / 350.0 * 37.0;
73        let medium_offset = f64::from(i) / 250.0 * 37.0;
74        let small_offset = f64::from(i) / 150.0 * 37.0;
75
76        canvas.clear();
77
78        big_sine.clear();
79        medium_sine.clear();
80        small_sine.clear();
81
82        Plot::function(&mut big_sine, -10.0 + big_offset, 10.0 + big_offset, &f);
83        Plot::function(&mut medium_sine, -10.0 + medium_offset, 10.0 + medium_offset, &f);
84        Plot::function(&mut small_sine, -10.0 + small_offset, 10.0 + small_offset, &f);
85
86        canvas.merge_canvas(&big_sine, 0, 12);
87        canvas.merge_canvas(&medium_sine, 0, 20);
88        canvas.merge_canvas(&small_sine, 0, 25);
89
90        canvas.draw_text(&format!("  i: {i}"), 0, 0);
91        canvas.draw_text("Fixed", 37, 0);
92
93        i += 1;
94
95        Some(canvas.to_string())
96    });
97}
98
99/// Will loop as fast as possible.
100fn with_variable_time_step() {
101    let mut canvas = TextCanvas::new(80, 16);
102
103    let mut big_sine = TextCanvas::new(80, 10);
104    let mut medium_sine = TextCanvas::new(80, 6);
105    let mut small_sine = TextCanvas::new(80, 4);
106
107    let f = |x: f64| x.sin();
108
109    let mut accumulator = 0.0;
110    #[rustfmt::skip]
111    GameLoop::loop_variable(&mut |delta_time| {
112        if accumulator >= 333.33 {
113            // Break out of the loop.
114            return None;
115        }
116
117        let big_offset = accumulator / 350.0 * 12.0;
118        let medium_offset = accumulator / 250.0 * 12.0;
119        let small_offset = accumulator / 150.0 * 12.0;
120
121        canvas.clear();
122
123        big_sine.clear();
124        medium_sine.clear();
125        small_sine.clear();
126
127        Plot::function(&mut big_sine, -10.0 + big_offset, 10.0 + big_offset, &f);
128        Plot::function(&mut medium_sine, -10.0 + medium_offset, 10.0 + medium_offset, &f);
129        Plot::function(&mut small_sine, -10.0 + small_offset, 10.0 + small_offset, &f);
130
131        canvas.merge_canvas(&big_sine, 0, 12);
132        canvas.merge_canvas(&medium_sine, 0, 20);
133        canvas.merge_canvas(&small_sine, 0, 25);
134
135        canvas.draw_text(&format!(" Δt: {delta_time:.9}"), 0, 0);
136        canvas.draw_text(&format!("acc: {accumulator:>11.7}"), 0, 1);
137        canvas.draw_text("Variable", 36, 0);
138
139        accumulator += 100.0 * delta_time;
140
141        Some(canvas.to_string())
142    });
143}
examples/cube.rs (line 59)
52fn main() {
53    let mut canvas = TextCanvas::new(CANVAS_WIDTH as i32, CANVAS_HEIGHT as i32);
54
55    let camera = Camera::new();
56    let mut cube = Cube::new();
57
58    GameLoop::loop_variable(&mut |delta_time| {
59        canvas.clear();
60
61        let rotate = 1.0 * delta_time;
62        cube.rotate(rotate, rotate, rotate);
63
64        let mut model = camera.project_cube(&cube);
65        for face in &mut model {
66            let mut first: Option<Vec2D> = None;
67            let mut previous: Option<Vec2D> = None;
68            for vertex in face {
69                let [x, y, z] = *vertex;
70
71                let [x, y] = world_to_screen([x, y, z]);
72                *vertex = [x, y, 0.0]; // `z` gets ditched.
73
74                if let Some(previous) = previous {
75                    canvas.stroke_line(
76                        previous[0].trunc() as i32,
77                        previous[1].trunc() as i32,
78                        x.trunc() as i32,
79                        y.trunc() as i32,
80                    );
81                }
82
83                if first.is_none() {
84                    first = Some([x, y]);
85                }
86
87                previous = Some([x, y]);
88            }
89
90            // Close.
91            if let (Some(first), Some(previous)) = (first, previous) {
92                canvas.stroke_line(
93                    previous[0].trunc() as i32,
94                    previous[1].trunc() as i32,
95                    first[0].trunc() as i32,
96                    first[1].trunc() as i32,
97                );
98            }
99        }
100
101        // Don't eat up the whole CPU.
102        std::thread::sleep(std::time::Duration::from_millis(7));
103
104        Some(canvas.to_string())
105    });
106}
Source

pub fn fill(&mut self)

Turn all pixels on.

This does not affect the color and text buffers.

Note: fill() is not affected by inverted mode, it works on a lower level.

Source

pub fn invert(&mut self)

Invert drawing mode.

In inverted mode, functions that usually turn pixels on, will turn them off, and vice versa. This can be used to cut out shapes for instance.

Source

pub fn is_colorized(&self) -> bool

Whether the canvas can contain colors.

Note: This does not mean that any colors are displayed. This only means the color buffer is active.

§Examples
use textcanvas::{Color, TextCanvas};

let mut canvas = TextCanvas::new(15, 5);

assert!(!canvas.is_colorized());

canvas.set_color(&Color::new().fix());  // Buffer is initialized.
assert!(canvas.is_colorized());
Source

pub fn is_textual(&self) -> bool

Whether the canvas can contain text.

Note: This does not mean that any text is displayed. This only means the text buffer is active.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

assert!(!canvas.is_textual());

canvas.draw_text("", 0, 0);  // Buffer is initialized.
assert!(canvas.is_textual());
Source

pub fn set_color(&mut self, color: &Color)

Set context color.

§Examples
use textcanvas::{Color, TextCanvas};

let mut canvas = TextCanvas::new(3, 1);
let green = Color::new().bright_green().fix();

canvas.set_color(&green);
assert!(canvas.is_colorized());

canvas.draw_text("foo", 0, 0);
assert_eq!(
    canvas.to_string(),
    "\x1b[0;92mf\x1b[0m\x1b[0;92mo\x1b[0m\x1b[0;92mo\x1b[0m\n"
);
Examples found in repository?
examples/matrix.rs (line 132)
122    pub fn draw_onto(&mut self, canvas: &mut TextCanvas) {
123        let chars = self.to_string();
124        debug_assert!(chars.chars().count() == STREAM_LENGTH as usize);
125
126        let i_tip = self.iy() + self.length - 1;
127
128        // Start at `y=0`, NOT `droplet.y`. The droplet is already
129        // rendered, including spacing, etc.
130        for (i, char_) in chars.chars().enumerate() {
131            let i = i as i32;
132            canvas.set_color(&self.determine_char_color(i, i_tip));
133            // `merge_text()` ignores spaces.
134            canvas.merge_text(&char_.to_string(), self.x, i);
135        }
136    }
137
138    fn determine_char_color(&self, i: i32, i_tip: i32) -> Color {
139        if i == i_tip {
140            return Shade::Tip.into();
141        }
142        if i == i_tip - 1 {
143            return Shade::PreTip.into();
144        }
145
146        // Use `self.x` and `self.length` to deterministically randomize
147        // bucket size and bright spots distribution.
148        let s = f64::from(self.x).sin().abs(); // [0; 1]
149        let d = f64::from(self.length).sin() - 0.3; // [-1.3; 0.7] (slightly skewed towards dim).
150
151        if f64::from(i_tip - i).sin() * s <= d {
152            // `sin(x) * S >= D`
153            // Deterministic way (`i_tip - i`) to modulate shade.
154            // `S` influences the size of the (base) buckets.
155            // `D` (`[-1; 1]`) affects the distribution. Higher means
156            //     bias towards bright, lower means bias towards dim,
157            //     `0.0` being neutral.
158            Shade::BrightGreen.into()
159        } else {
160            Shade::DimGreen.into()
161        }
162    }
163}
164
165impl fmt::Display for Droplet {
166    /// Render droplet as part of a stream, with leading and trailing whitespace.
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        // Not yet visible (above screen).
169        if self.iy() + self.length <= 0 {
170            return write!(f, "{}", " ".repeat(STREAM_LENGTH as usize));
171        }
172        // No longer visible (below screen).
173        if self.iy() >= STREAM_LENGTH {
174            return write!(f, "{}", " ".repeat(STREAM_LENGTH as usize));
175        }
176        let window_start = self.iy().clamp(0, STREAM_LENGTH - 1) as usize;
177        let window_end = (self.iy() + self.length - 1).clamp(0, STREAM_LENGTH - 1) as usize;
178
179        write!(
180            f,
181            "{}{}{}",
182            " ".repeat(window_start),
183            // Equivalent to `&self.chars[window_start..=window_end]`, but with `chars()`.
184            &self
185                .chars
186                .chars()
187                .skip(window_start)
188                .take(window_end - window_start + 1)
189                .collect::<String>(),
190            " ".repeat(STREAM_LENGTH as usize - window_end - 1)
191        )
192    }
193}
194
195fn main() {
196    debug_assert!(CHARS.chars().count() > STREAM_LENGTH as usize);
197
198    if std::env::args().any(|arg| arg == "-i" || arg == "--with-intro") {
199        do_intro();
200    }
201
202    let mut canvas = TextCanvas::new(NB_STREAMS, STREAM_LENGTH);
203    let mut rng = Rng::new();
204
205    let mut droplets: Vec<Droplet> = (0..(NB_STREAMS * 11 / 10))
206        .map(|_| Droplet::new(&mut rng))
207        .collect();
208
209    GameLoop::loop_fixed(time::Duration::from_millis(30), &mut || {
210        canvas.clear();
211
212        for droplet in &mut droplets {
213            droplet.fall();
214            if droplet.has_fallen_out_of_screen() {
215                droplet.recycle(&mut rng);
216            }
217            droplet.maybe_glitch(&mut rng);
218            droplet.draw_onto(&mut canvas);
219        }
220
221        Some(canvas.to_string())
222    });
223}
224
225fn do_intro() {
226    let sleep = |duration| std::thread::sleep(time::Duration::from_millis(duration));
227
228    let mut game_loop = GameLoop::new();
229    game_loop.set_up();
230
231    let mut canvas = TextCanvas::new(NB_STREAMS, STREAM_LENGTH);
232    let mut rng = Rng::new_from_seed(42);
233
234    canvas.set_color(Color::new().bright_green());
235
236    // Wake up, Neo...
237    for (x, c) in "Wake up, Neo...".chars().enumerate() {
238        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
239        game_loop.update(&canvas.to_string());
240        sleep(if c == ',' {
241            300
242        } else if c == ' ' {
243            100
244        } else {
245            50
246        });
247    }
248    sleep(2000);
249
250    // The Matrix has you...
251    canvas.clear();
252    for (x, c) in "The Matrix has you...".chars().enumerate() {
253        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
254        game_loop.update(&canvas.to_string());
255
256        sleep(if x < 3 {
257            400
258        } else {
259            u64::from(rng.urand_between(150, 300))
260        });
261    }
262    sleep(2000);
263
264    // Follow the white rabbit.
265    canvas.clear();
266    for (x, c) in "Follow the white rabbit.".chars().enumerate() {
267        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
268        game_loop.update(&canvas.to_string());
269
270        sleep(if x < 4 { 100 } else { 50 });
271    }
272    sleep(3000);
273
274    // Knock, knock, Neo.
275    canvas.clear();
276    game_loop.update(&canvas.to_string());
277    sleep(70);
278    canvas.draw_text("Knock, knock, Neo.", 3, 1);
279    game_loop.update(&canvas.to_string());
280
281    // Don't tear down.
282    // game_loop.tear_down();
283
284    sleep(4000);
285}
Source

pub fn get_pixel(&self, x: i32, y: i32) -> Option<bool>

Get the state of a screen pixel.

Some(true) if the pixel is turned on, Some(false) if it is turned off, and None if the coordinates are outside the bounds of the buffer.

§Arguments
  • x - Screen X (high resolution).
  • y - Screen Y (high resolution).
Source

pub fn set_pixel(&mut self, x: i32, y: i32, state: bool)

Set the state of a screen pixel.

Note: Coordinates outside the screen bounds are ignored.

Note: Turning a pixel off also removes color. This side effect does not affect text, as text has a separate color buffer.

§Arguments
  • x - Screen X (high resolution).
  • y - Screen Y (high resolution).
  • state - true means on, false means off.
Source

pub fn draw_text(&mut self, text: &str, x: i32, y: i32)

Draw text onto the canvas.

Note: Spaces are transparent (you see pixels through). But drawing spaces over text erases the text beneath. If you want to keep the text, use the merge_text() method.

Note: Coordinates outside the screen bounds are ignored.

Note: Text is rendered on top of pixels, as a separate layer.

Note: set_color() works for text as well, but text does not share its color buffer with pixels.

Examples found in repository?
examples/sines.rs (line 90)
55fn with_fixed_time_step() {
56    let mut canvas = TextCanvas::new(80, 16);
57
58    let mut big_sine = TextCanvas::new(80, 10);
59    let mut medium_sine = TextCanvas::new(80, 6);
60    let mut small_sine = TextCanvas::new(80, 4);
61
62    let f = |x: f64| x.sin();
63
64    let mut i = 0;
65    #[rustfmt::skip]
66    GameLoop::loop_fixed(time::Duration::from_millis(30), &mut || {
67        if i == 108 {
68            // Break out of the loop.
69            return None;
70        }
71
72        let big_offset = f64::from(i) / 350.0 * 37.0;
73        let medium_offset = f64::from(i) / 250.0 * 37.0;
74        let small_offset = f64::from(i) / 150.0 * 37.0;
75
76        canvas.clear();
77
78        big_sine.clear();
79        medium_sine.clear();
80        small_sine.clear();
81
82        Plot::function(&mut big_sine, -10.0 + big_offset, 10.0 + big_offset, &f);
83        Plot::function(&mut medium_sine, -10.0 + medium_offset, 10.0 + medium_offset, &f);
84        Plot::function(&mut small_sine, -10.0 + small_offset, 10.0 + small_offset, &f);
85
86        canvas.merge_canvas(&big_sine, 0, 12);
87        canvas.merge_canvas(&medium_sine, 0, 20);
88        canvas.merge_canvas(&small_sine, 0, 25);
89
90        canvas.draw_text(&format!("  i: {i}"), 0, 0);
91        canvas.draw_text("Fixed", 37, 0);
92
93        i += 1;
94
95        Some(canvas.to_string())
96    });
97}
98
99/// Will loop as fast as possible.
100fn with_variable_time_step() {
101    let mut canvas = TextCanvas::new(80, 16);
102
103    let mut big_sine = TextCanvas::new(80, 10);
104    let mut medium_sine = TextCanvas::new(80, 6);
105    let mut small_sine = TextCanvas::new(80, 4);
106
107    let f = |x: f64| x.sin();
108
109    let mut accumulator = 0.0;
110    #[rustfmt::skip]
111    GameLoop::loop_variable(&mut |delta_time| {
112        if accumulator >= 333.33 {
113            // Break out of the loop.
114            return None;
115        }
116
117        let big_offset = accumulator / 350.0 * 12.0;
118        let medium_offset = accumulator / 250.0 * 12.0;
119        let small_offset = accumulator / 150.0 * 12.0;
120
121        canvas.clear();
122
123        big_sine.clear();
124        medium_sine.clear();
125        small_sine.clear();
126
127        Plot::function(&mut big_sine, -10.0 + big_offset, 10.0 + big_offset, &f);
128        Plot::function(&mut medium_sine, -10.0 + medium_offset, 10.0 + medium_offset, &f);
129        Plot::function(&mut small_sine, -10.0 + small_offset, 10.0 + small_offset, &f);
130
131        canvas.merge_canvas(&big_sine, 0, 12);
132        canvas.merge_canvas(&medium_sine, 0, 20);
133        canvas.merge_canvas(&small_sine, 0, 25);
134
135        canvas.draw_text(&format!(" Δt: {delta_time:.9}"), 0, 0);
136        canvas.draw_text(&format!("acc: {accumulator:>11.7}"), 0, 1);
137        canvas.draw_text("Variable", 36, 0);
138
139        accumulator += 100.0 * delta_time;
140
141        Some(canvas.to_string())
142    });
143}
More examples
Hide additional examples
examples/matrix.rs (line 238)
225fn do_intro() {
226    let sleep = |duration| std::thread::sleep(time::Duration::from_millis(duration));
227
228    let mut game_loop = GameLoop::new();
229    game_loop.set_up();
230
231    let mut canvas = TextCanvas::new(NB_STREAMS, STREAM_LENGTH);
232    let mut rng = Rng::new_from_seed(42);
233
234    canvas.set_color(Color::new().bright_green());
235
236    // Wake up, Neo...
237    for (x, c) in "Wake up, Neo...".chars().enumerate() {
238        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
239        game_loop.update(&canvas.to_string());
240        sleep(if c == ',' {
241            300
242        } else if c == ' ' {
243            100
244        } else {
245            50
246        });
247    }
248    sleep(2000);
249
250    // The Matrix has you...
251    canvas.clear();
252    for (x, c) in "The Matrix has you...".chars().enumerate() {
253        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
254        game_loop.update(&canvas.to_string());
255
256        sleep(if x < 3 {
257            400
258        } else {
259            u64::from(rng.urand_between(150, 300))
260        });
261    }
262    sleep(2000);
263
264    // Follow the white rabbit.
265    canvas.clear();
266    for (x, c) in "Follow the white rabbit.".chars().enumerate() {
267        canvas.draw_text(&c.to_string(), x as i32 + 3, 1);
268        game_loop.update(&canvas.to_string());
269
270        sleep(if x < 4 { 100 } else { 50 });
271    }
272    sleep(3000);
273
274    // Knock, knock, Neo.
275    canvas.clear();
276    game_loop.update(&canvas.to_string());
277    sleep(70);
278    canvas.draw_text("Knock, knock, Neo.", 3, 1);
279    game_loop.update(&canvas.to_string());
280
281    // Don't tear down.
282    // game_loop.tear_down();
283
284    sleep(4000);
285}
Source

pub fn draw_text_vertical(&mut self, text: &str, x: i32, y: i32)

Source

pub fn merge_text(&mut self, text: &str, x: i32, y: i32)

Merge text onto the canvas.

This is the same as draw_text(), but spaces do not erase text underneath.

Examples found in repository?
examples/matrix.rs (line 134)
122    pub fn draw_onto(&mut self, canvas: &mut TextCanvas) {
123        let chars = self.to_string();
124        debug_assert!(chars.chars().count() == STREAM_LENGTH as usize);
125
126        let i_tip = self.iy() + self.length - 1;
127
128        // Start at `y=0`, NOT `droplet.y`. The droplet is already
129        // rendered, including spacing, etc.
130        for (i, char_) in chars.chars().enumerate() {
131            let i = i as i32;
132            canvas.set_color(&self.determine_char_color(i, i_tip));
133            // `merge_text()` ignores spaces.
134            canvas.merge_text(&char_.to_string(), self.x, i);
135        }
136    }
Source

pub fn merge_text_vertical(&mut self, text: &str, x: i32, y: i32)

Source

pub fn iter_buffer(&self) -> IterPixelBuffer<i32>

Iterate over all cells of the pixel buffer.

Yields all X/Y coordinates, left-right, top-bottom.

Values are i32, this is meant to be used with the public API.

Source

pub fn uiter_buffer(&self) -> IterPixelBuffer<usize>

Iterate over all cells of the pixel buffer.

Yields all X/Y coordinates, left-right, top-bottom.

Values are usize, this is meant to index directly into the buffer.

Source§

impl TextCanvas

Implementation of drawing primitives.

Source

pub fn stroke_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32)

Stroke line.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

canvas.stroke_line(5, 5, 25, 15);

assert_eq!(
    canvas.to_string(),
    "\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠐⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠉⠒⠤⣀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠒⠤⣀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
Examples found in repository?
examples/cube.rs (lines 75-80)
52fn main() {
53    let mut canvas = TextCanvas::new(CANVAS_WIDTH as i32, CANVAS_HEIGHT as i32);
54
55    let camera = Camera::new();
56    let mut cube = Cube::new();
57
58    GameLoop::loop_variable(&mut |delta_time| {
59        canvas.clear();
60
61        let rotate = 1.0 * delta_time;
62        cube.rotate(rotate, rotate, rotate);
63
64        let mut model = camera.project_cube(&cube);
65        for face in &mut model {
66            let mut first: Option<Vec2D> = None;
67            let mut previous: Option<Vec2D> = None;
68            for vertex in face {
69                let [x, y, z] = *vertex;
70
71                let [x, y] = world_to_screen([x, y, z]);
72                *vertex = [x, y, 0.0]; // `z` gets ditched.
73
74                if let Some(previous) = previous {
75                    canvas.stroke_line(
76                        previous[0].trunc() as i32,
77                        previous[1].trunc() as i32,
78                        x.trunc() as i32,
79                        y.trunc() as i32,
80                    );
81                }
82
83                if first.is_none() {
84                    first = Some([x, y]);
85                }
86
87                previous = Some([x, y]);
88            }
89
90            // Close.
91            if let (Some(first), Some(previous)) = (first, previous) {
92                canvas.stroke_line(
93                    previous[0].trunc() as i32,
94                    previous[1].trunc() as i32,
95                    first[0].trunc() as i32,
96                    first[1].trunc() as i32,
97                );
98            }
99        }
100
101        // Don't eat up the whole CPU.
102        std::thread::sleep(std::time::Duration::from_millis(7));
103
104        Some(canvas.to_string())
105    });
106}
Source

pub fn stroke_rect(&mut self, x: i32, y: i32, width: i32, height: i32)

Stroke rectangle.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

canvas.stroke_rect(5, 5, 20, 10);

assert_eq!(
    canvas.to_string(),
    "\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢰⠒⠒⠒⠒⠒⠒⠒⠒⠒⡆⠀⠀
⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀
⠀⠀⠸⠤⠤⠤⠤⠤⠤⠤⠤⠤⠇⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
Source

pub fn frame(&mut self)

Draw a border around the canvas.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

canvas.frame();

assert_eq!(
    canvas.to_string(),
    "\
⡏⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⢹
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⣇⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣸
"
);
Source

pub fn fill_rect(&mut self, x: i32, y: i32, width: i32, height: i32)

Fill rectangle.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

canvas.fill_rect(5, 5, 20, 10);

assert_eq!(
    canvas.to_string(),
    "\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢰⣶⣶⣶⣶⣶⣶⣶⣶⣶⡆⠀⠀
⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀
⠀⠀⠸⠿⠿⠿⠿⠿⠿⠿⠿⠿⠇⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
Source

pub fn stroke_triangle( &mut self, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32, )

Stroke triangle.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

canvas.stroke_triangle(5, 5, 20, 10, 4, 17);

assert_eq!(
    canvas.to_string(),
    "\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢰⠢⠤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢸⠀⠀⠀⠈⠉⢒⡢⠄⠀⠀⠀⠀
⠀⠀⡇⠀⣀⠤⠔⠊⠁⠀⠀⠀⠀⠀⠀
⠀⠀⠓⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
Source

pub fn fill_triangle( &mut self, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32, )

Fill triangle.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

canvas.fill_triangle(5, 5, 20, 10, 4, 17);

assert_eq!(
    canvas.to_string(),
    "\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢰⣦⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢸⣿⣿⣿⣿⣿⣶⡦⠄⠀⠀⠀⠀
⠀⠀⣿⣿⣿⠿⠟⠋⠁⠀⠀⠀⠀⠀⠀
⠀⠀⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
Source

pub fn stroke_circle(&mut self, x: i32, y: i32, radius: i32)

Stroke circle.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

canvas.stroke_circle(canvas.cx(), canvas.cy(), 7);

assert_eq!(
    canvas.to_string(),
    "\
⠀⠀⠀⠀⠀⠀⣀⣀⣀⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⡠⠊⠀⠀⠀⠈⠢⡀⠀⠀⠀
⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀
⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⡠⠃⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠒⠒⠒⠊⠀⠀⠀⠀⠀
"
);
Source

pub fn fill_circle(&mut self, x: i32, y: i32, radius: i32)

Fill circle.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

canvas.fill_circle(canvas.cx(), canvas.cy(), 7);

assert_eq!(
    canvas.to_string(),
    "\
⠀⠀⠀⠀⠀⠀⣀⣀⣀⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣦⡀⠀⠀⠀
⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀
⠀⠀⠀⠀⠻⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠛⠛⠛⠋⠀⠀⠀⠀⠀
"
);
Source

pub fn stroke_ngon( &mut self, x: i32, y: i32, radius: i32, sides: i32, angle: f64, )

Stroke n-gon.

§Examples
use textcanvas::TextCanvas;
use std::f64::consts::PI;

let mut canvas = TextCanvas::new(15, 5);

canvas.stroke_ngon(canvas.cx(), canvas.cy(), 7, 5, PI / 2.0);

assert_eq!(
    canvas.to_string(),
    "\
⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢀⡠⠊⠁⠉⠢⣀⠀⠀⠀⠀
⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⢠⠃⠀⠀⠀
⠀⠀⠀⠀⠀⢇⠀⠀⠀⢀⠎⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠀⠀⠀⠀⠀
"
);
§Panics

Panics if sides < 3.

Source

pub fn fill_ngon(&mut self, x: i32, y: i32, radius: i32, sides: i32, angle: f64)

Fill n-gon.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);

canvas.fill_ngon(canvas.cx(), canvas.cy(), 7, 4, 0.0);

assert_eq!(
    canvas.to_string(),
    "\
⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⢀⣴⣿⣷⣄⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢴⣿⣿⣿⣿⣿⣷⠄⠀⠀⠀
⠀⠀⠀⠀⠀⠙⢿⣿⣿⠟⠁⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀
"
);
§Panics

Panics if sides < 3.

Source

pub fn draw_canvas(&mut self, canvas: &Self, dx: i32, dy: i32)

Draw another canvas onto the current canvas.

The other canvas completely overrides the current canvas where it is drawn (but it does not affect the portions where it is not drawn).

Note: Inverted mode has no effect here, this is a low level copy-paste.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);
canvas.stroke_line(0, 0, canvas.w(), canvas.h());
canvas.stroke_line(0, canvas.h(), canvas.w(), 0);

let mut overlay = TextCanvas::new(7, 3);
overlay.frame();

canvas.draw_canvas(&overlay, 8, 4);

assert_eq!(
    canvas.to_string(),
    "\
⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠊
⠀⠀⠀⠑⡏⠉⠉⠉⠉⠉⢹⠊⠀⠀⠀
⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀
⠀⠀⠀⡠⣇⣀⣀⣀⣀⣀⣸⢄⠀⠀⠀
⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄
"
);
Source

pub fn merge_canvas(&mut self, canvas: &Self, dx: i32, dy: i32)

Merge another canvas with the current canvas.

The other canvas is merged with the current canvas. That is, pixels that are turned on get draw, but those that are off are ignored.

Note: Inverted mode has no effect here, this is a low level copy-paste.

§Examples
use textcanvas::TextCanvas;

let mut canvas = TextCanvas::new(15, 5);
canvas.stroke_line(0, 0, canvas.w(), canvas.h());
canvas.stroke_line(0, canvas.h(), canvas.w(), 0);

let mut overlay = TextCanvas::new(7, 3);
overlay.frame();

canvas.merge_canvas(&overlay, 8, 4);

assert_eq!(
    canvas.to_string(),
    "\
⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠊
⠀⠀⠀⠑⡯⣉⠉⠉⠉⣉⢽⠊⠀⠀⠀
⠀⠀⠀⠀⡇⠀⡱⠶⢎⠀⢸⠀⠀⠀⠀
⠀⠀⠀⡠⣗⣉⣀⣀⣀⣉⣺⢄⠀⠀⠀
⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄
"
);
Examples found in repository?
examples/sines.rs (line 86)
55fn with_fixed_time_step() {
56    let mut canvas = TextCanvas::new(80, 16);
57
58    let mut big_sine = TextCanvas::new(80, 10);
59    let mut medium_sine = TextCanvas::new(80, 6);
60    let mut small_sine = TextCanvas::new(80, 4);
61
62    let f = |x: f64| x.sin();
63
64    let mut i = 0;
65    #[rustfmt::skip]
66    GameLoop::loop_fixed(time::Duration::from_millis(30), &mut || {
67        if i == 108 {
68            // Break out of the loop.
69            return None;
70        }
71
72        let big_offset = f64::from(i) / 350.0 * 37.0;
73        let medium_offset = f64::from(i) / 250.0 * 37.0;
74        let small_offset = f64::from(i) / 150.0 * 37.0;
75
76        canvas.clear();
77
78        big_sine.clear();
79        medium_sine.clear();
80        small_sine.clear();
81
82        Plot::function(&mut big_sine, -10.0 + big_offset, 10.0 + big_offset, &f);
83        Plot::function(&mut medium_sine, -10.0 + medium_offset, 10.0 + medium_offset, &f);
84        Plot::function(&mut small_sine, -10.0 + small_offset, 10.0 + small_offset, &f);
85
86        canvas.merge_canvas(&big_sine, 0, 12);
87        canvas.merge_canvas(&medium_sine, 0, 20);
88        canvas.merge_canvas(&small_sine, 0, 25);
89
90        canvas.draw_text(&format!("  i: {i}"), 0, 0);
91        canvas.draw_text("Fixed", 37, 0);
92
93        i += 1;
94
95        Some(canvas.to_string())
96    });
97}
98
99/// Will loop as fast as possible.
100fn with_variable_time_step() {
101    let mut canvas = TextCanvas::new(80, 16);
102
103    let mut big_sine = TextCanvas::new(80, 10);
104    let mut medium_sine = TextCanvas::new(80, 6);
105    let mut small_sine = TextCanvas::new(80, 4);
106
107    let f = |x: f64| x.sin();
108
109    let mut accumulator = 0.0;
110    #[rustfmt::skip]
111    GameLoop::loop_variable(&mut |delta_time| {
112        if accumulator >= 333.33 {
113            // Break out of the loop.
114            return None;
115        }
116
117        let big_offset = accumulator / 350.0 * 12.0;
118        let medium_offset = accumulator / 250.0 * 12.0;
119        let small_offset = accumulator / 150.0 * 12.0;
120
121        canvas.clear();
122
123        big_sine.clear();
124        medium_sine.clear();
125        small_sine.clear();
126
127        Plot::function(&mut big_sine, -10.0 + big_offset, 10.0 + big_offset, &f);
128        Plot::function(&mut medium_sine, -10.0 + medium_offset, 10.0 + medium_offset, &f);
129        Plot::function(&mut small_sine, -10.0 + small_offset, 10.0 + small_offset, &f);
130
131        canvas.merge_canvas(&big_sine, 0, 12);
132        canvas.merge_canvas(&medium_sine, 0, 20);
133        canvas.merge_canvas(&small_sine, 0, 25);
134
135        canvas.draw_text(&format!(" Δt: {delta_time:.9}"), 0, 0);
136        canvas.draw_text(&format!("acc: {accumulator:>11.7}"), 0, 1);
137        canvas.draw_text("Variable", 36, 0);
138
139        accumulator += 100.0 * delta_time;
140
141        Some(canvas.to_string())
142    });
143}

Trait Implementations§

Source§

impl Debug for TextCanvas

Source§

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

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

impl Default for TextCanvas

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl Display for TextCanvas

Source§

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

Formats the value using the given formatter. Read more

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> 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> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

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

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.