Add TextEdit::password to hide input characters

This commit is contained in:
Emil Ernerfeldt 2021-04-02 09:58:55 +02:00
parent 33a4058381
commit d848b2a664
6 changed files with 60 additions and 24 deletions

View file

@ -19,6 +19,7 @@ NOTE: `eframe`, `egui_web` and `egui_glium` has their own changelogs!
* Add `DebugOptions::show_widgets` to debug layouting by hovering widgets. * Add `DebugOptions::show_widgets` to debug layouting by hovering widgets.
* Add `ComboBox` to more easily customize combo boxes. * Add `ComboBox` to more easily customize combo boxes.
* Add `Slider::new` and `DragValue::new` to replace old type-specific constructors. * Add `Slider::new` and `DragValue::new` to replace old type-specific constructors.
* Add `TextEdit::password` to hide input characters.
### Changed 🔧 ### Changed 🔧
* `kb_focus` is now just called `focus`. * `kb_focus` is now just called `focus`.

View file

@ -313,12 +313,14 @@ impl Painter {
color: Color32, color: Color32,
fake_italics: bool, fake_italics: bool,
) { ) {
self.add(Shape::Text { if !galley.is_empty() {
pos, self.add(Shape::Text {
galley, pos,
color, galley,
fake_italics, color,
}); fake_italics,
});
}
} }
} }

View file

