Skip to content

Commit 7bcb6f0

Browse files
committed
Initial pass at adding support for "password" fields.
This works, but may definitely want to be designed and implemented in a different way, as adding this in does seem a bit hacky.
1 parent 15077a3 commit 7bcb6f0

File tree

2 files changed

+54
-24
lines changed

2 files changed

+54
-24
lines changed

src/widget/text_box.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ widget_style!{
3939
- justify: text::Justify { text::Justify::Left }
4040
/// The font used for the `Text`.
4141
- font_id: Option<text::font::Id> { theme.font_id }
42+
/// Display text with all characters replaced by this
43+
- char_replace: Option<char> { None }
4244
}
4345
}
4446

@@ -91,6 +93,7 @@ impl<'a> TextBox<'a> {
9193
pub font_size { style.font_size = Some(FontSize) }
9294
pub justify { style.justify = Some(text::Justify) }
9395
pub pad_text { style.text_padding = Some(Scalar) }
96+
pub hide_with_char { style.char_replace = Some(Option<char>) }
9497
}
9598

9699
}
@@ -156,6 +159,8 @@ impl<'a> Widget for TextBox<'a> {
156159
.border_color(border_color)
157160
.set(state.ids.rectangle, ui);
158161

162+
let char_replace = style.char_replace(ui.theme());
163+
159164
let mut events = Vec::new();
160165

161166
let text_color = style.text_color(ui.theme());
@@ -167,6 +172,7 @@ impl<'a> Widget for TextBox<'a> {
167172
.font_size(font_size)
168173
.color(text_color)
169174
.justify(justify)
175+
.hide_with_char(char_replace)
170176
.parent(id)
171177
.set(state.ids.text_edit, ui)
172178
{

src/widget/text_edit.rs

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ widget_style!{
4040
- restrict_to_height: bool { true }
4141
/// The font used for the `Text`.
4242
- font_id: Option<text::font::Id> { theme.font_id }
43+
/// Display text with all characters replaced by this
44+
- char_replace: Option<char> { None }
4345
}
4446
}
4547

@@ -164,6 +166,7 @@ impl<'a> TextEdit<'a> {
164166
pub line_wrap { style.line_wrap = Some(Wrap) }
165167
pub line_spacing { style.line_spacing = Some(Scalar) }
166168
pub restrict_to_height { style.restrict_to_height = Some(bool) }
169+
pub hide_with_char { style.char_replace = Some(Option<char>) }
167170
}
168171

169172
}
@@ -275,6 +278,10 @@ impl<'a> Widget for TextEdit<'a> {
275278
}
276279
}
277280

