Add secret mode to EditView

Only prints `*`. Prints `**` with wide chars.
This commit is contained in:
Alexandre Bury 2016-07-30 12:26:41 -07:00
parent 3726df46b7
commit ca6e16311f
3 changed files with 99 additions and 50 deletions

View File

@ -6,7 +6,10 @@ fn main() {
let mut siv = Cursive::new(); let mut siv = Cursive::new();
// Create a dialog with an edit text and a button. // Create a dialog with an edit text and a button.
siv.add_layer(Dialog::new(EditView::new() siv.add_layer(Dialog::empty()
.title("Enter your name")
.padding((1, 1, 1, 0))
.content(EditView::new()
.min_length(20) .min_length(20)
.on_submit(|s, name| { .on_submit(|s, name| {
if name.is_empty() { if name.is_empty() {
@ -19,8 +22,7 @@ fn main() {
.button("Quit", |s| s.quit())); .button("Quit", |s| s.quit()));
} }
})) }))
.padding((1, 1, 1, 0)) );
.title("Enter your name"));
siv.run(); siv.run();
} }

View File

@ -1,6 +1,7 @@
//! Toolbox to make text layout easier. //! Toolbox to make text layout easier.
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use unicode_segmentation::UnicodeSegmentation;
mod lines_iterator; mod lines_iterator;
mod reader; mod reader;
@ -8,7 +9,7 @@ mod reader;
pub use self::lines_iterator::{LinesIterator, Row}; pub use self::lines_iterator::{LinesIterator, Row};
pub use self::reader::ProgressReader; pub use self::reader::ProgressReader;
/// Computes a sub-string length that fits in the given `width`. /// Computes the length of a prefix that fits in the given `width`.
/// ///
/// Takes non-breakable elements from `iter`, while keeping the /// Takes non-breakable elements from `iter`, while keeping the
/// string width under `width` (and adding the length of `delimiter` /// string width under `width` (and adding the length of `delimiter`
@ -60,3 +61,22 @@ pub fn prefix_length<'a, I: Iterator<Item = &'a str>>(iter: I, width: usize,
sum - delimiter_len sum - delimiter_len
} }
} }
/// Computes the length of a suffix that fits in the given `width`.
///
/// Returns the number of bytes of the longest
/// suffix from `text` that fits in `width`.
pub fn suffix_length(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)
}

View File

