mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Add secret mode to EditView
Only prints `*`. Prints `**` with wide chars.
This commit is contained in:
parent
3726df46b7
commit
ca6e16311f
@ -6,21 +6,23 @@ 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()
|
||||||
.min_length(20)
|
.title("Enter your name")
|
||||||
.on_submit(|s, name| {
|
.padding((1, 1, 1, 0))
|
||||||
if name.is_empty() {
|
.content(EditView::new()
|
||||||
s.add_layer(Dialog::new(TextView::new("Please enter a name!"))
|
.min_length(20)
|
||||||
.dismiss_button("Ok"));
|
.on_submit(|s, name| {
|
||||||
} else {
|
if name.is_empty() {
|
||||||
let content = format!("Hello {}!", name);
|
s.add_layer(Dialog::new(TextView::new("Please enter a name!"))
|
||||||
s.pop_layer();
|
.dismiss_button("Ok"));
|
||||||
s.add_layer(Dialog::new(TextView::new(&content))
|
} else {
|
||||||
.button("Quit", |s| s.quit()));
|
let content = format!("Hello {}!", name);
|
||||||
}
|
s.pop_layer();
|
||||||
}))
|
s.add_layer(Dialog::new(TextView::new(&content))
|
||||||
.padding((1, 1, 1, 0))
|
.button("Quit", |s| s.quit()));
|
||||||
.title("Enter your name"));
|
}
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
siv.run();
|
siv.run();
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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.
|
||||||
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.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)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user