pub mod display;
pub mod touch;
use display::ContiguousPixels;
pub(crate) const SCREEN_WIDTH: usize = 320;
pub(crate) const SCREEN_HEIGHT: usize = 240;
pub const SCREEN_PIXELS: usize = SCREEN_WIDTH * SCREEN_HEIGHT;
use crate::pixel_target::rgb565_from_rgb888;
use embedded_graphics::{
pixelcolor::{Rgb565, Rgb888, raw::RawU16},
prelude::{Point, Size},
primitives::Rectangle,
};
use display::Orientation;
use touch::{RawTouchEvent, TouchEvent, calibration::CalibrationConfig};
#[cfg_attr(
feature = "doc-images",
doc = ::embed_doc_image::embed_image!("cyd_trait_preview", "docs/assets/cyd_trait_preview.png")
)]
#[cfg_attr(
feature = "host",
doc = r#"
Implementations include the in-memory mock [`CydMemory`](crate::memory::CydMemory), the browser-simulated [`CydWasm`](crate::wasm::CydWasm), and platform crates for ESP32 and Pico boards.
```rust
use device_envoy_core::cyd::{
Cyd, CydDisplay, CydTouch,
display::{CydFrame, DrawItem},
touch::TouchEvent,
};
use embedded_graphics::pixelcolor::Rgb888;
# use device_envoy_core::memory::CydMemory;
# use device_envoy_core::memory::assert_framebuffer_matches_expected_png;
# use embedded_graphics::{
# mono_font::ascii::FONT_9X15_BOLD,
# pixelcolor::Rgb565,
# prelude::{Point, RgbColor, Size},
# };
# futures_executor::block_on(async {
# let mut cyd = CydMemory::new(
# Size::new(320, 240),
# Rgb888::BLACK,
# Rgb888::WHITE,
# &FONT_9X15_BOLD,
# );
# cyd.push_touch_event(TouchEvent::Down { point: Point::new(160, 120) });
// Create a pixel-buffer covering the whole screen that starts filled with background color.
let (display, touch) = cyd.parts();
let touch_event = touch.read()?;
let mut frame = display.full_frame_mut();
frame.write_text("Hello CYD");
// An app would usually run this in a loop: read touch, draw, flush, repeat.
if let Some(TouchEvent::Down { point } | TouchEvent::Move { point }) = touch_event {
DrawItem::Circle {
center: (point.x as f32, point.y as f32),
pixel_radius: 24.0,
color: Rgb888::RED,
}
.draw(&mut frame);
}
frame.flush().await?;
# assert_eq!(cyd.pixel(160, 120), Rgb565::RED);
# if let Err(error) = assert_framebuffer_matches_expected_png(
# &cyd,
# env!("CARGO_MANIFEST_DIR"),
# "cyd_trait_preview.png",
# ) {
# panic!("{error}");
# }
# Ok::<(), device_envoy_core::memory::Error>(())
# })?;
# Ok::<(), device_envoy_core::memory::Error>(())
```
![CYD trait preview][cyd_trait_preview]
"#
)]
pub trait Cyd: Sized {
type Error;
type Display: CydDisplay<Error = Self::Error>;
type Touch: CydTouch<Error = Self::Error>;
fn parts(&mut self) -> (&mut Self::Display, &mut Self::Touch);
fn display(&mut self) -> &mut Self::Display {
self.parts().0
}
fn orientation(&self) -> Orientation;
}
#[doc(hidden)]
pub trait CydTouchUncalibrated: Sized {
type Error;
type Calibrated: CydTouch<Error = Self::Error, Uncalibrated = Self>;
fn read_raw_touch_event(&mut self) -> Result<Option<RawTouchEvent>, Self::Error>;
fn calibrate(self, calibration_config: CalibrationConfig) -> Self::Calibrated;
}
#[doc(hidden)]
pub trait CydTouch: Sized {
type Error;
type Uncalibrated: CydTouchUncalibrated<Error = Self::Error, Calibrated = Self>;
fn read(&mut self) -> Result<Option<TouchEvent>, Self::Error>;
fn calibration_config(&self) -> CalibrationConfig;
fn decalibrate(self) -> Self::Uncalibrated;
}
pub trait CydDisplay {
type Error;
type Frame<'a>: display::CydFrame<Error = Self::Error>
where
Self: 'a;
#[cfg_attr(
feature = "host",
doc = r#"
```rust
use device_envoy_core::cyd::CydDisplay;
use device_envoy_core::memory::CydMemory;
use embedded_graphics::pixelcolor::Rgb888;
# use embedded_graphics::{
# mono_font::ascii::FONT_9X15_BOLD,
# prelude::{RgbColor, Size},
# };
let display = CydMemory::new(
Size::new(320, 240),
Rgb888::BLACK,
Rgb888::WHITE,
&FONT_9X15_BOLD,
)
.display();
assert_eq!(display.screen_size(), Size::new(320, 240));
assert_eq!(display.background_565(), display.to_rgb565(display.background_color()));
assert_eq!(display.foreground_565(), display.to_rgb565(display.foreground_color()));
# Ok::<(), device_envoy_core::memory::Error>(())
```
"#
)]
fn screen_size(&self) -> Size;
fn background_color(&self) -> Rgb888;
fn foreground_color(&self) -> Rgb888;
fn background_565(&self) -> Rgb565;
fn foreground_565(&self) -> Rgb565;
fn to_rgb565(&self, color: Rgb888) -> Rgb565 {
rgb565_from_rgb888(color)
}
#[cfg_attr(
feature = "doc-images",
doc = ::embed_doc_image::embed_image!(
"cyd_frame_mut_with_tile_top_left_preview",
"docs/assets/cyd_frame_mut_with_tile_top_left_preview.png"
)
)]
#[cfg_attr(
feature = "host",
doc = r#"
```rust
use device_envoy_core::cyd::{CydDisplay, display::CydFrame};
use device_envoy_core::UnwrapInfallible;
use device_envoy_core::memory::{CydMemory, assert_framebuffer_matches_expected_png};
use embedded_graphics::{
Drawable,
pixelcolor::{Rgb565, Rgb888},
prelude::{Point, Primitive, RgbColor, Size},
primitives::{PrimitiveStyle, Rectangle},
};
# use embedded_graphics::mono_font::ascii::FONT_9X15_BOLD;
# futures_executor::block_on(async {
# let memory_cyd = CydMemory::new(
# Size::new(320, 240),
# Rgb888::BLACK,
# Rgb888::WHITE,
# &FONT_9X15_BOLD,
# );
# let mut display = memory_cyd.display();
let mut frame = display.frame_mut_with_tile_top_left(
Rectangle::new(Point::new(32, 24), Size::new(48, 32)),
Point::new(32, 24),
);
frame.fill(Rgb565::GREEN);
Rectangle::new(Point::new(36, 28), Size::new(6, 6))
.into_styled(PrimitiveStyle::with_fill(Rgb565::RED))
.draw(&mut frame)
.unwrap_infallible();
Rectangle::new(Point::new(70, 46), Size::new(6, 6))
.into_styled(PrimitiveStyle::with_fill(Rgb565::WHITE))
.draw(&mut frame)
.unwrap_infallible();
frame.flush().await?;
# if let Err(error) = assert_framebuffer_matches_expected_png(
# &memory_cyd,
# env!("CARGO_MANIFEST_DIR"),
# "cyd_frame_mut_with_tile_top_left_preview.png",
# ) {
# panic!("{error}");
# }
# Ok::<(), device_envoy_core::memory::Error>(())
# })?;
# Ok::<(), device_envoy_core::memory::Error>(())
```
![CYD tiled frame preview][cyd_frame_mut_with_tile_top_left_preview]
"#
)]
fn frame_mut_with_tile_top_left(
&mut self,
rectangle: Rectangle,
tile_top_left: Point,
) -> Self::Frame<'_>;
#[cfg_attr(
feature = "doc-images",
doc = ::embed_doc_image::embed_image!(
"cyd_frame_mut_preview",
"docs/assets/cyd_frame_mut_preview.png"
)
)]
#[cfg_attr(
feature = "host",
doc = r#"
```rust
use device_envoy_core::cyd::{CydDisplay, display::CydFrame};
use device_envoy_core::memory::{CydMemory, assert_framebuffer_matches_expected_png};
use embedded_graphics::{
pixelcolor::{Rgb565, Rgb888},
prelude::{RgbColor, Size},
primitives::Rectangle,
};
# use embedded_graphics::{mono_font::ascii::FONT_9X15_BOLD, prelude::Point};
# futures_executor::block_on(async {
# let memory_cyd = CydMemory::new(
# Size::new(320, 240),
# Rgb888::BLACK,
# Rgb888::WHITE,
# &FONT_9X15_BOLD,
# );
# let mut display = memory_cyd.display();
let mut frame = display.frame_mut(Rectangle::new(Point::new(10, 10), Size::new(50, 40)));
frame.fill(Rgb565::RED);
frame.flush().await?;
# if let Err(error) = assert_framebuffer_matches_expected_png(
# &memory_cyd,
# env!("CARGO_MANIFEST_DIR"),
# "cyd_frame_mut_preview.png",
# ) {
# panic!("{error}");
# }
# Ok::<(), device_envoy_core::memory::Error>(())
# })?;
# Ok::<(), device_envoy_core::memory::Error>(())
```
![CYD frame preview][cyd_frame_mut_preview]
"#
)]
fn frame_mut(&mut self, rectangle: Rectangle) -> Self::Frame<'_> {
self.frame_mut_with_tile_top_left(rectangle, Point::zero())
}
fn full_frame_mut(&mut self) -> Self::Frame<'_> {
self.frame_mut(Rectangle::new(Point::zero(), self.screen_size()))
}
fn fill_rectangle(&mut self, rectangle: Rectangle, color: Rgb565) -> Result<(), Self::Error>;
fn fill_contiguous<I>(&mut self, rectangle: Rectangle, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Rgb565>;
fn fill_contiguous_full<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Rgb565>,
{
self.fill_contiguous(Rectangle::new(Point::zero(), self.screen_size()), pixels)
}
fn flush_at(
&mut self,
buffer: &impl display::RectanglePixels,
top_left: Point,
) -> Result<(), Self::Error> {
let rectangle = Rectangle::new(
top_left,
Size::new(buffer.width() as u32, buffer.height() as u32),
);
self.fill_contiguous(
rectangle,
buffer
.raw_pixels()
.iter()
.copied()
.map(|pixel| Rgb565::from(RawU16::new(pixel))),
)
}
fn draw_items<const PIXEL_SOURCE_COUNT: usize>(
&mut self,
bounds: Rectangle,
background_color: Rgb565,
items: impl IntoIterator<Item = display::DrawItem>,
) -> Result<(), Self::Error> {
let bounds = bounds.intersection(&Rectangle::new(Point::zero(), self.screen_size()));
let pixel_sources = ContiguousPixels::<PIXEL_SOURCE_COUNT>::from_draw_items(
bounds,
background_color,
items,
);
self.fill_contiguous(pixel_sources.bounds(), pixel_sources.iter())
}
fn clear(&mut self) -> Result<(), Self::Error> {
self.fill(self.background_565())
}
fn fill(&mut self, color: Rgb565) -> Result<(), Self::Error> {
self.fill_rectangle(Rectangle::new(Point::zero(), self.screen_size()), color)
}
fn tiles(&mut self, grid: display::tiling::TileGrid) -> display::tiling::Tiles<'_, Self>
where
Self: Sized,
{
display::tiling::Tiles::new(self, grid)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cyd::display::CydFrame;
use crate::pixel_target::PixelTarget;
use core::convert::Infallible;
use embedded_graphics::pixelcolor::WebColors;
use embedded_graphics::{
Pixel,
prelude::{DrawTarget, OriginDimensions},
};
struct TestCyd;
struct TestFrame {
rectangle: Rectangle,
tile_top_left: Point,
}
impl CydDisplay for TestCyd {
type Error = Infallible;
type Frame<'a> = TestFrame;
fn screen_size(&self) -> Size {
Size::new(320, 240)
}
fn background_color(&self) -> Rgb888 {
Rgb888::CSS_BLACK
}
fn foreground_color(&self) -> Rgb888 {
Rgb888::CSS_WHITE
}
fn background_565(&self) -> Rgb565 {
self.to_rgb565(self.background_color())
}
fn foreground_565(&self) -> Rgb565 {
self.to_rgb565(self.foreground_color())
}
fn frame_mut_with_tile_top_left(
&mut self,
rectangle: Rectangle,
tile_top_left: Point,
) -> TestFrame {
TestFrame {
rectangle,
tile_top_left,
}
}
fn fill_rectangle(
&mut self,
_rectangle: Rectangle,
_color: Rgb565,
) -> Result<(), Infallible> {
Ok(())
}
fn fill_contiguous<I>(
&mut self,
_rectangle: Rectangle,
_pixels: I,
) -> Result<(), Infallible>
where
I: IntoIterator<Item = Rgb565>,
{
Ok(())
}
}
impl DrawTarget for TestFrame {
type Color = Rgb565;
type Error = Infallible;
fn draw_iter<I>(&mut self, _pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
Ok(())
}
}
impl OriginDimensions for TestFrame {
fn size(&self) -> Size {
self.rectangle.size
}
}
impl PixelTarget for TestFrame {
fn width(&self) -> usize {
self.rectangle.size.width as usize
}
fn height(&self) -> usize {
self.rectangle.size.height as usize
}
fn put_pixel(&mut self, _x: usize, _y: usize, _color: Rgb888) {}
}
impl CydFrame for TestFrame {
type Error = Infallible;
fn tile_top_left(&self) -> Point {
self.tile_top_left
}
fn rectangle(&self) -> Rectangle {
self.rectangle
}
fn fill(&mut self, _color: Rgb565) -> &mut Self {
self
}
fn clear(&mut self) -> &mut Self {
self
}
fn write_text(&mut self, _text: &str) -> &mut Self {
self
}
fn copy_from_565(&mut self, _src: &[u16]) -> crate::Result<()> {
Ok(())
}
async fn flush(&mut self) -> Result<(), Infallible> {
Ok(())
}
}
#[test]
fn tiled_frames_use_screen_tile_top_left() {
let mut cyd = TestCyd;
let grid = display::tiling::TileGrid::new(Point::new(10, 20), Size::new(8, 6), 2, 2);
let mut tiles = cyd.tiles(grid);
{
let first = tiles.next().expect("first tile exists");
assert_eq!(
first.rectangle(),
Rectangle::new(Point::new(10, 20), Size::new(4, 3))
);
assert_eq!(first.tile_top_left(), Point::new(10, 20));
}
{
let second = tiles.next().expect("second tile exists");
assert_eq!(
second.rectangle(),
Rectangle::new(Point::new(14, 20), Size::new(4, 3))
);
assert_eq!(second.tile_top_left(), Point::new(14, 20));
}
let third = tiles.next().expect("third tile exists");
assert_eq!(
third.rectangle(),
Rectangle::new(Point::new(10, 23), Size::new(4, 3))
);
assert_eq!(third.tile_top_left(), Point::new(10, 23));
}
}