use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
use crate::event::Event;
use crate::geom::{Rect, Size};
use crate::layout::Constraint;
use crate::render::RenderCx;
use crate::style::Style;
pub struct Input {
text: String,
cursor_byte: usize,
focused: bool,
placeholder: String,
style: Style,
focus_style: Style,
on_submit: Option<Box<dyn FnMut(&str)>>,
}
impl Input {
pub fn new() -> Self {
Self {
text: String::new(),
cursor_byte: 0,
focused: false,
placeholder: String::new(),
style: Style::default(),
focus_style: Style::default()
.bg(crate::style::Color::White)
.fg(crate::style::Color::Black),
on_submit: None,
}
}
pub fn placeholder(mut self, text: impl Into<String>) -> Self {
self.placeholder = text.into();
self
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn focus_style(mut self, style: Style) -> Self {
self.focus_style = style;
self
}
pub fn on_submit(mut self, f: impl FnMut(&str) + 'static) -> Self {
self.on_submit = Some(Box::new(f));
self
}
pub fn text(&self) -> &str {
&self.text
}
fn clamp_cursor(&mut self) {
if self.cursor_byte > self.text.len() {
self.cursor_byte = self.text.len();
}
}
fn cursor_left(&mut self) {
if self.cursor_byte == 0 {
return;
}
if let Some((i, _)) = self.text.char_indices().rev().find(|&(i, _)| i < self.cursor_byte) {
self.cursor_byte = i;
} else {
self.cursor_byte = 0;
}
}
fn cursor_right(&mut self) {
if let Some((i, _)) = self
.text
.char_indices()
.find(|&(i, _)| i > self.cursor_byte)
{
self.cursor_byte = i;
} else {
self.cursor_byte = self.text.len();
}
}
fn cursor_char_index(&self) -> usize {
self.text[..self.cursor_byte].chars().count()
}
}
impl Component for Input {
fn render(&self, cx: &mut RenderCx) {
let placeholder_mode = self.text.is_empty() && !self.focused;
let display_text = if placeholder_mode {
&self.placeholder
} else {
&self.text
};
let base_style = if placeholder_mode {
Style::default().fg(crate::style::Color::Gray)
} else {
Style::default()
};
let cursor_style = Style::default()
.bg(crate::style::Color::White)
.fg(crate::style::Color::Black);
let cursor_char = if placeholder_mode {
0
} else {
self.cursor_char_index()
};
for (i, ch) in display_text.chars().enumerate() {
if i == cursor_char && self.focused {
cx.set_style(cursor_style.clone());
} else {
cx.set_style(base_style.clone());
}
cx.text(ch.to_string());
}
if self.cursor_byte >= self.text.len() && self.focused {
cx.set_style(cursor_style);
cx.text(" ");
}
}
fn measure(&self, _constraint: Constraint, _cx: &mut MeasureCx) -> Size {
let display = if self.text.is_empty() {
&self.placeholder
} else {
&self.text
};
let width: u16 = display
.chars()
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16)
.sum();
Size {
width: (width + 1),
height: 1,
}
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
match event {
Event::Focus => {
self.focused = true;
cx.invalidate_paint();
return;
}
Event::Blur => {
self.focused = false;
cx.invalidate_paint();
return;
}
_ => {}
}
if cx.phase() != crate::event::EventPhase::Target {
return;
}
if let Event::Key(key_event) = event {
if key_event.key == crate::event::Key::Char('c') && key_event.modifiers.ctrl {
cx.quit();
return;
}
if key_event.key == crate::event::Key::Char('d') && key_event.modifiers.ctrl {
if self.cursor_byte < self.text.len() {
let end = self.text[self.cursor_byte..]
.chars()
.next()
.map(|c| self.cursor_byte + c.len_utf8())
.unwrap_or(self.cursor_byte);
self.text.drain(self.cursor_byte..end);
cx.invalidate_paint();
}
return;
}
if key_event.modifiers.ctrl || key_event.modifiers.alt {
return;
}
self.clamp_cursor();
match &key_event.key {
crate::event::Key::Enter => {
if let Some(ref mut f) = self.on_submit {
f(&self.text);
}
}
crate::event::Key::Char(ch) => {
self.text.insert(self.cursor_byte, *ch);
self.cursor_byte += ch.len_utf8();
cx.invalidate_paint();
}
crate::event::Key::Backspace => {
if self.cursor_byte > 0 {
let old = self.cursor_byte;
self.cursor_left();
self.text.drain(self.cursor_byte..old);
cx.invalidate_paint();
}
}
crate::event::Key::Delete => {
if self.cursor_byte < self.text.len() {
let end = self.text[self.cursor_byte..]
.chars()
.next()
.map(|c| self.cursor_byte + c.len_utf8())
.unwrap_or(self.cursor_byte);
self.text.drain(self.cursor_byte..end);
cx.invalidate_paint();
}
}
crate::event::Key::Left => {
let old = self.cursor_byte;
self.cursor_left();
if self.cursor_byte != old {
cx.invalidate_paint();
}
}
crate::event::Key::Right => {
let old = self.cursor_byte;
self.cursor_right();
if self.cursor_byte != old {
cx.invalidate_paint();
}
}
crate::event::Key::Home => {
if self.cursor_byte != 0 {
self.cursor_byte = 0;
cx.invalidate_paint();
}
}
crate::event::Key::End => {
if self.cursor_byte != self.text.len() {
self.cursor_byte = self.text.len();
cx.invalidate_paint();
}
}
_ => {}
}
}
}
fn layout(&mut self, _rect: Rect, _cx: &mut LayoutCx) {}
fn style(&self) -> Style {
self.style.clone()
}
}