diff options
| author | William <[email protected]> | 2024-10-25 15:40:18 +0200 |
|---|---|---|
| committer | William <[email protected]> | 2024-10-25 15:40:18 +0200 |
| commit | be5222421177b632cd120272e08156b64cc0b886 (patch) | |
| tree | 060f09df5e8625eaa87d353c06535d7cdef1cf55 /examples/stm32u5/src | |
| parent | 45e7a7a55aa6b5b8d41d81949b75b1b4a154f9bd (diff) | |
Add LTDC example for STM32U5G9J-DK2 demo board
Diffstat (limited to 'examples/stm32u5/src')
| -rw-r--r-- | examples/stm32u5/src/bin/ferris.bmp | bin | 0 -> 6794 bytes | |||
| -rw-r--r-- | examples/stm32u5/src/bin/ltdc.rs | 462 |
2 files changed, 462 insertions, 0 deletions
diff --git a/examples/stm32u5/src/bin/ferris.bmp b/examples/stm32u5/src/bin/ferris.bmp new file mode 100644 index 000000000..7a222ab84 --- /dev/null +++ b/examples/stm32u5/src/bin/ferris.bmp | |||
| Binary files differ | |||
diff --git a/examples/stm32u5/src/bin/ltdc.rs b/examples/stm32u5/src/bin/ltdc.rs new file mode 100644 index 000000000..5bf2cd67d --- /dev/null +++ b/examples/stm32u5/src/bin/ltdc.rs | |||
| @@ -0,0 +1,462 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![macro_use] | ||
| 4 | #![allow(static_mut_refs)] | ||
| 5 | |||
| 6 | /// This example was derived from examples\stm32h735\src\bin\ltdc.rs | ||
| 7 | /// It demonstrates the LTDC lcd display peripheral and was tested on an STM32U5G9J-DK2 demo board (embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ") | ||
| 8 | /// | ||
| 9 | use bouncy_box::BouncyBox; | ||
| 10 | use defmt::{info, unwrap}; | ||
| 11 | use embassy_executor::Spawner; | ||
| 12 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 13 | use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}; | ||
| 14 | use embassy_stm32::{bind_interrupts, peripherals}; | ||
| 15 | use embassy_time::{Duration, Timer}; | ||
| 16 | use embedded_graphics::draw_target::DrawTarget; | ||
| 17 | use embedded_graphics::geometry::{OriginDimensions, Point, Size}; | ||
| 18 | use embedded_graphics::image::Image; | ||
| 19 | use embedded_graphics::pixelcolor::raw::RawU24; | ||
| 20 | use embedded_graphics::pixelcolor::Rgb888; | ||
| 21 | use embedded_graphics::prelude::*; | ||
| 22 | use embedded_graphics::primitives::Rectangle; | ||
| 23 | use embedded_graphics::Pixel; | ||
| 24 | use heapless::{Entry, FnvIndexMap}; | ||
| 25 | use tinybmp::Bmp; | ||
| 26 | use {defmt_rtt as _, panic_probe as _}; | ||
| 27 | |||
| 28 | const DISPLAY_WIDTH: usize = 800; | ||
| 29 | const DISPLAY_HEIGHT: usize = 480; | ||
| 30 | const MY_TASK_POOL_SIZE: usize = 2; | ||
| 31 | |||
| 32 | // the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu | ||
| 33 | pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; | ||
| 34 | pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; | ||
| 35 | |||
| 36 | bind_interrupts!(struct Irqs { | ||
| 37 | LTDC => ltdc::InterruptHandler<peripherals::LTDC>; | ||
| 38 | }); | ||
| 39 | |||
| 40 | const NUM_COLORS: usize = 256; | ||
| 41 | |||
| 42 | #[embassy_executor::main] | ||
| 43 | async fn main(spawner: Spawner) { | ||
| 44 | let p = rcc_setup::stm32u5g9zj_init(); | ||
| 45 | |||
| 46 | // enable ICACHE | ||
| 47 | embassy_stm32::pac::ICACHE.cr().write(|w| { | ||
| 48 | w.set_en(true); | ||
| 49 | }); | ||
| 50 | |||
| 51 | // blink the led on another task | ||
| 52 | let led = Output::new(p.PD2, Level::High, Speed::Low); | ||
| 53 | unwrap!(spawner.spawn(led_task(led))); | ||
| 54 | |||
| 55 | // numbers from STM32U5G9J-DK2.ioc | ||
| 56 | const RK050HR18H_HSYNC: u16 = 5; // Horizontal synchronization | ||
| 57 | const RK050HR18H_HBP: u16 = 8; // Horizontal back porch | ||
| 58 | const RK050HR18H_HFP: u16 = 8; // Horizontal front porch | ||
| 59 | const RK050HR18H_VSYNC: u16 = 5; // Vertical synchronization | ||
| 60 | const RK050HR18H_VBP: u16 = 8; // Vertical back porch | ||
| 61 | const RK050HR18H_VFP: u16 = 8; // Vertical front porch | ||
| 62 | |||
| 63 | // NOTE: all polarities have to be reversed with respect to the STM32U5G9J-DK2 CubeMX parametrization | ||
| 64 | let ltdc_config = LtdcConfiguration { | ||
| 65 | active_width: DISPLAY_WIDTH as _, | ||
| 66 | active_height: DISPLAY_HEIGHT as _, | ||
| 67 | h_back_porch: RK050HR18H_HBP, | ||
| 68 | h_front_porch: RK050HR18H_HFP, | ||
| 69 | v_back_porch: RK050HR18H_VBP, | ||
| 70 | v_front_porch: RK050HR18H_VFP, | ||
| 71 | h_sync: RK050HR18H_HSYNC, | ||
| 72 | v_sync: RK050HR18H_VSYNC, | ||
| 73 | h_sync_polarity: PolarityActive::ActiveHigh, | ||
| 74 | v_sync_polarity: PolarityActive::ActiveHigh, | ||
| 75 | data_enable_polarity: PolarityActive::ActiveHigh, | ||
| 76 | pixel_clock_polarity: PolarityEdge::RisingEdge, | ||
| 77 | }; | ||
| 78 | |||
| 79 | info!("init ltdc"); | ||
| 80 | let mut ltdc_de = Output::new(p.PD6, Level::Low, Speed::High); | ||
| 81 | let mut ltdc_disp_ctrl = Output::new(p.PE4, Level::Low, Speed::High); | ||
| 82 | let mut ltdc_bl_ctrl = Output::new(p.PE6, Level::Low, Speed::High); | ||
| 83 | let mut ltdc = Ltdc::new_with_pins( | ||
| 84 | p.LTDC, // PERIPHERAL | ||
| 85 | Irqs, // IRQS | ||
| 86 | p.PD3, // CLK | ||
| 87 | p.PE0, // HSYNC | ||
| 88 | p.PD13, // VSYNC | ||
| 89 | p.PB9, // B0 | ||
| 90 | p.PB2, // B1 | ||
| 91 | p.PD14, // B2 | ||
| 92 | p.PD15, // B3 | ||
| 93 | p.PD0, // B4 | ||
| 94 | p.PD1, // B5 | ||
| 95 | p.PE7, // B6 | ||
| 96 | p.PE8, // B7 | ||
| 97 | p.PC8, // G0 | ||
| 98 | p.PC9, // G1 | ||
| 99 | p.PE9, // G2 | ||
| 100 | p.PE10, // G3 | ||
| 101 | p.PE11, // G4 | ||
| 102 | p.PE12, // G5 | ||
| 103 | p.PE13, // G6 | ||
| 104 | p.PE14, // G7 | ||
| 105 | p.PC6, // R0 | ||
| 106 | p.PC7, // R1 | ||
| 107 | p.PE15, // R2 | ||
| 108 | p.PD8, // R3 | ||
| 109 | p.PD9, // R4 | ||
| 110 | p.PD10, // R5 | ||
| 111 | p.PD11, // R6 | ||
| 112 | p.PD12, // R7 | ||
| 113 | ); | ||
| 114 | ltdc.init(<dc_config); | ||
| 115 | ltdc_de.set_low(); | ||
| 116 | ltdc_bl_ctrl.set_high(); | ||
| 117 | ltdc_disp_ctrl.set_high(); | ||
| 118 | |||
| 119 | // we only need to draw on one layer for this example (not to be confused with the double buffer) | ||
| 120 | info!("enable bottom layer"); | ||
| 121 | let layer_config = LtdcLayerConfig { | ||
| 122 | pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel | ||
| 123 | layer: LtdcLayer::Layer1, | ||
| 124 | window_x0: 0, | ||
| 125 | window_x1: DISPLAY_WIDTH as _, | ||
| 126 | window_y0: 0, | ||
| 127 | window_y1: DISPLAY_HEIGHT as _, | ||
| 128 | }; | ||
| 129 | |||
| 130 | let ferris_bmp: Bmp<Rgb888> = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap(); | ||
| 131 | let color_map = build_color_lookup_map(&ferris_bmp); | ||
| 132 | let clut = build_clut(&color_map); | ||
| 133 | |||
| 134 | // enable the bottom layer with a 256 color lookup table | ||
| 135 | ltdc.init_layer(&layer_config, Some(&clut)); | ||
| 136 | |||
| 137 | // Safety: the DoubleBuffer controls access to the statically allocated frame buffers | ||
| 138 | // and it is the only thing that mutates their content | ||
| 139 | let mut double_buffer = DoubleBuffer::new( | ||
| 140 | unsafe { FB1.as_mut() }, | ||
| 141 | unsafe { FB2.as_mut() }, | ||
| 142 | layer_config, | ||
| 143 | color_map, | ||
| 144 | ); | ||
| 145 | |||
| 146 | // this allows us to perform some simple animation for every frame | ||
| 147 | let mut bouncy_box = BouncyBox::new( | ||
| 148 | ferris_bmp.bounding_box(), | ||
| 149 | Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)), | ||
| 150 | 2, | ||
| 151 | ); | ||
| 152 | |||
| 153 | loop { | ||
| 154 | // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen | ||
| 155 | double_buffer.clear(); | ||
| 156 | let position = bouncy_box.next_point(); | ||
| 157 | let ferris = Image::new(&ferris_bmp, position); | ||
| 158 | unwrap!(ferris.draw(&mut double_buffer)); | ||
| 159 | |||
| 160 | // perform async dma data transfer to the lcd screen | ||
| 161 | unwrap!(double_buffer.swap(&mut ltdc).await); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | /// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work. | ||
| 166 | fn build_color_lookup_map(bmp: &Bmp<Rgb888>) -> FnvIndexMap<u32, u8, NUM_COLORS> { | ||
| 167 | let mut color_map: FnvIndexMap<u32, u8, NUM_COLORS> = heapless::FnvIndexMap::new(); | ||
| 168 | let mut counter: u8 = 0; | ||
| 169 | |||
| 170 | // add black to position 0 | ||
| 171 | color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap(); | ||
| 172 | counter += 1; | ||
| 173 | |||
| 174 | for Pixel(_point, color) in bmp.pixels() { | ||
| 175 | let raw = color.into_storage(); | ||
| 176 | if let Entry::Vacant(v) = color_map.entry(raw) { | ||
| 177 | v.insert(counter).expect("more than 256 colors detected"); | ||
| 178 | counter += 1; | ||
| 179 | } | ||
| 180 | } | ||
| 181 | color_map | ||
| 182 | } | ||
| 183 | |||
| 184 | /// builds the color look-up table from the color map provided | ||
| 185 | fn build_clut(color_map: &FnvIndexMap<u32, u8, NUM_COLORS>) -> [ltdc::RgbColor; NUM_COLORS] { | ||
| 186 | let mut clut = [ltdc::RgbColor::default(); NUM_COLORS]; | ||
| 187 | for (color, index) in color_map.iter() { | ||
| 188 | let color = Rgb888::from(RawU24::new(*color)); | ||
| 189 | clut[*index as usize] = ltdc::RgbColor { | ||
| 190 | red: color.r(), | ||
| 191 | green: color.g(), | ||
| 192 | blue: color.b(), | ||
| 193 | }; | ||
| 194 | } | ||
| 195 | |||
| 196 | clut | ||
| 197 | } | ||
| 198 | |||
| 199 | #[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] | ||
| 200 | async fn led_task(mut led: Output<'static>) { | ||
| 201 | let mut counter = 0; | ||
| 202 | loop { | ||
| 203 | info!("blink: {}", counter); | ||
| 204 | counter += 1; | ||
| 205 | |||
| 206 | // on | ||
| 207 | led.set_low(); | ||
| 208 | Timer::after(Duration::from_millis(50)).await; | ||
| 209 | |||
| 210 | // off | ||
| 211 | led.set_high(); | ||
| 212 | Timer::after(Duration::from_millis(450)).await; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | pub type TargetPixelType = u8; | ||
| 217 | |||
| 218 | // A simple double buffer | ||
| 219 | pub struct DoubleBuffer { | ||
| 220 | buf0: &'static mut [TargetPixelType], | ||
| 221 | buf1: &'static mut [TargetPixelType], | ||
| 222 | is_buf0: bool, | ||
| 223 | layer_config: LtdcLayerConfig, | ||
| 224 | color_map: FnvIndexMap<u32, u8, NUM_COLORS>, | ||
| 225 | } | ||
| 226 | |||
| 227 | impl DoubleBuffer { | ||
| 228 | pub fn new( | ||
| 229 | buf0: &'static mut [TargetPixelType], | ||
| 230 | buf1: &'static mut [TargetPixelType], | ||
| 231 | layer_config: LtdcLayerConfig, | ||
| 232 | color_map: FnvIndexMap<u32, u8, NUM_COLORS>, | ||
| 233 | ) -> Self { | ||
| 234 | Self { | ||
| 235 | buf0, | ||
| 236 | buf1, | ||
| 237 | is_buf0: true, | ||
| 238 | layer_config, | ||
| 239 | color_map, | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | pub fn current(&mut self) -> (&FnvIndexMap<u32, u8, NUM_COLORS>, &mut [TargetPixelType]) { | ||
| 244 | if self.is_buf0 { | ||
| 245 | (&self.color_map, self.buf0) | ||
| 246 | } else { | ||
| 247 | (&self.color_map, self.buf1) | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | pub async fn swap<T: ltdc::Instance>(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> { | ||
| 252 | let (_, buf) = self.current(); | ||
| 253 | let frame_buffer = buf.as_ptr(); | ||
| 254 | self.is_buf0 = !self.is_buf0; | ||
| 255 | ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await | ||
| 256 | } | ||
| 257 | |||
| 258 | /// Clears the buffer | ||
| 259 | pub fn clear(&mut self) { | ||
| 260 | let (color_map, buf) = self.current(); | ||
| 261 | let black = Rgb888::new(0, 0, 0).into_storage(); | ||
| 262 | let color_index = color_map.get(&black).expect("no black found in the color map"); | ||
| 263 | |||
| 264 | for a in buf.iter_mut() { | ||
| 265 | *a = *color_index; // solid black | ||
| 266 | } | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | // Implement DrawTarget for | ||
| 271 | impl DrawTarget for DoubleBuffer { | ||
| 272 | type Color = Rgb888; | ||
| 273 | type Error = (); | ||
| 274 | |||
| 275 | /// Draw a pixel | ||
| 276 | fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> | ||
| 277 | where | ||
| 278 | I: IntoIterator<Item = Pixel<Self::Color>>, | ||
| 279 | { | ||
| 280 | let size = self.size(); | ||
| 281 | let width = size.width as i32; | ||
| 282 | let height = size.height as i32; | ||
| 283 | let (color_map, buf) = self.current(); | ||
| 284 | |||
| 285 | for pixel in pixels { | ||
| 286 | let Pixel(point, color) = pixel; | ||
| 287 | |||
| 288 | if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height { | ||
| 289 | let index = point.y * width + point.x; | ||
| 290 | let raw_color = color.into_storage(); | ||
| 291 | |||
| 292 | match color_map.get(&raw_color) { | ||
| 293 | Some(x) => { | ||
| 294 | buf[index as usize] = *x; | ||
| 295 | } | ||
| 296 | None => panic!("color not found in color map: {}", raw_color), | ||
| 297 | }; | ||
| 298 | } else { | ||
| 299 | // Ignore invalid points | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | Ok(()) | ||
| 304 | } | ||
| 305 | } | ||
| 306 | |||
| 307 | impl OriginDimensions for DoubleBuffer { | ||
| 308 | /// Return the size of the display | ||
| 309 | fn size(&self) -> Size { | ||
| 310 | Size::new( | ||
| 311 | (self.layer_config.window_x1 - self.layer_config.window_x0) as _, | ||
| 312 | (self.layer_config.window_y1 - self.layer_config.window_y0) as _, | ||
| 313 | ) | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | mod rcc_setup { | ||
| 318 | |||
| 319 | use embassy_stm32::rcc; | ||
| 320 | use embassy_stm32::time::Hertz; | ||
| 321 | use embassy_stm32::{Config, Peripherals}; | ||
| 322 | |||
| 323 | /// Sets up clocks for the stm32u5g9zj mcu | ||
| 324 | /// change this if you plan to use a different microcontroller | ||
| 325 | pub fn stm32u5g9zj_init() -> Peripherals { | ||
| 326 | // setup power and clocks for an STM32U5G9J-DK2 run from an external 16 Mhz external oscillator | ||
| 327 | let mut config = Config::default(); | ||
| 328 | config.rcc.hse = Some(rcc::Hse { | ||
| 329 | freq: Hertz(16_000_000), | ||
| 330 | mode: rcc::HseMode::Oscillator, | ||
| 331 | }); | ||
| 332 | config.rcc.pll1 = Some(rcc::Pll { | ||
| 333 | source: rcc::PllSource::HSE, | ||
| 334 | prediv: rcc::PllPreDiv::DIV1, | ||
| 335 | mul: rcc::PllMul::MUL10, | ||
| 336 | divp: None, | ||
| 337 | divq: None, | ||
| 338 | divr: Some(rcc::PllDiv::DIV1), | ||
| 339 | }); | ||
| 340 | config.rcc.sys = rcc::Sysclk::PLL1_R; // 160 Mhz | ||
| 341 | config.rcc.pll3 = Some(rcc::Pll { | ||
| 342 | source: rcc::PllSource::HSE, | ||
| 343 | prediv: rcc::PllPreDiv::DIV4, // PLL_M | ||
| 344 | mul: rcc::PllMul::MUL125, // PLL_N | ||
| 345 | divp: None, | ||
| 346 | divq: None, | ||
| 347 | divr: Some(rcc::PllDiv::DIV20), | ||
| 348 | }); | ||
| 349 | config.rcc.mux.ltdcsel = rcc::mux::Ltdcsel::PLL3_R; // 25 MHz | ||
| 350 | embassy_stm32::init(config) | ||
| 351 | } | ||
| 352 | } | ||
| 353 | |||
| 354 | mod bouncy_box { | ||
| 355 | use embedded_graphics::geometry::Point; | ||
| 356 | use embedded_graphics::primitives::Rectangle; | ||
| 357 | |||
| 358 | enum Direction { | ||
| 359 | DownLeft, | ||
| 360 | DownRight, | ||
| 361 | UpLeft, | ||
| 362 | UpRight, | ||
| 363 | } | ||
| 364 | |||
| 365 | pub struct BouncyBox { | ||
| 366 | direction: Direction, | ||
| 367 | child_rect: Rectangle, | ||
| 368 | parent_rect: Rectangle, | ||
| 369 | current_point: Point, | ||
| 370 | move_by: usize, | ||
| 371 | } | ||
| 372 | |||
| 373 | // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box | ||
| 374 | impl BouncyBox { | ||
| 375 | pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self { | ||
| 376 | let center_box = parent_rect.center(); | ||
| 377 | let center_img = child_rect.center(); | ||
| 378 | let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2); | ||
| 379 | Self { | ||
| 380 | direction: Direction::DownRight, | ||
| 381 | child_rect, | ||
| 382 | parent_rect, | ||
| 383 | current_point, | ||
| 384 | move_by, | ||
| 385 | } | ||
| 386 | } | ||
| 387 | |||
| 388 | pub fn next_point(&mut self) -> Point { | ||
| 389 | let direction = &self.direction; | ||
| 390 | let img_height = self.child_rect.size.height as i32; | ||
| 391 | let box_height = self.parent_rect.size.height as i32; | ||
| 392 | let img_width = self.child_rect.size.width as i32; | ||
| 393 | let box_width = self.parent_rect.size.width as i32; | ||
| 394 | let move_by = self.move_by as i32; | ||
| 395 | |||
| 396 | match direction { | ||
| 397 | Direction::DownLeft => { | ||
| 398 | self.current_point.x -= move_by; | ||
| 399 | self.current_point.y += move_by; | ||
| 400 | |||
| 401 | let x_out_of_bounds = self.current_point.x < 0; | ||
| 402 | let y_out_of_bounds = (self.current_point.y + img_height) > box_height; | ||
| 403 | |||
| 404 | if x_out_of_bounds && y_out_of_bounds { | ||
| 405 | self.direction = Direction::UpRight | ||
| 406 | } else if x_out_of_bounds && !y_out_of_bounds { | ||
| 407 | self.direction = Direction::DownRight | ||
| 408 | } else if !x_out_of_bounds && y_out_of_bounds { | ||
| 409 | self.direction = Direction::UpLeft | ||
| 410 | } | ||
| 411 | } | ||
| 412 | Direction::DownRight => { | ||
| 413 | self.current_point.x += move_by; | ||
| 414 | self.current_point.y += move_by; | ||
| 415 | |||
| 416 | let x_out_of_bounds = (self.current_point.x + img_width) > box_width; | ||
| 417 | let y_out_of_bounds = (self.current_point.y + img_height) > box_height; | ||
| 418 | |||
| 419 | if x_out_of_bounds && y_out_of_bounds { | ||
| 420 | self.direction = Direction::UpLeft | ||
| 421 | } else if x_out_of_bounds && !y_out_of_bounds { | ||
| 422 | self.direction = Direction::DownLeft | ||
| 423 | } else if !x_out_of_bounds && y_out_of_bounds { | ||
| 424 | self.direction = Direction::UpRight | ||
| 425 | } | ||
| 426 | } | ||
| 427 | Direction::UpLeft => { | ||
| 428 | self.current_point.x -= move_by; | ||
| 429 | self.current_point.y -= move_by; | ||
| 430 | |||
| 431 | let x_out_of_bounds = self.current_point.x < 0; | ||
| 432 | let y_out_of_bounds = self.current_point.y < 0; | ||
| 433 | |||
| 434 | if x_out_of_bounds && y_out_of_bounds { | ||
| 435 | self.direction = Direction::DownRight | ||
| 436 | } else if x_out_of_bounds && !y_out_of_bounds { | ||
| 437 | self.direction = Direction::UpRight | ||
| 438 | } else if !x_out_of_bounds && y_out_of_bounds { | ||
| 439 | self.direction = Direction::DownLeft | ||
| 440 | } | ||
| 441 | } | ||
| 442 | Direction::UpRight => { | ||
| 443 | self.current_point.x += move_by; | ||
| 444 | self.current_point.y -= move_by; | ||
| 445 | |||
| 446 | let x_out_of_bounds = (self.current_point.x + img_width) > box_width; | ||
| 447 | let y_out_of_bounds = self.current_point.y < 0; | ||
| 448 | |||
| 449 | if x_out_of_bounds && y_out_of_bounds { | ||
| 450 | self.direction = Direction::DownLeft | ||
| 451 | } else if x_out_of_bounds && !y_out_of_bounds { | ||
| 452 | self.direction = Direction::UpLeft | ||
| 453 | } else if !x_out_of_bounds && y_out_of_bounds { | ||
| 454 | self.direction = Direction::DownRight | ||
| 455 | } | ||
| 456 | } | ||
| 457 | } | ||
| 458 | |||
| 459 | self.current_point | ||
| 460 | } | ||
| 461 | } | ||
| 462 | } | ||
