From 7e826cb72f2e4b7b28c481423594e2815441b344 Mon Sep 17 00:00:00 2001 From: Andres Pineda Date: Fri, 17 Apr 2020 22:04:59 -0400 Subject: [PATCH] feat: (macOS) Add SecureTextBox for PasswordBox --- .../Mixins/macOS/FrameworkElementMixins.tt | 1 + .../Xaml/Controls/PasswordBox/PasswordBox.cs | 3 + ...wordBox.iOS.cs => PasswordBox.iOSmacOS.cs} | 0 .../PasswordBox/SecuredTextBoxView.macOS.cs | 191 ++++++++++++++++++ .../UI/Xaml/Controls/TextBox/TextBox.macOS.cs | 35 +++- 5 files changed, 219 insertions(+), 11 deletions(-) rename src/Uno.UI/UI/Xaml/Controls/PasswordBox/{PasswordBox.iOS.cs => PasswordBox.iOSmacOS.cs} (100%) create mode 100644 src/Uno.UI/UI/Xaml/Controls/PasswordBox/SecuredTextBoxView.macOS.cs diff --git a/src/Uno.UI/Mixins/macOS/FrameworkElementMixins.tt b/src/Uno.UI/Mixins/macOS/FrameworkElementMixins.tt index f104dea4563f..0e4219657aed 100644 --- a/src/Uno.UI/Mixins/macOS/FrameworkElementMixins.tt +++ b/src/Uno.UI/Mixins/macOS/FrameworkElementMixins.tt @@ -11,6 +11,7 @@ // AddClass("Windows.UI.Xaml.Controls", "ProgressRing", hasAttachedToWindow: false, overridesAttachedToWindow: true); // AddClass("Uno.UI.Controls.Legacy", "ListViewBase", hasAttachedToWindow: false, overridesAttachedToWindow: true, defineSetNeedsLayout: false, defineLayoutSubviews: false); AddClass("Windows.UI.Xaml.Controls", "TextBoxView", hasAutomationPeer: false, hasAttachedToWindow: true, overridesAttachedToWindow: false); + AddClass("Windows.UI.Xaml.Controls", "SecureTextBoxView", hasAutomationPeer: false, hasAttachedToWindow: true, overridesAttachedToWindow: false); #> <#@include file="..\..\UI\Xaml\IFrameworkElementImplementation.macOS.tt"#> #endif diff --git a/src/Uno.UI/UI/Xaml/Controls/PasswordBox/PasswordBox.cs b/src/Uno.UI/UI/Xaml/Controls/PasswordBox/PasswordBox.cs index 3151278673cc..f16a52dfe2d6 100644 --- a/src/Uno.UI/UI/Xaml/Controls/PasswordBox/PasswordBox.cs +++ b/src/Uno.UI/UI/Xaml/Controls/PasswordBox/PasswordBox.cs @@ -17,6 +17,9 @@ public partial class PasswordBox : TextBox private readonly SerialDisposable _revealButtonSubscription = new SerialDisposable(); public PasswordBox() +#if __MACOS__ + : base(true) +#endif { } diff --git a/src/Uno.UI/UI/Xaml/Controls/PasswordBox/PasswordBox.iOS.cs b/src/Uno.UI/UI/Xaml/Controls/PasswordBox/PasswordBox.iOSmacOS.cs similarity index 100% rename from src/Uno.UI/UI/Xaml/Controls/PasswordBox/PasswordBox.iOS.cs rename to src/Uno.UI/UI/Xaml/Controls/PasswordBox/PasswordBox.iOSmacOS.cs diff --git a/src/Uno.UI/UI/Xaml/Controls/PasswordBox/SecuredTextBoxView.macOS.cs b/src/Uno.UI/UI/Xaml/Controls/PasswordBox/SecuredTextBoxView.macOS.cs new file mode 100644 index 000000000000..d42d989fcd7d --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/PasswordBox/SecuredTextBoxView.macOS.cs @@ -0,0 +1,191 @@ +using CoreGraphics; +using System; +using Uno.Extensions; +using Windows.UI.Xaml.Media; +using Uno.UI.Controls; +using Foundation; +using System.Collections; +using System.Linq; +using AppKit; + +namespace Windows.UI.Xaml.Controls +{ + public partial class SecureTextBoxView : NSSecureTextField, ITextBoxView, DependencyObject, IFontScalable + { + //private TextBoxViewDelegate _delegate; + private readonly WeakReference _textBox; + + public SecureTextBoxView(TextBox textBox) + { + _textBox = new WeakReference(textBox); + + InitializeBinder(); + Initialize(); + } + + + private void OnEditingChanged(object sender, EventArgs e) + { + OnTextChanged(); + } + + internal void OnChanged() + { + OnTextChanged(); + } + + public string Text + { + get => base.StringValue; + + set + { + // The native control will ignore a value of null and retain an empty string. We coalesce the null to prevent a spurious empty string getting bounced back via two-way binding. + value = value ?? string.Empty; + if (base.StringValue != value) + { + base.StringValue = value; + OnTextChanged(); + } + } + } + + private void OnTextChanged() + { + var textBox = _textBox?.GetTarget(); + if (textBox != null) + { + var text = textBox.ProcessTextInput(Text); + SetTextNative(text); + } + } + + public void SetTextNative(string text) => Text = text; + + private void Initialize() + { + //Delegate = _delegate = new TextBoxViewDelegate(_textBox, new WeakReference(this)) + //{ + // IsKeyboardHiddenOnEnter = true + //}; + + DrawsBackground = false; + Bezeled = false; + } + + public override CGSize SizeThatFits(CGSize size) + { + return IFrameworkElementHelper.SizeThatFits(this, base.SizeThatFits(size)); + } + + public void UpdateFont() + { + var textBox = _textBox.GetTarget(); + + if (textBox != null) + { + var newFont = NSFontHelper.TryGetFont((float)textBox.FontSize, textBox.FontWeight, textBox.FontStyle, textBox.FontFamily); + + if (newFont != null) + { + base.Font = newFont; + this.InvalidateMeasure(); + } + } + } + + public Brush Foreground + { + get { return (Brush)GetValue(ForegroundProperty); } + set { SetValue(ForegroundProperty, value); } + } + + public bool HasMarkedText => throw new NotImplementedException(); + + public nint ConversationIdentifier => throw new NotImplementedException(); + + public NSRange MarkedRange => throw new NotImplementedException(); + + public NSRange SelectedRange => throw new NotImplementedException(); + + public NSString[] ValidAttributesForMarkedText => null; + + public static readonly DependencyProperty ForegroundProperty = + DependencyProperty.Register( + "Foreground", + typeof(Brush), + typeof(SecureTextBoxView), + new FrameworkPropertyMetadata( + defaultValue: SolidColorBrushHelper.Black, + options: FrameworkPropertyMetadataOptions.Inherits, + propertyChangedCallback: (s, e) => ((SecureTextBoxView)s).OnForegroundChanged((Brush)e.OldValue, (Brush)e.NewValue) + ) + ); + + public void OnForegroundChanged(Brush oldValue, Brush newValue) + { + var textBox = _textBox.GetTarget(); + + if (textBox != null) + { + var scb = newValue as SolidColorBrush; + + if (scb != null) + { + this.TextColor = scb.Color; + } + } + + UpdateCaretColor(); + } + + private void UpdateCaretColor() + { + if (CurrentEditor is NSTextView textField && Foreground is SolidColorBrush scb) + { + textField.InsertionPointColor = scb.Color; + } + } + + public void RefreshFont() + { + UpdateFont(); + } + + public override bool BecomeFirstResponder() + { + UpdateCaretColor(); + return base.BecomeFirstResponder(); + } + + public void InsertText(NSObject insertString) + { + throw new NotImplementedException(); + } + + public void SetMarkedText(NSObject @string, NSRange selRange) + { + throw new NotImplementedException(); + } + + public void UnmarkText() + { + throw new NotImplementedException(); + } + + public NSAttributedString GetAttributedSubstring(NSRange range) + { + throw new NotImplementedException(); + } + + public CGRect GetFirstRectForCharacterRange(NSRange range) + { + throw new NotImplementedException(); + } + + public nuint GetCharacterIndex(CGPoint point) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.macOS.cs b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.macOS.cs index 348485700dc9..5cc297cef994 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.macOS.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.macOS.cs @@ -1,16 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Uno.UI; -using Windows.UI.Xaml.Data; -using AppKit; +using AppKit; using CoreGraphics; using Uno.UI.Extensions; using Uno.Extensions; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Input; -using Uno.Client; -using Foundation; using Uno.Logging; namespace Windows.UI.Xaml.Controls @@ -63,12 +56,19 @@ private void UpdateTextBoxView() { if (_contentElement != null) { - if (_textBoxView is TextBoxView) + if (_textBoxView is TextBoxView || _textBoxView is SecureTextBoxView) { return; } - _textBoxView = new TextBoxView(this) { UsesSingleLineMode = AcceptsReturn || TextWrapping != TextWrapping.NoWrap }; + if (_isPassword) + { + _textBoxView = new SecureTextBoxView(this) { UsesSingleLineMode = true }; + } + else + { + _textBoxView = new TextBoxView(this) { UsesSingleLineMode = AcceptsReturn || TextWrapping != TextWrapping.NoWrap }; + } _contentElement.Content = _textBoxView; _textBoxView.SetTextNative(Text); @@ -134,10 +134,14 @@ public int SelectionStart } set { - if(_textBoxView is TextBoxView sltbv) + if (_textBoxView is TextBoxView sltbv) { sltbv.SelectWithFrame(sltbv.Frame, sltbv.CurrentEditor, null, value, _textBoxView.SelectedRange.Length); } + else if (_textBoxView is SecureTextBoxView securedtv) + { + securedtv.SelectWithFrame(securedtv.Frame, securedtv.CurrentEditor, null, value, _textBoxView.SelectedRange.Length); + } } } @@ -158,6 +162,10 @@ public int SelectionLength { sltbv.SelectWithFrame(sltbv.Frame, sltbv.CurrentEditor, null, _textBoxView.SelectedRange.Location, value); } + else if (_textBoxView is SecureTextBoxView securedtv) + { + securedtv.SelectWithFrame(securedtv.Frame, securedtv.CurrentEditor, null, _textBoxView.SelectedRange.Location, value); + } } } @@ -179,5 +187,10 @@ partial void OnForegroundColorChangedPartial(Brush newValue) _textBoxView.Foreground = newValue; } } + + protected void SetSecureTextEntry(bool isSecure) + { + } + } }