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
impl TextCanvas
Sourcepub fn new(width: i32, height: i32) -> Self
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 i32
s. 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?
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
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}
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}
Sourcepub fn new_auto() -> Result<Self, TextCanvasError>
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.
Sourcepub fn get_default_size() -> (i32, i32)
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.
Sourcepub fn get_auto_size() -> Result<(i32, i32), TextCanvasError>
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.
Sourcepub fn repr(&self) -> String
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)))"
);
Sourcepub fn clear(&mut self)
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?
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
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}
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}
Sourcepub fn fill(&mut self)
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.
Sourcepub fn invert(&mut self)
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.
Sourcepub fn is_colorized(&self) -> bool
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());
Sourcepub fn is_textual(&self) -> bool
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());
Sourcepub fn set_color(&mut self, color: &Color)
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?
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}
Sourcepub fn get_pixel(&self, x: i32, y: i32) -> Option<bool>
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).
Sourcepub fn set_pixel(&mut self, x: i32, y: i32, state: bool)
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.
Sourcepub fn draw_text(&mut self, text: &str, x: i32, y: i32)
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?
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
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}
pub fn draw_text_vertical(&mut self, text: &str, x: i32, y: i32)
Sourcepub fn merge_text(&mut self, text: &str, x: i32, y: i32)
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?
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 }
pub fn merge_text_vertical(&mut self, text: &str, x: i32, y: i32)
Sourcepub fn iter_buffer(&self) -> IterPixelBuffer<i32> ⓘ
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.
Sourcepub fn uiter_buffer(&self) -> IterPixelBuffer<usize> ⓘ
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.
impl TextCanvas
Implementation of drawing primitives.
Sourcepub fn stroke_line(&mut self, x1: i32, y1: i32, x2: i32, y2: i32)
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?
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}
Sourcepub fn stroke_rect(&mut self, x: i32, y: i32, width: i32, height: i32)
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(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢰⠒⠒⠒⠒⠒⠒⠒⠒⠒⡆⠀⠀
⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀
⠀⠀⠸⠤⠤⠤⠤⠤⠤⠤⠤⠤⠇⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
Sourcepub fn frame(&mut self)
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(),
"\
⡏⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⢹
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⣇⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣸
"
);
Sourcepub fn fill_rect(&mut self, x: i32, y: i32, width: i32, height: i32)
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(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢰⣶⣶⣶⣶⣶⣶⣶⣶⣶⡆⠀⠀
⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀
⠀⠀⠸⠿⠿⠿⠿⠿⠿⠿⠿⠿⠇⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
Sourcepub fn stroke_triangle(
&mut self,
x1: i32,
y1: i32,
x2: i32,
y2: i32,
x3: i32,
y3: i32,
)
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(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢰⠢⠤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢸⠀⠀⠀⠈⠉⢒⡢⠄⠀⠀⠀⠀
⠀⠀⡇⠀⣀⠤⠔⠊⠁⠀⠀⠀⠀⠀⠀
⠀⠀⠓⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
Sourcepub fn fill_triangle(
&mut self,
x1: i32,
y1: i32,
x2: i32,
y2: i32,
x3: i32,
y3: i32,
)
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(),
"\
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢰⣦⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⢸⣿⣿⣿⣿⣿⣶⡦⠄⠀⠀⠀⠀
⠀⠀⣿⣿⣿⠿⠟⠋⠁⠀⠀⠀⠀⠀⠀
⠀⠀⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
"
);
Sourcepub fn stroke_circle(&mut self, x: i32, y: i32, radius: i32)
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(),
"\
⠀⠀⠀⠀⠀⠀⣀⣀⣀⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⡠⠊⠀⠀⠀⠈⠢⡀⠀⠀⠀
⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀
⠀⠀⠀⠀⠣⡀⠀⠀⠀⠀⡠⠃⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠒⠒⠒⠊⠀⠀⠀⠀⠀
"
);
Sourcepub fn fill_circle(&mut self, x: i32, y: i32, radius: i32)
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(),
"\
⠀⠀⠀⠀⠀⠀⣀⣀⣀⡀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣦⡀⠀⠀⠀
⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀
⠀⠀⠀⠀⠻⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠛⠛⠛⠋⠀⠀⠀⠀⠀
"
);
Sourcepub fn stroke_ngon(
&mut self,
x: i32,
y: i32,
radius: i32,
sides: i32,
angle: f64,
)
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.
Sourcepub fn draw_canvas(&mut self, canvas: &Self, dx: i32, dy: i32)
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(),
"\
⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⠊
⠀⠀⠀⠑⡏⠉⠉⠉⠉⠉⢹⠊⠀⠀⠀
⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀
⠀⠀⠀⡠⣇⣀⣀⣀⣀⣀⣸⢄⠀⠀⠀
⡠⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠢⢄
"
);
Sourcepub fn merge_canvas(&mut self, canvas: &Self, dx: i32, dy: i32)
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?
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}