#![cfg_attr(
feature = "doc-images",
doc = ::embed_doc_image::embed_image!(
"cyd_application_preview",
"docs/assets/cyd_application_preview.png"
)
)]
#![cfg_attr(
feature = "doc-images",
doc = ::embed_doc_image::embed_image!(
"linkage_blaze_gallery",
"docs/assets/linkage_blaze_gallery.png"
)
)]
#![cfg_attr(
not(feature = "doc-images"),
doc = "\n> **Incomplete documentation preview:** Gallery images are omitted because the `doc-images` feature is disabled. From the workspace root, use `just docs` for authoritative local documentation.\n"
)]
#![doc = include_str!("../docs/cyd/gallery.md")]
#![doc = include_str!("../docs/cyd/application-example.md")]
#![doc = include_str!("../docs/cyd/drawing-strategies.md")]
#![doc = include_str!("../docs/cyd/implementations.md")]
#[doc(hidden)]
pub mod backend;
pub mod display;
pub mod touch;
use display::{ContiguousPixels, CydFrame};
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},
prelude::{Point, Size},
primitives::Rectangle,
};
use display::Orientation;
use touch::TouchEvent;
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 touch(&mut self) -> &mut Self::Touch {
self.parts().1
}
fn orientation(&self) -> Orientation;
}
pub trait CydTouch: Sized {
type Error;
fn try_read(&mut self) -> Result<Option<TouchEvent>, Self::Error>;
}
pub trait CydDisplay: backend::DisplayBackend {
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_preview",
"docs/assets/cyd_frame_mut_preview.png"
)
)]
#[cfg_attr(
feature = "host",
doc = r#"
```rust
use device_envoy_core::cyd::{CydDisplay, display::CydFrame};
use embedded_graphics::{
pixelcolor::{Rgb565, RgbColor},
prelude::{Point, Size},
primitives::Rectangle,
};
async fn draw<D: CydDisplay>(display: &mut D) -> Result<(), D::Error> {
let mut frame = display.frame_mut(Rectangle::new(
Point::new(10, 10),
Size::new(100, 40),
));
frame.fill(Rgb565::BLUE).write_text("CYD").flush().await
}
# use device_envoy_core::memory::{CydMemory, assert_framebuffer_matches_expected_png};
# use embedded_graphics::{mono_font::ascii::FONT_9X15_BOLD, pixelcolor::Rgb888};
# let memory_cyd = CydMemory::new(
# Size::new(320, 240),
# Rgb888::BLACK,
# Rgb888::WHITE,
# &FONT_9X15_BOLD,
# );
# let mut display = memory_cyd.display();
# futures_executor::block_on(draw(&mut display))?;
# 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>(())
```
"#
)]
#[cfg_attr(
all(feature = "host", feature = "doc-images"),
doc = "\n![CYD frame preview][cyd_frame_mut_preview]\n"
)]
fn frame_mut(&mut self, rectangle: Rectangle) -> Self::Frame<'_> {
backend::DisplayBackend::create_frame_mut(self, rectangle)
}
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>;
#[cfg_attr(
feature = "doc-images",
doc = ::embed_doc_image::embed_image!(
"cyd_fill_contiguous_preview",
"docs/assets/cyd_fill_contiguous_preview.png"
)
)]
#[cfg_attr(
feature = "doc-images",
doc = "\n![A bitmap streamed into a region of an in-memory CYD display.][cyd_fill_contiguous_preview]\n"
)]
fn fill_contiguous<I>(&mut self, rectangle: Rectangle, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Rgb565>;
#[cfg_attr(
feature = "doc-images",
doc = ::embed_doc_image::embed_image!(
"cyd_fill_contiguous_full_preview",
"docs/assets/cyd_fill_contiguous_full_preview.png"
)
)]
#[cfg_attr(
feature = "doc-images",
doc = "\n![A numerically generated gradient streamed into an in-memory CYD display.][cyd_fill_contiguous_full_preview]\n"
)]
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 draw_items<const DRAW_ITEM_CAPACITY: 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::<DRAW_ITEM_CAPACITY>::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 for_each_tile<'a, F>(
&'a mut self,
grid: display::tiling::TileGrid,
mut draw: F,
) -> impl Future<Output = Result<(), Self::Error>> + 'a
where
Self: Sized,
F: for<'frame> FnMut(&mut Self::Frame<'frame>) + 'a,
{
async move {
let mut tiles = display::tiling::Tiles::new(self, grid);
while let Some(mut frame) = tiles.next() {
draw(&mut frame);
frame.flush().await?;
}
Ok(())
}
}
}
#[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::{Dimensions, DrawTarget},
};
struct TestCyd;
struct TestFrame {
rectangle: Rectangle,
}
impl backend::DisplayBackend for TestCyd {
type Error = Infallible;
type Frame<'a> = TestFrame;
fn create_frame_mut(&mut self, rectangle: Rectangle) -> TestFrame {
TestFrame { rectangle }
}
}
impl CydDisplay for TestCyd {
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 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 Dimensions for TestFrame {
fn bounding_box(&self) -> Rectangle {
self.rectangle
}
}
impl PixelTarget for TestFrame {
fn width(&self) -> usize {
(self.rectangle.top_left.x as usize) + self.rectangle.size.width as usize
}
fn height(&self) -> usize {
(self.rectangle.top_left.y as 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 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_logical_display_rectangles() {
let mut cyd = TestCyd;
let grid = display::tiling::TileGrid::new(
Rectangle::new(Point::new(10, 20), Size::new(8, 6)),
2,
2,
);
let mut tiles = display::tiling::Tiles::new(&mut cyd, 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.bounding_box(), first.rectangle());
}
{
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.bounding_box(), second.rectangle());
}
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.bounding_box(), third.rectangle());
}
}