aboutsummaryrefslogtreecommitdiff
path: root/examples/stm32h735
diff options
context:
space:
mode:
authorDavid Haig <[email protected]>2024-06-28 18:11:34 +0100
committerDavid Haig <[email protected]>2024-06-28 18:11:34 +0100
commit79f00e54cc4d13a28c7ccc9a13345bf2f3730f42 (patch)
treef84018216825751ae22401bda35ccc2490efd602 /examples/stm32h735
parent1123e3fd41e7b23dd0507816f1ff67fc0de6b5d1 (diff)
Moved ltdc example to its own crate
Diffstat (limited to 'examples/stm32h735')
-rw-r--r--examples/stm32h735/.cargo/config.toml8
-rw-r--r--examples/stm32h735/Cargo.toml61
-rw-r--r--examples/stm32h735/build.rs35
-rw-r--r--examples/stm32h735/memory.x5
-rw-r--r--examples/stm32h735/src/bin/ferris.bmpbin0 -> 6794 bytes
-rw-r--r--examples/stm32h735/src/bin/ltdc.rs473
6 files changed, 582 insertions, 0 deletions
diff --git a/examples/stm32h735/.cargo/config.toml b/examples/stm32h735/.cargo/config.toml
new file mode 100644
index 000000000..95536c6a8
--- /dev/null
+++ b/examples/stm32h735/.cargo/config.toml
@@ -0,0 +1,8 @@
1[target.thumbv7em-none-eabihf]
2runner = 'probe-rs run --chip STM32H735IGKx'
3
4[build]
5target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
6
7[env]
8DEFMT_LOG = "trace"
diff --git a/examples/stm32h735/Cargo.toml b/examples/stm32h735/Cargo.toml
new file mode 100644
index 000000000..fc21cc894
--- /dev/null
+++ b/examples/stm32h735/Cargo.toml
@@ -0,0 +1,61 @@
1[package]
2edition = "2021"
3name = "embassy-stm32h735-examples"
4version = "0.1.0"
5license = "MIT OR Apache-2.0"
6
7[dependencies]
8embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] }
9embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] }
10embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
11embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
12embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
13embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
14
15defmt = "0.3"
16defmt-rtt = "0.4"
17
18cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
19cortex-m-rt = "0.7.0"
20panic-probe = { version = "0.3", features = ["print-defmt"] }
21heapless = { version = "0.8", default-features = false }
22embedded-graphics = { version = "0.8.1" }
23tinybmp = { version = "0.5" }
24
25# cargo build/run
26[profile.dev]
27codegen-units = 1
28debug = 2
29debug-assertions = true # <-
30incremental = false
31opt-level = 3 # <-
32overflow-checks = true # <-
33
34# cargo test
35[profile.test]
36codegen-units = 1
37debug = 2
38debug-assertions = true # <-
39incremental = false
40opt-level = 3 # <-
41overflow-checks = true # <-
42
43# cargo build/run --release
44[profile.release]
45codegen-units = 1
46debug = 2
47debug-assertions = false # <-
48incremental = false
49lto = 'fat'
50opt-level = 3 # <-
51overflow-checks = false # <-
52
53# cargo test --release
54[profile.bench]
55codegen-units = 1
56debug = 2
57debug-assertions = false # <-
58incremental = false
59lto = 'fat'
60opt-level = 3 # <-
61overflow-checks = false # <-
diff --git a/examples/stm32h735/build.rs b/examples/stm32h735/build.rs
new file mode 100644
index 000000000..30691aa97
--- /dev/null
+++ b/examples/stm32h735/build.rs
@@ -0,0 +1,35 @@
1//! This build script copies the `memory.x` file from the crate root into
2//! a directory where the linker can always find it at build time.
3//! For many projects this is optional, as the linker always searches the
4//! project root directory -- wherever `Cargo.toml` is. However, if you
5//! are using a workspace or have a more complicated build setup, this
6//! build script becomes required. Additionally, by requesting that
7//! Cargo re-run the build script whenever `memory.x` is changed,
8//! updating `memory.x` ensures a rebuild of the application with the
9//! new memory settings.
10
11use std::env;
12use std::fs::File;
13use std::io::Write;
14use std::path::PathBuf;
15
16fn main() {
17 // Put `memory.x` in our output directory and ensure it's
18 // on the linker search path.
19 let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
20 File::create(out.join("memory.x"))
21 .unwrap()
22 .write_all(include_bytes!("memory.x"))
23 .unwrap();
24 println!("cargo:rustc-link-search={}", out.display());
25
26 // By default, Cargo will re-run a build script whenever
27 // any file in the project changes. By specifying `memory.x`
28 // here, we ensure the build script is only re-run when
29 // `memory.x` is changed.
30 println!("cargo:rerun-if-changed=memory.x");
31
32 println!("cargo:rustc-link-arg-bins=--nmagic");
33 println!("cargo:rustc-link-arg-bins=-Tlink.x");
34 println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
35}
diff --git a/examples/stm32h735/memory.x b/examples/stm32h735/memory.x
new file mode 100644
index 000000000..3a70d24d2
--- /dev/null
+++ b/examples/stm32h735/memory.x
@@ -0,0 +1,5 @@
1MEMORY
2{
3 FLASH : ORIGIN = 0x08000000, LENGTH = 1024K
4 RAM : ORIGIN = 0x24000000, LENGTH = 320K
5} \ No newline at end of file
diff --git a/examples/stm32h735/src/bin/ferris.bmp b/examples/stm32h735/src/bin/ferris.bmp
new file mode 100644
index 000000000..7a222ab84
--- /dev/null
+++ b/examples/stm32h735/src/bin/ferris.bmp
Binary files differ
diff --git a/examples/stm32h735/src/bin/ltdc.rs b/examples/stm32h735/src/bin/ltdc.rs
new file mode 100644
index 000000000..5c75a7db1
--- /dev/null
+++ b/examples/stm32h735/src/bin/ltdc.rs
@@ -0,0 +1,473 @@
1#![no_std]
2#![no_main]
3#![macro_use]
4#![allow(static_mut_refs)]
5
6/// This example demonstrates the LTDC lcd display peripheral and was tested to run on an stm32h735g-dk (embassy-stm32 feature "stm32h735ig" and probe-rs chip "STM32H735IGKx")
7/// Even though the dev kit has 16MB of attached PSRAM this example uses the 320KB of internal AXIS RAM found on the mcu itself to make the example more standalone and portable.
8/// For this reason a 256 color lookup table had to be used to keep the memory requirement down to an acceptable level.
9/// The example bounces a ferris crab bitmap around the screen while blinking an led on another task
10///
11use bouncy_box::BouncyBox;
12use defmt::{info, unwrap};
13use embassy_executor::Spawner;
14use embassy_stm32::{
15 bind_interrupts,
16 gpio::{Level, Output, Speed},
17 ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge},
18 peripherals,
19};
20use embassy_time::{Duration, Timer};
21use embedded_graphics::{
22 draw_target::DrawTarget,
23 geometry::{OriginDimensions, Point, Size},
24 image::Image,
25 pixelcolor::{raw::RawU24, Rgb888},
26 prelude::*,
27 primitives::Rectangle,
28 Pixel,
29};
30use heapless::{Entry, FnvIndexMap};
31use tinybmp::Bmp;
32use {defmt_rtt as _, panic_probe as _};
33
34const DISPLAY_WIDTH: usize = 480;
35const DISPLAY_HEIGHT: usize = 272;
36const MY_TASK_POOL_SIZE: usize = 2;
37
38// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu
39pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT];
40pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT];
41
42bind_interrupts!(struct Irqs {
43 LTDC => ltdc::InterruptHandler<peripherals::LTDC>;
44});
45
46const NUM_COLORS: usize = 256;
47
48#[embassy_executor::main]
49async fn main(spawner: Spawner) {
50 let p = rcc_setup::stm32h735g_init();
51
52 // blink the led on another task
53 let led = Output::new(p.PC3, Level::High, Speed::Low);
54 unwrap!(spawner.spawn(led_task(led)));
55
56 // numbers from STMicroelectronics/STM32CubeH7 STM32H735G-DK C-based example
57 const RK043FN48H_HSYNC: u16 = 41; // Horizontal synchronization
58 const RK043FN48H_HBP: u16 = 13; // Horizontal back porch
59 const RK043FN48H_HFP: u16 = 32; // Horizontal front porch
60 const RK043FN48H_VSYNC: u16 = 10; // Vertical synchronization
61 const RK043FN48H_VBP: u16 = 2; // Vertical back porch
62 const RK043FN48H_VFP: u16 = 2; // Vertical front porch
63
64 let ltdc_config = LtdcConfiguration {
65 active_width: DISPLAY_WIDTH as _,
66 active_height: DISPLAY_HEIGHT as _,
67 h_back_porch: RK043FN48H_HBP - 11, // -11 from MX_LTDC_Init
68 h_front_porch: RK043FN48H_HFP,
69 v_back_porch: RK043FN48H_VBP,
70 v_front_porch: RK043FN48H_VFP,
71 h_sync: RK043FN48H_HSYNC,
72 v_sync: RK043FN48H_VSYNC,
73 h_sync_polarity: PolarityActive::ActiveLow,
74 v_sync_polarity: PolarityActive::ActiveLow,
75 data_enable_polarity: PolarityActive::ActiveHigh,
76 pixel_clock_polarity: PolarityEdge::FallingEdge,
77 };
78
79 info!("init ltdc");
80 let mut ltdc = Ltdc::new_with_pins(
81 p.LTDC, Irqs, p.PG7, p.PC6, p.PA4, p.PG14, p.PD0, p.PD6, p.PA8, p.PE12, p.PA3, p.PB8, p.PB9, p.PB1, p.PB0,
82 p.PA6, p.PE11, p.PH15, p.PH4, p.PC7, p.PD3, p.PE0, p.PH3, p.PH8, p.PH9, p.PH10, p.PH11, p.PE1, p.PE15,
83 );
84 ltdc.init(&ltdc_config);
85
86 // we only need to draw on one layer for this example (not to be confused with the double buffer)
87 info!("enable bottom layer");
88 let layer_config = LtdcLayerConfig {
89 pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel
90 layer: LtdcLayer::Layer1,
91 window_x0: 0,
92 window_x1: DISPLAY_WIDTH as _,
93 window_y0: 0,
94 window_y1: DISPLAY_HEIGHT as _,
95 };
96
97 let ferris_bmp: Bmp<Rgb888> = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap();
98 let color_map = build_color_lookup_map(&ferris_bmp);
99 let clut = build_clut(&color_map);
100
101 // enable the bottom layer with a 256 color lookup table
102 ltdc.init_layer(&layer_config, Some(&clut));
103
104 // Safety: the DoubleBuffer controls access to the statically allocated frame buffers
105 // and it is the only thing that mutates their content
106 let mut double_buffer = DoubleBuffer::new(
107 unsafe { FB1.as_mut() },
108 unsafe { FB2.as_mut() },
109 layer_config,
110 color_map,
111 );
112
113 // this allows us to perform some simple animation for every frame
114 let mut bouncy_box = BouncyBox::new(
115 ferris_bmp.bounding_box(),
116 Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)),
117 2,
118 );
119
120 loop {
121 // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen
122 double_buffer.clear();
123 let position = bouncy_box.next_point();
124 let ferris = Image::new(&ferris_bmp, position);
125 unwrap!(ferris.draw(&mut double_buffer));
126
127 // perform async dma data transfer to the lcd screen
128 unwrap!(double_buffer.swap(&mut ltdc).await);
129 }
130}
131
132/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work.
133fn build_color_lookup_map(bmp: &Bmp<Rgb888>) -> FnvIndexMap<u32, u8, NUM_COLORS> {
134 let mut color_map: FnvIndexMap<u32, u8, NUM_COLORS> = heapless::FnvIndexMap::new();
135 let mut counter: u8 = 0;
136
137 // add black to position 0
138 color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap();
139 counter += 1;
140
141 for Pixel(_point, color) in bmp.pixels() {
142 let raw = color.into_storage();
143 if let Entry::Vacant(v) = color_map.entry(raw) {
144 v.insert(counter).expect("more than 256 colors detected");
145 counter += 1;
146 }
147 }
148 color_map
149}
150
151/// builds the color look-up table from the color map provided
152fn build_clut(color_map: &FnvIndexMap<u32, u8, NUM_COLORS>) -> [ltdc::RgbColor; NUM_COLORS] {
153 let mut clut = [ltdc::RgbColor::default(); NUM_COLORS];
154 for (color, index) in color_map.iter() {
155 let color = Rgb888::from(RawU24::new(*color));
156 clut[*index as usize] = ltdc::RgbColor {
157 red: color.r(),
158 green: color.g(),
159 blue: color.b(),
160 };
161 }
162
163 clut
164}
165
166#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)]
167async fn led_task(mut led: Output<'static>) {
168 let mut counter = 0;
169 loop {
170 info!("blink: {}", counter);
171 counter += 1;
172
173 // on
174 led.set_low();
175 Timer::after(Duration::from_millis(50)).await;
176
177 // off
178 led.set_high();
179 Timer::after(Duration::from_millis(450)).await;
180 }
181}
182
183pub type TargetPixelType = u8;
184
185// A simple double buffer
186pub struct DoubleBuffer {
187 buf0: &'static mut [TargetPixelType],
188 buf1: &'static mut [TargetPixelType],
189 is_buf0: bool,
190 layer_config: LtdcLayerConfig,
191 color_map: FnvIndexMap<u32, u8, NUM_COLORS>,
192}
193
194impl DoubleBuffer {
195 pub fn new(
196 buf0: &'static mut [TargetPixelType],
197 buf1: &'static mut [TargetPixelType],
198 layer_config: LtdcLayerConfig,
199 color_map: FnvIndexMap<u32, u8, NUM_COLORS>,
200 ) -> Self {
201 Self {
202 buf0,
203 buf1,
204 is_buf0: true,
205 layer_config,
206 color_map,
207 }
208 }
209
210 pub fn current(&mut self) -> (&FnvIndexMap<u32, u8, NUM_COLORS>, &mut [TargetPixelType]) {
211 if self.is_buf0 {
212 (&self.color_map, self.buf0)
213 } else {
214 (&self.color_map, self.buf1)
215 }
216 }
217
218 pub async fn swap<T: ltdc::Instance>(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> {
219 let (_, buf) = self.current();
220 let frame_buffer = buf.as_ptr();
221 self.is_buf0 = !self.is_buf0;
222 ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await
223 }
224
225 /// Clears the buffer
226 pub fn clear(&mut self) {
227 let (color_map, buf) = self.current();
228 let black = Rgb888::new(0, 0, 0).into_storage();
229 let color_index = color_map.get(&black).expect("no black found in the color map");
230
231 for a in buf.iter_mut() {
232 *a = *color_index; // solid black
233 }
234 }
235}
236
237// Implement DrawTarget for
238impl DrawTarget for DoubleBuffer {
239 type Color = Rgb888;
240 type Error = ();
241
242 /// Draw a pixel
243 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
244 where
245 I: IntoIterator<Item = Pixel<Self::Color>>,
246 {
247 let size = self.size();
248 let width = size.width as i32;
249 let height = size.height as i32;
250 let (color_map, buf) = self.current();
251
252 for pixel in pixels {
253 let Pixel(point, color) = pixel;
254
255 if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height {
256 let index = point.y * width + point.x;
257 let raw_color = color.into_storage();
258
259 match color_map.get(&raw_color) {
260 Some(x) => {
261 buf[index as usize] = *x;
262 }
263 None => panic!("color not found in color map: {}", raw_color),
264 };
265 } else {
266 // Ignore invalid points
267 }
268 }
269
270 Ok(())
271 }
272}
273
274impl OriginDimensions for DoubleBuffer {
275 /// Return the size of the display
276 fn size(&self) -> Size {
277 Size::new(
278 (self.layer_config.window_x1 - self.layer_config.window_x0) as _,
279 (self.layer_config.window_y1 - self.layer_config.window_y0) as _,
280 )
281 }
282}
283
284mod rcc_setup {
285
286 use embassy_stm32::{rcc::*, Peripherals};
287 use embassy_stm32::{
288 rcc::{Hse, HseMode},
289 time::Hertz,
290 Config,
291 };
292
293 /// Sets up clocks for the stm32h735g mcu
294 /// change this if you plan to use a different microcontroller
295 pub fn stm32h735g_init() -> Peripherals {
296 /*
297 https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/STM32H735G-DK/Examples/GPIO/GPIO_EXTI/Src/main.c
298 @brief System Clock Configuration
299 The system Clock is configured as follow :
300 System Clock source = PLL (HSE)
301 SYSCLK(Hz) = 520000000 (CPU Clock)
302 HCLK(Hz) = 260000000 (AXI and AHBs Clock)
303 AHB Prescaler = 2
304 D1 APB3 Prescaler = 2 (APB3 Clock 130MHz)
305 D2 APB1 Prescaler = 2 (APB1 Clock 130MHz)
306 D2 APB2 Prescaler = 2 (APB2 Clock 130MHz)
307 D3 APB4 Prescaler = 2 (APB4 Clock 130MHz)
308 HSE Frequency(Hz) = 25000000
309 PLL_M = 5
310 PLL_N = 104
311 PLL_P = 1
312 PLL_Q = 4
313 PLL_R = 2
314 VDD(V) = 3.3
315 Flash Latency(WS) = 3
316 */
317
318 // setup power and clocks for an stm32h735g-dk run from an external 25 Mhz external oscillator
319 let mut config = Config::default();
320 config.rcc.hse = Some(Hse {
321 freq: Hertz::mhz(25),
322 mode: HseMode::Oscillator,
323 });
324 config.rcc.hsi = None;
325 config.rcc.csi = false;
326 config.rcc.pll1 = Some(Pll {
327 source: PllSource::HSE,
328 prediv: PllPreDiv::DIV5, // PLL_M
329 mul: PllMul::MUL104, // PLL_N
330 divp: Some(PllDiv::DIV1),
331 divq: Some(PllDiv::DIV4),
332 divr: Some(PllDiv::DIV2),
333 });
334 // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_ospi.c
335 // MX_OSPI_ClockConfig
336 config.rcc.pll2 = Some(Pll {
337 source: PllSource::HSE,
338 prediv: PllPreDiv::DIV5, // PLL_M
339 mul: PllMul::MUL80, // PLL_N
340 divp: Some(PllDiv::DIV5),
341 divq: Some(PllDiv::DIV2),
342 divr: Some(PllDiv::DIV2),
343 });
344 // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_lcd.c
345 // MX_LTDC_ClockConfig
346 config.rcc.pll3 = Some(Pll {
347 source: PllSource::HSE,
348 prediv: PllPreDiv::DIV5, // PLL_M
349 mul: PllMul::MUL160, // PLL_N
350 divp: Some(PllDiv::DIV2),
351 divq: Some(PllDiv::DIV2),
352 divr: Some(PllDiv::DIV83),
353 });
354 config.rcc.voltage_scale = VoltageScale::Scale0;
355 config.rcc.supply_config = SupplyConfig::DirectSMPS;
356 config.rcc.sys = Sysclk::PLL1_P;
357 config.rcc.ahb_pre = AHBPrescaler::DIV2;
358 config.rcc.apb1_pre = APBPrescaler::DIV2;
359 config.rcc.apb2_pre = APBPrescaler::DIV2;
360 config.rcc.apb3_pre = APBPrescaler::DIV2;
361 config.rcc.apb4_pre = APBPrescaler::DIV2;
362 embassy_stm32::init(config)
363 }
364}
365
366mod bouncy_box {
367 use embedded_graphics::{geometry::Point, primitives::Rectangle};
368
369 enum Direction {
370 DownLeft,
371 DownRight,
372 UpLeft,
373 UpRight,
374 }
375
376 pub struct BouncyBox {
377 direction: Direction,
378 child_rect: Rectangle,
379 parent_rect: Rectangle,
380 current_point: Point,
381 move_by: usize,
382 }
383
384 // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box
385 impl BouncyBox {
386 pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self {
387 let center_box = parent_rect.center();
388 let center_img = child_rect.center();
389 let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2);
390 Self {
391 direction: Direction::DownRight,
392 child_rect,
393 parent_rect,
394 current_point,
395 move_by,
396 }
397 }
398
399 pub fn next_point(&mut self) -> Point {
400 let direction = &self.direction;
401 let img_height = self.child_rect.size.height as i32;
402 let box_height = self.parent_rect.size.height as i32;
403 let img_width = self.child_rect.size.width as i32;
404 let box_width = self.parent_rect.size.width as i32;
405 let move_by = self.move_by as i32;
406
407 match direction {
408 Direction::DownLeft => {
409 self.current_point.x -= move_by;
410 self.current_point.y += move_by;
411
412 let x_out_of_bounds = self.current_point.x < 0;
413 let y_out_of_bounds = (self.current_point.y + img_height) > box_height;
414
415 if x_out_of_bounds && y_out_of_bounds {
416 self.direction = Direction::UpRight
417 } else if x_out_of_bounds && !y_out_of_bounds {
418 self.direction = Direction::DownRight
419 } else if !x_out_of_bounds && y_out_of_bounds {
420 self.direction = Direction::UpLeft
421 }
422 }
423 Direction::DownRight => {
424 self.current_point.x += move_by;
425 self.current_point.y += move_by;
426
427 let x_out_of_bounds = (self.current_point.x + img_width) > box_width;
428 let y_out_of_bounds = (self.current_point.y + img_height) > box_height;
429
430 if x_out_of_bounds && y_out_of_bounds {
431 self.direction = Direction::UpLeft
432 } else if x_out_of_bounds && !y_out_of_bounds {
433 self.direction = Direction::DownLeft
434 } else if !x_out_of_bounds && y_out_of_bounds {
435 self.direction = Direction::UpRight
436 }
437 }
438 Direction::UpLeft => {
439 self.current_point.x -= move_by;
440 self.current_point.y -= move_by;
441
442 let x_out_of_bounds = self.current_point.x < 0;
443 let y_out_of_bounds = self.current_point.y < 0;
444
445 if x_out_of_bounds && y_out_of_bounds {
446 self.direction = Direction::DownRight
447 } else if x_out_of_bounds && !y_out_of_bounds {
448 self.direction = Direction::UpRight
449 } else if !x_out_of_bounds && y_out_of_bounds {
450 self.direction = Direction::DownLeft
451 }
452 }
453 Direction::UpRight => {
454 self.current_point.x += move_by;
455 self.current_point.y -= move_by;
456
457 let x_out_of_bounds = (self.current_point.x + img_width) > box_width;
458 let y_out_of_bounds = self.current_point.y < 0;
459
460 if x_out_of_bounds && y_out_of_bounds {
461 self.direction = Direction::DownLeft
462 } else if x_out_of_bounds && !y_out_of_bounds {
463 self.direction = Direction::UpLeft
464 } else if !x_out_of_bounds && y_out_of_bounds {
465 self.direction = Direction::DownRight
466 }
467 }
468 }
469
470 self.current_point
471 }
472 }
473}