#[cfg(feature = "async")]
use crate::mode::DisplayConfigAsync;
use crate::{command::AddrMode, mode::DisplayConfig, rotation::DisplayRotation, size::*, Ssd1306};
#[cfg(feature = "async")]
use crate::{size::DisplaySizeAsync, Ssd1306Async};
use core::{cmp::min, fmt};
#[cfg(feature = "async")]
use display_interface::AsyncWriteOnlyDataCommand;
use display_interface::{DisplayError, WriteOnlyDataCommand};
#[maybe_async_cfg::maybe(
sync(keep_self),
async(
feature = "async",
idents(
TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),
DisplaySize(async = "DisplaySizeAsync"),
)
)
)]
pub trait TerminalDisplaySize: DisplaySize {
const CHAR_NUM: u8;
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(
feature = "async",
keep_self,
idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
)
)]
impl TerminalDisplaySize for DisplaySize128x64 {
const CHAR_NUM: u8 = 128;
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(
feature = "async",
keep_self,
idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
)
)]
impl TerminalDisplaySize for DisplaySize128x32 {
const CHAR_NUM: u8 = 64;
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(
feature = "async",
keep_self,
idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
)
)]
impl TerminalDisplaySize for DisplaySize96x16 {
const CHAR_NUM: u8 = 24;
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(
keep_self,
feature = "async",
idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
)
)]
impl TerminalDisplaySize for DisplaySize72x40 {
const CHAR_NUM: u8 = 45;
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(
feature = "async",
keep_self,
idents(TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),)
)
)]
impl TerminalDisplaySize for DisplaySize64x48 {
const CHAR_NUM: u8 = 48;
}
struct CursorWrapEvent(u8);
#[derive(Copy, Clone, Debug)]
struct Cursor {
col: u8,
row: u8,
width: u8,
height: u8,
}
impl Cursor {
pub fn new(width_pixels: u8, height_pixels: u8) -> Self {
let width = width_pixels / 8;
let height = height_pixels / 8;
Cursor {
col: 0,
row: 0,
width,
height,
}
}
pub fn advance(&mut self) -> Option<CursorWrapEvent> {
self.col = (self.col + 1) % self.width;
if self.col == 0 {
self.row = (self.row + 1) % self.height;
Some(CursorWrapEvent(self.row))
} else {
None
}
}
pub fn advance_line(&mut self) -> CursorWrapEvent {
self.row = (self.row + 1) % self.height;
self.col = 0;
CursorWrapEvent(self.row)
}
pub fn set_position(&mut self, col: u8, row: u8) {
self.col = min(col, self.width - 1);
self.row = min(row, self.height - 1);
}
pub fn get_position(&self) -> (u8, u8) {
(self.col, self.row)
}
pub fn get_dimensions(&self) -> (u8, u8) {
(self.width, self.height)
}
}
#[derive(Clone)]
pub enum TerminalModeError {
InterfaceError(DisplayError),
Uninitialized,
OutOfBounds,
}
impl fmt::Debug for TerminalModeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
Self::InterfaceError(_) => "InterfaceError".fmt(f),
Self::Uninitialized => "Uninitialized".fmt(f),
Self::OutOfBounds => "OutOfBound".fmt(f),
}
}
}
impl From<DisplayError> for TerminalModeError {
fn from(value: DisplayError) -> Self {
TerminalModeError::InterfaceError(value)
}
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(feature = "async", idents(TerminalMode(async = "TerminalModeAsync")))
)]
#[derive(Debug, Copy, Clone, Default)]
pub struct TerminalMode {
cursor: Option<Cursor>,
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(feature = "async", idents(TerminalMode(async = "TerminalModeAsync")))
)]
impl TerminalMode {
pub fn new() -> Self {
Self::default()
}
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(
feature = "async",
idents(
DisplaySize(async = "DisplaySizeAsync"),
DisplayConfig(async = "DisplayConfigAsync"),
WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"),
TerminalMode(async = "TerminalModeAsync"),
TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),
)
)
)]
impl<DI, SIZE> DisplayConfig for Ssd1306<DI, SIZE, TerminalMode>
where
DI: WriteOnlyDataCommand,
SIZE: TerminalDisplaySize,
{
type Error = TerminalModeError;
async fn set_rotation(&mut self, rot: DisplayRotation) -> Result<(), TerminalModeError> {
self.set_rotation(rot).await?;
self.reset_pos().await
}
async fn init(&mut self) -> Result<(), TerminalModeError> {
self.init_with_addr_mode(AddrMode::Page).await?;
self.reset_pos().await
}
}
#[maybe_async_cfg::maybe(
sync(keep_self),
async(
feature = "async",
idents(
DisplaySize(async = "DisplaySizeAsync"),
DisplayConfig(async = "DisplayConfigAsync"),
WriteOnlyDataCommand(async = "AsyncWriteOnlyDataCommand"),
TerminalMode(async = "TerminalModeAsync"),
TerminalDisplaySize(async = "TerminalDisplaySizeAsync"),
)
)
)]
impl<DI, SIZE> Ssd1306<DI, SIZE, TerminalMode>
where
DI: WriteOnlyDataCommand,
SIZE: TerminalDisplaySize,
{
pub async fn clear(&mut self) -> Result<(), TerminalModeError> {
self.set_addr_mode(AddrMode::Horizontal).await?;
let offset_x = match self.rotation() {
DisplayRotation::Rotate0 | DisplayRotation::Rotate270 => SIZE::OFFSETX,
DisplayRotation::Rotate180 | DisplayRotation::Rotate90 => {
SIZE::DRIVER_COLS - SIZE::WIDTH - SIZE::OFFSETX
}
};
self.set_draw_area(
(offset_x, SIZE::OFFSETY),
(SIZE::WIDTH + offset_x, SIZE::HEIGHT + SIZE::OFFSETY),
)
.await?;
for _ in 0..SIZE::CHAR_NUM {
self.draw(&[0; 8]).await?;
}
self.set_addr_mode(AddrMode::Page).await?;
self.reset_pos().await
}
pub async fn print_char(&mut self, c: char) -> Result<(), TerminalModeError> {
match c {
'\n' => {
let CursorWrapEvent(new_line) = self.ensure_cursor()?.advance_line();
self.set_position(0, new_line).await?;
}
'\r' => {
self.set_column(0).await?;
let (_, cur_line) = self.ensure_cursor()?.get_position();
self.ensure_cursor()?.set_position(0, cur_line);
}
_ => {
let bitmap = match self.rotation {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
Self::char_to_bitmap(c)
}
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
let bitmap = Self::char_to_bitmap(c);
Self::rotate_bitmap(bitmap)
}
};
self.draw(&bitmap).await?;
self.advance_cursor().await?;
}
}
Ok(())
}
pub fn position(&self) -> Result<(u8, u8), TerminalModeError> {
self.mode
.cursor
.as_ref()
.map(Cursor::get_position)
.ok_or(TerminalModeError::Uninitialized)
}
pub async fn set_position(&mut self, column: u8, row: u8) -> Result<(), TerminalModeError> {
let (width, height) = self.ensure_cursor()?.get_dimensions();
if column >= width || row >= height {
Err(TerminalModeError::OutOfBounds)
} else {
let offset_x = match self.rotation() {
DisplayRotation::Rotate0 | DisplayRotation::Rotate270 => SIZE::OFFSETX,
DisplayRotation::Rotate180 | DisplayRotation::Rotate90 => {
SIZE::DRIVER_COLS - SIZE::WIDTH - SIZE::OFFSETX
}
};
match self.rotation() {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => {
self.set_column(offset_x + column * 8).await?;
self.set_row(SIZE::OFFSETY + row * 8).await?;
}
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => {
self.set_column(offset_x + row * 8).await?;
self.set_row(SIZE::OFFSETY + column * 8).await?;
}
}
self.ensure_cursor()?.set_position(column, row);
Ok(())
}
}
async fn reset_pos(&mut self) -> Result<(), TerminalModeError> {
let (w, h) = match self.rotation() {
DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => (SIZE::WIDTH, SIZE::HEIGHT),
DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => (SIZE::HEIGHT, SIZE::WIDTH),
};
self.mode.cursor = Some(Cursor::new(w, h));
self.set_position(0, 0).await
}
async fn advance_cursor(&mut self) -> Result<(), TerminalModeError> {
let cursor = self.ensure_cursor()?;
cursor.advance();
let (c, r) = cursor.get_position();
self.set_position(c, r).await
}
fn ensure_cursor(&mut self) -> Result<&mut Cursor, TerminalModeError> {
self.mode
.cursor
.as_mut()
.ok_or(TerminalModeError::Uninitialized)
}
fn char_to_bitmap(input: char) -> [u8; 8] {
const CHARS: [[u8; 6]; 95] = [
[0x00, 0x2f, 0x00, 0x00, 0x00, 0x00],
[0x03, 0x00, 0x03, 0x00, 0x00, 0x00],
[0x12, 0x3f, 0x12, 0x12, 0x3f, 0x12],
[0x2e, 0x2a, 0x7f, 0x2a, 0x3a, 0x00],
[0x23, 0x13, 0x08, 0x04, 0x32, 0x31],
[0x10, 0x2a, 0x25, 0x2a, 0x10, 0x20],
[0x02, 0x01, 0x00, 0x00, 0x00, 0x00],
[0x1e, 0x21, 0x00, 0x00, 0x00, 0x00],
[0x21, 0x1e, 0x00, 0x00, 0x00, 0x00],
[0x08, 0x2a, 0x1c, 0x2a, 0x08, 0x00],
[0x08, 0x08, 0x3e, 0x08, 0x08, 0x00],
[0x80, 0x60, 0x00, 0x00, 0x00, 0x00],
[0x08, 0x08, 0x08, 0x08, 0x08, 0x00],
[0x30, 0x30, 0x00, 0x00, 0x00, 0x00],
[0x20, 0x10, 0x08, 0x04, 0x02, 0x00],
[0x1e, 0x31, 0x29, 0x25, 0x23, 0x1e],
[0x22, 0x21, 0x3f, 0x20, 0x20, 0x20],
[0x32, 0x29, 0x29, 0x29, 0x29, 0x26],
[0x12, 0x21, 0x21, 0x25, 0x25, 0x1a],
[0x18, 0x14, 0x12, 0x3f, 0x10, 0x00],
[0x17, 0x25, 0x25, 0x25, 0x25, 0x19],
[0x1e, 0x25, 0x25, 0x25, 0x25, 0x18],
[0x01, 0x01, 0x31, 0x09, 0x05, 0x03],
[0x1a, 0x25, 0x25, 0x25, 0x25, 0x1a],
[0x06, 0x29, 0x29, 0x29, 0x29, 0x1e],
[0x24, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x80, 0x64, 0x00, 0x00, 0x00, 0x00],
[0x08, 0x14, 0x22, 0x00, 0x00, 0x00],
[0x14, 0x14, 0x14, 0x14, 0x14, 0x00],
[0x22, 0x14, 0x08, 0x00, 0x00, 0x00],
[0x02, 0x01, 0x01, 0x29, 0x05, 0x02],
[0x1e, 0x21, 0x2d, 0x2b, 0x2d, 0x0e],
[0x3e, 0x09, 0x09, 0x09, 0x09, 0x3e],
[0x3f, 0x25, 0x25, 0x25, 0x25, 0x1a],
[0x1e, 0x21, 0x21, 0x21, 0x21, 0x12],
[0x3f, 0x21, 0x21, 0x21, 0x12, 0x0c],
[0x3f, 0x25, 0x25, 0x25, 0x25, 0x21],
[0x3f, 0x05, 0x05, 0x05, 0x05, 0x01],
[0x1e, 0x21, 0x21, 0x21, 0x29, 0x1a],
[0x3f, 0x04, 0x04, 0x04, 0x04, 0x3f],
[0x21, 0x21, 0x3f, 0x21, 0x21, 0x00],
[0x10, 0x20, 0x20, 0x20, 0x20, 0x1f],
[0x3f, 0x04, 0x0c, 0x0a, 0x11, 0x20],
[0x3f, 0x20, 0x20, 0x20, 0x20, 0x20],
[0x3f, 0x02, 0x04, 0x04, 0x02, 0x3f],
[0x3f, 0x02, 0x04, 0x08, 0x10, 0x3f],
[0x1e, 0x21, 0x21, 0x21, 0x21, 0x1e],
[0x3f, 0x09, 0x09, 0x09, 0x09, 0x06],
[0x1e, 0x21, 0x29, 0x31, 0x21, 0x5e],
[0x3f, 0x09, 0x09, 0x09, 0x19, 0x26],
[0x12, 0x25, 0x25, 0x25, 0x25, 0x18],
[0x01, 0x01, 0x01, 0x3f, 0x01, 0x01],
[0x1f, 0x20, 0x20, 0x20, 0x20, 0x1f],
[0x0f, 0x10, 0x20, 0x20, 0x10, 0x0f],
[0x1f, 0x20, 0x10, 0x10, 0x20, 0x1f],
[0x21, 0x12, 0x0c, 0x0c, 0x12, 0x21],
[0x01, 0x02, 0x3c, 0x02, 0x01, 0x00],
[0x21, 0x31, 0x29, 0x25, 0x23, 0x21],
[0x3f, 0x21, 0x00, 0x00, 0x00, 0x00],
[0x02, 0x04, 0x08, 0x10, 0x20, 0x00],
[0x21, 0x3f, 0x00, 0x00, 0x00, 0x00],
[0x04, 0x02, 0x3f, 0x02, 0x04, 0x00],
[0x40, 0x40, 0x40, 0x40, 0x40, 0x40],
[0x01, 0x02, 0x00, 0x00, 0x00, 0x00],
[0x10, 0x2a, 0x2a, 0x2a, 0x3c, 0x00],
[0x3f, 0x24, 0x24, 0x24, 0x18, 0x00],
[0x1c, 0x22, 0x22, 0x22, 0x00, 0x00],
[0x18, 0x24, 0x24, 0x24, 0x3f, 0x00],
[0x1c, 0x2a, 0x2a, 0x2a, 0x24, 0x00],
[0x00, 0x3e, 0x05, 0x01, 0x00, 0x00],
[0x18, 0xa4, 0xa4, 0xa4, 0x7c, 0x00],
[0x3f, 0x04, 0x04, 0x04, 0x38, 0x00],
[0x00, 0x24, 0x3d, 0x20, 0x00, 0x00],
[0x20, 0x40, 0x40, 0x3d, 0x00, 0x00],
[0x3f, 0x0c, 0x12, 0x20, 0x00, 0x00],
[0x1f, 0x20, 0x20, 0x00, 0x00, 0x00],
[0x3e, 0x02, 0x3c, 0x02, 0x3c, 0x00],
[0x3e, 0x02, 0x02, 0x02, 0x3c, 0x00],
[0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00],
[0xfc, 0x24, 0x24, 0x24, 0x18, 0x00],
[0x18, 0x24, 0x24, 0x24, 0xfc, 0x00],
[0x3e, 0x04, 0x02, 0x02, 0x00, 0x00],
[0x24, 0x2a, 0x2a, 0x2a, 0x10, 0x00],
[0x02, 0x1f, 0x22, 0x20, 0x00, 0x00],
[0x1e, 0x20, 0x20, 0x20, 0x1e, 0x00],
[0x06, 0x18, 0x20, 0x18, 0x06, 0x00],
[0x1e, 0x30, 0x1c, 0x30, 0x1e, 0x00],
[0x22, 0x14, 0x08, 0x14, 0x22, 0x00],
[0x1c, 0xa0, 0xa0, 0xa0, 0x7c, 0x00],
[0x22, 0x32, 0x2a, 0x26, 0x22, 0x00],
[0x0c, 0x3f, 0x21, 0x00, 0x00, 0x00],
[0x3f, 0x00, 0x00, 0x00, 0x00, 0x00],
[0x21, 0x3f, 0x0c, 0x00, 0x00, 0x00],
[0x02, 0x01, 0x02, 0x01, 0x00, 0x00],
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
];
let g = (input as usize)
.checked_sub(b'!'.into())
.and_then(|idx| CHARS.get(idx))
.unwrap_or(&CHARS[CHARS.len() - 1]);
[0, g[0], g[1], g[2], g[3], g[4], g[5], 0]
}
fn rotate_bitmap(bitmap: [u8; 8]) -> [u8; 8] {
let mut rotated: [u8; 8] = [0; 8];
for (col, source) in bitmap.iter().enumerate() {
for (row, item) in rotated.iter_mut().enumerate() {
let bit = source & (1 << row) != 0;
if bit {
*item |= 1 << col;
}
}
}
rotated
}
}
#[cfg(feature = "async")]
impl<DI, SIZE> Ssd1306Async<DI, SIZE, TerminalModeAsync>
where
DI: AsyncWriteOnlyDataCommand,
SIZE: TerminalDisplaySizeAsync,
{
pub async fn write_str(&mut self, s: &str) -> Result<(), TerminalModeError> {
for c in s.chars() {
self.print_char(c).await?;
}
Ok(())
}
}
impl<DI, SIZE> fmt::Write for Ssd1306<DI, SIZE, TerminalMode>
where
DI: WriteOnlyDataCommand,
SIZE: TerminalDisplaySize,
{
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
s.chars().map(move |c| self.print_char(c)).next_back();
Ok(())
}
}