1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
use alloc::vec::Vec;
use embedded_graphics::mono_font::iso_8859_1::FONT_7X13_BOLD;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::prelude::{PixelColor, Point};
use embedded_graphics::text::{Baseline, Text};
use embedded_graphics::Drawable;
use all_is_cubes::character::Cursor;
use crate::camera::ImageSize;
use crate::{Flaws, RenderError};
/// Rendering a previously-specified scene to an in-memory image.
///
/// This trait is object-safe so that different renderers can be used without generics.
/// Therefore, all its `async` methods use boxed futures.
pub trait HeadlessRenderer {
/// Update the renderer's internal copy of the scene from the data sources
/// (`Handle<Character>` etc.) it is tracking.
///
/// Returns [`RenderError::Read`] if said sources are in use or if some other
/// prohibitive failure occurred. The resulting state of the renderer in such cases
/// is not specified, but a good implementation should attempt recovery on a future
/// call.
///
/// TODO: provide for returning performance info?
///
/// TODO: we may want more dynamic information than the cursor
fn update<'a>(
&'a mut self,
cursor: Option<&'a Cursor>,
) -> futures_core::future::BoxFuture<'a, Result<(), RenderError>>;
/// Produce an image of the current state of the scene this renderer was created to
/// track, as of the last call to [`Self::update()`], with the given overlaid text.
///
/// This operation should not attempt to access the scene objects and therefore may be
/// called while the [`Universe`] is being stepped on another thread.
///
/// [`Universe`]: all_is_cubes::universe::Universe
fn draw<'a>(
&'a mut self,
info_text: &'a str,
) -> futures_core::future::BoxFuture<'a, Result<Rendering, RenderError>>;
}
/// Image container produced by a [`HeadlessRenderer`].
// ---
// TODO: This is not very compatible with future changes, but it's not clear how to
// improve extensibility. We would also like to have renderer-specific performance info.
#[derive(Clone, Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct Rendering {
/// Width and height of the image.
pub size: ImageSize,
/// Image data, RGBA, 8 bits per component, in the sRGB color space.
pub data: Vec<[u8; 4]>,
/// Deficiencies of the rendering; ways in which it fails to accurately represent the
/// scene or apply the renderer’s configuration.
pub flaws: Flaws,
}
impl From<Rendering> for imgref::ImgVec<[u8; 4]> {
fn from(value: Rendering) -> Self {
imgref::Img::new(
value.data,
// cannot overflow — we statically assert size_of(usize) >= size_of(u32)
value.size.width as usize,
value.size.height as usize,
)
}
}
impl<'a> From<&'a Rendering> for imgref::ImgRef<'a, [u8; 4]> {
fn from(value: &'a Rendering) -> Self {
imgref::Img::new(
value.data.as_slice(),
// cannot overflow — we statically assert size_of(usize) >= size_of(u32)
value.size.width as usize,
value.size.height as usize,
)
}
}
/// Provides the standard text style and positioning to draw the “debug info text”
/// (as in [`HeadlessRenderer::draw()`]'s parameter).
///
/// Note: The conventional color is white with a black drop-shadow, but the exact color
/// format and means by which the shadow is accomplished depends on the specific renderer,
/// so this function makes no assumption about color.
#[doc(hidden)] // TODO: decide whether to make public
pub fn info_text_drawable<C: PixelColor + 'static>(
text: &str,
color_value: C,
) -> impl Drawable<Color = C> + '_ {
Text::with_baseline(
text,
Point::new(5, 5),
MonoTextStyle::new(&FONT_7X13_BOLD, color_value),
Baseline::Top,
)
}
#[cfg(test)]
mod tests {
use super::*;
fn _headless_renderer_is_object_safe(_: &dyn HeadlessRenderer) {}
}