diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 64118a00b4..481a459159 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -20,6 +20,7 @@ @@ -37,6 +38,8 @@ Text="Multiline TextBox with TextWrapping. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." /> + + resm fonts diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 6e534bbb2a..37490c3ef3 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -14,6 +14,9 @@ namespace Avalonia.Controls.Presenters o => o.CaretIndex, (o, v) => o.CaretIndex = v); + public static readonly StyledProperty RevealPasswordProperty = + AvaloniaProperty.Register(nameof(RevealPassword)); + public static readonly StyledProperty PasswordCharProperty = AvaloniaProperty.Register(nameof(PasswordChar)); @@ -75,7 +78,7 @@ namespace Avalonia.Controls.Presenters static TextPresenter() { AffectsRender(SelectionBrushProperty); - AffectsMeasure(TextProperty, PasswordCharProperty, + AffectsMeasure(TextProperty, PasswordCharProperty, RevealPasswordProperty, TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty, TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty); @@ -84,7 +87,7 @@ namespace Avalonia.Controls.Presenters TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed, TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed, SelectionStartProperty.Changed, SelectionEndProperty.Changed, - SelectionForegroundBrushProperty.Changed, PasswordCharProperty.Changed + SelectionForegroundBrushProperty.Changed, PasswordCharProperty.Changed, RevealPasswordProperty.Changed ).AddClassHandler((x, _) => x.InvalidateFormattedText()); CaretIndexProperty.Changed.AddClassHandler((x, e) => x.CaretIndexChanged((int)e.NewValue)); @@ -210,6 +213,12 @@ namespace Avalonia.Controls.Presenters set => SetValue(PasswordCharProperty, value); } + public bool RevealPassword + { + get => GetValue(RevealPasswordProperty); + set => SetValue(RevealPasswordProperty, value); + } + public IBrush SelectionBrush { get => GetValue(SelectionBrushProperty); @@ -426,7 +435,7 @@ namespace Avalonia.Controls.Presenters var text = Text; - if (PasswordChar != default(char)) + if (PasswordChar != default(char) && !RevealPassword) { result = CreateFormattedTextInternal(_constraint, new string(PasswordChar, text?.Length ?? 0)); } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 87a674fabd..7e28d88581 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -95,6 +95,15 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(NewLine), textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline); + public static readonly StyledProperty InnerLeftContentProperty = + AvaloniaProperty.Register(nameof(InnerLeftContent)); + + public static readonly StyledProperty InnerRightContentProperty = + AvaloniaProperty.Register(nameof(InnerRightContent)); + + public static readonly StyledProperty RevealPasswordProperty = + AvaloniaProperty.Register(nameof(RevealPassword)); + struct UndoRedoState : IEquatable { public string Text { get; } @@ -148,6 +157,8 @@ namespace Avalonia.Controls horizontalScrollBarVisibility, BindingPriority.Style); _undoRedoHelper = new UndoRedoHelper(this); + + UpdatePseudoclasses(); } public bool AcceptsReturn @@ -285,7 +296,7 @@ namespace Avalonia.Controls else { HandleTextInput(value); - } + } _undoRedoHelper.Snapshot(); } } @@ -326,6 +337,24 @@ namespace Avalonia.Controls set { SetValue(UseFloatingWatermarkProperty, value); } } + public object InnerLeftContent + { + get { return GetValue(InnerLeftContentProperty); } + set { SetValue(InnerLeftContentProperty, value); } + } + + public object InnerRightContent + { + get { return GetValue(InnerRightContentProperty); } + set { SetValue(InnerRightContentProperty, value); } + } + + public bool RevealPassword + { + get { return GetValue(RevealPasswordProperty); } + set { SetValue(RevealPasswordProperty, value); } + } + public TextWrapping TextWrapping { get { return GetValue(TextWrappingProperty); } @@ -351,6 +380,16 @@ namespace Avalonia.Controls } } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == TextProperty) + { + UpdatePseudoclasses(); + } + } + protected override void OnGotFocus(GotFocusEventArgs e) { base.OnGotFocus(e); @@ -374,6 +413,7 @@ namespace Avalonia.Controls SelectionStart = 0; SelectionEnd = 0; _presenter?.HideCaret(); + RevealPassword = false; } protected override void OnTextInput(TextInputEventArgs e) @@ -756,7 +796,7 @@ namespace Avalonia.Controls // if it did not, we change the selection to where the user clicked var firstSelection = Math.Min(SelectionStart, SelectionEnd); var lastSelection = Math.Max(SelectionStart, SelectionEnd); - var didClickInSelection = SelectionStart != SelectionEnd && + var didClickInSelection = SelectionStart != SelectionEnd && caretIndex >= firstSelection && caretIndex <= lastSelection; if (!didClickInSelection) { @@ -803,6 +843,11 @@ namespace Avalonia.Controls } } + public void Clear() + { + Text = string.Empty; + } + private int DeleteCharacter(int index) { var start = index + 1; @@ -1068,6 +1113,11 @@ namespace Avalonia.Controls SelectionEnd = CaretIndex; } + private void UpdatePseudoclasses() + { + PseudoClasses.Set(":empty", string.IsNullOrWhiteSpace(Text)); + } + private bool IsPasswordBox => PasswordChar != default(char); UndoRedoState UndoRedoHelper.IUndoRedoHost.UndoRedoState diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 6a746dda30..8384cccada 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -35,14 +35,16 @@ - - + + + + IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}" + Grid.Column="1" Grid.ColumnSpan="1"/> - + CaretBrush="{TemplateBinding CaretBrush}" + Grid.Column="1" Grid.ColumnSpan="1"/> + + diff --git a/src/Avalonia.Themes.Fluent/TextBox.xaml b/src/Avalonia.Themes.Fluent/TextBox.xaml index 6a26e7dcae..5eddd54226 100644 --- a/src/Avalonia.Themes.Fluent/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/TextBox.xaml @@ -4,13 +4,18 @@ 0,0,0,4 + M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z + m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z + m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z + - - @@ -123,12 +122,12 @@ - - @@ -137,23 +136,152 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 217dec2d6d..af3b68f4a0 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -521,6 +521,40 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, lfcount); } } + + [Fact] + public void TextBox_Reveal_Password_Reset_When_Lost_Focus() + { + using (UnitTestApplication.Start(FocusServices)) + { + var target1 = new TextBox + { + Template = CreateTemplate(), + Text = "1234", + PasswordChar = '*' + }; + var target2 = new TextBox + { + Template = CreateTemplate(), + Text = "5678" + }; + var sp = new StackPanel(); + sp.Children.Add(target1); + sp.Children.Add(target2); + + target1.ApplyTemplate(); + target2.ApplyTemplate(); + + var root = new TestRoot { Child = sp }; + + target1.Focus(); + target1.RevealPassword = true; + + target2.Focus(); + + Assert.False(target1.RevealPassword); + } + } [Fact] public void Setting_Bound_Text_To_Null_Works()