use std::{thread, time::Duration};
use embedded_graphics::{
mono_font::{ascii::FONT_6X10, MonoTextStyle},
pixelcolor::BinaryColor,
prelude::*,
primitives::Rectangle,
};
use embedded_graphics_simulator::{
BinaryColorTheme, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
};
use embedded_text::{
alignment::HorizontalAlignment,
plugin::{tail::Tail, Plugin},
style::{HeightMode, TextBoxStyleBuilder, VerticalOverdraw},
TextBox, Token,
};
trait StrExt {
fn first_n_chars<'a>(&'a self, n: u32) -> &'a str;
}
impl StrExt for str {
fn first_n_chars<'a>(&'a self, n: u32) -> &'a str {
if let Some((i, (idx, _))) = self.char_indices().enumerate().take(n as usize + 1).last() {
if i < n as usize {
self
} else {
&self[0..idx]
}
} else {
self
}
}
}
#[derive(Clone)]
struct CharacterLimiter {
characters: u32,
measured: u32,
rendered: u32,
last_line: bool,
}
impl CharacterLimiter {
fn new(characters: u32) -> Self {
Self {
characters,
measured: 0,
rendered: 0,
last_line: false,
}
}
}
impl<'a, C> Plugin<'a, C> for CharacterLimiter
where
C: PixelColor,
{
fn new_line(&mut self) {
self.last_line = self.measured > self.characters;
}
fn next_token(
&mut self,
mut next_token: impl FnMut() -> Option<Token<'a, C>>,
) -> Option<Token<'a, C>> {
if self.last_line {
return None;
}
let token = next_token();
match token {
Some(Token::Whitespace(_, _)) => {
token
}
Some(Token::Word(word)) => {
self.measured += word.chars().count() as u32;
Some(Token::Word(word))
}
Some(Token::Break(_)) => {
self.measured += 1;
token
}
token => token,
}
}
fn render_token(&mut self, token: Token<'a, C>) -> Option<Token<'a, C>> {
if self.measured <= self.characters {
self.rendered = self.measured;
return Some(token);
}
let to_render = self.characters.saturating_sub(self.rendered);
if to_render == 0 {
return None;
}
self.rendered = self.measured;
match token {
Token::Whitespace(n, s) => {
let to_render = n.min(to_render);
Some(Token::Whitespace(to_render, s.first_n_chars(to_render)))
}
Token::Word(s) => Some(Token::Word(s.first_n_chars(to_render))),
Token::Break(repl) => Some(Token::Break(repl.first_n_chars(to_render))),
_ => Some(token),
}
}
}
fn main() {
let text = "Hello, World!\n\
Lorem Ipsum is simply dummy text of the printing and typesetting industry. \
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when \
an unknown printer took a galley of type and scrambled it to make a type specimen book.";
let output_settings = OutputSettingsBuilder::new()
.theme(BinaryColorTheme::OledBlue)
.scale(2)
.build();
let mut window = Window::new("TextBox demonstration", &output_settings);
let mut chars: u32 = 1;
loop {
let mut display = SimulatorDisplay::new(Size::new(128, 64));
let character_style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
let textbox_style = TextBoxStyleBuilder::new()
.alignment(HorizontalAlignment::Justified)
.height_mode(HeightMode::Exact(VerticalOverdraw::Hidden))
.build();
chars = chars.saturating_add(1);
let bounds = Rectangle::new(Point::zero(), Size::new(128, 64));
TextBox::with_textbox_style(text, bounds, character_style, textbox_style)
.add_plugin(CharacterLimiter::new(chars))
.add_plugin(Tail)
.draw(&mut display)
.unwrap();
window.update(&display);
for event in window.events() {
if event == SimulatorEvent::Quit {
return;
}
}
thread::sleep(Duration::from_millis(25));
}
}