From 34bdfe941613d4bb51848eec1dc0f99f2a59ff21 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 7 Dec 2019 07:18:15 +0100 Subject: Implement `TextInput::password` for secure data --- native/src/widget/text_input.rs | 65 ++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index c57b80aa..985d1b80 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -39,6 +39,7 @@ pub struct TextInput<'a, Message> { state: &'a mut State, placeholder: String, value: Value, + is_secure: bool, width: Length, max_width: Length, padding: u16, @@ -71,6 +72,7 @@ impl<'a, Message> TextInput<'a, Message> { state, placeholder: String::from(placeholder), value: Value::new(value), + is_secure: false, width: Length::Fill, max_width: Length::Shrink, padding: 0, @@ -80,6 +82,14 @@ impl<'a, Message> TextInput<'a, Message> { } } + /// Converts the [`TextInput`] into a secure password input. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn password(mut self) -> Self { + self.is_secure = true; + self + } + /// Sets the width of the [`TextInput`]. /// /// [`TextInput`]: struct.TextInput.html @@ -177,6 +187,15 @@ where if target < 0.0 { self.state.cursor_position = 0; + } else if self.is_secure { + self.state.cursor_position = find_cursor_position( + renderer, + target, + &self.value.secure(), + self.size.unwrap_or(renderer.default_size()), + 0, + self.value.len(), + ); } else { self.state.cursor_position = find_cursor_position( renderer, @@ -235,14 +254,14 @@ where } } keyboard::KeyCode::Left => { - if modifiers.control { + if modifiers.control && !self.is_secure { self.state.move_cursor_left_by_words(&self.value); } else { self.state.move_cursor_left(&self.value); } } keyboard::KeyCode::Right => { - if modifiers.control { + if modifiers.control && !self.is_secure { self.state.move_cursor_right_by_words(&self.value); } else { self.state.move_cursor_right(&self.value); @@ -269,15 +288,27 @@ where let bounds = layout.bounds(); let text_bounds = layout.children().next().unwrap().bounds(); - renderer.draw( - bounds, - text_bounds, - cursor_position, - self.size.unwrap_or(renderer.default_size()), - &self.placeholder, - &self.value, - &self.state, - ) + if self.is_secure { + renderer.draw( + bounds, + text_bounds, + cursor_position, + self.size.unwrap_or(renderer.default_size()), + &self.placeholder, + &self.value.secure(), + &self.state, + ) + } else { + renderer.draw( + bounds, + text_bounds, + cursor_position, + self.size.unwrap_or(renderer.default_size()), + &self.placeholder, + &self.value, + &self.state, + ) + } } fn hash_layout(&self, state: &mut Hasher) { @@ -547,6 +578,18 @@ impl Value { pub fn remove(&mut self, index: usize) { let _ = self.graphemes.remove(index); } + + /// Returns a new [`Value`] with all its graphemes replaced with the + /// dot ('•') character. + /// + /// [`Value`]: struct.Value.html + pub fn secure(&self) -> Self { + Self { + graphemes: std::iter::repeat(String::from("•")) + .take(self.graphemes.len()) + .collect(), + } + } } // TODO: Reduce allocations -- cgit From cdee847cea47b84568048ff8f48194a24dab80f1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 7 Dec 2019 07:19:05 +0100 Subject: Showcase new `TextInput::password` in `tour` --- examples/tour.rs | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/examples/tour.rs b/examples/tour.rs index b06fbc37..77ed21ab 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -140,6 +140,7 @@ impl Steps { Step::Scrollable, Step::TextInput { value: String::new(), + is_secure: false, state: text_input::State::new(), }, Step::Debugger, @@ -210,6 +211,7 @@ enum Step { Scrollable, TextInput { value: String, + is_secure: bool, state: text_input::State, }, Debugger, @@ -226,6 +228,7 @@ pub enum StepMessage { LanguageSelected(Language), ImageWidthChanged(f32), InputChanged(String), + ToggleSecureInput(bool), DebugToggled(bool), } @@ -277,6 +280,12 @@ impl<'a> Step { *value = new_value; } } + + StepMessage::ToggleSecureInput(toggle) => { + if let Step::TextInput { is_secure, .. } = self { + *is_secure = toggle; + } + } }; } @@ -328,7 +337,11 @@ impl<'a> Step { spacing, } => Self::rows_and_columns(*layout, spacing_slider, *spacing), Step::Scrollable => Self::scrollable(), - Step::TextInput { value, state } => Self::text_input(value, state), + Step::TextInput { + value, + is_secure, + state, + } => Self::text_input(value, *is_secure, state), Step::Debugger => Self::debugger(debug), Step::End => Self::end(), } @@ -582,22 +595,31 @@ impl<'a> Step { fn text_input( value: &str, + is_secure: bool, state: &'a mut text_input::State, ) -> Column<'a, StepMessage> { + let text_input = TextInput::new( + state, + "Type something to continue...", + value, + StepMessage::InputChanged, + ) + .padding(10) + .size(30); Self::container("Text input") .push(Text::new( "Use a text input to ask for different kinds of information.", )) - .push( - TextInput::new( - state, - "Type something to continue...", - value, - StepMessage::InputChanged, - ) - .padding(10) - .size(30), - ) + .push(if is_secure { + text_input.password() + } else { + text_input + }) + .push(Checkbox::new( + is_secure, + "Enable password mode", + StepMessage::ToggleSecureInput, + )) .push(Text::new( "A text input produces a message every time it changes. It is \ very easy to keep track of its contents:", -- cgit From c59ff694734ba8bfc0f8e5bd2da3e23acfbe3bc4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 7 Dec 2019 07:24:55 +0100 Subject: Change `TextInput` word-jump modifier key on macOS --- native/src/widget/text_input.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 985d1b80..f0bb9f87 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -254,14 +254,26 @@ where } } keyboard::KeyCode::Left => { - if modifiers.control && !self.is_secure { + let jump_modifier_pressed = if cfg!(target_os = "macos") { + modifiers.alt + } else { + modifiers.control + }; + + if jump_modifier_pressed && !self.is_secure { self.state.move_cursor_left_by_words(&self.value); } else { self.state.move_cursor_left(&self.value); } } keyboard::KeyCode::Right => { - if modifiers.control && !self.is_secure { + let jump_modifier_pressed = if cfg!(target_os = "macos") { + modifiers.alt + } else { + modifiers.control + }; + + if jump_modifier_pressed && !self.is_secure { self.state.move_cursor_right_by_words(&self.value); } else { self.state.move_cursor_right(&self.value); -- cgit From a7694e0112d45cffa0d25e3f19fc0289c3804c47 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 8 Dec 2019 06:24:47 +0100 Subject: Update native `CHANGELOG` --- examples/tour.rs | 1 - native/CHANGELOG.md | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/tour.rs b/examples/tour.rs index 77ed21ab..da05b396 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -280,7 +280,6 @@ impl<'a> Step { *value = new_value; } } - StepMessage::ToggleSecureInput(toggle) => { if let Step::TextInput { is_secure, .. } = self { *is_secure = toggle; diff --git a/native/CHANGELOG.md b/native/CHANGELOG.md index 463b2dbe..cdf02c4b 100644 --- a/native/CHANGELOG.md +++ b/native/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Home` and `End` keys support for `TextInput`. [#108] - `Ctrl+Left` and `Ctrl+Right` cursor word jump for `TextInput`. [#108] - `keyboard::ModifiersState` struct which contains the state of the keyboard modifiers. [#108] +- `TextInput::password` method to enable secure password input mode. [#113] ### Changed - `Image::new` takes an `Into` now instead of an `Into`. [#90] @@ -25,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#90]: https://github.com/hecrj/iced/pull/90 [#108]: https://github.com/hecrj/iced/pull/108 +[#113]: https://github.com/hecrj/iced/pull/113 ## [0.1.0] - 2019-11-25 -- cgit