1mod animation_frame;
23pub mod clock;
24pub mod cyd_web;
25pub mod dns;
26pub mod simulator;
27
28use core::{
29 cell::{Cell, RefCell},
30 convert::Infallible,
31 ops::Range,
32};
33use std::{collections::VecDeque, rc::Rc};
34
35use crate::cyd::{
36 Cyd, CydDisplay, CydTouch,
37 backend::{CalibrationConfig, RawTouchEvent},
38 display::{CydFrame, Orientation},
39 touch::{RawPoint, TouchEvent},
40};
41use crate::{
42 button::{__ButtonMonitor, BUTTON_POLL_INTERVAL, Button},
43 flash_block::{
44 Error as FlashBlockError, FlashBlock, FlashDevice, clear_block, load_block, save_block,
45 },
46 pixel_target::{PixelTarget, rgb888_from_rgb565},
47};
48use embassy_time::Timer;
49use embedded_graphics::pixelcolor::RgbColor;
50use embedded_graphics::{
51 Drawable, Pixel,
52 mono_font::{MonoFont, MonoTextStyle},
53 pixelcolor::{IntoStorage, Rgb565, Rgb888},
54 prelude::{Dimensions, DrawTarget, Point, Size},
55 primitives::Rectangle,
56 text::{Baseline, Text},
57};
58use serde::{Deserialize, Serialize};
59use wasm_bindgen::Clamped;
60use web_sys::{CanvasRenderingContext2d, ImageData, Storage};
61
62pub use animation_frame::next_animation_frame;
63pub use clock::ClockSyncWasm;
64pub use dns::DnsSimulatorWasm;
65pub use simulator::{
66 CydSimulatorControlWasm, CydSimulatorWasm, WifiConnectOutcome, WifiSimulatorWasm,
67};
68
69const FLASH_BLOCK_SIZE: usize = 4096;
70const FLASH_BLOCK_OFFSET: u32 = 0;
71const FLASH_ERASED_BYTE: u8 = 0xFF;
72
73const fn identity_calibration_config() -> CalibrationConfig {
74 CalibrationConfig::new(1.0, 0.0, 0.0, 0.0, 1.0, 0.0)
75}
76
77#[cfg_attr(
78 feature = "doc-images",
79 doc = ::embed_doc_image::embed_image!(
80 "linkage_blaze_gallery",
81 "docs/assets/linkage_blaze_gallery.png"
82 )
83)]
84#[cfg_attr(
102 feature = "doc-images",
103 doc = "\n[![Linkage Blaze gallery showing CYD applications][linkage_blaze_gallery]][interactive Linkage Blaze gallery]\n"
104)]
105pub struct CydWasm {
143 display: CydDisplayWasm,
144 touch: CydTouchWasm,
145}
146
147#[derive(Clone)]
149pub struct CydDisplayWasm {
150 context: CanvasRenderingContext2d,
151 size: Size,
152 orientation: Orientation,
153 background_color: Rgb888,
154 foreground_color: Rgb888,
155 background565: Rgb565,
156 foreground565: Rgb565,
157 font: &'static MonoFont<'static>,
158}
159
160#[derive(Clone)]
162pub struct CydTouchWasm {
163 raw_touch_events: RawTouchEvents,
164 interaction_state: Rc<Cell<InteractionState>>,
165 latest_raw_point: Rc<Cell<Option<RawPoint>>>,
166 calibration_config: CalibrationConfig,
167 orientation: Orientation,
168}
169
170#[derive(Clone)]
172pub struct CydTouchWasmSource {
173 raw_touch_events: RawTouchEvents,
174 interaction_state: Rc<Cell<InteractionState>>,
175 latest_raw_point: Rc<Cell<Option<RawPoint>>>,
176}
177
178pub struct ButtonWasm {
180 pressed: Rc<Cell<bool>>,
181}
182
183#[derive(Clone)]
184pub struct ButtonWasmSource {
186 pressed: Rc<Cell<bool>>,
187}
188
189pub struct FlashBlockWasm {
191 flash_device: FlashDeviceWasm,
192}
193
194type RawTouchEvents = Rc<RefCell<VecDeque<RawTouchEvent>>>;
195
196struct FlashDeviceWasm {
197 storage: Storage,
198 storage_key: String,
199 bytes: [u8; FLASH_BLOCK_SIZE],
200}
201
202#[derive(Debug)]
207pub enum Error {
208 StorageUnavailable,
210 StorageAccess,
212}
213
214#[derive(Clone, Copy, Eq, PartialEq)]
215enum InteractionState {
216 Ready,
217 PointerDown,
218 WaitingForFreshPress,
219}
220
221impl CydWasm {
222 #[must_use]
229 pub fn new(
230 context: CanvasRenderingContext2d,
231 orientation: Orientation,
232 background_color: Rgb888,
233 foreground_color: Rgb888,
234 font: &'static MonoFont<'static>,
235 touch_source: CydTouchWasmSource,
236 ) -> Self {
237 let display = CydDisplayWasm {
238 context: context.clone(),
239 size: orientation.size(),
240 orientation,
241 background_color,
242 foreground_color,
243 background565: Rgb565::from(background_color),
244 foreground565: Rgb565::from(foreground_color),
245 font,
246 };
247 let touch = CydTouchWasm {
248 raw_touch_events: touch_source.raw_touch_events,
249 interaction_state: touch_source.interaction_state,
250 latest_raw_point: touch_source.latest_raw_point,
251 calibration_config: identity_calibration_config(),
252 orientation,
253 };
254 Self { display, touch }
255 }
256
257 #[must_use]
258 pub fn touch_source(&self) -> CydTouchWasmSource {
260 CydTouchWasmSource {
261 raw_touch_events: self.touch.raw_touch_events.clone(),
262 interaction_state: self.touch.interaction_state.clone(),
263 latest_raw_point: self.touch.latest_raw_point.clone(),
264 }
265 }
266
267 #[must_use]
268 pub fn display(&self) -> CydDisplayWasm {
270 self.display.clone()
271 }
272
273 #[must_use]
278 pub fn owned_parts(&self) -> (CydDisplayWasm, CydTouchWasm) {
279 (self.display.clone(), self.touch.clone())
280 }
281}
282
283impl Cyd for CydWasm {
284 type Error = Infallible;
286 type Display = CydDisplayWasm;
287 type Touch = CydTouchWasm;
288
289 fn parts(&mut self) -> (&mut Self::Display, &mut Self::Touch) {
290 (&mut self.display, &mut self.touch)
291 }
292
293 fn orientation(&self) -> Orientation {
294 self.display.orientation
295 }
296}
297
298impl CydTouchWasmSource {
299 #[must_use]
301 pub fn new() -> Self {
302 Self {
303 raw_touch_events: Rc::new(RefCell::new(VecDeque::new())),
304 interaction_state: Rc::new(Cell::new(InteractionState::Ready)),
305 latest_raw_point: Rc::new(Cell::new(None)),
306 }
307 }
308
309 pub fn touch_down(&self, x: f32, y: f32) {
315 match self.interaction_state.get() {
316 InteractionState::WaitingForFreshPress => return,
317 InteractionState::Ready | InteractionState::PointerDown => {
318 self.interaction_state.set(InteractionState::PointerDown);
319 }
320 }
321 assert!((0.0..=u16::MAX as f32).contains(&x));
322 assert!((0.0..=u16::MAX as f32).contains(&y));
323 let raw_point = RawPoint {
324 x: x as u16,
325 y: y as u16,
326 };
327 self.latest_raw_point.set(Some(raw_point));
328 self.push(RawTouchEvent::Down {
329 raw_x: raw_point.x,
330 raw_y: raw_point.y,
331 });
332 }
333
334 pub fn touch_move(&self, x: f32, y: f32) {
336 if self.interaction_state.get() != InteractionState::PointerDown {
337 return;
338 }
339 assert!((0.0..=u16::MAX as f32).contains(&x));
340 assert!((0.0..=u16::MAX as f32).contains(&y));
341 let raw_point = RawPoint {
342 x: x as u16,
343 y: y as u16,
344 };
345 self.latest_raw_point.set(Some(raw_point));
346 self.push(RawTouchEvent::Move {
347 raw_x: raw_point.x,
348 raw_y: raw_point.y,
349 });
350 }
351
352 pub fn touch_up(&self) {
354 let interaction_state = self.interaction_state.get();
355 self.interaction_state.set(InteractionState::Ready);
356 self.latest_raw_point.set(None);
357 if interaction_state == InteractionState::WaitingForFreshPress {
358 return;
359 }
360 self.push(RawTouchEvent::Up);
361 }
362
363 pub fn wait_for_fresh_press(&self) {
365 self.raw_touch_events.borrow_mut().clear();
366 self.latest_raw_point.set(None);
367 self.interaction_state
368 .set(InteractionState::WaitingForFreshPress);
369 }
370
371 fn push(&self, raw_touch_event: RawTouchEvent) {
372 self.raw_touch_events
373 .borrow_mut()
374 .push_back(raw_touch_event);
375 }
376}
377
378impl Default for CydTouchWasmSource {
379 fn default() -> Self {
380 Self::new()
381 }
382}
383
384impl ButtonWasmSource {
385 #[must_use]
387 pub fn new() -> Self {
388 Self {
389 pressed: Rc::new(Cell::new(false)),
390 }
391 }
392
393 #[must_use]
394 pub fn button(&self) -> ButtonWasm {
396 ButtonWasm {
397 pressed: self.pressed.clone(),
398 }
399 }
400
401 pub fn press(&self) {
403 self.pressed.set(true);
404 }
405
406 pub fn release(&self) {
408 self.pressed.set(false);
409 }
410}
411
412impl Default for ButtonWasmSource {
413 fn default() -> Self {
414 Self::new()
415 }
416}
417
418impl __ButtonMonitor for ButtonWasm {
421 fn is_pressed_raw(&self) -> bool {
422 self.pressed.get()
423 }
424
425 async fn wait_until_pressed_state(&mut self, pressed: bool) {
426 loop {
427 if self.is_pressed_raw() == pressed {
428 break;
429 }
430 Timer::after(BUTTON_POLL_INTERVAL).await;
431 }
432 }
433}
434
435impl Button for ButtonWasm {}
436
437impl FlashBlockWasm {
438 pub fn new(storage_key: &str) -> Result<Self, Error> {
440 Ok(Self {
441 flash_device: FlashDeviceWasm::new(storage_key)?,
442 })
443 }
444}
445
446impl FlashDeviceWasm {
447 fn new(storage_key: &str) -> Result<Self, Error> {
448 let window = web_sys::window().ok_or(Error::StorageUnavailable)?;
449 let storage = window
450 .local_storage()
451 .map_err(|_error| Error::StorageAccess)?
452 .ok_or(Error::StorageUnavailable)?;
453 let mut flash_device = Self {
454 storage,
455 storage_key: storage_key.to_owned(),
456 bytes: [FLASH_ERASED_BYTE; FLASH_BLOCK_SIZE],
457 };
458 flash_device.load_from_storage()?;
459 Ok(flash_device)
460 }
461
462 fn load_from_storage(&mut self) -> Result<(), Error> {
463 let Some(encoded_bytes) = self
464 .storage
465 .get_item(&self.storage_key)
466 .map_err(|_error| Error::StorageAccess)?
467 else {
468 return Ok(());
469 };
470
471 if encoded_bytes.len() != FLASH_BLOCK_SIZE * 2 {
472 return Ok(());
473 }
474
475 let mut decoded_bytes = [FLASH_ERASED_BYTE; FLASH_BLOCK_SIZE];
476 if !decode_hex_into(&encoded_bytes, &mut decoded_bytes) {
477 return Ok(());
478 }
479 self.bytes = decoded_bytes;
480 Ok(())
481 }
482
483 fn persist(&self) -> Result<(), Error> {
484 let encoded_bytes = encode_hex(&self.bytes);
485 self.storage
486 .set_item(&self.storage_key, &encoded_bytes)
487 .map_err(|_error| Error::StorageAccess)
488 }
489
490 fn checked_range(&self, offset: u32, len: usize) -> Range<usize> {
491 let start = usize::try_from(offset).expect("flash offset must fit in usize");
492 let end = start
493 .checked_add(len)
494 .expect("flash range must fit in usize");
495 assert!(
496 end <= FLASH_BLOCK_SIZE,
497 "flash range must stay within the block"
498 );
499 start..end
500 }
501}
502
503impl FlashDevice for FlashDeviceWasm {
504 type Error = Error;
505
506 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
507 let checked_range = self.checked_range(offset, bytes.len());
508 bytes.copy_from_slice(&self.bytes[checked_range]);
509 Ok(())
510 }
511
512 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
513 let checked_range = self.checked_range(offset, bytes.len());
514 self.bytes[checked_range].copy_from_slice(bytes);
515 self.persist()
516 }
517
518 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
519 let len = usize::try_from(to.saturating_sub(from)).expect("flash erase length fits usize");
520 let checked_range = self.checked_range(from, len);
521 self.bytes[checked_range].fill(FLASH_ERASED_BYTE);
522 self.persist()
523 }
524}
525
526impl FlashBlock for FlashBlockWasm {
527 type Error = FlashBlockError<Error>;
528
529 fn load<T>(&mut self) -> Result<Option<T>, Self::Error>
530 where
531 T: Serialize + for<'de> Deserialize<'de>,
532 {
533 load_block::<FLASH_BLOCK_SIZE, T, _>(&mut self.flash_device, FLASH_BLOCK_OFFSET)
534 }
535
536 fn save<T>(&mut self, value: &T) -> Result<(), Self::Error>
537 where
538 T: Serialize + for<'de> Deserialize<'de>,
539 {
540 save_block::<FLASH_BLOCK_SIZE, _, _>(&mut self.flash_device, FLASH_BLOCK_OFFSET, value)
541 }
542
543 fn clear(&mut self) -> Result<(), Self::Error> {
544 clear_block::<FLASH_BLOCK_SIZE, _>(&mut self.flash_device, FLASH_BLOCK_OFFSET)
545 }
546}
547
548fn encode_hex(bytes: &[u8]) -> String {
549 const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
550
551 let mut encoded = String::with_capacity(bytes.len() * 2);
552 for byte in bytes {
553 encoded.push(HEX_DIGITS[(byte >> 4) as usize] as char);
554 encoded.push(HEX_DIGITS[(byte & 0x0F) as usize] as char);
555 }
556 encoded
557}
558
559fn decode_hex_into(encoded_bytes: &str, dst: &mut [u8]) -> bool {
560 let encoded_bytes = encoded_bytes.as_bytes();
561 if encoded_bytes.len() != dst.len() * 2 {
562 return false;
563 }
564
565 for (dst_index, chunk) in encoded_bytes.chunks_exact(2).enumerate() {
566 let Some(high) = decode_hex_nibble(chunk[0]) else {
567 return false;
568 };
569 let Some(low) = decode_hex_nibble(chunk[1]) else {
570 return false;
571 };
572 dst[dst_index] = (high << 4) | low;
573 }
574
575 true
576}
577
578const fn decode_hex_nibble(byte: u8) -> Option<u8> {
579 match byte {
580 b'0'..=b'9' => Some(byte - b'0'),
581 b'a'..=b'f' => Some(byte - b'a' + 10),
582 b'A'..=b'F' => Some(byte - b'A' + 10),
583 _ => None,
584 }
585}
586
587impl crate::cyd::backend::DisplayBackend for CydDisplayWasm {
588 type Error = Infallible;
589 type Frame<'a>
590 = CydFrameWasm<'a>
591 where
592 Self: 'a;
593
594 fn create_frame_mut(&mut self, rectangle: Rectangle) -> Self::Frame<'_> {
595 let size = rectangle.size;
596 let pixel_count = size.width as usize * size.height as usize;
597 let pixels = vec![self.background565.into_storage(); pixel_count];
598 CydFrameWasm {
599 context: &self.context,
600 pixels,
601 rectangle,
602 background565: self.background565,
603 foreground565: self.foreground565,
604 font: self.font,
605 }
606 }
607}
608
609impl CydDisplay for CydDisplayWasm {
610 fn screen_size(&self) -> Size {
611 self.size
612 }
613
614 fn background_color(&self) -> Rgb888 {
615 self.background_color
616 }
617
618 fn foreground_color(&self) -> Rgb888 {
619 self.foreground_color
620 }
621
622 fn background_565(&self) -> Rgb565 {
623 self.background565
624 }
625
626 fn foreground_565(&self) -> Rgb565 {
627 self.foreground565
628 }
629
630 fn fill_rectangle(&mut self, rectangle: Rectangle, color: Rgb565) -> Result<(), Infallible> {
631 let screen_rectangle = Rectangle::new(Point::zero(), self.size);
632 let rectangle = rectangle.intersection(&screen_rectangle);
633 if rectangle.size.width == 0 || rectangle.size.height == 0 {
634 return Ok(());
635 }
636
637 let pixel_count = rectangle.size.width as usize * rectangle.size.height as usize;
638 let mut bytes = Vec::with_capacity(pixel_count * 4);
639 for _pixel_index in 0..pixel_count {
640 push_rgb565_rgba(&mut bytes, color.into_storage());
641 }
642
643 put_image_data(&self.context, rectangle, &bytes);
644 Ok(())
645 }
646
647 fn fill_contiguous<I>(&mut self, rectangle: Rectangle, pixels: I) -> Result<(), Infallible>
648 where
649 I: IntoIterator<Item = Rgb565>,
650 {
651 if rectangle.size.width == 0 || rectangle.size.height == 0 {
652 return Ok(());
653 }
654
655 let mut bytes =
656 Vec::with_capacity(rectangle.size.width as usize * rectangle.size.height as usize * 4);
657 for pixel in pixels {
658 push_rgb565_rgba(&mut bytes, pixel.into_storage());
659 }
660
661 put_image_data(&self.context, rectangle, &bytes);
662 Ok(())
663 }
664}
665
666impl CydTouch for CydTouchWasm {
667 type Error = Infallible;
668
669 fn try_read(&mut self) -> Result<Option<TouchEvent>, Infallible> {
670 Ok(self
671 .raw_touch_events
672 .borrow_mut()
673 .pop_front()
674 .map(|raw_touch_event| match raw_touch_event {
675 RawTouchEvent::Down { raw_x, raw_y } => {
676 let (x, y) = self.calibration_config.map_raw_to_screen(raw_x, raw_y);
677 TouchEvent::Down {
678 point: self
679 .orientation
680 .map_landscape_point(Point::new(x as i32, y as i32)),
681 }
682 }
683 RawTouchEvent::Move { raw_x, raw_y } => {
684 let (x, y) = self.calibration_config.map_raw_to_screen(raw_x, raw_y);
685 TouchEvent::Move {
686 point: self
687 .orientation
688 .map_landscape_point(Point::new(x as i32, y as i32)),
689 }
690 }
691 RawTouchEvent::Up => TouchEvent::Up,
692 }))
693 }
694}
695
696fn put_image_data(context: &CanvasRenderingContext2d, rectangle: Rectangle, bytes: &[u8]) {
697 let image_data = ImageData::new_with_u8_clamped_array_and_sh(
698 Clamped(bytes),
699 rectangle.size.width,
700 rectangle.size.height,
701 )
702 .expect("ImageData dimensions match the rectangle");
703 context
704 .put_image_data(
705 &image_data,
706 f64::from(rectangle.top_left.x),
707 f64::from(rectangle.top_left.y),
708 )
709 .expect("put_image_data with in-bounds coordinates cannot fail");
710}
711
712fn push_rgb565_rgba(bytes: &mut Vec<u8>, pixel: u16) {
713 let color = rgb888_from_rgb565(pixel);
714 bytes.push(color.r());
715 bytes.push(color.g());
716 bytes.push(color.b());
717 bytes.push(255);
718}
719
720pub struct CydFrameWasm<'a> {
725 context: &'a CanvasRenderingContext2d,
726 pixels: Vec<u16>,
727 rectangle: Rectangle,
730 background565: Rgb565,
731 foreground565: Rgb565,
732 font: &'static MonoFont<'static>,
733}
734
735impl CydFrameWasm<'_> {
736 fn width(&self) -> usize {
737 self.rectangle.size.width as usize
738 }
739
740 fn height(&self) -> usize {
741 self.rectangle.size.height as usize
742 }
743
744 fn local_x(&self, x: i32) -> Option<usize> {
745 usize::try_from(x.checked_sub(self.rectangle.top_left.x)?).ok()
746 }
747
748 fn local_y(&self, y: i32) -> Option<usize> {
749 usize::try_from(y.checked_sub(self.rectangle.top_left.y)?).ok()
750 }
751
752 fn present(&self) {
754 let mut bytes = Vec::with_capacity(self.pixels.len() * 4);
755 for pixel in &self.pixels {
756 let color = rgb888_from_rgb565(*pixel);
757 bytes.push(color.r());
758 bytes.push(color.g());
759 bytes.push(color.b());
760 bytes.push(255);
761 }
762 let image_data = ImageData::new_with_u8_clamped_array_and_sh(
763 Clamped(&bytes),
764 self.rectangle.size.width,
765 self.rectangle.size.height,
766 )
767 .expect("ImageData dimensions match the pixel buffer");
768 self.context
769 .put_image_data(
770 &image_data,
771 f64::from(self.rectangle.top_left.x),
772 f64::from(self.rectangle.top_left.y),
773 )
774 .expect("put_image_data with in-bounds coordinates cannot fail");
775 }
776}
777
778impl DrawTarget for CydFrameWasm<'_> {
779 type Color = Rgb565;
780 type Error = Infallible;
781
782 fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
783 CydFrame::fill(self, color);
784 Ok(())
785 }
786
787 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
788 where
789 I: IntoIterator<Item = Pixel<Self::Color>>,
790 {
791 for Pixel(point, color) in pixels {
792 let Some(local_x) = self.local_x(point.x) else {
793 continue;
794 };
795 let Some(local_y) = self.local_y(point.y) else {
796 continue;
797 };
798 if local_x < CydFrameWasm::width(self) && local_y < CydFrameWasm::height(self) {
799 let index = local_y * CydFrameWasm::width(self) + local_x;
800 self.pixels[index] = color.into_storage();
801 }
802 }
803 Ok(())
804 }
805}
806
807impl Dimensions for CydFrameWasm<'_> {
808 fn bounding_box(&self) -> Rectangle {
809 self.rectangle
810 }
811}
812
813impl PixelTarget for CydFrameWasm<'_> {
814 fn width(&self) -> usize {
815 usize::try_from(self.rectangle.top_left.x)
816 .expect("frame top-left x must be non-negative")
817 .checked_add(CydFrameWasm::width(self))
818 .expect("frame width must fit in usize")
819 }
820
821 fn height(&self) -> usize {
822 usize::try_from(self.rectangle.top_left.y)
823 .expect("frame top-left y must be non-negative")
824 .checked_add(CydFrameWasm::height(self))
825 .expect("frame height must fit in usize")
826 }
827
828 fn put_pixel(&mut self, x: usize, y: usize, color: Rgb888) {
829 let Some(local_x) = self.local_x(x as i32) else {
830 return;
831 };
832 let Some(local_y) = self.local_y(y as i32) else {
833 return;
834 };
835 if local_x >= CydFrameWasm::width(self) || local_y >= CydFrameWasm::height(self) {
836 return;
837 }
838 let stride = CydFrameWasm::width(self);
839 self.pixels[local_y * stride + local_x] = Rgb565::from(color).into_storage();
840 }
841
842 fn put_pixel_565(&mut self, x: usize, y: usize, rgb565: u16) {
845 let Some(local_x) = self.local_x(x as i32) else {
846 return;
847 };
848 let Some(local_y) = self.local_y(y as i32) else {
849 return;
850 };
851 if local_x >= CydFrameWasm::width(self) || local_y >= CydFrameWasm::height(self) {
852 return;
853 }
854 let stride = CydFrameWasm::width(self);
855 self.pixels[local_y * stride + local_x] = rgb565;
856 }
857}
858
859impl CydFrame for CydFrameWasm<'_> {
860 type Error = Infallible;
862
863 fn rectangle(&self) -> Rectangle {
864 self.rectangle
865 }
866
867 fn fill(&mut self, color: Rgb565) -> &mut Self {
868 self.pixels.fill(color.into_storage());
869 self
870 }
871
872 fn clear(&mut self) -> &mut Self {
873 self.fill(self.background565)
874 }
875
876 fn copy_from_565(&mut self, src: &[u16]) -> crate::Result<()> {
877 if self.pixels.len() != src.len() {
878 return Err(crate::Error::CopySize {
879 src_len: src.len(),
880 frame_len: self.pixels.len(),
881 });
882 }
883 self.pixels.copy_from_slice(src);
884 Ok(())
885 }
886
887 fn write_text(&mut self, text: &str) -> &mut Self {
888 let style = MonoTextStyle::new(self.font, self.foreground565);
889 Text::with_baseline(text, self.rectangle.top_left, style, Baseline::Top)
890 .draw(self)
891 .expect("drawing onto an Infallible frame cannot fail");
892 self
893 }
894
895 async fn flush(&mut self) -> Result<(), Infallible> {
896 self.present();
900 next_animation_frame().await;
901 Ok(())
902 }
903}