use core::cmp;
use core::marker::PhantomData;
use avrox_display::gfx::{Renderable, RenderPlane, GfxResult, XCoord, Point, YCoord, Area};
use avrox_display::{gfx, GfxError};
use avr_oxide::OxideResult::{Err,Ok};
#[cfg_attr(not(target_arch="avr"), derive(Debug))]
pub struct Rectangle<PIX,FILL>
where
PIX: Clone,
FILL: Renderable,
FILL::PIXEL: Into<PIX>
{
width: XCoord,
height: YCoord,
border: PIX,
fill: FILL
}
pub struct Overlay<LAYER1,LAYER2>
where
LAYER1: Renderable,
LAYER2: Renderable,
LAYER2::PIXEL: Into<LAYER1::PIXEL>
{
top: LAYER1,
bottom: LAYER2
}
pub struct ConstTranslate<const X:XCoord,const Y:YCoord,R:Renderable>{
inner: R
}
pub struct ConstScaleUp<const FX:XCoord,const FY:YCoord,R:Renderable>{
inner: R
}
pub trait Position {
fn get_x_offset(component_width: XCoord, available_width: XCoord) -> XCoord;
fn get_y_offset(component_height: YCoord, available_height: YCoord) -> YCoord;
fn map_x(original: XCoord, component_width: XCoord, available_width: XCoord) -> GfxResult<XCoord> {
let offset = Self::get_x_offset(component_width,available_width);
if original < offset {
Err(GfxError::OutOfBounds)
} else {
Ok(original - offset)
}
}
fn reverse_map_x(resulting: XCoord, component_width: XCoord, available_width: XCoord) -> XCoord {
let offset = Self::get_x_offset(component_width,available_width);
resulting + offset
}
fn map_y(original: YCoord, component_height: YCoord, available_height: YCoord) -> GfxResult<YCoord> {
let offset = Self::get_y_offset(component_height,available_height);
if original < offset {
Err(GfxError::OutOfBounds)
} else {
Ok(original - offset)
}
}
fn reverse_map_y(resulting: XCoord, component_height: YCoord, available_height: YCoord) -> YCoord {
let offset = Self::get_y_offset(component_height,available_height);
resulting + offset
}
}
pub struct HorizontalPair<LEFT,RIGHT,POSITION>
where
LEFT: Renderable,
RIGHT: Renderable,
RIGHT::PIXEL: Into<LEFT::PIXEL>,
POSITION: Position
{
left: LEFT,
right: RIGHT,
posn: PhantomData<POSITION>
}
pub mod position {
use avrox_display::gfx::{XCoord, YCoord};
use avrox_display::GfxResult;
use avrox_display::gfx::primitives::Position;
use avr_oxide::OxideResult::Ok;
pub struct Beginning {}
pub struct Middle {}
pub struct End {}
impl Position for Beginning {
fn get_x_offset(_component_width: XCoord, _available_width: XCoord) -> XCoord {
0
}
fn get_y_offset(_component_height: YCoord, _available_height: YCoord) -> YCoord {
0
}
fn map_x(original: XCoord, _component_width: XCoord, _available_width: XCoord) -> GfxResult<XCoord> {
Ok(original)
}
fn map_y(original: YCoord, _component_height: YCoord, _available_height: YCoord) -> GfxResult<YCoord> {
Ok(original)
}
}
impl Position for Middle {
fn get_x_offset(component_width: XCoord, available_width: XCoord) -> XCoord {
((available_width - component_width) / 2) as XCoord
}
fn get_y_offset(component_height: YCoord, available_height: YCoord) -> YCoord {
((available_height - component_height) / 2) as YCoord
}
}
impl Position for End {
fn get_x_offset(component_width: XCoord, available_width: XCoord) -> XCoord {
available_width - component_width
}
fn get_y_offset(component_height: YCoord, available_height: YCoord) -> YCoord {
available_height - component_height
}
}
}
impl<PIX,FILL> Rectangle<PIX, FILL>
where
PIX: Clone,
FILL: Renderable,
FILL::PIXEL: Into<PIX>
{
pub fn new(dimensions: (XCoord, YCoord), border: PIX, fill: FILL) -> Self {
Rectangle {
width: dimensions.0,
height: dimensions.1,
border,
fill
}
}
}
impl<LAYER1,LAYER2> Overlay<LAYER1,LAYER2>
where
LAYER1: Renderable,
LAYER2: Renderable,
LAYER2::PIXEL: Into<LAYER1::PIXEL>
{
pub fn new(top: LAYER1, bottom: LAYER2) -> Self {
Overlay {
top, bottom
}
}
}
impl<PIX, FILL> Renderable for Rectangle<PIX, FILL>
where
PIX: Clone,
FILL: Renderable,
FILL::PIXEL: Into<PIX>
{
type PIXEL = PIX;
fn get_pixel_at<PLANE: RenderPlane>(&self, coord: Point) -> GfxResult<Self::PIXEL> {
if coord.0 > self.width || coord.1 > self.height {
return Err(GfxError::OutOfBounds)
}
if coord.0 == 0 || coord.0 == self.width || coord.1 == 0 || coord.1 == self.height {
Ok(self.border.clone())
} else {
Ok(self.fill.get_pixel_at::<PLANE>(Point(coord.0 - 1, coord.1 - 1))?.into())
}
}
fn has_changes<P: RenderPlane>(&self) -> bool {
self.fill.has_changes::<P>()
}
fn get_change_area<P: RenderPlane>(&self) -> GfxResult<Option<Area>> {
match self.fill.get_change_area::<P>()? {
Some(area) => {
Ok(Some(Area {
tl: Point(area.tl.0+1, area.tl.1+1),
w: area.w,
h: area.h
}))
}
None => Ok(None)
}
}
}
impl<LAYER1,LAYER2> Renderable for Overlay<LAYER1,LAYER2>
where
LAYER1: Renderable,
LAYER2: Renderable,
LAYER2::PIXEL: Into<LAYER1::PIXEL>
{
type PIXEL = LAYER1::PIXEL;
fn get_pixel_at<P: RenderPlane>(&self, coord: Point) -> GfxResult<Self::PIXEL> {
match self.top.get_pixel_at::<P>(coord) {
Ok(pix) => Ok(pix),
Err(_e) => Ok(self.bottom.get_pixel_at::<P>(coord)?.into())
}
}
fn has_changes<P: RenderPlane>(&self) -> bool {
self.top.has_changes::<P>() || self.bottom.has_changes::<P>()
}
fn get_change_area<P: RenderPlane>(&self) -> GfxResult<Option<Area>> {
#[cfg(test)]
println!("Overlay get change area:");
let top_change = self.top.get_change_area::<P>()?;
#[cfg(test)]
println!(" ==> top: {:?}", top_change);
let bottom_change = self.bottom.get_change_area::<P>()?;
#[cfg(test)]
println!(" ==> bottom: {:?}", top_change);
#[cfg(test)]
println!("overlay merge areas");
Ok(gfx::merge_areas(top_change, bottom_change))
}
}
impl<const X:XCoord, const Y:YCoord,R:Renderable> ConstTranslate<X,Y,R> {
pub fn new(inner: R) -> Self {
ConstTranslate {
inner: inner
}
}
}
impl<const X:XCoord, const Y:YCoord,R:Renderable> Renderable for ConstTranslate<X,Y,R> {
type PIXEL = R::PIXEL;
fn get_pixel_at<P: RenderPlane>(&self, coord: Point) -> GfxResult<Self::PIXEL> {
if (coord.0 < X) || (coord.1 < Y) {
Err(GfxError::OutOfBounds)
} else {
self.inner.get_pixel_at::<P>(Point(coord.0-X, coord.1-Y))
}
}
fn get_dimensions<P: RenderPlane>(&self) -> GfxResult<(XCoord, YCoord)> {
let (inner_w,inner_h) = self.inner.get_dimensions::<P>()?;
Ok((inner_w+X,inner_h+Y))
}
fn has_changes<P: RenderPlane>(&self) -> bool {
self.inner.has_changes::<P>()
}
fn get_change_area<P: RenderPlane>(&self) -> GfxResult<Option<Area>> {
match self.inner.get_change_area::<P>()? {
Some(area) => {
Ok(Some(Area {
tl: Point(area.tl.0+X, area.tl.1+Y),
w: area.w,
h: area.h
}))
}
None => Ok(None)
}
}
}
impl<const XF:XCoord, const YF:YCoord,R:Renderable> ConstScaleUp<XF,YF,R> {
pub fn new(inner: R) -> Self {
ConstScaleUp {
inner: inner
}
}
}
impl<const XF:XCoord, const YF:YCoord,R:Renderable> Renderable for ConstScaleUp<XF,YF,R> {
type PIXEL = R::PIXEL;
fn get_pixel_at<P: RenderPlane>(&self, coord: Point) -> GfxResult<Self::PIXEL> {
self.inner.get_pixel_at::<P>(Point(coord.0/XF, coord.1/YF))
}
fn get_dimensions<P: RenderPlane>(&self) -> GfxResult<(XCoord, YCoord)> {
let inner_d = self.inner.get_dimensions::<P>()?;
Ok((inner_d.0 * XF, inner_d.1 * YF))
}
fn has_changes<P: RenderPlane>(&self) -> bool {
self.inner.has_changes::<P>()
}
fn get_change_area<P: RenderPlane>(&self) -> GfxResult<Option<Area>> {
match self.inner.get_change_area::<P>()? {
Some(area) => {
Ok(Some(Area {
tl: Point(area.tl.0 * XF, area.tl.1 * YF),
w: area.w * XF,
h: area.h * YF
}))
}
None => Ok(None)
}
}
}
impl<LEFT,RIGHT,POSITION> HorizontalPair<LEFT,RIGHT,POSITION>
where
LEFT: Renderable,
RIGHT: Renderable,
RIGHT::PIXEL: Into<LEFT::PIXEL>,
POSITION: Position
{
pub fn new(left: LEFT, right: RIGHT) -> Self {
HorizontalPair {
left: left,
right: right,
posn: Default::default()
}
}
}
impl<LEFT,RIGHT,POSITION> Renderable for HorizontalPair<LEFT,RIGHT,POSITION>
where
LEFT: Renderable,
RIGHT: Renderable,
RIGHT::PIXEL: Into<LEFT::PIXEL>,
POSITION: Position
{
type PIXEL = LEFT::PIXEL;
fn get_pixel_at<P: RenderPlane>(&self, coord: Point) -> GfxResult<Self::PIXEL> {
let left_dims = self.left.get_dimensions::<P>()?;
let right_dims = self.right.get_dimensions::<P>()?;
if coord.0 < left_dims.0 {
self.left.get_pixel_at::<P>(Point(coord.0,
POSITION::map_y(coord.1,left_dims.1,
cmp::max(left_dims.1,right_dims.1))?))
} else {
match self.right.get_pixel_at::<P>(Point(coord.0 - left_dims.0,
POSITION::map_y(coord.1,right_dims.1,
cmp::max(left_dims.1,right_dims.1))?)) {
Ok(pixel) => Ok(pixel.into()),
Err(e) => Err(e)
}
}
}
fn get_dimensions<P: RenderPlane>(&self) -> GfxResult<(XCoord, YCoord)> {
let left_dims = self.left.get_dimensions::<P>()?;
let right_dims = self.right.get_dimensions::<P>()?;
Ok((left_dims.0 + right_dims.0,
cmp::max(left_dims.1,right_dims.1)))
}
fn has_changes<P: RenderPlane>(&self) -> bool {
self.left.has_changes::<P>() || self.right.has_changes::<P>()
}
fn get_change_area<P: RenderPlane>(&self) -> GfxResult<Option<Area>> {
let left_dims = self.left.get_dimensions::<P>()?;
let right_dims = self.right.get_dimensions::<P>()?;
let avail_height = cmp::max(left_dims.1,right_dims.1);
#[cfg(test)]
println!("HorizontalPair get change area");
let left_change_area = match self.left.get_change_area::<P>()? {
None => None,
Some(area) => {
Some(Area {
tl: Point(
area.tl.0, POSITION::reverse_map_y(area.tl.1,left_dims.1, avail_height)
),
w: area.w,
h: area.h
})
}
};
#[cfg(test)]
println!(" Left = {:?}", &left_change_area);
let right_change_area = match self.right.get_change_area::<P>()? {
None => None,
Some(area) => {
Some(Area {
tl: Point(
area.tl.0 + left_dims.0, POSITION::reverse_map_y(area.tl.1,right_dims.1, avail_height)
),
w: area.w,
h: area.h
})
}
};
#[cfg(test)]
println!(" Right = {:?}", &left_change_area);
Ok(gfx::merge_areas(left_change_area,right_change_area))
}
}
#[cfg(test)]
mod tests {
use core::cell::RefCell;
use avrox_display::gfx::pixels::Monochromatic;
use avrox_display::gfx::primitives::{ConstScaleUp, ConstTranslate, HorizontalPair, Overlay, position, Rectangle };
use avrox_display::gfx::{Area, Point, Renderable};
use avrox_display::gfx::fills::SolidFill;
use avrox_display::gfx::sevenseg::{SevenSegmentDisplay, SevenSegmentHexDigit};
use avrox_display::gfx::test::MonochromeTestRenderer;
#[test]
fn test_translate() {
let renderer = MonochromeTestRenderer::new();
let mut display = SevenSegmentDisplay::<8,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK,Some(0xdeadbeefu32));
let scene = Overlay::new(
ConstTranslate::<5,5,_>::new(display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
}
#[test]
fn test_scale_up() {
let renderer = MonochromeTestRenderer::new();
let mut display = SevenSegmentDisplay::<8,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK,Some(0xdeadbeefu32));
let scene = Overlay::new(
ConstScaleUp::<2,2,_>::new(display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
}
#[test]
fn test_horizontal_pair_top() {
let renderer = MonochromeTestRenderer::new();
let small_display = SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xdeadu32));
let big_display = ConstScaleUp::<2,2,_>::new(SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xbeefu32)));
let scene = Overlay::new(
HorizontalPair::<_,_,position::Beginning>::new(small_display,big_display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
}
#[test]
fn test_horizontal_pair_middle() {
let renderer = MonochromeTestRenderer::new();
let small_display = SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xdeadu32));
let big_display = ConstScaleUp::<2,2,_>::new(SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xbeefu32)));
let scene = Overlay::new(
HorizontalPair::<_,_,position::Middle>::new(small_display,big_display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
}
#[test]
fn test_horizontal_pair_middle_flipped() {
let renderer = MonochromeTestRenderer::new();
let small_display = SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xdeadu32));
let big_display = ConstScaleUp::<2,2,_>::new(SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xbeefu32)));
let scene = Overlay::new(
HorizontalPair::<_,_,position::Middle>::new(big_display,small_display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
}
#[test]
fn test_horizontal_pair_bottom() {
let renderer = MonochromeTestRenderer::new();
let small_display = SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xdeadu32));
let big_display = ConstScaleUp::<2,2,_>::new(SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xbeefu32)));
let scene = Overlay::new(
HorizontalPair::<_,_,position::End>::new(small_display,big_display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
}
#[test]
fn test_change_areas_immutable() {
let renderer = MonochromeTestRenderer::new();
let small_display = SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xdeadu32));
let big_display = ConstScaleUp::<2,2,_>::new(SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xbeefu32)));
let scene = Overlay::new(
HorizontalPair::<_,_,position::Middle>::new(small_display,big_display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
println!("Change area 1: {:?}", scene.get_change_area::<MonochromeTestRenderer>());
assert_eq!(scene.get_change_area::<MonochromeTestRenderer>().unwrap(), None);
}
#[test]
fn test_change_areas_mutable() {
let renderer = MonochromeTestRenderer::new();
let mut mutable_cell = RefCell::new(Some(0xf00du32));
let small_display = SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xdeadu32));
let big_display = ConstScaleUp::<2,2,_>::new(SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, &mutable_cell));
let scene = Overlay::new(
HorizontalPair::<_,_,position::Middle>::new(small_display,big_display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
println!("MUTABLE Change area 1: {:?}", scene.get_change_area::<MonochromeTestRenderer>());
assert_eq!(scene.get_change_area::<MonochromeTestRenderer>().unwrap(), Some(Area {
tl: Point(24,0), w: 48, h: 14
}));
let small_display = SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, &mutable_cell);
let big_display = ConstScaleUp::<2,2,_>::new(SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, Some(0xbeefu32)));
let scene = Overlay::new(
HorizontalPair::<_,_,position::Middle>::new(small_display,big_display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
println!("MUTABLE Change area 2: {:?}", scene.get_change_area::<MonochromeTestRenderer>());
assert_eq!(scene.get_change_area::<MonochromeTestRenderer>().unwrap(), Some(Area {
tl: Point(0,3), w: 24, h: 7
}));
let small_display = SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, &mutable_cell);
let big_display = ConstScaleUp::<2,2,_>::new(SevenSegmentDisplay::<4,16,_,_>::new(Monochromatic::WHITE, Monochromatic::BLACK, &mutable_cell));
let scene = Overlay::new(
HorizontalPair::<_,_,position::Middle>::new(small_display,big_display),
SolidFill::new(Monochromatic::BLACK));
renderer.render_scene(&scene);
println!("MUTABLE Change area 3: {:?}", scene.get_change_area::<MonochromeTestRenderer>());
assert_eq!(scene.get_change_area::<MonochromeTestRenderer>().unwrap(), Some(Area {
tl: Point(0,0), w: 72, h: 14
}));
}
}