From 3d3127f2b44f504480ee296d180b7339b5ed66d2 Mon Sep 17 00:00:00 2001 From: wzid <112444354+wzid@users.noreply.github.com> Date: Thu, 16 Mar 2023 03:37:24 -0400 Subject: [PATCH 1/5] Add char_limit to TextEdit --- crates/egui/src/widgets/text_edit/builder.rs | 33 ++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 7467c11df7ac..d4fa8e2da3d5 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -70,6 +70,7 @@ pub struct TextEdit<'t> { min_size: Vec2, align: Align2, clip_text: bool, + char_limit: Option, } impl<'t> WidgetWithState for TextEdit<'t> { @@ -119,6 +120,7 @@ impl<'t> TextEdit<'t> { min_size: Vec2::ZERO, align: Align2::LEFT_TOP, clip_text: false, + char_limit: None, } } @@ -290,6 +292,14 @@ impl<'t> TextEdit<'t> { 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 = Some(limit); + self + } + /// Set the horizontal align of the inner text. pub fn horizontal_align(mut self, align: Align) -> Self { self.align.0[0] = align; @@ -412,6 +422,7 @@ impl<'t> TextEdit<'t> { min_size, align, clip_text, + char_limit, } = self; let text_color = text_color @@ -581,6 +592,7 @@ impl<'t> TextEdit<'t> { multiline, password, default_cursor_range, + char_limit, ); if changed { @@ -871,6 +883,7 @@ fn events( multiline: bool, password: bool, default_cursor_range: CursorRange, + char_limit: Option, ) -> (bool, CursorRange) { let mut cursor_range = state.cursor_range(galley).unwrap_or(default_cursor_range); @@ -912,7 +925,15 @@ fn events( Event::Paste(text_to_insert) => { if !text_to_insert.is_empty() { let mut ccursor = delete_selected(text, &cursor_range); - insert_text(&mut ccursor, text, text_to_insert); + + if !multiline && char_limit.is_some() { + let mut new_string = text_to_insert.clone(); + new_string.truncate(char_limit.unwrap() - text.as_str().len()); + insert_text(&mut ccursor, text, &new_string); + } else { + insert_text(&mut ccursor, text, &text_to_insert); + } + Some(CCursorRange::one(ccursor)) } else { None @@ -922,7 +943,15 @@ fn events( // Newlines are handled by `Key::Enter`. if !text_to_insert.is_empty() && text_to_insert != "\n" && text_to_insert != "\r" { let mut ccursor = delete_selected(text, &cursor_range); - insert_text(&mut ccursor, text, text_to_insert); + + if !multiline && char_limit.is_some() { + let mut new_string = text_to_insert.clone(); + new_string.truncate(char_limit.unwrap() - text.as_str().len()); + insert_text(&mut ccursor, text, &new_string); + } else { + insert_text(&mut ccursor, text, &text_to_insert); + } + Some(CCursorRange::one(ccursor)) } else { None From 02ed3ba886b928e45d8e615b0b5ccbf4f1653e03 Mon Sep 17 00:00:00 2001 From: wzid <112444354+wzid@users.noreply.github.com> Date: Thu, 16 Mar 2023 04:08:39 -0400 Subject: [PATCH 2/5] Use match statement instead --- crates/egui/src/widgets/text_edit/builder.rs | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index d4fa8e2da3d5..288c0d39dbe9 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -926,12 +926,13 @@ fn events( if !text_to_insert.is_empty() { let mut ccursor = delete_selected(text, &cursor_range); - if !multiline && char_limit.is_some() { - let mut new_string = text_to_insert.clone(); - new_string.truncate(char_limit.unwrap() - text.as_str().len()); - insert_text(&mut ccursor, text, &new_string); - } else { - insert_text(&mut ccursor, text, &text_to_insert); + match char_limit { + Some(limit) if !multiline => { + let mut new_string = text_to_insert.clone(); + new_string.truncate(limit - text.as_str().len()); + insert_text(&mut ccursor, text, &new_string); + } + _ => insert_text(&mut ccursor, text, text_to_insert), } Some(CCursorRange::one(ccursor)) @@ -944,12 +945,13 @@ fn events( if !text_to_insert.is_empty() && text_to_insert != "\n" && text_to_insert != "\r" { let mut ccursor = delete_selected(text, &cursor_range); - if !multiline && char_limit.is_some() { - let mut new_string = text_to_insert.clone(); - new_string.truncate(char_limit.unwrap() - text.as_str().len()); - insert_text(&mut ccursor, text, &new_string); - } else { - insert_text(&mut ccursor, text, &text_to_insert); + match char_limit { + Some(limit) if !multiline => { + let mut new_string = text_to_insert.clone(); + new_string.truncate(limit - text.as_str().len()); + insert_text(&mut ccursor, text, &new_string); + } + _ => insert_text(&mut ccursor, text, text_to_insert), } Some(CCursorRange::one(ccursor)) From c0d68eb9ad40aaa87b2d0b2c3c882fc1a25366fd Mon Sep 17 00:00:00 2001 From: wzid <112444354+wzid@users.noreply.github.com> Date: Thu, 16 Mar 2023 04:15:56 -0400 Subject: [PATCH 3/5] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa78867252c..a69d65fbb6f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG ## 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 * ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)): From 1e9d6ace29164731bfaa4c43bb6524ea91284e25 Mon Sep 17 00:00:00 2001 From: wzid <112444354+wzid@users.noreply.github.com> Date: Fri, 17 Mar 2023 01:34:38 -0400 Subject: [PATCH 4/5] Fixed panics Updated to fix panics for UTF-8 characters and subtract overflow --- crates/egui/src/widgets/text_edit/builder.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 288c0d39dbe9..da5a61ba8e6a 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -929,7 +929,14 @@ fn events( match char_limit { Some(limit) if !multiline => { let mut new_string = text_to_insert.clone(); - new_string.truncate(limit - text.as_str().len()); + // Avoid subtract with overflow panic + let cutoff = limit.saturating_sub(text.as_str().len()); + + new_string = match new_string.char_indices().nth(cutoff) { + None => new_string, + Some((idx, _)) => new_string[..idx].to_owned(), + }; + insert_text(&mut ccursor, text, &new_string); } _ => insert_text(&mut ccursor, text, text_to_insert), @@ -948,7 +955,14 @@ fn events( match char_limit { Some(limit) if !multiline => { let mut new_string = text_to_insert.clone(); - new_string.truncate(limit - text.as_str().len()); + // Avoid subtract with overflow panic + let cutoff = limit.saturating_sub(text.as_str().len()); + + new_string = match new_string.char_indices().nth(cutoff) { + None => new_string, + Some((idx, _)) => new_string[..idx].to_owned(), + }; + insert_text(&mut ccursor, text, &new_string); } _ => insert_text(&mut ccursor, text, text_to_insert), From a101035c18db59d0c37e2952261278e2385e4100 Mon Sep 17 00:00:00 2001 From: wzid <112444354+wzid@users.noreply.github.com> Date: Wed, 29 Mar 2023 17:34:04 -0400 Subject: [PATCH 5/5] Inline code in insert_text --- crates/egui/src/widgets/text_edit/builder.rs | 70 +++++++++----------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index da5a61ba8e6a..abc62363f363 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -70,7 +70,7 @@ pub struct TextEdit<'t> { min_size: Vec2, align: Align2, clip_text: bool, - char_limit: Option, + char_limit: usize, } impl<'t> WidgetWithState for TextEdit<'t> { @@ -120,7 +120,7 @@ impl<'t> TextEdit<'t> { min_size: Vec2::ZERO, align: Align2::LEFT_TOP, clip_text: false, - char_limit: None, + char_limit: usize::MAX, } } @@ -296,7 +296,7 @@ impl<'t> TextEdit<'t> { /// /// This only works for singleline [`TextEdit`] pub fn char_limit(mut self, limit: usize) -> Self { - self.char_limit = Some(limit); + self.char_limit = limit; self } @@ -883,7 +883,7 @@ fn events( multiline: bool, password: bool, default_cursor_range: CursorRange, - char_limit: Option, + char_limit: usize, ) -> (bool, CursorRange) { let mut cursor_range = state.cursor_range(galley).unwrap_or(default_cursor_range); @@ -926,21 +926,7 @@ fn events( if !text_to_insert.is_empty() { let mut ccursor = delete_selected(text, &cursor_range); - match char_limit { - Some(limit) if !multiline => { - let mut new_string = text_to_insert.clone(); - // Avoid subtract with overflow panic - let cutoff = limit.saturating_sub(text.as_str().len()); - - new_string = match new_string.char_indices().nth(cutoff) { - None => new_string, - Some((idx, _)) => new_string[..idx].to_owned(), - }; - - insert_text(&mut ccursor, text, &new_string); - } - _ => insert_text(&mut ccursor, text, text_to_insert), - } + insert_text(&mut ccursor, text, text_to_insert, char_limit); Some(CCursorRange::one(ccursor)) } else { @@ -952,21 +938,7 @@ fn events( if !text_to_insert.is_empty() && text_to_insert != "\n" && text_to_insert != "\r" { let mut ccursor = delete_selected(text, &cursor_range); - match char_limit { - Some(limit) if !multiline => { - let mut new_string = text_to_insert.clone(); - // Avoid subtract with overflow panic - let cutoff = limit.saturating_sub(text.as_str().len()); - - new_string = match new_string.char_indices().nth(cutoff) { - None => new_string, - Some((idx, _)) => new_string[..idx].to_owned(), - }; - - insert_text(&mut ccursor, text, &new_string); - } - _ => insert_text(&mut ccursor, text, text_to_insert), - } + insert_text(&mut ccursor, text, text_to_insert, char_limit); Some(CCursorRange::one(ccursor)) } else { @@ -985,7 +957,7 @@ fn events( // TODO(emilk): support removing indentation over a selection? decrease_identation(&mut ccursor, text); } else { - insert_text(&mut ccursor, text, "\t"); + insert_text(&mut ccursor, text, "\t", char_limit); } Some(CCursorRange::one(ccursor)) } else { @@ -999,7 +971,7 @@ fn events( } => { if multiline { 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 Some(CCursorRange::one(ccursor)) } else { @@ -1045,7 +1017,7 @@ fn events( let mut ccursor = delete_selected(text, &cursor_range); let start_cursor = ccursor; 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)) } else { @@ -1058,7 +1030,7 @@ fn events( state.has_ime = false; let mut ccursor = delete_selected(text, &cursor_range); if !prediction.is_empty() { - insert_text(&mut ccursor, text, prediction); + insert_text(&mut ccursor, text, prediction, char_limit); } Some(CCursorRange::one(ccursor)) } else { @@ -1204,8 +1176,26 @@ fn selected_str<'s>(text: &'s dyn TextBuffer, cursor_range: &CursorRange) -> &'s text.char_range(min.ccursor.index..max.ccursor.index) } -fn insert_text(ccursor: &mut CCursor, text: &mut dyn TextBuffer, text_to_insert: &str) { - ccursor.index += text.insert_text(text_to_insert, ccursor.index); +fn insert_text( + 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); + } } // ----------------------------------------------------------------------------