281+
let mut replacement_string = style.char_replace
282+
.and_then(|opt| opt)
283+
.map(|c: char| text.chars().map(|_| c).collect::<String>().into());
284+
278285
// Check to see if the given text has changed since the last time the widget was updated.
279286
{
280287
let maybe_new_line_infos = {
@@ -441,7 +448,8 @@ impl<'a> Widget for TextEdit<'a> {
441448
let abs_xy = utils::vec2_add(rel_xy, rect.xy());
442449
let infos = &state.line_infos;
443450
let font = ui.fonts.get(font_id).unwrap();
444-
let closest = closest_cursor_index_and_xy(abs_xy, &text, infos, font);
451+
let closest = closest_cursor_index_and_xy(abs_xy, &replacement_string.as_ref().unwrap_or(&text),
452+
infos, font);
445453
if let Some((closest_cursor, _)) = closest {
446454
cursor = Cursor::Idx(closest_cursor);
447455
}
@@ -506,6 +514,8 @@ impl<'a> Widget for TextEdit<'a> {
506514
*text.to_mut() = text.chars().take(start_idx)
507515
.chain(text.chars().skip(end_idx))
508516
.collect();
517+
replacement_string = style.char_replace.and_then(|opt| opt)
518+
.map(|c: char| text.chars().map(|_| c).collect::<String>().into());
509519
state.update(|state| {
510520
let font = ui.fonts.get(font_id).unwrap();
511521
let w = rect.w();
@@ -526,28 +536,30 @@ impl<'a> Widget for TextEdit<'a> {
526536
Cursor::Selection { start, end } => (start, end),
527537
};
528538

539+
let displayed_text = replacement_string.as_ref().unwrap_or(&text);
540+
529541
let new_cursor_idx = {
530542
let line_infos = state.line_infos.iter().cloned();
531543
match (key, move_word) {
532544
(input::Key::Left, true) => cursor_idx
533-
.previous_word_start(&text, line_infos),
545+
.previous_word_start(&displayed_text, line_infos),
534546
(input::Key::Right, true) => cursor_idx
535-
.next_word_end(&text, line_infos),
547+
.next_word_end(&displayed_text, line_infos),
536548
(input::Key::Left, false) => cursor_idx
537549
.previous(line_infos),
538550
(input::Key::Right, false) => cursor_idx
539551
.next(line_infos),
540552

541553
// Up/Down movement
542-
_ => cursor_xy_at(cursor_idx, &text, &state.line_infos, font)
554+
_ => cursor_xy_at(cursor_idx, &displayed_text, &state.line_infos, font)
543555
.and_then(|(x_pos, _)| {
544556
let text::cursor::Index { line, .. } = cursor_idx;
545557
let next_line = match key {
546558
input::Key::Up => line.saturating_sub(1),
547559
input::Key::Down => line + 1,
548560
_ => unreachable!(),
549561
};
550-
closest_cursor_index_on_line(x_pos, next_line, &text, &state.line_infos, font)
562+
closest_cursor_index_on_line(x_pos, next_line, &displayed_text, &state.line_infos, font)
551563
})
552564
}.unwrap_or(cursor_idx)
553565
};
@@ -581,10 +593,10 @@ impl<'a> Widget for TextEdit<'a> {
581593
let line_infos = state.line_infos.iter().cloned();
582594
match key {
583595
input::Key::Left | input::Key::Up => {
584-
cursor_idx.previous_word_start(&text, line_infos)
596+
cursor_idx.previous_word_start(&displayed_text, line_infos)
585597
},
586598
input::Key::Right | input::Key::Down => {
587-
cursor_idx.next_word_end(&text, line_infos)
599+
cursor_idx.next_word_end(&displayed_text, line_infos)
588600
}
589601
_ => unreachable!(),
590602
}.unwrap_or(cursor_idx)
@@ -656,6 +668,8 @@ impl<'a> Widget for TextEdit<'a> {
656668
match insert_text("\n", cursor, &text, &state.line_infos, font) {
657669
Some((new_text, new_cursor, new_line_infos)) => {
658670
*text.to_mut() = new_text;
671+
replacement_string = style.char_replace.and_then(|opt| opt)
672+
.map(|c: char| text.chars().map(|_| c).collect::<String>().into());
659673
cursor = new_cursor;
660674
state.update(|state| state.line_infos = new_line_infos);
661675
}, _ => ()
@@ -698,6 +712,8 @@ impl<'a> Widget for TextEdit<'a> {
698712
match insert_text(&string, cursor, &text, &state.line_infos, font) {
699713
Some((new_text, new_cursor, new_line_infos)) => {
700714
*text.to_mut() = new_text;
715+
replacement_string = style.char_replace.and_then(|opt| opt)
716+
.map(|c: char| text.chars().map(|_| c).collect::<String>().into());
701717
cursor = new_cursor;
702718
state.update(|state| state.line_infos = new_line_infos);
703719
}, _ => ()
@@ -706,8 +722,9 @@ impl<'a> Widget for TextEdit<'a> {
706722

707723
// Check whether or not we need to extend a text selection or drag some text.
708724
event::Widget::Drag(drag_event) if drag_event.button == input::MouseButton::Left => {
709-
match drag {
725+
let displayed_text = replacement_string.as_ref().unwrap_or(&text);
710726

727+
match drag {
711728
Some(Drag::Selecting) => {
712729
let start_cursor_idx = match cursor {
713730
Cursor::Idx(idx) => idx,
@@ -716,7 +733,7 @@ impl<'a> Widget for TextEdit<'a> {
716733
let abs_xy = utils::vec2_add(drag_event.to, rect.xy());
717734
let infos = &state.line_infos;
718735
let font = ui.fonts.get(font_id).unwrap();
719-
match closest_cursor_index_and_xy(abs_xy, &text, infos, font) {
736+
match closest_cursor_index_and_xy(abs_xy, &displayed_text, infos, font) {
720737
Some((end_cursor_idx, _)) =>
721738
cursor = Cursor::Selection {
722739
start: start_cursor_idx,
@@ -763,20 +780,26 @@ impl<'a> Widget for TextEdit<'a> {
763780
let text_y_range = Range::new(0.0, text_height).align_to(y_align, rect.y);
764781
let text_rect = Rect { x: rect.x, y: text_y_range };
765782

766-
match line_wrap {
767-
Wrap::Whitespace => widget::Text::new(&text).wrap_by_word(),
768-
Wrap::Character => widget::Text::new(&text).wrap_by_character(),
783+
{
784+
let display_text = match replacement_string {
785+
Some(ref s) => s,
786+
None => &text,
787+
};
788+
match line_wrap {
789+
Wrap::Whitespace => widget::Text::new(display_text).wrap_by_word(),
790+
Wrap::Character => widget::Text::new(display_text).wrap_by_character(),
791+
}
792+
.font_id(font_id)
793+
.wh(text_rect.dim())
794+
.xy(text_rect.xy())
795+
.justify(justify)
796+
.parent(id)
797+
.graphics_for(id)
798+
.color(color)
799+
.line_spacing(line_spacing)
800+
.font_size(font_size)
801+
.set(state.ids.text, ui);
769802
}
770-
.font_id(font_id)
771-
.wh(text_rect.dim())
772-
.xy(text_rect.xy())
773-
.justify(justify)
774-
.parent(id)
775-
.graphics_for(id)
776-
.color(color)
777-
.line_spacing(line_spacing)
778-
.font_size(font_size)
779-
.set(state.ids.text, ui);
780803

781804
// Draw the line for the cursor.
782805
let cursor_idx = match cursor {
@@ -791,7 +814,7 @@ impl<'a> Widget for TextEdit<'a> {
791814

792815
let (cursor_x, cursor_y_range) = {
793816
let font = ui.fonts.get(font_id).unwrap();
794-
cursor_xy_at(cursor_idx, &text, &state.line_infos, font)
817+
cursor_xy_at(cursor_idx, replacement_string.as_ref().unwrap_or(&text), &state.line_infos, font)
795818
.unwrap_or_else(|| {
796819
let x = rect.left();
797820
let y = Range::new(0.0, font_size as Scalar).align_to(y_align, rect.y);
@@ -839,7 +862,7 @@ impl<'a> Widget for TextEdit<'a> {
839862

840863
let selected_rects: Vec<Rect> = {
841864
let line_infos = state.line_infos.iter().cloned();
842-
let lines = line_infos.clone().map(|info| &text[info.byte_range()]);
865+
let lines = line_infos.clone().map(|info| &replacement_string.as_ref().unwrap_or(&text)[info.byte_range()]);
843866
let line_rects = text::line::rects(line_infos.clone(), font_size, rect,
844867
justify, y_align, line_spacing);
845868
let lines_with_rects = lines.zip(line_rects.clone());
@@ -867,6 +890,7 @@ impl<'a> Widget for TextEdit<'a> {
867890
}
868891
}
869892

893+
870894
take_if_owned(text)
871895
}
872896

0 commit comments

Comments
 (0)