2016-06-26 22:03:12 +00:00
|
|
|
use ncurses;
|
|
|
|
use unicode_segmentation::UnicodeSegmentation;
|
2015-05-26 22:46:05 +00:00
|
|
|
|
2015-07-30 13:40:03 +00:00
|
|
|
use std::cmp::min;
|
|
|
|
|
2015-06-06 01:08:05 +00:00
|
|
|
use theme::ColorPair;
|
2015-05-26 23:11:22 +00:00
|
|
|
use vec::Vec2;
|
2016-03-15 22:37:57 +00:00
|
|
|
use view::{View, IdView, SizeRequest};
|
2015-05-28 01:04:33 +00:00
|
|
|
use event::*;
|
2015-05-26 22:46:05 +00:00
|
|
|
use printer::Printer;
|
|
|
|
|
2015-07-30 13:40:03 +00:00
|
|
|
/// Input box where the user can enter and edit text.
|
2015-05-26 22:46:05 +00:00
|
|
|
pub struct EditView {
|
2015-07-30 13:40:03 +00:00
|
|
|
/// Current content
|
2015-05-26 22:46:05 +00:00
|
|
|
content: String,
|
2015-07-30 13:40:03 +00:00
|
|
|
/// Cursor position in the content
|
2015-05-26 22:46:05 +00:00
|
|
|
cursor: usize,
|
2015-07-30 13:40:03 +00:00
|
|
|
/// Minimum layout length asked to the parent
|
2015-05-26 23:11:22 +00:00
|
|
|
min_length: usize,
|
2015-07-30 13:40:03 +00:00
|
|
|
|
|
|
|
/// When the content is too long for the display, offset it
|
|
|
|
offset: usize,
|
|
|
|
/// Last display length, to know the possible offset range
|
2016-03-15 22:37:57 +00:00
|
|
|
last_length: usize, /* scrollable: bool,
|
|
|
|
* TODO: add a max text length? */
|
2015-05-26 22:46:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EditView {
|
2015-05-26 23:11:22 +00:00
|
|
|
/// Creates a new, empty edit view.
|
2015-05-26 22:46:05 +00:00
|
|
|
pub fn new() -> Self {
|
|
|
|
EditView {
|
|
|
|
content: String::new(),
|
|
|
|
cursor: 0,
|
2015-07-30 13:40:03 +00:00
|
|
|
offset: 0,
|
2015-05-26 23:11:22 +00:00
|
|
|
min_length: 1,
|
2016-06-25 23:36:22 +00:00
|
|
|
last_length: 0, // scrollable: false,
|
2015-05-26 22:46:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-26 23:11:22 +00:00
|
|
|
/// Replace the entire content of the view with the given one.
|
2015-05-26 22:46:05 +00:00
|
|
|
pub fn set_content<'a>(&mut self, content: &'a str) {
|
2015-07-30 13:40:03 +00:00
|
|
|
self.offset = 0;
|
2015-05-26 22:46:05 +00:00
|
|
|
self.content = content.to_string();
|
|
|
|
}
|
|
|
|
|
2015-05-26 23:11:22 +00:00
|
|
|
/// Get the current text.
|
2015-05-26 22:46:05 +00:00
|
|
|
pub fn get_content(&self) -> &str {
|
|
|
|
&self.content
|
|
|
|
}
|
|
|
|
|
2015-05-26 23:11:22 +00:00
|
|
|
/// Sets the current content to the given value. Convenient chainable method.
|
2015-05-26 22:46:05 +00:00
|
|
|
pub fn content<'a>(mut self, content: &'a str) -> Self {
|
|
|
|
self.set_content(content);
|
|
|
|
self
|
|
|
|
}
|
2015-05-26 23:11:22 +00:00
|
|
|
|
|
|
|
/// Sets the minimum length for this view.
|
|
|
|
/// (This applies to the layout, not the content.)
|
|
|
|
pub fn min_length(mut self, min_length: usize) -> Self {
|
|
|
|
self.min_length = min_length;
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
2015-07-28 13:57:52 +00:00
|
|
|
|
|
|
|
/// Wraps this view into an IdView with the given id.
|
|
|
|
pub fn with_id(self, label: &str) -> IdView<Self> {
|
|
|
|
IdView::new(label, self)
|
|
|
|
}
|
2015-05-26 22:46:05 +00:00
|
|
|
}
|
|
|
|
|
2015-05-29 00:32:28 +00:00
|
|
|
fn remove_char(s: &mut String, cursor: usize) {
|
|
|
|
let i = match s.char_indices().nth(cursor) {
|
2016-03-15 22:37:57 +00:00
|
|
|
Some((i, _)) => i,
|
2015-05-29 00:32:28 +00:00
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
s.remove(i);
|
|
|
|
}
|
|
|
|
|
2015-05-26 22:46:05 +00:00
|
|
|
impl View for EditView {
|
2015-05-31 04:05:34 +00:00
|
|
|
fn draw(&mut self, printer: &Printer) {
|
2015-05-27 04:45:00 +00:00
|
|
|
// let style = if focused { color::HIGHLIGHT } else { color::HIGHLIGHT_INACTIVE };
|
2015-05-29 00:32:28 +00:00
|
|
|
let len = self.content.chars().count();
|
2015-06-06 01:08:05 +00:00
|
|
|
printer.with_color(ColorPair::Secondary, |printer| {
|
2015-05-27 04:45:00 +00:00
|
|
|
printer.with_style(ncurses::A_REVERSE(), |printer| {
|
2015-07-30 13:40:03 +00:00
|
|
|
if len < self.last_length {
|
2016-03-15 22:37:57 +00:00
|
|
|
printer.print((0, 0), &self.content);
|
2016-06-26 22:03:12 +00:00
|
|
|
printer.print_hline((len, 0), printer.size.x - len, "_");
|
2015-07-30 13:40:03 +00:00
|
|
|
} else {
|
|
|
|
let visible_end = min(self.content.len(), self.offset + self.last_length);
|
|
|
|
|
|
|
|
let content = &self.content[self.offset..visible_end];
|
2016-03-15 22:37:57 +00:00
|
|
|
printer.print((0, 0), content);
|
2015-07-30 13:40:03 +00:00
|
|
|
if visible_end - self.offset < printer.size.x {
|
2016-03-15 22:37:57 +00:00
|
|
|
printer.print((printer.size.x - 1, 0), "_");
|
2015-07-30 13:40:03 +00:00
|
|
|
}
|
|
|
|
}
|
2015-05-27 04:45:00 +00:00
|
|
|
});
|
2015-05-27 07:01:23 +00:00
|
|
|
|
|
|
|
// Now print cursor
|
2015-05-31 04:05:34 +00:00
|
|
|
if printer.focused {
|
2015-05-29 00:32:28 +00:00
|
|
|
let c = if self.cursor == len {
|
2016-06-26 22:03:12 +00:00
|
|
|
"_"
|
2015-05-27 07:01:23 +00:00
|
|
|
} else {
|
|
|
|
// Get the char from the string... Is it so hard?
|
2016-03-15 22:37:57 +00:00
|
|
|
self.content
|
2016-06-26 22:03:12 +00:00
|
|
|
.graphemes(true)
|
2016-03-15 22:37:57 +00:00
|
|
|
.nth(self.cursor)
|
|
|
|
.expect(&format!("Found no char at cursor {} in {}",
|
|
|
|
self.cursor,
|
|
|
|
self.content))
|
2015-05-27 07:01:23 +00:00
|
|
|
};
|
2016-06-26 22:03:12 +00:00
|
|
|
printer.print_hline((self.cursor - self.offset, 0), 1, c);
|
2015-05-27 07:01:23 +00:00
|
|
|
}
|
2015-05-26 22:46:05 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-07-30 13:40:03 +00:00
|
|
|
fn layout(&mut self, size: Vec2) {
|
|
|
|
self.last_length = size.x;
|
|
|
|
}
|
|
|
|
|
2015-05-27 00:07:57 +00:00
|
|
|
fn get_min_size(&self, _: SizeRequest) -> Vec2 {
|
2015-05-26 23:11:22 +00:00
|
|
|
Vec2::new(self.min_length, 1)
|
|
|
|
}
|
|
|
|
|
2015-05-26 22:46:05 +00:00
|
|
|
fn take_focus(&mut self) -> bool {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
2015-05-28 01:04:33 +00:00
|
|
|
fn on_event(&mut self, event: Event) -> EventResult {
|
|
|
|
|
|
|
|
match event {
|
|
|
|
Event::CharEvent(ch) => {
|
2015-05-29 00:32:28 +00:00
|
|
|
// Find the byte index of the char at self.cursor
|
|
|
|
|
|
|
|
match self.content.char_indices().nth(self.cursor) {
|
|
|
|
None => self.content.push(ch),
|
2016-03-15 22:37:57 +00:00
|
|
|
Some((i, _)) => self.content.insert(i, ch),
|
2015-05-29 00:32:28 +00:00
|
|
|
}
|
2015-07-30 13:40:03 +00:00
|
|
|
// TODO: handle wide (CJK) chars
|
2015-05-28 01:04:33 +00:00
|
|
|
self.cursor += 1;
|
2016-03-15 22:37:57 +00:00
|
|
|
}
|
2016-06-25 23:36:22 +00:00
|
|
|
Event::KeyEvent(key) => {
|
|
|
|
match key {
|
|
|
|
Key::Home => self.cursor = 0,
|
|
|
|
Key::End => self.cursor = self.content.chars().count(),
|
|
|
|
Key::Left if self.cursor > 0 => self.cursor -= 1,
|
|
|
|
Key::Right if self.cursor < self.content.chars().count() => self.cursor += 1,
|
|
|
|
Key::Backspace if self.cursor > 0 => {
|
|
|
|
self.cursor -= 1;
|
|
|
|
remove_char(&mut self.content, self.cursor);
|
|
|
|
}
|
|
|
|
Key::Del if self.cursor < self.content.chars().count() => {
|
|
|
|
remove_char(&mut self.content, self.cursor);
|
|
|
|
}
|
|
|
|
_ => return EventResult::Ignored,
|
2016-03-15 22:37:57 +00:00
|
|
|
}
|
2016-06-25 23:36:22 +00:00
|
|
|
}
|
2015-05-26 22:46:05 +00:00
|
|
|
}
|
|
|
|
|
2015-07-30 13:40:03 +00:00
|
|
|
// Keep cursor in [offset, offset+last_length] by changing offset
|
|
|
|
// So keep offset in [last_length-cursor,cursor]
|
|
|
|
// Also call this on resize, but right now it is an event like any other
|
|
|
|
if self.cursor >= self.offset + self.last_length {
|
|
|
|
self.offset = self.cursor - self.last_length + 1;
|
|
|
|
} else if self.cursor < self.offset {
|
|
|
|
self.offset = self.cursor;
|
|
|
|
}
|
2016-03-15 22:37:57 +00:00
|
|
|
if self.offset + self.last_length > self.content.len() + 1 {
|
|
|
|
|
2015-07-30 13:40:03 +00:00
|
|
|
self.offset = if self.content.len() > self.last_length {
|
|
|
|
self.content.len() - self.last_length + 1
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-05-26 22:46:05 +00:00
|
|
|
EventResult::Consumed(None)
|
|
|
|
}
|
|
|
|
}
|