use std::convert::TryFrom;
use euclid::Size2D;
use keyboard_types::Key;
use unicode_segmentation::UnicodeSegmentation;
use crate::{event::Event, unit::Cell, Printer, View as ViewTrait};
const SOFT_MIN_LINE_WIDTH: u16 = 10;
pub fn new<F, M>(state: &mut State, on_submit: F) -> View<'_, F>
where
F: Fn(String) -> M,
{
View::new(state, on_submit)
}
pub struct View<'a, F> {
state: &'a mut State,
on_submit: F,
clear_on_submit: bool,
placeholder: Option<char>,
echo: Echo,
}
impl<'a, F, M> View<'a, F>
where
F: Fn(String) -> M,
{
pub fn new(state: &'a mut State, on_submit: F) -> Self {
Self {
state,
on_submit,
clear_on_submit: true,
placeholder: Some('_'),
echo: Default::default(),
}
}
pub fn placeholder(mut self, placeholder: Option<char>) -> Self {
self.placeholder = placeholder;
self
}
pub fn echo(mut self, echo: Echo) -> Self {
self.echo = echo;
self
}
pub fn clear_on_submit(mut self, enable: bool) -> Self {
self.clear_on_submit = enable;
self
}
}
pub enum Echo {
On,
Faux(char),
Off,
}
impl Default for Echo {
fn default() -> Self {
Self::On
}
}
#[derive(Default)]
pub struct State {
input: String,
}
impl State {
pub fn content(&self) -> &str {
&self.input
}
}
impl<T, M, F> ViewTrait<T, M> for View<'_, F>
where
F: Fn(String) -> M,
M: 'static,
{
fn draw(&self, printer: &Printer, focused: bool) {
if let Some(c) = self.placeholder {
let line = std::iter::repeat(c)
.take(printer.size().width.into())
.fold(String::new(), |mut s, c| {
s.push(c);
s
});
printer.print(&line, (0, 0)).unwrap();
}
let width = printer.size().width;
let start = self
.state
.input
.len()
.saturating_add(1)
.saturating_sub(width.into());
match self.echo {
Echo::On => {
printer.print(&self.state.input[start..], (0, 0)).unwrap();
}
Echo::Faux(c) => {
let line: String =
std::iter::repeat(c).take(self.state.input.len()).collect();
printer.print(&line[start..], (0, 0)).unwrap();
}
Echo::Off => (),
}
if focused {
match self.echo {
Echo::On | Echo::Faux(_) => {
let cursor_x =
u16::try_from(self.state.input.graphemes(true).count())
.unwrap()
.min(width.saturating_sub(1));
printer.show_cursor_at((cursor_x, 0)).unwrap();
}
Echo::Off => {
printer.show_cursor_at((0, 0)).unwrap();
}
}
}
}
fn width(&self) -> Size2D<u16, Cell> {
(SOFT_MIN_LINE_WIDTH, 1).into()
}
fn height(&self) -> Size2D<u16, Cell> {
(SOFT_MIN_LINE_WIDTH, 1).into()
}
fn layout(&self, constraint: Size2D<u16, Cell>) -> Size2D<u16, Cell> {
(constraint.width, 1).into()
}
fn event(
&mut self,
event: &Event<T>,
focused: bool,
) -> Box<dyn Iterator<Item = M>> {
if !focused {
return Box::new(std::iter::empty());
}
let message = if let Event::Key(k) = event {
match &k.key {
Key::Character(c) => {
self.state.input.push_str(c);
None
}
Key::Enter => {
let line = self.state.input.clone();
if self.clear_on_submit {
self.state.input.clear();
}
Some((self.on_submit)(line))
}
Key::Backspace => {
self.state.input.pop();
None
}
_ => None,
}
} else {
None
};
if let Some(message) = message {
Box::new(std::iter::once(message))
} else {
Box::new(std::iter::empty())
}
}
fn interactive(&self) -> bool {
true
}
}