use core::marker::PhantomData;
use embedded_graphics::{fonts::Font, geometry::Point, primitives::Rectangle};
#[derive(Copy, Clone, Debug)]
pub struct Cursor<F> {
pub position: Point,
pub bounds: Rectangle,
line_spacing: i32,
_marker: PhantomData<F>,
}
impl<F> Cursor<F>
where
F: Font,
{
#[inline]
#[must_use]
pub fn new(bounds: Rectangle, line_spacing: i32) -> Self {
Self {
_marker: PhantomData,
position: bounds.top_left,
line_spacing,
bounds: Rectangle::new(
bounds.top_left,
bounds.bottom_right + Point::new(1, 1 - F::CHARACTER_SIZE.height as i32),
),
}
}
#[inline]
#[must_use]
pub fn line_width(&self) -> u32 {
(self.bounds.bottom_right.x - self.bounds.top_left.x) as u32
}
#[inline]
pub fn new_line(&mut self) {
self.position.y += F::CHARACTER_SIZE.height as i32 + self.line_spacing;
}
#[inline]
pub fn carriage_return(&mut self) {
self.position.x = self.bounds.top_left.x;
}
#[inline]
#[must_use]
pub fn in_display_area(&self) -> bool {
self.bounds.top_left.y <= self.position.y && self.position.y <= self.bounds.bottom_right.y
}
#[inline]
#[must_use]
pub fn fits_in_line(&self, width: u32) -> bool {
let target = self.position.x + width as i32;
target <= self.bounds.bottom_right.x
}
#[inline]
#[must_use]
pub fn space(&self) -> u32 {
(self.bounds.bottom_right.x - self.position.x) as u32
}
#[inline]
pub fn advance_unchecked(&mut self, by: u32) {
self.position.x += by as i32;
}
#[inline]
pub fn x_in_line(&self) -> i32 {
self.position.x - self.bounds.top_left.x
}
#[inline]
pub fn advance(&mut self, by: u32) -> bool {
if self.fits_in_line(by) {
self.advance_unchecked(by);
true
} else {
false
}
}
#[inline]
pub fn rewind(&mut self, by: u32) -> bool {
let target = self.position.x - by as i32;
if self.bounds.top_left.x <= target {
self.position.x = target;
true
} else {
false
}
}
}
#[cfg(test)]
mod test {
use super::*;
use embedded_graphics::fonts::Font6x8;
#[test]
fn fits_in_line() {
let cursor: Cursor<Font6x8> =
Cursor::new(Rectangle::new(Point::zero(), Point::new(5, 7)), 0);
assert!(cursor.fits_in_line(6));
assert!(!cursor.fits_in_line(7));
}
#[test]
fn advance_moves_position() {
let mut cursor: Cursor<Font6x8> =
Cursor::new(Rectangle::new(Point::zero(), Point::new(5, 7)), 0);
assert!(cursor.fits_in_line(1));
cursor.advance(6);
assert!(!cursor.fits_in_line(1));
}
#[test]
fn rewind_moves_position_back() {
let mut cursor: Cursor<Font6x8> =
Cursor::new(Rectangle::new(Point::zero(), Point::new(5, 7)), 0);
cursor.advance(6);
assert_eq!(6, cursor.position.x);
assert!(cursor.rewind(3));
assert_eq!(3, cursor.position.x);
assert!(cursor.rewind(3));
assert_eq!(0, cursor.position.x);
assert!(!cursor.rewind(3));
assert_eq!(0, cursor.position.x);
}
#[test]
fn in_display_area() {
let mut cursor: Cursor<Font6x8> =
Cursor::new(Rectangle::new(Point::zero(), Point::new(5, 7)), 0);
let data = [(0, true), (-8, false), (-1, false), (1, false)];
for &(pos, inside) in data.iter() {
cursor.position.y = pos;
assert_eq!(inside, cursor.in_display_area());
}
}
}