use embedded_graphics::{
geometry::AnchorPoint,
mono_font::{iso_8859_2::FONT_6X10, MonoTextStyleBuilder},
pixelcolor::BinaryColor,
prelude::*,
primitives::{Line, PrimitiveStyle, Rectangle},
text::{renderer::TextRenderer, Baseline},
};
use embedded_graphics_simulator::{
BinaryColorTheme, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
};
use embedded_text::{alignment::VerticalAlignment, plugin::Plugin, style::TextBoxStyle, TextBox};
use sdl2::keyboard::{Keycode, Mod};
use std::{collections::HashMap, convert::Infallible, thread, time::Duration};
trait StrExt {
fn first_n_chars<'a>(&'a self, n: usize) -> &'a str;
}
impl StrExt for str {
fn first_n_chars<'a>(&'a self, n: usize) -> &'a str {
if let Some((i, (idx, _))) = self.char_indices().enumerate().take(n + 1).last() {
if i < n as usize {
self
} else {
&self[0..idx]
}
} else {
self
}
}
}
trait Selector {
fn select_modified(&self, keymod: Mod) -> &str;
}
impl Selector for (&str, &str, &str, &str) {
#[inline]
fn select_modified(&self, keymod: Mod) -> &str {
if keymod.contains(Mod::RALTMOD) {
self.3
} else if keymod.intersects(Mod::LSHIFTMOD | Mod::RSHIFTMOD) {
self.1
} else if keymod.contains(Mod::CAPSMOD) {
self.2
} else {
self.0
}
}
}
struct EditorInput {
pub text: String,
cursor_offset: usize,
}
impl EditorInput {
pub fn new(text: &str) -> Self {
Self {
cursor_offset: text.len(),
text: text.to_owned(),
}
}
pub fn insert(&mut self, s: &str) {
self.text.insert_str(self.cursor_offset, s);
self.cursor_offset += s.len();
}
pub fn delete_before(&mut self) {
if self.cursor_offset > 0 {
self.cursor_offset -= 1;
self.text.remove(self.cursor_offset);
}
}
pub fn delete_after(&mut self) {
if self.cursor_offset < self.text.chars().count() {
self.text.remove(self.cursor_offset);
}
}
pub fn cursor_left(&mut self) {
if self.cursor_offset > 0 {
self.cursor_offset -= 1;
}
}
pub fn cursor_right(&mut self) {
if self.cursor_offset < self.text.len() {
self.cursor_offset += 1;
}
}
pub fn cursor_plugin<C: PixelColor>(&self, color: C) -> EditorPlugin<C> {
EditorPlugin {
current_offset: 0,
cursor_offset: self.cursor_offset,
color,
cursor_drawn: false,
}
}
}
#[derive(Clone, Copy)]
struct EditorPlugin<C> {
current_offset: usize,
cursor_offset: usize,
color: C,
cursor_drawn: bool,
}
impl<C: PixelColor> EditorPlugin<C> {
fn draw_cursor<D>(
&self,
draw_target: &mut D,
bounds: Rectangle,
pos: Point,
) -> Result<(), D::Error>
where
D: DrawTarget<Color = C>,
{
let style = PrimitiveStyle::with_stroke(self.color, 1);
Line::new(
pos + Point::new(0, 1),
pos + Point::new(0, bounds.size.height as i32 - 1),
)
.into_styled(style)
.draw(draw_target)
}
}
impl<'a, C: PixelColor> Plugin<'a, C> for EditorPlugin<C> {
fn post_render<T, D>(
&mut self,
draw_target: &mut D,
character_style: &T,
text: &str,
bounds: Rectangle,
) -> Result<(), D::Error>
where
T: TextRenderer<Color = C>,
D: DrawTarget<Color = T::Color>,
{
let len = text.chars().count();
let current_offset = self.current_offset;
self.current_offset += len;
if self.cursor_drawn {
return Ok(());
}
if (len == 0 && current_offset == self.cursor_offset)
|| (current_offset..current_offset + len).contains(&self.cursor_offset)
{
let chars_before = self.cursor_offset - current_offset;
let pos = if chars_before == 0 {
bounds.top_left
} else {
let str_before = text.first_n_chars(chars_before);
let metrics =
character_style.measure_string(str_before, bounds.top_left, Baseline::Top);
metrics.bounding_box.anchor_point(AnchorPoint::TopRight) + Point::new(1, 0)
};
self.draw_cursor(draw_target, bounds, pos)?;
self.cursor_drawn = true;
}
Ok(())
}
}
fn main() -> Result<(), Infallible> {
let inputs: HashMap<_, _> = [
(Keycode::A, ("a", "A", "A", "ä")),
(Keycode::B, ("b", "B", "B", "{")),
(Keycode::C, ("c", "C", "C", "&")),
(Keycode::D, ("d", "D", "D", "Đ")),
(Keycode::E, ("e", "E", "E", "Ä")),
(Keycode::F, ("f", "F", "F", "[")),
(Keycode::G, ("g", "G", "G", "]")),
(Keycode::H, ("h", "H", "H", "")),
(Keycode::I, ("i", "I", "I", "Í")),
(Keycode::J, ("j", "J", "J", "í")),
(Keycode::K, ("k", "K", "K", "ł")),
(Keycode::L, ("l", "L", "L", "Ł")),
(Keycode::M, ("m", "M", "M", "<")),
(Keycode::N, ("n", "N", "N", "}")),
(Keycode::O, ("o", "O", "O", "")),
(Keycode::P, ("p", "P", "P", "")),
(Keycode::Q, ("q", "Q", "Q", "\\")),
(Keycode::R, ("r", "R", "R", "")),
(Keycode::S, ("s", "S", "S", "đ")),
(Keycode::T, ("t", "T", "T", "")),
(Keycode::U, ("u", "U", "U", "€")),
(Keycode::V, ("v", "V", "V", "@")),
(Keycode::W, ("w", "W", "W", "|")),
(Keycode::X, ("x", "X", "X", "#")),
(Keycode::Y, ("y", "Y", "Y", ">")),
(Keycode::Z, ("z", "Z", "Z", "")),
(Keycode::Num0, ("0", "§", "0", "")),
(Keycode::Num1, ("1", "'", "1", "~")),
(Keycode::Num2, ("2", "\"", "2", "ˇ")),
(Keycode::Num3, ("3", "+", "3", "^")),
(Keycode::Num4, ("4", "!", "4", "˘")),
(Keycode::Num5, ("5", "%", "5", "°")),
(Keycode::Num6, ("6", "/", "6", "˛")),
(Keycode::Num7, ("7", "=", "7", "`")),
(Keycode::Num8, ("8", "(", "8", "˙")),
(Keycode::Num9, ("9", ")", "9", "´")),
(Keycode::Space, (" ", " ", " ", " ")),
(Keycode::Comma, (",", "?", ",", " ")),
(Keycode::Period, (".", ":", ".", ">")),
(Keycode::Minus, ("-", "_", "-", "*")),
(Keycode::Return, ("\n", "\n", "\n", "\n")),
(Keycode::KpEnter, ("\n", "\n", "\n", "\n")),
]
.iter()
.cloned()
.collect();
let output_settings = OutputSettingsBuilder::new()
.theme(BinaryColorTheme::OledBlue)
.scale(2)
.build();
let mut window = Window::new("Interactive TextBox input demonstration", &output_settings);
let character_style = MonoTextStyleBuilder::new()
.font(&FONT_6X10)
.text_color(BinaryColor::On)
.build();
let textbox_style = TextBoxStyle::with_vertical_alignment(VerticalAlignment::Scrolling);
let mut input = EditorInput::new("Hello, World!");
'demo: loop {
let mut display = SimulatorDisplay::new(Size::new(128, 64));
TextBox::with_textbox_style(
&input.text,
display.bounding_box(),
character_style,
textbox_style,
)
.add_plugin(input.cursor_plugin(BinaryColor::On))
.draw(&mut display)?;
window.update(&display);
for event in window.events() {
match event {
SimulatorEvent::KeyDown {
keycode, keymod, ..
} => match keycode {
Keycode::Backspace => input.delete_before(),
Keycode::Delete => input.delete_after(),
Keycode::Left => input.cursor_left(),
Keycode::Right => input.cursor_right(),
_ => {
if let Some(k) = inputs.get(&keycode) {
input.insert(k.select_modified(keymod));
}
}
},
SimulatorEvent::Quit => break 'demo,
_ => {}
}
}
thread::sleep(Duration::from_millis(10));
}
Ok(())
}