Skip to content

Commit

Permalink
Type-level DisplayRotation
Browse files Browse the repository at this point in the history
  • Loading branch information
bugadani committed Jun 12, 2020
1 parent 669218e commit 787ddcc
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 163 deletions.
31 changes: 20 additions & 11 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,20 @@

use display_interface::WriteOnlyDataCommand;

use crate::{displayrotation::DisplayRotation, displaysize::*, properties::DisplayProperties};
use crate::{
displayrotation::{DisplayRotation, Rotate0},
displaysize::*,
properties::DisplayProperties,
};

/// Builder struct. Driver options and interface are set using its methods.
#[derive(Clone, Copy)]
pub struct Builder<DSIZE = DisplaySize128x64>
pub struct Builder<DSIZE = DisplaySize128x64, DROTATION = Rotate0>
where
DSIZE: DisplaySize,
DROTATION: DisplayRotation,
{
rotation: DisplayRotation,
_rotation: core::marker::PhantomData<DROTATION>,
_size: core::marker::PhantomData<DSIZE>,
}

Expand All @@ -80,39 +85,43 @@ impl Builder {
/// Create new builder with a default size of 128 x 64 pixels and no rotation.
pub fn new() -> Self {
Self {
rotation: DisplayRotation::Rotate0,
_rotation: core::marker::PhantomData,
_size: core::marker::PhantomData,
}
}
}