@ -3,14 +3,13 @@ use unicode_width::UnicodeWidthStr;
use std::rc::Rc; use std::rc::Rc;
use Cursive; use {Cursive, Printer, With};
use With;
use direction::Direction; use direction::Direction;
use theme::{ColorStyle, Effect}; use theme::{ColorStyle, Effect};
use vec::Vec2; use vec::Vec2;
use view::View; use view::View;
use event::*; use event::{Callback, Event, EventResult, Key};
use Printer; use utils::suffix_length;
/// Input box where the user can enter and edit text. /// Input box where the user can enter and edit text.
@ -73,6 +72,9 @@ pub struct EditView {
/// Callback when <Enter> is pressed. /// Callback when <Enter> is pressed.
on_submit: Option<Rc<Fn(&mut Cursive, &str)>>, on_submit: Option<Rc<Fn(&mut Cursive, &str)>>,
/// When `true`, only print `*` instead of the true content.
secret: bool,
enabled: bool, enabled: bool,
} }
@ -89,10 +91,25 @@ impl EditView {
last_length: 0, // scrollable: false, last_length: 0, // scrollable: false,
on_edit: None, on_edit: None,
on_submit: None, on_submit: None,
secret: false,
enabled: true, enabled: true,
} }
} }
/// If `secret` is `true`, the content won't be displayed in clear.
///
/// Only `*` will be shown.
pub fn set_secret(&mut self, secret: bool) {
self.secret = secret;
}
/// Hides the content of the view.
///
/// Only `*` will be shown.
pub fn secret(self) -> Self {
self.with(|s| s.set_secret(true))
}
/// Disables this view. /// Disables this view.
/// ///
/// A disabled view cannot be selected. /// A disabled view cannot be selected.
@ -116,7 +133,9 @@ impl EditView {
/// ///
/// `callback` will be called with the view /// `callback` will be called with the view
/// content and the current cursor position. /// content and the current cursor position.
pub fn on_edit<F: Fn(&mut Cursive, &str, usize) + 'static>(mut self, callback: F) -> Self { pub fn on_edit<F: Fn(&mut Cursive, &str, usize) + 'static>(mut self,
callback: F)
-> Self {
self.on_edit = Some(Rc::new(callback)); self.on_edit = Some(Rc::new(callback));
self self
} }
@ -124,7 +143,9 @@ impl EditView {
/// Sets a callback to be called when `<Enter>` is pressed. /// Sets a callback to be called when `<Enter>` is pressed.
/// ///
/// `callback` will be given the content of the view. /// `callback` will be given the content of the view.
pub fn on_submit<F: Fn(&mut Cursive, &str) + 'static>(mut self, callback: F) -> Self { pub fn on_submit<F: Fn(&mut Cursive, &str) + 'static>(mut self,
callback: F)
-> Self {
self.on_submit = Some(Rc::new(callback)); self.on_submit = Some(Rc::new(callback));
self self
} }
@ -182,6 +203,14 @@ impl EditView {
} }
} }
/// Returns a `&str` with `length` characters `*`.
///
/// Only works for small `length` (1 or 2).
/// Best used for single character replacement.
fn make_small_stars(length: usize) -> &'static str {
&"****"[..length]
}
impl View for EditView { impl View for EditView {
fn draw(&self, printer: &Printer) { fn draw(&self, printer: &Printer) {
assert!(printer.size.x == self.last_length, assert!(printer.size.x == self.last_length,
@ -199,7 +228,11 @@ 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.
if self.secret {
printer.print_hline((0, 0), width, "*");
} else {
printer.print((0, 0), self.get_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,
"_"); "_");
@ -218,10 +251,14 @@ impl View for EditView {
.fold(0, |a, b| a + b); .fold(0, |a, b| a + b);
let content = &content[..display_bytes]; let content = &content[..display_bytes];
printer.print((0, 0), content);
let width = content.width(); let width = content.width();
if self.secret {
printer.print_hline((0, 0), width, "*");
} else {
printer.print((0, 0), content);
}
if width < self.last_length { if width < self.last_length {
printer.print_hline((width, 0), printer.print_hline((width, 0),
self.last_length - width, self.last_length - width,
@ -232,16 +269,21 @@ impl View for EditView {
// Now print cursor // Now print cursor
if printer.focused { if printer.focused {
let c = if self.cursor == self.content.len() { let c: &str = if self.cursor == self.content.len() {
"_" "_"
} else { } else {
// Get the char from the string... Is it so hard? // Get the char from the string... Is it so hard?
self.content[self.cursor..] let selected = self.content[self.cursor..]
.graphemes(true) .graphemes(true)
.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));
if self.secret {
make_small_stars(selected.width())
} else {
selected
}
}; };
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);
@ -337,17 +379,19 @@ impl View for EditView {
// Look at the content before the cursor (we will print its tail). // Look at the content before the cursor (we will print its tail).
// From the end, count the length until we reach `available`. // From the end, count the length until we reach `available`.
// Then sum the byte lengths. // Then sum the byte lengths.
let tail_bytes = let suffix_length =
tail_bytes(&self.content[self.offset..self.cursor], available); suffix_length(&self.content[self.offset..self.cursor],
self.offset = self.cursor - tail_bytes; available);
self.offset = self.cursor - suffix_length;
assert!(self.cursor >= self.offset); assert!(self.cursor >= self.offset);
} }
// If we have too much space // If we have too much space
if self.content[self.offset..].width() < self.last_length { if self.content[self.offset..].width() < self.last_length {
let tail_bytes = tail_bytes(&self.content, self.last_length - 1); let suffix_length = suffix_length(&self.content,
self.offset = self.content.len() - tail_bytes; self.last_length - 1);
self.offset = self.content.len() - suffix_length;
} }
let cb = self.on_edit.clone().map(|cb| { let cb = self.on_edit.clone().map(|cb| {
@ -363,20 +407,3 @@ impl View for EditView {
EventResult::Consumed(cb) EventResult::Consumed(cb)
} }
} }
// 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)
}