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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/* mod.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Graphics primitives for our display driver layer.  The general philosophy
//! we are taking is that we have far more CPU cycles spare than memory on
//! a chip like an AVR.  So rather than keeping a bitmap buffer and writing to
//! it, we take the approach of not holding any buffer and instead
//! implement primitives that can calculate the appropriate value for any
//! given pixel on-demand.

// Imports ===================================================================
use avrox_display::GfxResult;
pub mod primitives;
pub mod pixels;
mod patterns;
pub mod sevenseg;
pub mod dynamic;
pub mod img;
pub mod fills;

#[cfg(test)]
mod test;

use avr_oxide::OxideResult::{Ok};

// Declarations ==============================================================
/// Hints that tell a component the order in which a device will typically
/// ask for pixel data.  This may be used by the graphics command to
/// optimise how it retrieves, calculates or caches data.
#[cfg_attr(not(target_arch="avr"), derive(Debug))]
#[cfg_attr(target_arch="avr", derive(ufmt::derive::uDebug))]
pub enum RenderOrderingHint {
  /// The device typically renders row-by-row
  Rows,
  /// The device typically renders column-by-column
  Columns,
  /// No device follows no rhyme or reason
  Random
}



/// X-coordinates
pub type XCoord = u16;
/// Y-coordinates
pub type YCoord = u16;

/// An (X,Y) coordinate pair.
#[cfg_attr(not(target_arch="avr"), derive(Debug))]
#[cfg_attr(target_arch="avr", derive(ufmt::derive::uDebug))]
#[derive(Copy,Clone,Eq,PartialEq)]
pub struct Point(pub XCoord, pub YCoord);

#[cfg_attr(not(target_arch="avr"), derive(Debug,PartialEq))]
#[cfg_attr(target_arch="avr", derive(ufmt::derive::uDebug))]
pub struct Area {
  pub(crate) tl: Point,
  pub(crate) w: XCoord,
  pub(crate) h: YCoord
}

pub trait RenderPlane {
  const WIDTH : XCoord;
  const HEIGHT : YCoord;
  const ORDERHINT: RenderOrderingHint;
}

/// Trait implemented by any graphics command that is capable of being
/// rendered onto a 2d plane
pub trait Renderable {
  type PIXEL;

  /// Return the value of the pixel at the given X-Y coordinate
  fn get_pixel_at<P: RenderPlane>(&self, coord: Point) -> GfxResult<Self::PIXEL>;

  /// Return the dimensions of this primitive.  Other container types may
  /// use this to aid in optimisation or layout.
  fn get_dimensions<P: RenderPlane>(&self) -> GfxResult<(XCoord,YCoord)> {
    Ok(( P::WIDTH, P::HEIGHT ))
  }

  /// Indicate if any of the content of this renderable has (or may have)
  /// changed.
  fn has_changes<P: RenderPlane>(&self) -> bool {
    true
  }

  /// Return the area of this renderable that has (or may have) changed.
  fn get_change_area<P: RenderPlane>(&self) -> GfxResult<Option<Area>> {
    if self.has_changes::<P>() {
      // Default implementation is just to return our entire area; this will
      // always be 'correct' (it 'may' have changed) even if inefficient
      let dims = self.get_dimensions::<P>()?;

      Ok(Some(Area {
        tl: Point(0, 0),
        w: dims.0,
        h: dims.1
      }))
    } else {
      Ok(None)
    }
  }
}


// Code ======================================================================

/// Merge two areas into a new one which encompasses both the original areas.
fn merge_areas(area1: Option<Area>, area2: Option<Area>) -> Option<Area> {
  #[cfg(test)]
  println!("  Merging {:?} with {:?}", &area1, &area2);

  let merged = match (area1, area2) {
    (None, None)          => None,
    (Some(a), None) => Some(a),
    (None, Some(b)) => Some(b),
    (Some(a), Some(b)) => {
      let ( tl_a_x, tl_a_y ) = ( a.tl.0, a.tl.1 );
      let ( tl_b_x, tl_b_y ) = ( b.tl.0, b.tl.1 );

      let ( br_a_x, br_a_y ) = ( tl_a_x + a.w, tl_a_y + a.h );
      let ( br_b_x, br_b_y ) = ( tl_b_x + b.w, tl_b_y + b.h );

      let tl_x = core::cmp::min(tl_a_x, tl_b_x);
      let tl_y = core::cmp::min(tl_a_y, tl_b_y);

      let br_x = core::cmp::max(br_a_x, br_b_x);
      let br_y = core::cmp::max(br_a_y, br_b_y);

      let w = br_x - tl_x;
      let h = br_y - tl_y;

      Some(Area {
        tl: Point(tl_x, tl_y),w,h
      })
    }
  };

  #[cfg(test)]
  println!("    ==> {:?}", &merged);

  merged
}

// Tests =====================================================================
#[cfg(test)]
mod tests {
  use avrox_display::gfx::{Area, XCoord, Point, YCoord, merge_areas};

  fn area(tlx: XCoord, tly: YCoord, w: XCoord, h: YCoord) -> Area {
    Area {
      tl: Point(tlx, tly),
      w,
      h
    }
  }

  #[test]
  fn test_area_merges() {
    let area1 = Some(area(0,0,24,7));
    let area2 = Some(area(24, 0, 72, 14));

    assert_eq!(merge_areas(area1,area2), Some(area(0,0,24+72,14)));

  }
}