Add on_edit callback to EditView

Also fix <Del>/<Backspace> handling with precomposed characters
This commit is contained in:
Alexandre Bury 2016-07-30 01:18:12 -07:00
parent db9df3dfc9
commit 2e05a0825a
2 changed files with 73 additions and 24 deletions

View File

@ -1,6 +1,9 @@
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use std::rc::Rc;
use Cursive;
use With; use With;
use direction::Direction; use direction::Direction;
use theme::{ColorStyle, Effect}; use theme::{ColorStyle, Effect};
@ -49,7 +52,7 @@ use Printer;
/// ``` /// ```
pub struct EditView { pub struct EditView {
/// Current content. /// Current content.
content: String, content: Rc<String>,
/// Cursor position in the content, in bytes. /// Cursor position in the content, in bytes.
cursor: usize, cursor: usize,
/// Minimum layout length asked to the parent. /// Minimum layout length asked to the parent.
@ -62,6 +65,11 @@ pub struct EditView {
/// Last display length, to know the possible offset range /// Last display length, to know the possible offset range
last_length: usize, last_length: usize,
/// Callback when the content is modified.
///
/// Will be called with the current content and the cursor position
on_edit: Option<Rc<Fn(&mut Cursive, &str, usize)>>,
enabled: bool, enabled: bool,
} }
@ -71,11 +79,12 @@ impl EditView {
/// Creates a new, empty edit view. /// Creates a new, empty edit view.
pub fn new() -> Self { pub fn new() -> Self {
EditView { EditView {
content: String::new(), content: Rc::new(String::new()),
cursor: 0, cursor: 0,
offset: 0, offset: 0,
min_length: 1, min_length: 1,
last_length: 0, // scrollable: false, last_length: 0, // scrollable: false,
on_edit: None,
enabled: true, enabled: true,
} }
} }
@ -99,6 +108,15 @@ impl EditView {
self.enabled = true; self.enabled = true;
} }
/// Sets a callback to be called whenever the content is modified.
///
/// `callback` will be called with the view
/// content and the current cursor position.
pub fn on_edit<F: Fn(&mut Cursive, &str, usize) + 'static>(mut self, callback: F) -> Self {
self.on_edit = Some(Rc::new(callback));
self
}
/// Enable or disable this view. /// Enable or disable this view.
pub fn set_enabled(&mut self, enabled: bool) { pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled; self.enabled = enabled;
@ -112,12 +130,12 @@ impl EditView {
/// Replace the entire content of the view with the given one. /// Replace the entire content of the view with the given one.
pub fn set_content(&mut self, content: &str) { pub fn set_content(&mut self, content: &str) {
self.offset = 0; self.offset = 0;
self.content = content.to_string(); self.content = Rc::new(content.to_string());
} }
/// Get the current text. /// Get the current text.
pub fn get_content(&self) -> &str { pub fn get_content(&self) -> &str {
&self.content &&self.content
} }
/// Sets the current content to the given value. /// Sets the current content to the given value.
@ -135,6 +153,21 @@ impl EditView {
self self
} }
/// Insert `ch` at the current cursor position.
pub fn insert(&mut self, ch: char) {
// `make_mut` applies copy-on-write
// It means it'll just return a ref if no one else has a ref,
// and it will clone it into `self.content` otherwise.
Rc::make_mut(&mut self.content).insert(self.cursor, ch);
}
/// Remove the character at the current cursor position.
pub fn remove(&mut self, len: usize) {
let start = self.cursor;
let end = self.cursor + len;
Rc::make_mut(&mut self.content).drain(start..end).collect::<Vec<_>>();
}
} }
impl View for EditView { impl View for EditView {
@ -154,7 +187,7 @@ impl View for EditView {
printer.with_effect(effect, |printer| { printer.with_effect(effect, |printer| {
if width < self.last_length { if width < self.last_length {
// No problem, everything fits. // No problem, everything fits.
printer.print((0, 0), &self.content); printer.print((0, 0), self.get_content());
printer.print_hline((width, 0), printer.print_hline((width, 0),
printer.size.x - width, printer.size.x - width,
"_"); "_");
@ -196,7 +229,7 @@ impl View for EditView {
.next() .next()
.expect(&format!("Found no char at cursor {} in {}", .expect(&format!("Found no char at cursor {} in {}",
self.cursor, self.cursor,
self.content)) &self.content))
}; };
let offset = self.content[self.offset..self.cursor].width(); let offset = self.content[self.offset..self.cursor].width();
printer.print((offset, 0), c); printer.print((offset, 0), c);
@ -222,7 +255,7 @@ impl View for EditView {
Event::Char(ch) => { Event::Char(ch) => {
// Find the byte index of the char at self.cursor // Find the byte index of the char at self.cursor
self.content.insert(self.cursor, ch); self.insert(ch);
self.cursor += ch.len_utf8(); self.cursor += ch.len_utf8();
} }
// TODO: handle ctrl-key? // TODO: handle ctrl-key?
@ -251,10 +284,15 @@ impl View for EditView {
.unwrap() .unwrap()
.len(); .len();
self.cursor -= len; self.cursor -= len;
self.content.remove(self.cursor); self.remove(len);
} }
Event::Key(Key::Del) if self.cursor < self.content.len() => { Event::Key(Key::Del) if self.cursor < self.content.len() => {
self.content.remove(self.cursor); let len = self.content[self.cursor..]
.graphemes(true)
.next()
.unwrap()
.len();
self.remove(len);
} }
_ => return EventResult::Ignored, _ => return EventResult::Ignored,
} }
@ -293,7 +331,17 @@ impl View for EditView {
self.offset = self.content.len() - tail_bytes; self.offset = self.content.len() - tail_bytes;
} }
EventResult::Consumed(None) let cb = self.on_edit.clone().map(|cb| {
// Get a new Rc on it
let content = self.content.clone();
let cursor = self.cursor;
Callback::from_fn(move |s| {
cb(s, &content, cursor);
})
});
EventResult::Consumed(cb)
} }
} }

View File

@ -16,20 +16,6 @@ use Printer;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
struct Item<T> {
label: String,
value: Rc<T>,
}
impl<T> Item<T> {
fn new(label: &str, value: T) -> Self {
Item {
label: label.to_string(),
value: Rc::new(value),
}
}
}
/// View to select an item among a list. /// View to select an item among a list.
/// ///
/// It contains a list of values of type T, with associated labels. /// It contains a list of values of type T, with associated labels.
@ -424,3 +410,18 @@ impl<T: 'static> View for SelectView<T> {
} }
} }
} }
struct Item<T> {
label: String,
value: Rc<T>,
}
impl<T> Item<T> {
fn new(label: &str, value: T) -> Self {
Item {
label: label.to_string(),
value: Rc::new(value),
}
}
}