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 `ComboBox` to more easily customize combo boxes.
* Add `Slider::new` and `DragValue::new` to replace old type-specific constructors.
* Add `TextEdit::password` to hide input characters.
### Changed 🔧
* `kb_focus` is now just called `focus`.

View file

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

View file

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

View file

@ -24,6 +24,7 @@ pub struct Widgets {
color: Color32,
single_line_text_input: String,
multiline_text_input: String,
show_password: bool,
}
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(),
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(),
show_password: false,
}
}
}
@ -128,11 +130,15 @@ impl Widgets {
ui.separator();
ui.horizontal(|ui| {
ui.label("Single line text input:");
let response = ui.text_edit_singleline(&mut self.single_line_text_input);
ui.label("Password:");
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) {
// …
}
ui.checkbox(&mut self.show_password, "Show password");
});
ui.label("Multiline text input:");

View file

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

View file

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