From ca6e16311fd884f6f754c2c93649e3fd254c60fc Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sat, 30 Jul 2016 12:26:41 -0700 Subject: [PATCH] Add secret mode to `EditView` Only prints `*`. Prints `**` with wide chars. --- examples/edit.rs | 32 +++++++------- src/utils/mod.rs | 22 +++++++++- src/views/edit_view.rs | 95 +++++++++++++++++++++++++++--------------- 3 files changed, 99 insertions(+), 50 deletions(-) diff --git a/examples/edit.rs b/examples/edit.rs index 6221339..955163d 100644 --- a/examples/edit.rs +++ b/examples/edit.rs @@ -6,21 +6,23 @@ fn main() { let mut siv = Cursive::new(); // Create a dialog with an edit text and a button. - siv.add_layer(Dialog::new(EditView::new() - .min_length(20) - .on_submit(|s, name| { - if name.is_empty() { - s.add_layer(Dialog::new(TextView::new("Please enter a name!")) - .dismiss_button("Ok")); - } else { - let content = format!("Hello {}!", name); - s.pop_layer(); - s.add_layer(Dialog::new(TextView::new(&content)) - .button("Quit", |s| s.quit())); - } - })) - .padding((1, 1, 1, 0)) - .title("Enter your name")); + siv.add_layer(Dialog::empty() + .title("Enter your name") + .padding((1, 1, 1, 0)) + .content(EditView::new() + .min_length(20) + .on_submit(|s, name| { + if name.is_empty() { + s.add_layer(Dialog::new(TextView::new("Please enter a name!")) + .dismiss_button("Ok")); + } else { + let content = format!("Hello {}!", name); + s.pop_layer(); + s.add_layer(Dialog::new(TextView::new(&content)) + .button("Quit", |s| s.quit())); + } + })) + ); siv.run(); } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e6f516f..2718a2c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,7 @@ //! Toolbox to make text layout easier. use unicode_width::UnicodeWidthStr; +use unicode_segmentation::UnicodeSegmentation; mod lines_iterator; mod reader; @@ -8,7 +9,7 @@ mod reader; pub use self::lines_iterator::{LinesIterator, Row}; 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 /// string width under `width` (and adding the length of `delimiter` @@ -60,3 +61,22 @@ pub fn prefix_length<'a, I: Iterator>(iter: I, width: usize, 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) +} diff --git a/src/views/edit_view.rs b/src/views/edit_view.rs index 8775c8f..66626a8 100644 --- a/src/views/edit_view.rs +++ b/src/views/edit_view.rs @@ -3,14 +3,13 @@ use unicode_width::UnicodeWidthStr; use std::rc::Rc; -use Cursive; -use With; +use {Cursive, Printer, With}; use direction::Direction; use theme::{ColorStyle, Effect}; use vec::Vec2; use view::View; -use event::*; -use Printer; +use event::{Callback, Event, EventResult, Key}; +use utils::suffix_length; /// Input box where the user can enter and edit text. @@ -73,6 +72,9 @@ pub struct EditView { /// Callback when is pressed. on_submit: Option>, + /// When `true`, only print `*` instead of the true content. + secret: bool, + enabled: bool, } @@ -89,10 +91,25 @@ impl EditView { last_length: 0, // scrollable: false, on_edit: None, on_submit: None, + secret: false, 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. /// /// A disabled view cannot be selected. @@ -116,7 +133,9 @@ impl EditView { /// /// `callback` will be called with the view /// content and the current cursor position. - pub fn on_edit(mut self, callback: F) -> Self { + pub fn on_edit(mut self, + callback: F) + -> Self { self.on_edit = Some(Rc::new(callback)); self } @@ -124,7 +143,9 @@ impl EditView { /// Sets a callback to be called when `` is pressed. /// /// `callback` will be given the content of the view. - pub fn on_submit(mut self, callback: F) -> Self { + pub fn on_submit(mut self, + callback: F) + -> Self { self.on_submit = Some(Rc::new(callback)); 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 { fn draw(&self, printer: &Printer) { assert!(printer.size.x == self.last_length, @@ -199,7 +228,11 @@ impl View for EditView { printer.with_effect(effect, |printer| { if width < self.last_length { // No problem, everything fits. - printer.print((0, 0), self.get_content()); + if self.secret { + printer.print_hline((0, 0), width, "*"); + } else { + printer.print((0, 0), self.get_content()); + } printer.print_hline((width, 0), printer.size.x - width, "_"); @@ -218,10 +251,14 @@ impl View for EditView { .fold(0, |a, b| a + b); let content = &content[..display_bytes]; - - printer.print((0, 0), content); 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, @@ -232,16 +269,21 @@ impl View for EditView { // Now print cursor if printer.focused { - let c = if self.cursor == self.content.len() { + let c: &str = if self.cursor == self.content.len() { "_" } else { // Get the char from the string... Is it so hard? - self.content[self.cursor..] + let selected = self.content[self.cursor..] .graphemes(true) .next() .expect(&format!("Found no char at cursor {} in {}", 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(); 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). // 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; + let suffix_length = + suffix_length(&self.content[self.offset..self.cursor], + available); + self.offset = self.cursor - suffix_length; assert!(self.cursor >= self.offset); } // 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; + let suffix_length = suffix_length(&self.content, + self.last_length - 1); + self.offset = self.content.len() - suffix_length; } let cb = self.on_edit.clone().map(|cb| { @@ -363,20 +407,3 @@ impl View for EditView { 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) -}