widgets: allow text overflow in text fields

Show text content of a text field that exceeds the visible width
properly.
jmap-eventsource
Manos Pitsidianakis 2020-11-24 09:31:38 +02:00
parent f69f623818
commit d4f508642a
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
2 changed files with 159 additions and 17 deletions

View File

@ -33,6 +33,8 @@ pub use wcwidth::*;
pub trait Truncate { pub trait Truncate {
fn truncate_at_boundary(&mut self, new_len: usize); fn truncate_at_boundary(&mut self, new_len: usize);
fn trim_at_boundary(&self, new_len: usize) -> &str; fn trim_at_boundary(&self, new_len: usize) -> &str;
fn trim_left_at_boundary(&self, new_len: usize) -> &str;
fn truncate_left_at_boundary(&mut self, new_len: usize);
} }
impl Truncate for &str { impl Truncate for &str {
@ -67,6 +69,39 @@ impl Truncate for &str {
self self
} }
} }
fn trim_left_at_boundary(&self, skip_len: usize) -> &str {
if skip_len >= self.len() {
return "";
}
extern crate unicode_segmentation;
use unicode_segmentation::UnicodeSegmentation;
if let Some((first, _)) = UnicodeSegmentation::grapheme_indices(*self, true)
.skip(skip_len)
.next()
{
&self[first..]
} else {
self
}
}
fn truncate_left_at_boundary(&mut self, skip_len: usize) {
if skip_len >= self.len() {
*self = "";
return;
}
extern crate unicode_segmentation;
use unicode_segmentation::UnicodeSegmentation;
if let Some((first, _)) = UnicodeSegmentation::grapheme_indices(*self, true)
.skip(skip_len)
.next()
{
*self = &self[first..];
}
}
} }
impl Truncate for String { impl Truncate for String {
@ -101,6 +136,39 @@ impl Truncate for String {
self.as_str() self.as_str()
} }
} }
fn trim_left_at_boundary(&self, skip_len: usize) -> &str {
if skip_len >= self.len() {
return "";
}
extern crate unicode_segmentation;
use unicode_segmentation::UnicodeSegmentation;
if let Some((first, _)) = UnicodeSegmentation::grapheme_indices(self.as_str(), true)
.skip(skip_len)
.next()
{
&self[first..]
} else {
self.as_str()
}
}
fn truncate_left_at_boundary(&mut self, skip_len: usize) {
if skip_len >= self.len() {
self.clear();
return;
}
extern crate unicode_segmentation;
use unicode_segmentation::UnicodeSegmentation;
if let Some((first, _)) = UnicodeSegmentation::grapheme_indices(self.as_str(), true)
.skip(skip_len)
.next()
{
*self = self[first..].to_string();
}
}
} }
pub trait GlobMatch { pub trait GlobMatch {

View File

@ -111,12 +111,15 @@ impl Field {
let upper_left = upper_left!(area); let upper_left = upper_left!(area);
match self { match self {
Text(ref term, auto_complete_fn) => { Text(ref term, auto_complete_fn) => {
let width = width!(area);
let pos = if width < term.grapheme_pos() {
width
} else {
term.grapheme_pos()
};
change_colors( change_colors(
grid, grid,
( (pos_inc(upper_left, (pos, 0)), pos_inc(upper_left, (pos, 0))),
pos_inc(upper_left, (term.grapheme_pos(), 0)),
(pos_inc(upper_left, (term.grapheme_pos(), 0))),
),
Color::Default, Color::Default,
Color::Byte(248), Color::Byte(248),
); );
@ -137,15 +140,89 @@ impl Field {
impl Component for Field { impl Component for Field {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let theme_attr = crate::conf::value(context, "widgets.form.field"); let theme_attr = crate::conf::value(context, "widgets.form.field");
write_string_to_grid( let width = width!(area);
self.as_str(), let str = self.as_str();
grid, match self {
theme_attr.fg, Text(ref term, _) => {
theme_attr.bg, /* Calculate which part of the str is visible
theme_attr.attrs, * ##########################################
area, *
None, * Example:
); * For the string "The quick brown fox jumps over the lazy dog" with visible width
* of field of 10 columns
*
*
* Cursor <= width
* =================
* Cursor at:
*
* The quick brown fox jumps over the lazy dog
*
* cursor
*
*
* The quick brown fox jumps over the lazy dog
*
*
* No skip.
*
* Cursor at the end
* =================
* Cursor at:
*
* The quick brown fox jumps over the lazy dog
*
* remainder cursor
*
* ++
* |The| quick brown fox jumps over the lazy dog
* ++
* ++++++
* skip offset
*
* Intermediate cursor
* ===================
* Cursor at:
*
* The quick brown fox jumps over the lazy dog
*
* remainder cursor
*
* +
* T|he quick brown fox jumps over the lazy dog
* +
* +++
* skip offset
*/
write_string_to_grid(
if width < term.grapheme_pos() {
str.trim_left_at_boundary(
width * term.grapheme_pos().wrapping_div(width).saturating_sub(1)
+ term.grapheme_pos().wrapping_rem(width),
)
} else {
str
},
grid,
theme_attr.fg,
theme_attr.bg,
theme_attr.attrs,
area,
None,
);
}
Choice(_, _) => {
write_string_to_grid(
str,
grid,
theme_attr.fg,
theme_attr.bg,
theme_attr.attrs,
area,
None,
);
}
}
} }
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
match *event { match *event {
@ -496,10 +573,7 @@ impl<T: 'static + std::fmt::Debug + Copy + Default + Send + Sync> Component for
grid, grid,
( (
pos_inc(upper_left, (self.field_name_max_length + 3, i)), pos_inc(upper_left, (self.field_name_max_length + 3, i)),
( (get_x(bottom_right), i + get_y(upper_left)),
get_x(upper_left) + self.field_name_max_length + 3,
i + get_y(upper_left),
),
), ),
( (
pos_inc(upper_left, (self.field_name_max_length + 3, i + 1)), pos_inc(upper_left, (self.field_name_max_length + 3, i + 1)),