impl<DSIZE> Builder<DSIZE>
impl<DSIZE, DROTATION> Builder<DSIZE, DROTATION>
where
DSIZE: DisplaySize,
DROTATION: DisplayRotation,
{
/// Set the size of the display. Supported sizes are defined by [DisplaySize].
pub fn size<SIZE: DisplaySize>(self) -> Builder<SIZE> {
pub fn size<SIZE: DisplaySize>(self) -> Builder<SIZE, DROTATION> {
Builder {
rotation: self.rotation,
_rotation: core::marker::PhantomData,
_size: core::marker::PhantomData,
}
}

/// Set the rotation of the display to one of four values. Defaults to no rotation. Note that
/// 90º and 270º rotations are not supported by
/// [`TerminalMode`](../mode/terminal/struct.TerminalMode.html).
pub fn with_rotation(self, rotation: DisplayRotation) -> Self {
Self { rotation, ..self }
pub fn with_rotation<ROTATION: DisplayRotation>(self) -> Builder<DSIZE, ROTATION> {
Builder {
_rotation: core::marker::PhantomData,
_size: core::marker::PhantomData,
}
}

/// Finish the builder and use some interface communicate with the display
///
/// This method consumes the builder and must come last in the method call chain
pub fn connect<I>(self, interface: I) -> DisplayProperties<I, DSIZE>
pub fn connect<I>(self, interface: I) -> DisplayProperties<I, DSIZE, DROTATION>
where
I: WriteOnlyDataCommand,
{
DisplayProperties::new(interface, self.rotation)
DisplayProperties::new(interface)
}
}

Expand Down
84 changes: 69 additions & 15 deletions src/displayrotation.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,72 @@
//! Display rotation

// TODO: Add to prelude
/// Display rotation.
///
/// Note that 90º and 270º rotations are not supported by
// [`TerminalMode`](../mode/terminal/struct.TerminalMode.html).
#[derive(Clone, Copy)]
pub enum DisplayRotation {
/// No rotation, normal display
Rotate0,
/// Rotate by 90 degress clockwise
Rotate90,
/// Rotate by 180 degress clockwise
Rotate180,
/// Rotate 270 degress clockwise
Rotate270,
// These are not instantiated, so no need to implement Copy
#![allow(missing_copy_implementations)]

use crate::command::Command;
use display_interface::{DisplayError, WriteOnlyDataCommand};

/// A valid display rotation value
pub trait DisplayRotation {
/// Send rotation related configuration
fn configure(iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError>;

/// Flip coordinates if necessary
fn transform(x: u8, y: u8) -> (u8, u8);
}

/// No rotation
pub struct Rotate0;
impl DisplayRotation for Rotate0 {
fn configure(iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> {
Command::SegmentRemap(true).send(iface)?;
Command::ReverseComDir(true).send(iface)?;
Ok(())
}

fn transform(x: u8, y: u8) -> (u8, u8) {
(x, y)
}
}

/// 90° CW rotation
pub struct Rotate90;
impl DisplayRotation for Rotate90 {
fn configure(iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> {
Command::SegmentRemap(false).send(iface)?;
Command::ReverseComDir(true).send(iface)?;
Ok(())
}

fn transform(x: u8, y: u8) -> (u8, u8) {
(y, x)
}
}

/// 180° rotation
pub struct Rotate180;
impl DisplayRotation for Rotate180 {
fn configure(iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> {
Command::SegmentRemap(false).send(iface)?;
Command::ReverseComDir(false).send(iface)?;
Ok(())
}

fn transform(x: u8, y: u8) -> (u8, u8) {
(x, y)
}
}

/// 270° CW rotation
pub struct Rotate270;
impl DisplayRotation for Rotate270 {
fn configure(iface: &mut impl WriteOnlyDataCommand) -> Result<(), DisplayError> {
Command::SegmentRemap(true).send(iface)?;
Command::ReverseComDir(false).send(iface)?;
Ok(())
}

fn transform(x: u8, y: u8) -> (u8, u8) {
(y, x)
}
}
2 changes: 1 addition & 1 deletion src/displaysize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// These are not instantiated, so no need to implement Copy
#![allow(missing_copy_implementations)]

use display_interface::{DisplayError, WriteOnlyDataCommand};
use super::command::Command;
use display_interface::{DisplayError, WriteOnlyDataCommand};
use generic_array::ArrayLength;
use typenum::{U1024, U192, U360, U384, U512};

Expand Down
6 changes: 3 additions & 3 deletions src/mode/displaymode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ use crate::Error;
use hal::{blocking::delay::DelayMs, digital::v2::OutputPin};

/// Trait with core functionality for display mode switching
pub trait DisplayModeTrait<DI, DSIZE>: Sized {
pub trait DisplayModeTrait<DI, DSIZE, DROTATION>: Sized {
/// Allocate all required data and initialise display for mode
fn new(properties: DisplayProperties<DI, DSIZE>) -> Self;
fn new(properties: DisplayProperties<DI, DSIZE, DROTATION>) -> Self;

/// Deconstruct object and retrieve DisplayProperties
fn into_properties(self) -> DisplayProperties<DI, DSIZE>;
fn into_properties(self) -> DisplayProperties<DI, DSIZE, DROTATION>;

/// Release display interface
fn release(self) -> DI {
Expand Down
111 changes: 38 additions & 73 deletions src/mode/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,35 +55,38 @@
//!
//! [embedded_graphics]: https://crates.io/crates/embedded_graphics

use crate::displayrotation::{DisplayRotation, Rotate0};
use crate::displaysize::{DisplaySize, DisplaySize128x64};
use display_interface::{DisplayError, WriteOnlyDataCommand};
use generic_array::GenericArray;

use crate::{
brightness::Brightness, displayrotation::DisplayRotation, mode::displaymode::DisplayModeTrait,
properties::DisplayProperties,
brightness::Brightness, mode::displaymode::DisplayModeTrait, properties::DisplayProperties,
};

// TODO: Add to prelude
/// Graphics mode handler
pub struct GraphicsMode<DI, DSIZE = DisplaySize128x64>
pub struct GraphicsMode<DI, DSIZE = DisplaySize128x64, DROTATION = Rotate0>
where
DSIZE: DisplaySize,
DROTATION: DisplayRotation,
{
properties: DisplayProperties<DI, DSIZE>,
properties: DisplayProperties<DI, DSIZE, DROTATION>,
buffer: GenericArray<u8, DSIZE::BufferSize>,
min_x: u8,
max_x: u8,
min_y: u8,
max_y: u8,
}

impl<DI, DSIZE> DisplayModeTrait<DI, DSIZE> for GraphicsMode<DI, DSIZE>
impl<DI, DSIZE, DROTATION> DisplayModeTrait<DI, DSIZE, DROTATION>
for GraphicsMode<DI, DSIZE, DROTATION>
where
DSIZE: DisplaySize,
DROTATION: DisplayRotation,
{
/// Create new GraphicsMode instance
fn new(properties: DisplayProperties<DI, DSIZE>) -> Self {
fn new(properties: DisplayProperties<DI, DSIZE, DROTATION>) -> Self {
GraphicsMode {
properties,
buffer: GenericArray::default(),
Expand All @@ -95,15 +98,16 @@ where
}

/// Release display interface used by `GraphicsMode`
fn into_properties(self) -> DisplayProperties<DI, DSIZE> {
fn into_properties(self) -> DisplayProperties<DI, DSIZE, DROTATION> {
self.properties
}
}

impl<DI, DSIZE> GraphicsMode<DI, DSIZE>
impl<DI, DSIZE, DROTATION> GraphicsMode<DI, DSIZE, DROTATION>
where
DSIZE: DisplaySize,
DI: WriteOnlyDataCommand,
DROTATION: DisplayRotation,
{
/// Clear the display buffer. You need to call `disp.flush()` for any effect on the screen
pub fn clear(&mut self) {
Expand All @@ -128,81 +132,46 @@ where
let (width, height) = self.get_dimensions();

// Determine which bytes need to be sent
let disp_min_x = self.min_x;
let disp_min_y = self.min_y;
let (disp_min_x, disp_min_y) = DROTATION::transform(self.min_x, self.min_y);
let (disp_max_x, disp_max_y) = DROTATION::transform(self.max_x, self.max_y);

let (disp_max_x, disp_max_y) = match self.properties.get_rotation() {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
((self.max_x + 1).min(width), (self.max_y | 7).min(height))
}
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
((self.max_x | 7).min(width), (self.max_y + 1).min(height))
}
};
let (disp_max_x, disp_max_y) = ((disp_max_x + 1).min(width), (disp_max_y | 7).min(height));

self.min_x = width - 1;
self.min_x = 255;
self.max_x = 0;
self.min_y = width - 1;
self.min_y = 255;
self.max_y = 0;

// Tell the display to update only the part that has changed
match self.properties.get_rotation() {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
self.properties.set_draw_area(
(disp_min_x + DSIZE::OFFSETX, disp_min_y + DSIZE::OFFSETY),
(disp_max_x + DSIZE::OFFSETX, disp_max_y + DSIZE::OFFSETY),
)?;

self.properties.bounded_draw(
&self.buffer,
width as usize,
(disp_min_x, disp_min_y),
(disp_max_x, disp_max_y),
)
}
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
self.properties.set_draw_area(
(disp_min_y + DSIZE::OFFSETY, disp_min_x + DSIZE::OFFSETX),
(disp_max_y + DSIZE::OFFSETY, disp_max_x + DSIZE::OFFSETX),
)?;

self.properties.bounded_draw(
&self.buffer,
height as usize,
(disp_min_y, disp_min_x),
(disp_max_y, disp_max_x),
)
}
}
self.properties.set_draw_area(
DROTATION::transform(disp_min_x + DSIZE::OFFSETX, disp_min_y + DSIZE::OFFSETY),
DROTATION::transform(disp_max_x + DSIZE::OFFSETX, disp_max_y + DSIZE::OFFSETY),
)?;

self.properties.bounded_draw(
&self.buffer,
DSIZE::WIDTH as usize,
DROTATION::transform(disp_min_x, disp_min_y),
DROTATION::transform(disp_max_x, disp_max_y),
)
}

/// Turn a pixel on or off. A non-zero `value` is treated as on, `0` as off. If the X and Y
/// coordinates are out of the bounds of the display, this method call is a noop.
pub fn set_pixel(&mut self, x: u32, y: u32, value: u8) {
let display_rotation = self.properties.get_rotation();

let (idx, bit) = match display_rotation {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
let idx = ((y as usize) / 8 * DSIZE::WIDTH as usize) + (x as usize);
let bit = y % 8;
// umm... cast up and down, must cleanup
let (x, y) = DROTATION::transform(x as u8, y as u8);

(idx, bit)
}
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
let idx = ((x as usize) / 8 * DSIZE::WIDTH as usize) + (y as usize);
let bit = x % 8;

(idx, bit)
}
};
let idx = ((x as usize) / 8 * DSIZE::WIDTH as usize) + (y as usize);
let bit = y % 8;

if let Some(byte) = self.buffer.get_mut(idx) {
// Keep track of max and min values
self.min_x = self.min_x.min(x as u8);
self.max_x = self.max_x.max(x as u8);
self.min_x = self.min_x.min(x);
self.max_x = self.max_x.max(x);

self.min_y = self.min_y.min(y as u8);
self.max_y = self.max_y.max(y as u8);
self.min_y = self.min_y.min(y);
self.max_y = self.max_y.max(y);

// Set pixel value in byte
// Ref this comment https://stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit#comment46654671_47990
Expand All @@ -222,11 +191,6 @@ where
self.properties.get_dimensions()
}

/// Set the display rotation
pub fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), DisplayError> {
self.properties.set_rotation(rot)
}

/// Turn the display on or off. The display can be drawn to and retains all
/// of its memory even while off.
pub fn display_on(&mut self, on: bool) -> Result<(), DisplayError> {
Expand All @@ -251,10 +215,11 @@ use embedded_graphics::{
};

#[cfg(feature = "graphics")]
impl<DI, DSIZE> DrawTarget<BinaryColor> for GraphicsMode<DI, DSIZE>
impl<DI, DSIZE, DROTATION> DrawTarget<BinaryColor> for GraphicsMode<DI, DSIZE, DROTATION>
where
DI: WriteOnlyDataCommand,
DSIZE: DisplaySize,
DROTATION: DisplayRotation,
{
type Error = DisplayError;

Expand Down
Loading

0 comments on commit 787ddcc

Please sign in to comment.