cursive/src/view/edit_view.rs

249 lines
8.0 KiB
Rust
Raw Normal View History

use unicode_segmentation::UnicodeSegmentation;
2016-07-04 23:04:32 +00:00
use unicode_width::UnicodeWidthStr;
use direction::Direction;
use theme::{ColorStyle, Effect};
2015-05-26 23:11:22 +00:00
use vec::Vec2;
use view::{IdView, View};
use event::*;
2016-07-14 06:25:54 +00:00
use Printer;
2016-07-04 23:04:32 +00:00
/// Input box where the user can enter and edit text.
pub struct EditView {
2016-07-04 23:04:32 +00:00
/// Current content.
content: String,
2016-07-04 23:04:32 +00:00
/// Cursor position in the content, in bytes.
cursor: usize,
2016-07-04 23:04:32 +00:00
/// Minimum layout length asked to the parent.
2015-05-26 23:11:22 +00:00
min_length: usize,
2016-07-04 23:04:32 +00:00
/// Number of bytes to skip at the beginning of the content.
///
/// (When the content is too long for the display, we hide part of 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? */
}
2016-06-28 05:40:11 +00:00
impl Default for EditView {
fn default() -> Self {
Self::new()
}
}
impl EditView {
2015-05-26 23:11:22 +00:00
/// Creates a new, empty edit view.
pub fn new() -> Self {
EditView {
content: String::new(),
cursor: 0,
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 23:11:22 +00:00
/// Replace the entire content of the view with the given one.
2016-06-28 05:40:11 +00:00
pub fn set_content(&mut self, content: &str) {
self.offset = 0;
self.content = content.to_string();
}
2015-05-26 23:11:22 +00:00
/// Get the current text.
pub fn get_content(&self) -> &str {
&self.content
}
2016-07-10 02:05:51 +00:00
/// Sets the current content to the given value.
///
/// Convenient chainable method.
2016-06-28 05:40:11 +00:00
pub fn content(mut self, content: &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)
}
}
impl View for EditView {
fn draw(&mut self, printer: &Printer) {
2016-07-04 23:04:32 +00:00
assert!(printer.size.x == self.last_length);
let width = self.content.width();
printer.with_color(ColorStyle::Secondary, |printer| {
printer.with_effect(Effect::Reverse, |printer| {
2016-07-04 23:04:32 +00:00
if width < self.last_length {
// No problem, everything fits.
2016-03-15 22:37:57 +00:00
printer.print((0, 0), &self.content);
2016-07-04 23:04:32 +00:00
printer.print_hline((width, 0),
printer.size.x - width,
"_");
} else {
2016-07-04 23:04:32 +00:00
let content = &self.content[self.offset..];
let display_bytes = content.graphemes(true)
2016-07-10 02:05:51 +00:00
.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);
2016-07-04 23:04:32 +00:00
let content = &content[..display_bytes];
2016-03-15 22:37:57 +00:00
printer.print((0, 0), content);
2016-07-04 23:04:32 +00:00
let width = content.width();
if width < self.last_length {
printer.print_hline((width, 0),
self.last_length - width,
"_");
}
}
});
2015-05-27 07:01:23 +00:00
// Now print cursor
if printer.focused {
2016-07-04 23:04:32 +00:00
let c = if self.cursor == self.content.len() {
"_"
2015-05-27 07:01:23 +00:00
} else {
// Get the char from the string... Is it so hard?
2016-07-04 23:04:32 +00:00
self.content[self.cursor..]
.graphemes(true)
2016-07-04 23:04:32 +00:00
.next()
2016-03-15 22:37:57 +00:00
.expect(&format!("Found no char at cursor {} in {}",
self.cursor,
self.content))
2015-05-27 07:01:23 +00:00
};
2016-07-04 23:04:32 +00:00
let offset = self.content[self.offset..self.cursor].width();
printer.print((offset, 0), c);
2015-05-27 07:01:23 +00:00
}
});
}
fn layout(&mut self, size: Vec2) {
self.last_length = size.x;
}
fn get_min_size(&mut self, _: Vec2) -> Vec2 {
2015-05-26 23:11:22 +00:00
Vec2::new(self.min_length, 1)
}
fn take_focus(&mut self, _: Direction) -> bool {
true
}
fn on_event(&mut self, event: Event) -> EventResult {
match event {
2016-06-28 05:40:11 +00:00
Event::Char(ch) => {
// Find the byte index of the char at self.cursor
2016-07-04 23:04:32 +00:00
self.content.insert(self.cursor, ch);
self.cursor += ch.len_utf8();
2016-03-15 22:37:57 +00:00
}
// TODO: handle ctrl-key?
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.content.remove(self.cursor);
}
Event::Key(Key::Del) if self.cursor < self.content.len() => {
self.content.remove(self.cursor);
2016-06-25 23:36:22 +00:00
}
_ => return EventResult::Ignored,
}
// Keep cursor in [offset, offset+last_length] by changing offset
// So keep offset in [last_length-cursor,cursor]
2016-07-10 02:05:51 +00:00
// Also call this on resize,
// but right now it is an event like any other
2016-07-04 23:04:32 +00:00
if self.cursor < self.offset {
self.offset = self.cursor;
2016-07-04 23:04:32 +00:00
} else {
// So we're against the right wall.
2016-07-10 02:05:51 +00:00
// Let's find how much space will be taken by the selection
// (either a char, or _)
2016-07-04 23:04:32 +00:00
let c_len = self.content[self.cursor..]
2016-07-10 02:05:51 +00:00
.graphemes(true)
.map(|g| g.width())
.next()
.unwrap_or(1);
// Now, we have to fit self.content[..self.cursor]
// into self.last_length - c_len.
2016-07-04 23:04:32 +00:00
let available = self.last_length - c_len;
// Look at the content before the cursor (we will print its tail).
// From the end, count the length until we reach `available`.
// Then sum the byte lengths.
let tail_bytes =
tail_bytes(&self.content[self.offset..self.cursor], available);
self.offset = self.cursor - tail_bytes;
assert!(self.cursor >= self.offset);
}
2016-03-15 22:37:57 +00:00
2016-07-04 23:04:32 +00:00
// If we have too much space
if self.content[self.offset..].width() < self.last_length {
let tail_bytes = tail_bytes(&self.content, self.last_length - 1);
self.offset = self.content.len() - tail_bytes;
}
EventResult::Consumed(None)
}
}
2016-07-04 23:04:32 +00:00
// Return the number of bytes, from the end of text,
// which constitute the longest tail that fits in the given width.
fn tail_bytes(text: &str, width: usize) -> usize {
text.graphemes(true)
.rev()
.scan(0, |w, g| {
*w += g.width();
if *w > width {
None
} else {
Some(g)
}
})
.map(|g| g.len())
.fold(0, |a, b| a + b)
}