Add character limit to TextEdit (#2816)
* Add char_limit to TextEdit * Use match statement instead * Update CHANGELOG.md * Fixed panics Updated to fix panics for UTF-8 characters and subtract overflow * Inline code in insert_text
This commit is contained in:
parent
fb726aaabb
commit
5fc5f087ff
|
|
@ -5,7 +5,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
* Add `char_limit` to `TextEdit` singleline mode to limit the amount of characters
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08 - Deadlock fix and style customizability
|
## 0.21.0 - 2023-02-08 - Deadlock fix and style customizability
|
||||||
* ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)):
|
* ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)):
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ pub struct TextEdit<'t> {
|
||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
align: Align2,
|
align: Align2,
|
||||||
clip_text: bool,
|
clip_text: bool,
|
||||||
|
char_limit: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> WidgetWithState for TextEdit<'t> {
|
impl<'t> WidgetWithState for TextEdit<'t> {
|
||||||
|
|
@ -119,6 +120,7 @@ impl<'t> TextEdit<'t> {
|
||||||
min_size: Vec2::ZERO,
|
min_size: Vec2::ZERO,
|
||||||
align: Align2::LEFT_TOP,
|
align: Align2::LEFT_TOP,
|
||||||
clip_text: false,
|
clip_text: false,
|
||||||
|
char_limit: usize::MAX,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,6 +292,14 @@ impl<'t> TextEdit<'t> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the limit for the amount of characters can be entered
|
||||||
|
///
|
||||||
|
/// This only works for singleline [`TextEdit`]
|
||||||
|
pub fn char_limit(mut self, limit: usize) -> Self {
|
||||||
|
self.char_limit = limit;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the horizontal align of the inner text.
|
/// Set the horizontal align of the inner text.
|
||||||
pub fn horizontal_align(mut self, align: Align) -> Self {
|
pub fn horizontal_align(mut self, align: Align) -> Self {
|
||||||
self.align.0[0] = align;
|
self.align.0[0] = align;
|
||||||
|
|
@ -412,6 +422,7 @@ impl<'t> TextEdit<'t> {
|
||||||
min_size,
|
min_size,
|
||||||
align,
|
align,
|
||||||
clip_text,
|
clip_text,
|
||||||
|
char_limit,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let text_color = text_color
|
let text_color = text_color
|
||||||
|
|
@ -581,6 +592,7 @@ impl<'t> TextEdit<'t> {
|
||||||
multiline,
|
multiline,
|
||||||
password,
|
password,
|
||||||
default_cursor_range,
|
default_cursor_range,
|
||||||
|
char_limit,
|
||||||
);
|
);
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
|
|
@ -871,6 +883,7 @@ fn events(
|
||||||
multiline: bool,
|
multiline: bool,
|
||||||
password: bool,
|
password: bool,
|
||||||
default_cursor_range: CursorRange,
|
default_cursor_range: CursorRange,
|
||||||
|
char_limit: usize,
|
||||||
) -> (bool, CursorRange) {
|
) -> (bool, CursorRange) {
|
||||||
let mut cursor_range = state.cursor_range(galley).unwrap_or(default_cursor_range);
|
let mut cursor_range = state.cursor_range(galley).unwrap_or(default_cursor_range);
|
||||||
|
|
||||||
|
|
@ -912,7 +925,9 @@ fn events(
|
||||||
Event::Paste(text_to_insert) => {
|
Event::Paste(text_to_insert) => {
|
||||||
if !text_to_insert.is_empty() {
|
if !text_to_insert.is_empty() {
|
||||||
let mut ccursor = delete_selected(text, &cursor_range);
|
let mut ccursor = delete_selected(text, &cursor_range);
|
||||||
insert_text(&mut ccursor, text, text_to_insert);
|
|
||||||
|
insert_text(&mut ccursor, text, text_to_insert, char_limit);
|
||||||
|
|
||||||
Some(CCursorRange::one(ccursor))
|
Some(CCursorRange::one(ccursor))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -922,7 +937,9 @@ fn events(
|
||||||
// Newlines are handled by `Key::Enter`.
|
// Newlines are handled by `Key::Enter`.
|
||||||
if !text_to_insert.is_empty() && text_to_insert != "\n" && text_to_insert != "\r" {
|
if !text_to_insert.is_empty() && text_to_insert != "\n" && text_to_insert != "\r" {
|
||||||
let mut ccursor = delete_selected(text, &cursor_range);
|
let mut ccursor = delete_selected(text, &cursor_range);
|
||||||
insert_text(&mut ccursor, text, text_to_insert);
|
|
||||||
|
insert_text(&mut ccursor, text, text_to_insert, char_limit);
|
||||||
|
|
||||||
Some(CCursorRange::one(ccursor))
|
Some(CCursorRange::one(ccursor))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -940,7 +957,7 @@ fn events(
|
||||||
// TODO(emilk): support removing indentation over a selection?
|
// TODO(emilk): support removing indentation over a selection?
|
||||||
decrease_identation(&mut ccursor, text);
|
decrease_identation(&mut ccursor, text);
|
||||||
} else {
|
} else {
|
||||||
insert_text(&mut ccursor, text, "\t");
|
insert_text(&mut ccursor, text, "\t", char_limit);
|
||||||
}
|
}
|
||||||
Some(CCursorRange::one(ccursor))
|
Some(CCursorRange::one(ccursor))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -954,7 +971,7 @@ fn events(
|
||||||
} => {
|
} => {
|
||||||
if multiline {
|
if multiline {
|
||||||
let mut ccursor = delete_selected(text, &cursor_range);
|
let mut ccursor = delete_selected(text, &cursor_range);
|
||||||
insert_text(&mut ccursor, text, "\n");
|
insert_text(&mut ccursor, text, "\n", char_limit);
|
||||||
// TODO(emilk): if code editor, auto-indent by same leading tabs, + one if the lines end on an opening bracket
|
// TODO(emilk): if code editor, auto-indent by same leading tabs, + one if the lines end on an opening bracket
|
||||||
Some(CCursorRange::one(ccursor))
|
Some(CCursorRange::one(ccursor))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1000,7 +1017,7 @@ fn events(
|
||||||
let mut ccursor = delete_selected(text, &cursor_range);
|
let mut ccursor = delete_selected(text, &cursor_range);
|
||||||
let start_cursor = ccursor;
|
let start_cursor = ccursor;
|
||||||
if !text_mark.is_empty() {
|
if !text_mark.is_empty() {
|
||||||
insert_text(&mut ccursor, text, text_mark);
|
insert_text(&mut ccursor, text, text_mark, char_limit);
|
||||||
}
|
}
|
||||||
Some(CCursorRange::two(start_cursor, ccursor))
|
Some(CCursorRange::two(start_cursor, ccursor))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1013,7 +1030,7 @@ fn events(
|
||||||
state.has_ime = false;
|
state.has_ime = false;
|
||||||
let mut ccursor = delete_selected(text, &cursor_range);
|
let mut ccursor = delete_selected(text, &cursor_range);
|
||||||
if !prediction.is_empty() {
|
if !prediction.is_empty() {
|
||||||
insert_text(&mut ccursor, text, prediction);
|
insert_text(&mut ccursor, text, prediction, char_limit);
|
||||||
}
|
}
|
||||||
Some(CCursorRange::one(ccursor))
|
Some(CCursorRange::one(ccursor))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1159,8 +1176,26 @@ fn selected_str<'s>(text: &'s dyn TextBuffer, cursor_range: &CursorRange) -> &'s
|
||||||
text.char_range(min.ccursor.index..max.ccursor.index)
|
text.char_range(min.ccursor.index..max.ccursor.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_text(ccursor: &mut CCursor, text: &mut dyn TextBuffer, text_to_insert: &str) {
|
fn insert_text(
|
||||||
ccursor.index += text.insert_text(text_to_insert, ccursor.index);
|
ccursor: &mut CCursor,
|
||||||
|
text: &mut dyn TextBuffer,
|
||||||
|
text_to_insert: &str,
|
||||||
|
char_limit: usize,
|
||||||
|
) {
|
||||||
|
if char_limit < usize::MAX {
|
||||||
|
let mut new_string = text_to_insert;
|
||||||
|
// Avoid subtract with overflow panic
|
||||||
|
let cutoff = char_limit.saturating_sub(text.as_str().len());
|
||||||
|
|
||||||
|
new_string = match new_string.char_indices().nth(cutoff) {
|
||||||
|
None => new_string,
|
||||||
|
Some((idx, _)) => &new_string[..idx],
|
||||||
|
};
|
||||||
|
|
||||||
|
ccursor.index += text.insert_text(new_string, ccursor.index);
|
||||||
|
} else {
|
||||||
|
ccursor.index += text.insert_text(text_to_insert, ccursor.index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue