use {Cursive, Printer, With};
use direction::Direction;
use event::{Callback, Event, EventResult, Key};
use std::rc::Rc;
use theme::{ColorStyle, Effect};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use utils::simple_suffix_length;
use vec::Vec2;
use view::View;
pub struct EditView {
content: Rc<String>,
cursor: usize,
offset: usize,
last_length: usize,
on_edit: Option<Rc<Fn(&mut Cursive, &str, usize)>>,
on_submit: Option<Rc<Fn(&mut Cursive, &str)>>,
secret: bool,
enabled: bool,
}
new_default!(EditView);
impl EditView {
pub fn new() -> Self {
EditView {
content: Rc::new(String::new()),
cursor: 0,
offset: 0,
last_length: 0, on_edit: None,
on_submit: None,
secret: false,
enabled: true,
}
}
pub fn set_secret(&mut self, secret: bool) {
self.secret = secret;
}
pub fn secret(self) -> Self {
self.with(|s| s.set_secret(true))
}
pub fn disable(&mut self) {
self.enabled = false;
}
pub fn disabled(self) -> Self {
self.with(Self::disable)
}
pub fn enable(&mut self) {
self.enabled = true;
}
pub fn on_edit<F>(mut self, callback: F) -> Self
where F: Fn(&mut Cursive, &str, usize) + 'static
{
self.on_edit = Some(Rc::new(callback));
self
}
pub fn on_submit<F>(mut self, callback: F) -> Self
where F: Fn(&mut Cursive, &str) + 'static
{
self.on_submit = Some(Rc::new(callback));
self
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn set_content<S: Into<String>>(&mut self, content: S) {
let content = content.into();
let len = content.len();
self.content = Rc::new(content);
self.offset = 0;
self.set_cursor(len);
}
pub fn get_content(&self) -> Rc<String> {
self.content.clone()
}
pub fn content<S: Into<String>>(mut self, content: S) -> Self {
self.set_content(content);
self
}
pub fn set_cursor(&mut self, cursor: usize) {
self.cursor = cursor;
self.keep_cursor_in_view();
}
pub fn insert(&mut self, ch: char) {
Rc::make_mut(&mut self.content).insert(self.cursor, ch);
self.cursor += ch.len_utf8();
}
pub fn remove(&mut self, len: usize) {
let start = self.cursor;
let end = self.cursor + len;
for _ in Rc::make_mut(&mut self.content).drain(start..end) {}
}
fn keep_cursor_in_view(&mut self) {
if self.cursor < self.offset {
self.offset = self.cursor;
} else {
let c_len = self.content[self.cursor..]
.graphemes(true)
.map(|g| g.width())
.next()
.unwrap_or(1);
if c_len > self.last_length {
return;
}
let available = self.last_length - c_len;
let suffix_length =
simple_suffix_length(&self.content[self.offset..self.cursor],
available);
self.offset = self.cursor - suffix_length;
assert!(self.cursor >= self.offset);
}
if self.content[self.offset..].width() < self.last_length {
let suffix_length = simple_suffix_length(&self.content,
self.last_length - 1);
self.offset = self.content.len() - suffix_length;
}
}
}
fn make_small_stars(length: usize) -> &'static str {
&"****"[..length]
}
impl View for EditView {
fn draw(&self, printer: &Printer) {
assert!(printer.size.x == self.last_length,
"Was promised {}, received {}",
self.last_length,
printer.size.x);
let width = self.content.width();
printer.with_color(ColorStyle::Secondary, |printer| {
let effect = if self.enabled {
Effect::Reverse
} else {
Effect::Simple
};
printer.with_effect(effect, |printer| {
if width < self.last_length {
if self.secret {
printer.print_hline((0, 0), width, "*");
} else {
printer.print((0, 0), &self.content);
}
printer.print_hline((width, 0),
printer.size.x - width,
"_");
} else {
let content = &self.content[self.offset..];
let display_bytes = content.graphemes(true)
.scan(0, |w, g| {
*w += g.width();
if *w > self.last_length { None } else { Some(g) }
})
.map(|g| g.len())
.fold(0, |a, b| a + b);
let content = &content[..display_bytes];
let width = content.width();
if self.secret {
printer.print_hline((0, 0), width, "*");
} else {
printer.print((0, 0), content);
}
if width < self.last_length {
printer.print_hline((width, 0),
self.last_length - width,
"_");
}
}
});
if printer.focused {
let c: &str = if self.cursor == self.content.len() {
"_"
} else {
let selected = self.content[self.cursor..]
.graphemes(true)
.next()
.expect(&format!("Found no char at cursor {} in {}",
self.cursor,
&self.content));
if self.secret {
make_small_stars(selected.width())
} else {
selected
}
};
let offset = self.content[self.offset..self.cursor].width();
printer.print((offset, 0), c);
}
});
}
fn layout(&mut self, size: Vec2) {
self.last_length = size.x;
}
fn take_focus(&mut self, _: Direction) -> bool {
self.enabled
}
fn on_event(&mut self, event: Event) -> EventResult {
match event {
Event::Char(ch) => self.insert(ch),
Event::Key(Key::Home) => self.cursor = 0,
Event::Key(Key::End) => self.cursor = self.content.len(),
Event::Key(Key::Left) if self.cursor > 0 => {
let len = self.content[..self.cursor]
.graphemes(true)
.last()
.unwrap()
.len();
self.cursor -= len;
}
Event::Key(Key::Right) if self.cursor < self.content.len() => {
let len = self.content[self.cursor..]
.graphemes(true)
.next()
.unwrap()
.len();
self.cursor += len;
}
Event::Key(Key::Backspace) if self.cursor > 0 => {
let len = self.content[..self.cursor]
.graphemes(true)
.last()
.unwrap()
.len();
self.cursor -= len;
self.remove(len);
}
Event::Key(Key::Del) if self.cursor < self.content.len() => {
let len = self.content[self.cursor..]
.graphemes(true)
.next()
.unwrap()
.len();
self.remove(len);
}
Event::Key(Key::Enter) if self.on_submit.is_some() => {
let cb = self.on_submit.clone().unwrap();
let content = self.content.clone();
return EventResult::with_cb(move |s| {
cb(s, &content);
});
}
_ => return EventResult::Ignored,
}
self.keep_cursor_in_view();
let cb = self.on_edit.clone().map(|cb| {
let content = self.content.clone();
let cursor = self.cursor;
Callback::from_fn(move |s| {
cb(s, &content, cursor);
})
});
EventResult::Consumed(cb)
}
}