Skip to content

Commit

Permalink
Add character limit to TextEdit (#2816)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
cameron authored Mar 30, 2023
1 parent fb726aa commit 5fc5f08
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 9 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)):
Expand Down
51 changes: 43 additions & 8 deletions crates/egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub struct TextEdit<'t> {
min_size: Vec2,
align: Align2,
clip_text: bool,
char_limit: usize,
}

impl<'t> WidgetWithState for TextEdit<'t> {
Expand Down Expand Up @@ -119,6 +120,7 @@ impl<'t> TextEdit<'t> {
min_size: Vec2::ZERO,
align: Align2::LEFT_TOP,
clip_text: false,
char_limit: usize::MAX,
}
}

Expand Down Expand Up @@ -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 = limit;
self
}

/// Set the horizontal align of the inner text.
pub fn horizontal_align(mut self, align: Align) -> Self {
self.align.0[0] = align;
Expand Down Expand Up @@ -412,6 +422,7 @@ impl<'t> TextEdit<'t> {
min_size,
align,
clip_text,
char_limit,
} = self;

let text_color = text_color
Expand Down Expand Up @@ -581,6 +592,7 @@ impl<'t> TextEdit<'t> {
multiline,
password,
default_cursor_range,
char_limit,
);

if changed {
Expand Down Expand Up @@ -871,6 +883,7 @@ fn events(
multiline: bool,
password: bool,
default_cursor_range: CursorRange,
char_limit: usize,
) -> (bool, CursorRange) {
let mut cursor_range = state.cursor_range(galley).unwrap_or(default_cursor_range);

Expand Down Expand Up @@ -912,7 +925,9 @@ 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);

insert_text(&mut ccursor, text, text_to_insert, char_limit);

Some(CCursorRange::one(ccursor))
} else {
None
Expand All @@ -922,7 +937,9 @@ 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);

insert_text(&mut ccursor, text, text_to_insert, char_limit);

Some(CCursorRange::one(ccursor))
} else {
None
Expand All @@ -940,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 {
Expand All @@ -954,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 {
Expand Down Expand Up @@ -1000,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 {
Expand All @@ -1013,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 {
Expand Down Expand Up @@ -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)
}

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);
}
}

// ----------------------------------------------------------------------------
Expand Down

0 comments on commit 5fc5f08

Please sign in to comment.