aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2024-07-01 23:18:15 +0000
committerGitHub <[email protected]>2024-07-01 23:18:15 +0000
commit00babd2ec4c618f910706e50cb2b0742bb9de6dd (patch)
treedfbe22c0eb8ce042b9ed0d1945b5262ab66abd37 /examples
parent92eb6011d60b95a7c249212fef874ff952f98f78 (diff)
parent6edf7b4688361e165c2ea9af03df9725a89a853e (diff)
Merge pull request #3126 from ninjasource/stm32-ltdc
Add support for the stm32 LTDC display peripheral
Diffstat (limited to 'examples')
-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.rs467
6 files changed, 576 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..a36fdef2c
--- /dev/null
+++ b/examples/stm32h735/src/bin/ltdc.rs
@@ -0,0 +1,467 @@
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::gpio::{Level, Output, Speed};
15use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge};
16use embassy_stm32::{bind_interrupts, peripherals};
17use embassy_time::{Duration, Timer};
18use embedded_graphics::draw_target::DrawTarget;
19use embedded_graphics::geometry::{OriginDimensions, Point, Size};
20use embedded_graphics::image::Image;
21use embedded_graphics::pixelcolor::raw::RawU24;
22use embedded_graphics::pixelcolor::Rgb888;
23use embedded_graphics::prelude::*;
24use embedded_graphics::primitives::Rectangle;
25use embedded_graphics::Pixel;
26use heapless::{Entry, FnvIndexMap};
27use tinybmp::Bmp;
28use {defmt_rtt as _, panic_probe as _};
29
30const DISPLAY_WIDTH: usize = 480;
31const DISPLAY_HEIGHT: usize = 272;
32const MY_TASK_POOL_SIZE: usize = 2;
33
34// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu
35pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT];
36pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT];
37
38bind_interrupts!(struct Irqs {
39 LTDC => ltdc::InterruptHandler<peripherals::LTDC>;
40});
41
42const NUM_COLORS: usize = 256;
43
44#[embassy_executor::main]
45async fn main(spawner: Spawner) {
46 let p = rcc_setup::stm32h735g_init();
47
48 // blink the led on another task
49 let led = Output::new(p.PC3, Level::High, Speed::Low);
50 unwrap!(spawner.spawn(led_task(led)));
51
52 // numbers from STMicroelectronics/STM32CubeH7 STM32H735G-DK C-based example
53 const RK043FN48H_HSYNC: u16 = 41; // Horizontal synchronization
54 const RK043FN48H_HBP: u16 = 13; // Horizontal back porch
55 const RK043FN48H_HFP: u16 = 32; // Horizontal front porch
56 const RK043FN48H_VSYNC: u16 = 10; // Vertical synchronization
57 const RK043FN48H_VBP: u16 = 2; // Vertical back porch
58 const RK043FN48H_VFP: u16 = 2; // Vertical front porch
59
60 let ltdc_config = LtdcConfiguration {
61 active_width: DISPLAY_WIDTH as _,
62 active_height: DISPLAY_HEIGHT as _,
63 h_back_porch: RK043FN48H_HBP - 11, // -11 from MX_LTDC_Init
64 h_front_porch: RK043FN48H_HFP,
65 v_back_porch: RK043FN48H_VBP,
66 v_front_porch: RK043FN48H_VFP,
67 h_sync: RK043FN48H_HSYNC,
68 v_sync: RK043FN48H_VSYNC,
69 h_sync_polarity: PolarityActive::ActiveLow,
70 v_sync_polarity: PolarityActive::ActiveLow,
71 data_enable_polarity: PolarityActive::ActiveHigh,
72 pixel_clock_polarity: PolarityEdge::FallingEdge,
73 };
74
75 info!("init ltdc");
76 let mut ltdc = Ltdc::new_with_pins(
77 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,
78 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,
79 );
80 ltdc.init(&ltdc_config);
81
82 // we only need to draw on one layer for this example (not to be confused with the double buffer)
83 info!("enable bottom layer");
84 let layer_config = LtdcLayerConfig {
85 pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel
86 layer: LtdcLayer::Layer1,
87 window_x0: 0,
88 window_x1: DISPLAY_WIDTH as _,
89 window_y0: 0,
90 window_y1: DISPLAY_HEIGHT as _,
91 };
92
93 let ferris_bmp: Bmp<Rgb888> = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap();
94 let color_map = build_color_lookup_map(&ferris_bmp);
95 let clut = build_clut(&color_map);
96
97 // enable the bottom layer with a 256 color lookup table
98 ltdc.init_layer(&layer_config, Some(&clut));
99
100 // Safety: the DoubleBuffer controls access to the statically allocated frame buffers
101 // and it is the only thing that mutates their content
102 let mut double_buffer = DoubleBuffer::new(
103 unsafe { FB1.as_mut() },
104 unsafe { FB2.as_mut() },
105 layer_config,
106 color_map,
107 );
108
109 // this allows us to perform some simple animation for every frame
110 let mut bouncy_box = BouncyBox::new(
111 ferris_bmp.bounding_box(),
112 Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)),
113 2,
114 );
115
116 loop {
117 // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen
118 double_buffer.clear();
119 let position = bouncy_box.next_point();
120 let ferris = Image::new(&ferris_bmp, position);
121 unwrap!(ferris.draw(&mut double_buffer));
122
123 // perform async dma data transfer to the lcd screen
124 unwrap!(double_buffer.swap(&mut ltdc).await);
125 }
126}
127
128/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work.
129fn build_color_lookup_map(bmp: &Bmp<Rgb888>) -> FnvIndexMap<u32, u8, NUM_COLORS> {
130 let mut color_map: FnvIndexMap<u32, u8, NUM_COLORS> = heapless::FnvIndexMap::new();
131 let mut counter: u8 = 0;
132
133 // add black to position 0
134 color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap();
135 counter += 1;
136
137 for Pixel(_point, color) in bmp.pixels() {
138 let raw = color.into_storage();
139 if let Entry::Vacant(v) = color_map.entry(raw) {
140 v.insert(counter).expect("more than 256 colors detected");
141 counter += 1;
142 }
143 }
144 color_map
145}
146
147/// builds the color look-up table from the color map provided
148fn build_clut(color_map: &FnvIndexMap<u32, u8, NUM_COLORS>) -> [ltdc::RgbColor; NUM_COLORS] {
149 let mut clut = [ltdc::RgbColor::default(); NUM_COLORS];
150 for (color, index) in color_map.iter() {
151 let color = Rgb888::from(RawU24::new(*color));
152 clut[*index as usize] = ltdc::RgbColor {
153 red: color.r(),
154 green: color.g(),
155 blue: color.b(),
156 };
157 }
158
159 clut
160}
161
162#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)]
163async fn led_task(mut led: Output<'static>) {
164 let mut counter = 0;
165 loop {
166 info!("blink: {}", counter);
167 counter += 1;
168
169 // on
170 led.set_low();
171 Timer::after(Duration::from_millis(50)).await;
172
173 // off
174 led.set_high();
175 Timer::after(Duration::from_millis(450)).await;
176 }
177}
178
179pub type TargetPixelType = u8;
180
181// A simple double buffer
182pub struct DoubleBuffer {
183 buf0: &'static mut [TargetPixelType],
184 buf1: &'static mut [TargetPixelType],
185 is_buf0: bool,
186 layer_config: LtdcLayerConfig,
187 color_map: FnvIndexMap<u32, u8, NUM_COLORS>,
188}
189
190impl DoubleBuffer {
191 pub fn new(
192 buf0: &'static mut [TargetPixelType],
193 buf1: &'static mut [TargetPixelType],
194 layer_config: LtdcLayerConfig,
195 color_map: FnvIndexMap<u32, u8, NUM_COLORS>,
196 ) -> Self {
197 Self {
198 buf0,
199 buf1,
200 is_buf0: true,
201 layer_config,
202 color_map,
203 }
204 }
205
206 pub fn current(&mut self) -> (&FnvIndexMap<u32, u8, NUM_COLORS>, &mut [TargetPixelType]) {
207 if self.is_buf0 {
208 (&self.color_map, self.buf0)
209 } else {
210 (&self.color_map, self.buf1)
211 }
212 }
213
214 pub async fn swap<T: ltdc::Instance>(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> {
215 let (_, buf) = self.current();
216 let frame_buffer = buf.as_ptr();
217 self.is_buf0 = !self.is_buf0;
218 ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await
219 }
220
221 /// Clears the buffer
222 pub fn clear(&mut self) {
223 let (color_map, buf) = self.current();
224 let black = Rgb888::new(0, 0, 0).into_storage();
225 let color_index = color_map.get(&black).expect("no black found in the color map");
226
227 for a in buf.iter_mut() {
228 *a = *color_index; // solid black
229 }
230 }
231}
232
233// Implement DrawTarget for
234impl DrawTarget for DoubleBuffer {
235 type Color = Rgb888;
236 type Error = ();
237
238 /// Draw a pixel
239 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
240 where
241 I: IntoIterator<Item = Pixel<Self::Color>>,
242 {
243 let size = self.size();
244 let width = size.width as i32;
245 let height = size.height as i32;
246 let (color_map, buf) = self.current();
247
248 for pixel in pixels {
249 let Pixel(point, color) = pixel;
250
251 if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height {
252 let index = point.y * width + point.x;
253 let raw_color = color.into_storage();
254
255 match color_map.get(&raw_color) {
256 Some(x) => {
257 buf[index as usize] = *x;
258 }
259 None => panic!("color not found in color map: {}", raw_color),
260 };
261 } else {
262 // Ignore invalid points
263 }
264 }
265
266 Ok(())
267 }
268}
269
270impl OriginDimensions for DoubleBuffer {
271 /// Return the size of the display
272 fn size(&self) -> Size {
273 Size::new(
274 (self.layer_config.window_x1 - self.layer_config.window_x0) as _,
275 (self.layer_config.window_y1 - self.layer_config.window_y0) as _,
276 )
277 }
278}
279
280mod rcc_setup {
281
282 use embassy_stm32::rcc::{Hse, HseMode, *};
283 use embassy_stm32::time::Hertz;
284 use embassy_stm32::{Config, Peripherals};
285
286 /// Sets up clocks for the stm32h735g mcu
287 /// change this if you plan to use a different microcontroller
288 pub fn stm32h735g_init() -> Peripherals {
289 /*
290 https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Projects/STM32H735G-DK/Examples/GPIO/GPIO_EXTI/Src/main.c
291 @brief System Clock Configuration
292 The system Clock is configured as follow :
293 System Clock source = PLL (HSE)
294 SYSCLK(Hz) = 520000000 (CPU Clock)
295 HCLK(Hz) = 260000000 (AXI and AHBs Clock)
296 AHB Prescaler = 2
297 D1 APB3 Prescaler = 2 (APB3 Clock 130MHz)
298 D2 APB1 Prescaler = 2 (APB1 Clock 130MHz)
299 D2 APB2 Prescaler = 2 (APB2 Clock 130MHz)
300 D3 APB4 Prescaler = 2 (APB4 Clock 130MHz)
301 HSE Frequency(Hz) = 25000000
302 PLL_M = 5
303 PLL_N = 104
304 PLL_P = 1
305 PLL_Q = 4
306 PLL_R = 2
307 VDD(V) = 3.3
308 Flash Latency(WS) = 3
309 */
310
311 // setup power and clocks for an stm32h735g-dk run from an external 25 Mhz external oscillator
312 let mut config = Config::default();
313 config.rcc.hse = Some(Hse {
314 freq: Hertz::mhz(25),
315 mode: HseMode::Oscillator,
316 });
317 config.rcc.hsi = None;
318 config.rcc.csi = false;
319 config.rcc.pll1 = Some(Pll {
320 source: PllSource::HSE,
321 prediv: PllPreDiv::DIV5, // PLL_M
322 mul: PllMul::MUL104, // PLL_N
323 divp: Some(PllDiv::DIV1),
324 divq: Some(PllDiv::DIV4),
325 divr: Some(PllDiv::DIV2),
326 });
327 // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_ospi.c
328 // MX_OSPI_ClockConfig
329 config.rcc.pll2 = Some(Pll {
330 source: PllSource::HSE,
331 prediv: PllPreDiv::DIV5, // PLL_M
332 mul: PllMul::MUL80, // PLL_N
333 divp: Some(PllDiv::DIV5),
334 divq: Some(PllDiv::DIV2),
335 divr: Some(PllDiv::DIV2),
336 });
337 // numbers adapted from Drivers/BSP/STM32H735G-DK/stm32h735g_discovery_lcd.c
338 // MX_LTDC_ClockConfig
339 config.rcc.pll3 = Some(Pll {
340 source: PllSource::HSE,
341 prediv: PllPreDiv::DIV5, // PLL_M
342 mul: PllMul::MUL160, // PLL_N
343 divp: Some(PllDiv::DIV2),
344 divq: Some(PllDiv::DIV2),
345 divr: Some(PllDiv::DIV83),
346 });
347 config.rcc.voltage_scale = VoltageScale::Scale0;
348 config.rcc.supply_config = SupplyConfig::DirectSMPS;
349 config.rcc.sys = Sysclk::PLL1_P;
350 config.rcc.ahb_pre = AHBPrescaler::DIV2;
351 config.rcc.apb1_pre = APBPrescaler::DIV2;
352 config.rcc.apb2_pre = APBPrescaler::DIV2;
353 config.rcc.apb3_pre = APBPrescaler::DIV2;
354 config.rcc.apb4_pre = APBPrescaler::DIV2;
355 embassy_stm32::init(config)
356 }
357}
358
359mod bouncy_box {
360 use embedded_graphics::geometry::Point;
361 use embedded_graphics::primitives::Rectangle;
362
363 enum Direction {
364 DownLeft,
365 DownRight,
366 UpLeft,
367 UpRight,
368 }
369
370 pub struct BouncyBox {
371 direction: Direction,
372 child_rect: Rectangle,
373 parent_rect: Rectangle,
374 current_point: Point,
375 move_by: usize,
376 }
377
378 // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box
379 impl BouncyBox {
380 pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self {
381 let center_box = parent_rect.center();
382 let center_img = child_rect.center();
383 let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2);
384 Self {
385 direction: Direction::DownRight,
386 child_rect,
387 parent_rect,
388 current_point,
389 move_by,
390 }
391 }
392
393 pub fn next_point(&mut self) -> Point {
394 let direction = &self.direction;
395 let img_height = self.child_rect.size.height as i32;
396 let box_height = self.parent_rect.size.height as i32;
397 let img_width = self.child_rect.size.width as i32;
398 let box_width = self.parent_rect.size.width as i32;
399 let move_by = self.move_by as i32;
400
401 match direction {
402 Direction::DownLeft => {
403 self.current_point.x -= move_by;
404 self.current_point.y += move_by;
405
406 let x_out_of_bounds = self.current_point.x < 0;
407 let y_out_of_bounds = (self.current_point.y + img_height) > box_height;
408
409 if x_out_of_bounds && y_out_of_bounds {
410 self.direction = Direction::UpRight
411 } else if x_out_of_bounds && !y_out_of_bounds {
412 self.direction = Direction::DownRight
413 } else if !x_out_of_bounds && y_out_of_bounds {
414 self.direction = Direction::UpLeft
415 }
416 }
417 Direction::DownRight => {
418 self.current_point.x += move_by;
419 self.current_point.y += move_by;
420
421 let x_out_of_bounds = (self.current_point.x + img_width) > box_width;
422 let y_out_of_bounds = (self.current_point.y + img_height) > box_height;
423
424 if x_out_of_bounds && y_out_of_bounds {
425 self.direction = Direction::UpLeft
426 } else if x_out_of_bounds && !y_out_of_bounds {
427 self.direction = Direction::DownLeft
428 } else if !x_out_of_bounds && y_out_of_bounds {
429 self.direction = Direction::UpRight
430 }
431 }
432 Direction::UpLeft => {
433 self.current_point.x -= move_by;
434 self.current_point.y -= move_by;
435
436 let x_out_of_bounds = self.current_point.x < 0;
437 let y_out_of_bounds = self.current_point.y < 0;
438
439 if x_out_of_bounds && y_out_of_bounds {
440 self.direction = Direction::DownRight
441 } else if x_out_of_bounds && !y_out_of_bounds {
442 self.direction = Direction::UpRight
443 } else if !x_out_of_bounds && y_out_of_bounds {
444 self.direction = Direction::DownLeft
445 }
446 }
447 Direction::UpRight => {
448 self.current_point.x += move_by;
449 self.current_point.y -= move_by;
450
451 let x_out_of_bounds = (self.current_point.x + img_width) > box_width;
452 let y_out_of_bounds = self.current_point.y < 0;
453
454 if x_out_of_bounds && y_out_of_bounds {
455 self.direction = Direction::DownLeft
456 } else if x_out_of_bounds && !y_out_of_bounds {
457 self.direction = Direction::UpLeft
458 } else if !x_out_of_bounds && y_out_of_bounds {
459 self.direction = Direction::DownRight
460 }
461 }
462 }
463
464 self.current_point
465 }
466 }
467}