diff options
-rw-r--r-- | examples/tour.rs | 43 | ||||
-rw-r--r-- | native/CHANGELOG.md | 2 | ||||
-rw-r--r-- | native/src/widget/text_input.rs | 77 |
3 files changed, 100 insertions, 22 deletions
diff --git a/examples/tour.rs b/examples/tour.rs index b06fbc37..da05b396 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,11 @@ impl<'a> Step { *value = new_value; } } + StepMessage::ToggleSecureInput(toggle) => { + if let Step::TextInput { is_secure, .. } = self { + *is_secure = toggle; + } + } }; } @@ -328,7 +336,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 +594,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:", 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<image::Handle>` now instead of an `Into<String>`. [#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 diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index c57b80aa..f0bb9f87 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,26 @@ where } } keyboard::KeyCode::Left => { - if modifiers.control { + 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 { + 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); @@ -269,15 +300,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 +590,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 |