@ -130,6 +130,7 @@ pub struct TextEdit<'t> {
id_source: Option<Id>, id_source: Option<Id>,
text_style: Option<TextStyle>, text_style: Option<TextStyle>,
text_color: Option<Color32>, text_color: Option<Color32>,
password: bool,
frame: bool, frame: bool,
multiline: bool, multiline: bool,
enabled: bool, enabled: bool,
@ -160,6 +161,7 @@ impl<'t> TextEdit<'t> {
id_source: None, id_source: None,
text_style: None, text_style: None,
text_color: None, text_color: None,
password: false,
frame: true, frame: true,
multiline: false, multiline: false,
enabled: true, enabled: true,
@ -176,8 +178,9 @@ impl<'t> TextEdit<'t> {
id: None, id: None,
id_source: None, id_source: None,
text_style: None, text_style: None,
frame: true,
text_color: None, text_color: None,
password: false,
frame: true,
multiline: true, multiline: true,
enabled: true, enabled: true,
desired_width: None, desired_width: None,
@ -202,6 +205,12 @@ impl<'t> TextEdit<'t> {
self self
} }
/// If true, hide the letters from view and prevent copying from the field.
pub fn password(mut self, password: bool) -> Self {
self.password = password;
self
}
pub fn text_style(mut self, text_style: TextStyle) -> Self { pub fn text_style(mut self, text_style: TextStyle) -> Self {
self.text_style = Some(text_style); self.text_style = Some(text_style);
self self
@ -292,6 +301,7 @@ impl<'t> TextEdit<'t> {
id_source, id_source,
text_style, text_style,
text_color, text_color,
password,
frame: _, frame: _,
multiline, multiline,
enabled, enabled,
@ -302,13 +312,31 @@ impl<'t> TextEdit<'t> {
let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style); let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style);
let line_spacing = ui.fonts().row_height(text_style); let line_spacing = ui.fonts().row_height(text_style);
let available_width = ui.available_width(); let available_width = ui.available_width();
let mut galley = if multiline {
ui.fonts() let make_galley = |ui: &Ui, text: &str| {
.layout_multiline(text_style, text.clone(), available_width) let text = if password {
} else { std::iter::repeat(epaint::text::PASSWORD_REPLACEMENT_CHAR)
ui.fonts().layout_single_line(text_style, text.clone()) .take(text.chars().count())
.collect::<String>()
} else {
text.to_owned()
};
if multiline {
ui.fonts()
.layout_multiline(text_style, text, available_width)
} else {
ui.fonts().layout_single_line(text_style, text)
}
}; };
let copy_if_not_password = |ui: &Ui, text: String| {
if !password {
ui.ctx().output().copied_text = text;
}
};
let mut galley = make_galley(ui, text);
let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width); let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width);
let desired_height = (desired_height_rows.at_least(1) as f32) * line_spacing; let desired_height = (desired_height_rows.at_least(1) as f32) * line_spacing;
let desired_size = vec2( let desired_size = vec2(
@ -411,18 +439,18 @@ impl<'t> TextEdit<'t> {
let did_mutate_text = match event { let did_mutate_text = match event {
Event::Copy => { Event::Copy => {
if cursorp.is_empty() { if cursorp.is_empty() {
ui.ctx().output().copied_text = text.clone(); copy_if_not_password(ui, text.clone());
} else { } else {
ui.ctx().output().copied_text = selected_str(text, &cursorp).to_owned(); copy_if_not_password(ui, selected_str(text, &cursorp).to_owned());
} }
None None
} }
Event::Cut => { Event::Cut => {
if cursorp.is_empty() { if cursorp.is_empty() {
ui.ctx().output().copied_text = std::mem::take(text); copy_if_not_password(ui, std::mem::take(text));
Some(CCursorPair::default()) Some(CCursorPair::default())
} else { } else {
ui.ctx().output().copied_text = selected_str(text, &cursorp).to_owned(); copy_if_not_password(ui, selected_str(text, &cursorp).to_owned());
Some(CCursorPair::one(delete_selected(text, &cursorp))) Some(CCursorPair::one(delete_selected(text, &cursorp)))
} }
} }
@ -482,12 +510,7 @@ impl<'t> TextEdit<'t> {
response.mark_changed(); response.mark_changed();
// Layout again to avoid frame delay, and to keep `text` and `galley` in sync. // Layout again to avoid frame delay, and to keep `text` and `galley` in sync.
galley = if multiline { galley = make_galley(ui, text);
ui.fonts()
.layout_multiline(text_style, text.clone(), available_width)
} else {
ui.fonts().layout_single_line(text_style, text.clone())
};
// Set cursorp using new galley: // Set cursorp using new galley:
cursorp = CursorPair { cursorp = CursorPair {

View file

@ -24,6 +24,7 @@ pub struct Widgets {
color: Color32, color: Color32,
single_line_text_input: String, single_line_text_input: String,
multiline_text_input: String, multiline_text_input: String,
show_password: bool,
} }
impl Default for Widgets { impl Default for Widgets {
@ -36,6 +37,7 @@ impl Default for Widgets {
color: (Rgba::from_rgb(0.0, 1.0, 0.5) * 0.75).into(), color: (Rgba::from_rgb(0.0, 1.0, 0.5) * 0.75).into(),
single_line_text_input: "Hello World!".to_owned(), single_line_text_input: "Hello World!".to_owned(),
multiline_text_input: "Text can both be so wide that it needs a line break, but you can also add manual line break by pressing enter, creating new paragraphs.\nThis is the start of the next paragraph.\n\nClick me to edit me!".to_owned(), multiline_text_input: "Text can both be so wide that it needs a line break, but you can also add manual line break by pressing enter, creating new paragraphs.\nThis is the start of the next paragraph.\n\nClick me to edit me!".to_owned(),
show_password: false,
} }
} }
} }
@ -128,11 +130,15 @@ impl Widgets {
ui.separator(); ui.separator();
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Single line text input:"); ui.label("Password:");
let response = ui.text_edit_singleline(&mut self.single_line_text_input); let response = ui.add(
egui::TextEdit::singleline(&mut self.single_line_text_input)
.password(!self.show_password),
);
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) { if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
// … // …
} }
ui.checkbox(&mut self.show_password, "Show password");
}); });
ui.label("Multiline text input:"); ui.label("Multiline text input:");

View file

@ -211,6 +211,7 @@ impl Font {
slf.glyph_info(c); slf.glyph_info(c);
} }
slf.glyph_info('°'); slf.glyph_info('°');
slf.glyph_info(crate::text::PASSWORD_REPLACEMENT_CHAR); // password replacement character
slf slf
} }

View file

@ -9,3 +9,6 @@ pub use {
fonts::{FontDefinitions, FontFamily, Fonts, TextStyle}, fonts::{FontDefinitions, FontFamily, Fonts, TextStyle},
galley::{Galley, Row}, galley::{Galley, Row},
}; };
/// Suggested character to use to replace those in password text fields.
pub const PASSWORD_REPLACEMENT_CHAR: char = '•';