diff --git a/src/builder.rs b/src/builder.rs index 22711776..aa38c6df 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -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 +pub struct Builder where DSIZE: DisplaySize, + DROTATION: DisplayRotation, { - rotation: DisplayRotation, + _rotation: core::marker::PhantomData, _size: core::marker::PhantomData, } @@ -80,20 +85,21 @@ 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 Builder +impl Builder where DSIZE: DisplaySize, + DROTATION: DisplayRotation, { /// Set the size of the display. Supported sizes are defined by [DisplaySize]. - pub fn size(self) -> Builder { + pub fn size(self) -> Builder { Builder { - rotation: self.rotation, + _rotation: core::marker::PhantomData, _size: core::marker::PhantomData, } } @@ -101,18 +107,21 @@ where /// 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(self) -> Builder { + 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(self, interface: I) -> DisplayProperties + pub fn connect(self, interface: I) -> DisplayProperties where I: WriteOnlyDataCommand, { - DisplayProperties::new(interface, self.rotation) + DisplayProperties::new(interface) } } diff --git a/src/displayrotation.rs b/src/displayrotation.rs index a0a33504..67d4bf6f 100644 --- a/src/displayrotation.rs +++ b/src/displayrotation.rs @@ -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) + } } diff --git a/src/displaysize.rs b/src/displaysize.rs index 869a8899..e52a0590 100644 --- a/src/displaysize.rs +++ b/src/displaysize.rs @@ -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}; diff --git a/src/mode/displaymode.rs b/src/mode/displaymode.rs index 31455452..933c07da 100644 --- a/src/mode/displaymode.rs +++ b/src/mode/displaymode.rs @@ -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: Sized { +pub trait DisplayModeTrait: Sized { /// Allocate all required data and initialise display for mode - fn new(properties: DisplayProperties) -> Self; + fn new(properties: DisplayProperties) -> Self; /// Deconstruct object and retrieve DisplayProperties - fn into_properties(self) -> DisplayProperties; + fn into_properties(self) -> DisplayProperties; /// Release display interface fn release(self) -> DI { diff --git a/src/mode/graphics.rs b/src/mode/graphics.rs index d7338d49..fa88c566 100644 --- a/src/mode/graphics.rs +++ b/src/mode/graphics.rs @@ -55,22 +55,23 @@ //! //! [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 +pub struct GraphicsMode where DSIZE: DisplaySize, + DROTATION: DisplayRotation, { - properties: DisplayProperties, + properties: DisplayProperties, buffer: GenericArray, min_x: u8, max_x: u8, @@ -78,12 +79,14 @@ where max_y: u8, } -impl DisplayModeTrait for GraphicsMode +impl DisplayModeTrait + for GraphicsMode where DSIZE: DisplaySize, + DROTATION: DisplayRotation, { /// Create new GraphicsMode instance - fn new(properties: DisplayProperties) -> Self { + fn new(properties: DisplayProperties) -> Self { GraphicsMode { properties, buffer: GenericArray::default(), @@ -95,15 +98,16 @@ where } /// Release display interface used by `GraphicsMode` - fn into_properties(self) -> DisplayProperties { + fn into_properties(self) -> DisplayProperties { self.properties } } -impl GraphicsMode +impl GraphicsMode 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) { @@ -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 @@ -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> { @@ -251,10 +215,11 @@ use embedded_graphics::{ }; #[cfg(feature = "graphics")] -impl DrawTarget for GraphicsMode +impl DrawTarget for GraphicsMode where DI: WriteOnlyDataCommand, DSIZE: DisplaySize, + DROTATION: DisplayRotation, { type Error = DisplayError; diff --git a/src/mode/terminal.rs b/src/mode/terminal.rs index fb46bc3e..47eabfbd 100644 --- a/src/mode/terminal.rs +++ b/src/mode/terminal.rs @@ -30,7 +30,7 @@ use display_interface::{DisplayError, WriteOnlyDataCommand}; use crate::{ brightness::Brightness, command::AddrMode, - displayrotation::DisplayRotation, + displayrotation::{DisplayRotation, Rotate0}, displaysize::*, mode::{ displaymode::DisplayModeTrait, @@ -162,21 +162,24 @@ impl IntoTerminalModeResult for Result { // TODO: Add to prelude /// Terminal mode handler -pub struct TerminalMode +pub struct TerminalMode where DSIZE: TerminalDisplaySize, + DROTATION: DisplayRotation, { - properties: DisplayProperties, + properties: DisplayProperties, cursor: Option, } -impl DisplayModeTrait for TerminalMode +impl DisplayModeTrait + for TerminalMode where DI: WriteOnlyDataCommand, DSIZE: TerminalDisplaySize, + DROTATION: DisplayRotation, { /// Create new TerminalMode instance - fn new(properties: DisplayProperties) -> Self { + fn new(properties: DisplayProperties) -> Self { TerminalMode { properties, cursor: None, @@ -184,15 +187,16 @@ where } /// Release display interface used by `TerminalMode` - fn into_properties(self) -> DisplayProperties { + fn into_properties(self) -> DisplayProperties { self.properties } } -impl TerminalMode +impl TerminalMode where DI: WriteOnlyDataCommand, DSIZE: TerminalDisplaySize, + DROTATION: DisplayRotation, { /// Clear the display and reset the cursor to the top left corner pub fn clear(&mut self) -> Result<(), TerminalModeError> { @@ -264,12 +268,6 @@ where Ok(()) } - /// Set the display rotation - pub fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), TerminalModeError> { - // we don't need to touch the cursor because rotating 90º or 270º currently just flips - self.properties.set_rotation(rot).terminal_err() - } - /// 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<(), TerminalModeError> { diff --git a/src/properties.rs b/src/properties.rs index f0e6f292..b9b149da 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -3,45 +3,47 @@ use crate::mode::displaymode::DisplayModeTrait; use crate::{ brightness::Brightness, command::{AddrMode, Command, VcomhLevel}, - displayrotation::DisplayRotation, + displayrotation::{DisplayRotation, Rotate0}, displaysize::{DisplaySize, DisplaySize128x64}, }; use display_interface::{DataFormat::U8, DisplayError, WriteOnlyDataCommand}; /// Display properties struct -pub struct DisplayProperties { +pub struct DisplayProperties { iface: DI, - display_rotation: DisplayRotation, addr_mode: AddrMode, + _display_rotation: core::marker::PhantomData, _size: core::marker::PhantomData, } -impl DisplayProperties +impl DisplayProperties where DSIZE: DisplaySize, + DROTATION: DisplayRotation, { /// Create new DisplayProperties instance - pub fn new(iface: DI, display_rotation: DisplayRotation) -> DisplayProperties { + pub fn new(iface: DI) -> DisplayProperties { DisplayProperties { iface, - display_rotation, addr_mode: AddrMode::Page, // reset value + _display_rotation: core::marker::PhantomData, _size: core::marker::PhantomData, } } } -impl DisplayProperties { +impl DisplayProperties { /// Releases the display interface pub fn release(self) -> DI { self.iface } } -impl DisplayProperties +impl DisplayProperties where DI: WriteOnlyDataCommand, DSIZE: DisplaySize, + DROTATION: DisplayRotation, { /// Initialise the display in column mode (i.e. a byte walks down a column of 8 pixels) with /// column 0 on the left and column _(display_width - 1)_ on the right. @@ -51,8 +53,6 @@ where /// Initialise the display in one of the available addressing modes pub fn init_with_mode(&mut self, mode: AddrMode) -> Result<(), DisplayError> { - let display_rotation = self.display_rotation; - Command::DisplayOn(false).send(&mut self.iface)?; Command::DisplayClockDiv(0x8, 0x0).send(&mut self.iface)?; Command::Multiplex(DSIZE::HEIGHT - 1).send(&mut self.iface)?; @@ -62,7 +62,7 @@ where Command::ChargePump(true).send(&mut self.iface)?; Command::AddressMode(mode).send(&mut self.iface)?; - self.set_rotation(display_rotation)?; + DROTATION::configure(&mut self.iface)?; DSIZE::configure(&mut self.iface)?; @@ -181,41 +181,7 @@ where /// assert_eq!(rotated_disp.get_dimensions(), (64, 128)); /// ``` pub fn get_dimensions(&self) -> (u8, u8) { - match self.display_rotation { - DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (DSIZE::WIDTH, DSIZE::HEIGHT), - DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => (DSIZE::HEIGHT, DSIZE::WIDTH), - } - } - - /// Get the display rotation - pub fn get_rotation(&self) -> DisplayRotation { - self.display_rotation - } - - /// Set the display rotation - pub fn set_rotation(&mut self, display_rotation: DisplayRotation) -> Result<(), DisplayError> { - self.display_rotation = display_rotation; - - match display_rotation { - DisplayRotation::Rotate0 => { - Command::SegmentRemap(true).send(&mut self.iface)?; - Command::ReverseComDir(true).send(&mut self.iface)?; - } - DisplayRotation::Rotate90 => { - Command::SegmentRemap(false).send(&mut self.iface)?; - Command::ReverseComDir(true).send(&mut self.iface)?; - } - DisplayRotation::Rotate180 => { - Command::SegmentRemap(false).send(&mut self.iface)?; - Command::ReverseComDir(false).send(&mut self.iface)?; - } - DisplayRotation::Rotate270 => { - Command::SegmentRemap(true).send(&mut self.iface)?; - Command::ReverseComDir(false).send(&mut self.iface)?; - } - }; - - Ok(()) + DROTATION::transform(DSIZE::WIDTH, DSIZE::HEIGHT) } /// Turn the display on or off. The display can be drawn to and retains all @@ -237,7 +203,7 @@ where } /// Change into any mode implementing DisplayModeTrait - pub fn into>(self) -> NMODE + pub fn into>(self) -> NMODE where DI: WriteOnlyDataCommand, {