From 424863d5ff007a21eaa6cd483e57f2046313e4d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez?= Date: Thu, 5 Feb 2026 17:35:47 +0100 Subject: [PATCH] Add PlaceholderForeground property to TextBox, AutoCompleteBox, CalendarDatePicker, NumericUpDown (#20303) * Added WatermarkForeground property * Added samples * Added unit tests * Added render tests * Fix merge issues * Updated render tests * Standardize watermark foreground naming * Pending changes * More changes * Use UseFloatingPlaceholder * Fix tests --- samples/BindingDemo/MainWindow.xaml | 44 +++--- .../Pages/AutoCompleteBoxPage.xaml | 8 +- .../Pages/CalendarDatePickerPage.xaml | 19 ++- .../ControlCatalog/Pages/ClipboardPage.xaml | 4 +- samples/ControlCatalog/Pages/DialogsPage.xaml | 12 +- .../Pages/NumericUpDownPage.xaml | 26 ++-- samples/ControlCatalog/Pages/TextBoxPage.xaml | 43 +++--- samples/ControlCatalog/Pages/ThemePage.axaml | 6 +- .../Controls/SignUpView.xaml | 14 +- .../Pages/ScreensPage.axaml | 14 +- .../Pages/WindowDecorationsPage.axaml | 2 +- .../IntegrationTestApp/Pages/WindowPage.axaml | 2 +- samples/SafeAreaDemo/Views/MainView.xaml | 4 +- samples/SingleProjectSandbox/MainView.axaml | 8 +- .../AutoCompleteBox.Properties.cs | 75 +++++++++- .../CalendarDatePicker.Properties.cs | 95 ++++++++++-- .../CalendarDatePicker/CalendarDatePicker.cs | 24 +-- .../NumericUpDown/NumericUpDown.cs | 81 ++++++++-- src/Avalonia.Controls/TextBox.cs | 113 +++++++++++--- .../Controls/AutoCompleteBox.xaml | 13 +- .../Controls/CalendarDatePicker.xaml | 9 +- .../Controls/ComboBox.xaml | 6 +- .../Controls/ManagedFileChooser.xaml | 8 +- .../Controls/NumericUpDown.xaml | 25 ++-- .../Controls/TextBox.xaml | 39 +++-- .../Strings/InvariantResources.xaml | 2 +- .../Controls/AutoCompleteBox.xaml | 5 +- .../Controls/CalendarDatePicker.xaml | 5 +- .../Controls/ManagedFileChooser.xaml | 4 +- .../Controls/NumericUpDown.xaml | 3 +- .../Controls/TextBox.xaml | 20 +-- .../AutoCompleteBoxTests.cs | 27 +++- .../CalendarDatePickerTests.cs | 17 ++- .../NumericUpDownTests.cs | 31 ++-- .../TextBoxTests.cs | 141 ++++++++++++------ .../Views/AttachedProps.xml | 4 +- .../Views/ControlWithoutWindow.xml | 4 +- .../Views/DataTemplates.xml | 8 +- .../Views/FieldModifier.xml | 16 +- .../Views/NamedControl.xml | 4 +- .../Views/NamedControls.xml | 8 +- .../Views/NoNamedControls.xml | 4 +- .../Views/SignUpView.xml | 12 +- .../Views/xNamedControl.xml | 4 +- .../Views/xNamedControls.xml | 8 +- .../Data/BindingTests.cs | 4 +- .../Controls/TextBoxTests.cs | 135 +++++++++++++++++ ...ceholder_With_Blue_Foreground.expected.png | Bin 0 -> 1645 bytes ...older_With_Default_Foreground.expected.png | Bin 0 -> 2059 bytes ...aceholder_With_Red_Foreground.expected.png | Bin 0 -> 1587 bytes 50 files changed, 844 insertions(+), 316 deletions(-) create mode 100644 tests/Avalonia.RenderTests/Controls/TextBoxTests.cs create mode 100644 tests/TestFiles/Skia/Controls/TextBox/Placeholder_With_Blue_Foreground.expected.png create mode 100644 tests/TestFiles/Skia/Controls/TextBox/Placeholder_With_Default_Foreground.expected.png create mode 100644 tests/TestFiles/Skia/Controls/TextBox/Placeholder_With_Red_Foreground.expected.png diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml index 9d68c8da8a..3ff80069f4 100644 --- a/samples/BindingDemo/MainWindow.xaml +++ b/samples/BindingDemo/MainWindow.xaml @@ -1,7 +1,7 @@ - + - - - - + + + + - + - + !BooleanString !!BooleanString @@ -43,24 +43,24 @@ - + - - - - + - + @@ -91,19 +91,19 @@ - + - + - - + + - + - - + + diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml index b682ebf51d..35e917f996 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml @@ -39,8 +39,12 @@ - - + + + + + + diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml index 7a22c0ddab..8734758d26 100644 --- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml +++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml @@ -5,7 +5,7 @@ x:Class="ControlCatalog.Pages.CalendarDatePickerPage"> A control for selecting dates with a calendar drop-down - + + PlaceholderText="Placeholder"/> - + PlaceholderText="Floating Placeholder" + UseFloatingPlaceholder="True"/> + + + + - + - + diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml b/samples/ControlCatalog/Pages/ClipboardPage.xaml index 80b3f4d1ed..864a520aca 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml +++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml @@ -1,4 +1,4 @@ - @@ -21,7 +21,7 @@ + PlaceholderText="Text to copy of file names per line" /> diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml b/samples/ControlCatalog/Pages/DialogsPage.xaml index 7320c1f3d7..f32cc601ab 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml @@ -58,17 +58,17 @@ - + - + - + Desktop @@ -80,17 +80,17 @@ - + - + + PlaceholderText="Picked file content" /> diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml index b01b4a93bc..a112f79c02 100644 --- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml +++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml @@ -1,4 +1,4 @@ - - Watermark: - + PlaceholderText: + Text: @@ -81,23 +81,23 @@ + PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"/> + PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"/> - + + PlaceholderText="Enter text" FormatString="{Binding SelectedFormat.Value}"> - + @@ -110,10 +110,18 @@ - + + + + - - + + + + - + - + - + + SelectionStart="5" SelectionEnd="22" + SelectionBrush="Green" SelectionForegroundBrush="Yellow" ClearSelectionOnLostFocus="False"/> @@ -54,11 +61,11 @@ - + + FontFamily="Comic Sans MS" + FontSize="10" + Foreground="Red"/> - diff --git a/samples/ControlCatalog/Pages/ThemePage.axaml b/samples/ControlCatalog/Pages/ThemePage.axaml index 2d948c44a0..7eb95471a0 100644 --- a/samples/ControlCatalog/Pages/ThemePage.axaml +++ b/samples/ControlCatalog/Pages/ThemePage.axaml @@ -1,4 +1,4 @@ - - - + + @@ -184,7 +184,7 @@ IsVisible="{Binding ShowFilters}" ItemsSource="{Binding Filters}" SelectedItem="{Binding SelectedFilter}" /> - + @@ -344,7 +344,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml index 22b6ef045c..b6c19484e9 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml @@ -1,21 +1,21 @@ - + - + Maximum="10" + Increment="0.5" + VerticalContentAlignment="Center" + HorizontalContentAlignment="Center" + ButtonSpinnerLocation="Left" + PlaceholderText="Enter text" /> Clear Reveal Password Password Revealed - - - Content + + + Content @@ -20,7 +20,7 @@ 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 - + @@ -99,6 +99,7 @@ + @@ -132,11 +133,12 @@ Grid.Column="1" Grid.ColumnSpan="1" Margin="{TemplateBinding Padding}"> - + - + - @@ -217,19 +218,12 @@ - - - - - + - @@ -203,7 +205,7 @@ diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index b4e0a055e3..26ece37acf 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -406,7 +406,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(control.Text, control.ItemSelector(input, selectedItem)); }); } - + [Fact] public void Text_Validation() { @@ -421,7 +421,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal([exception], DataValidationErrors.GetErrors(control)); }); } - + [Fact] public void Text_Validation_TextBox_Errors_Binding() { @@ -430,20 +430,20 @@ namespace Avalonia.Controls.UnitTests // simulate the TemplateBinding that would be used within the AutoCompleteBox control theme for the inner PART_TextBox // DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" textbox.Bind(DataValidationErrors.ErrorsProperty, control.GetBindingObservable(DataValidationErrors.ErrorsProperty)); - + var exception = new InvalidCastException("failed validation"); var textObservable = new BehaviorSubject(new BindingNotification(exception, BindingErrorType.DataValidationError)); control.Bind(AutoCompleteBox.TextProperty, textObservable); Dispatcher.UIThread.RunJobs(); - + Assert.True(DataValidationErrors.GetHasErrors(control)); Assert.Equal([exception], DataValidationErrors.GetErrors(control)); - + Assert.True(DataValidationErrors.GetHasErrors(textbox)); Assert.Equal([exception], DataValidationErrors.GetErrors(textbox)); }); } - + [Fact] public void SelectedItem_Validation() { @@ -583,7 +583,7 @@ namespace Avalonia.Controls.UnitTests } /// - /// Retrieves a defined predicate filter through a new AutoCompleteBox + /// Retrieves a defined predicate filter through a new AutoCompleteBox /// control instance. /// /// The FilterMode of interest. @@ -1283,5 +1283,18 @@ namespace Avalonia.Controls.UnitTests IsOpen = true; } } + + [Fact] + public void PlaceholderForeground_Can_Be_Set() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var control = CreateControl(); + control.PlaceholderText = "Search..."; + control.PlaceholderForeground = Media.Brushes.Green; + + Assert.Equal(Media.Brushes.Green, control.PlaceholderForeground); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs index 9124c317b8..d97a9729fb 100644 --- a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs @@ -117,7 +117,7 @@ namespace Avalonia.Controls.UnitTests { return new FuncControlTemplate((control, scope) => { - var textBox = + var textBox = new TextBox { Name = "PART_TextBox" @@ -130,7 +130,7 @@ namespace Avalonia.Controls.UnitTests var calendar = new Calendar { - Name = "PART_Calendar", + Name = "PART_Calendar", [!Calendar.SelectedDateProperty] = control[!CalendarDatePicker.SelectedDateProperty], [!Calendar.DisplayDateProperty] = control[!CalendarDatePicker.DisplayDateProperty], [!Calendar.DisplayDateStartProperty] = control[!CalendarDatePicker.DisplayDateStartProperty], @@ -179,5 +179,18 @@ namespace Avalonia.Controls.UnitTests }); } + [Fact] + public void PlaceholderForeground_Can_Be_Set() + { + using (UnitTestApplication.Start(Services)) + { + var control = CreateControl(); + control.PlaceholderText = "Select date"; + control.PlaceholderForeground = Media.Brushes.Purple; + + Assert.Equal(Media.Brushes.Purple, control.PlaceholderForeground); + } + } + } } diff --git a/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs b/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs index ab8766e216..868b33d7bd 100644 --- a/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs +++ b/tests/Avalonia.Controls.UnitTests/NumericUpDownTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -58,7 +58,7 @@ namespace Avalonia.Controls.UnitTests var spinner = GetSpinner(control); spinner.RaiseEvent(new SpinEventArgs(Spinner.SpinEvent, direction)); - + Assert.Equal(control.Value, expected); } @@ -105,16 +105,16 @@ namespace Avalonia.Controls.UnitTests // if min and max are not defined and value was null, 0 should be ne new value after spin yield return [decimal.MinValue, decimal.MaxValue, null, SpinDirection.Decrease, 0m]; yield return [decimal.MinValue, decimal.MaxValue, null, SpinDirection.Increase, 0m]; - + // if no value was defined, but Min or Max are defined, use these as the new value yield return [-400m, -200m, null, SpinDirection.Decrease, -200m]; yield return [200m, 400m, null, SpinDirection.Increase, 200m]; - + // Value should be clamped to Min / Max after spinning yield return [200m, 400m, 5m, SpinDirection.Increase, 200m]; yield return [200m, 400m, 200m, SpinDirection.Decrease, 200m]; } - + private void RunTest(Action test) { using (UnitTestApplication.Start(Services)) @@ -148,14 +148,14 @@ namespace Avalonia.Controls.UnitTests .OfType() .First(); } - + private static ButtonSpinner GetSpinner(NumericUpDown control) { return control.GetTemplateChildren() .OfType() .First(); } - + private static IControlTemplate CreateTemplate() { return new FuncControlTemplate((control, scope) => @@ -180,14 +180,27 @@ namespace Avalonia.Controls.UnitTests { // Set TabIndex on NumericUpDown control.TabIndex = 5; - + // The inner TextBox should inherit the same TabIndex Assert.Equal(5, textbox.TabIndex); - + // Change TabIndex and verify it gets synchronized control.TabIndex = 10; Assert.Equal(10, textbox.TabIndex); }); } + + [Fact] + public void PlaceholderForeground_Can_Be_Set() + { + using (UnitTestApplication.Start(Services)) + { + var control = CreateControl(); + control.PlaceholderText = "Enter value"; + control.PlaceholderForeground = Media.Brushes.Red; + + Assert.Equal(Media.Brushes.Red, control.PlaceholderForeground); + } + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 3e830859a4..31b8fe45e8 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -42,25 +42,25 @@ namespace Avalonia.Controls.UnitTests 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.SelectionStart = 0; target1.SelectionEnd = 3; - + target1.Focus(); Assert.False(target2.IsFocused); Assert.True(target1.IsFocused); target2.Focus(); - + Assert.Equal("123", target1.SelectedText); } } @@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests } } }; - + target1.ApplyTemplate(); @@ -165,16 +165,16 @@ namespace Avalonia.Controls.UnitTests }; target.ApplyTemplate(); - + target.Measure(Size.Infinity); - + target.CaretIndex = 3; RaiseKeyEvent(target, Key.Right, 0); Assert.Equal(4, target.CaretIndex); } } - + [Fact] public void Control_Backspace_Should_Set_Caret_Position_To_The_Start_Of_The_Deletion() { @@ -194,11 +194,11 @@ namespace Avalonia.Controls.UnitTests // (First Second |Third) RaiseKeyEvent(target, Key.Back, KeyModifiers.Control); // (First |Third) - + Assert.Equal(6, target.CaretIndex); } } - + [Fact] public void Control_Backspace_Should_Remove_The_Double_Whitespace_If_Caret_Index_Was_At_The_End_Of_A_Word() { @@ -213,7 +213,7 @@ namespace Avalonia.Controls.UnitTests }; target.ApplyTemplate(); - + // (First Second| Third) RaiseKeyEvent(target, Key.Back, KeyModifiers.Control); // (First| Third) @@ -236,11 +236,11 @@ namespace Avalonia.Controls.UnitTests }; target.ApplyTemplate(); - + // (First Second| Third) RaiseKeyEvent(target, Key.Back, KeyModifiers.Control); // (First| Third) - + target.Undo(); // (First Second| Third) @@ -258,7 +258,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "1234" }; - + target.ApplyTemplate(); RaiseKeyEvent(target, Key.A, KeyModifiers.Control); @@ -314,7 +314,7 @@ namespace Avalonia.Controls.UnitTests SelectionStart = 5, SelectionEnd = 5 }; - + textBox.ApplyTemplate(); // (First| Second Third Fourth) @@ -356,7 +356,7 @@ namespace Avalonia.Controls.UnitTests Text = "First Second Third Fourth", CaretIndex = 19, }; - + textBox.ApplyTemplate(); // (First Second Third |Fourth) @@ -400,7 +400,7 @@ namespace Avalonia.Controls.UnitTests textBox.SelectionStart = 2; textBox.SelectionEnd = 2; - + Assert.Equal(2, textBox.CaretIndex); } } @@ -443,7 +443,7 @@ namespace Avalonia.Controls.UnitTests AcceptsReturn = false, Text = "1234" }; - + target.ApplyTemplate(); RaiseKeyEvent(target, Key.Enter, 0); @@ -462,7 +462,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), AcceptsReturn = true }; - + target.ApplyTemplate(); RaiseKeyEvent(target, Key.Enter, 0); @@ -482,7 +482,7 @@ namespace Avalonia.Controls.UnitTests AcceptsReturn = true, NewLine = "Test" }; - + target.ApplyTemplate(); RaiseKeyEvent(target, Key.Enter, 0); @@ -523,7 +523,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "0123456789" }; - + target.ApplyTemplate(); target.SelectionStart = 0; @@ -547,7 +547,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "0123456789" }; - + target.ApplyTemplate(); target.SelectionStart = 8; @@ -592,7 +592,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "0123456789" }; - + target.ApplyTemplate(); Assert.True(target.SelectedText == ""); @@ -614,7 +614,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "0123" }; - + target.ApplyTemplate(); target.SelectedText = "AA"; @@ -679,7 +679,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(true); } } - + [Theory] [InlineData(Key.Up)] [InlineData(Key.Down)] @@ -725,7 +725,7 @@ namespace Avalonia.Controls.UnitTests target1.ApplyTemplate(); target2.ApplyTemplate(); - + var root = new TestRoot { Child = sp }; var gfcount = 0; @@ -746,7 +746,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, lfcount); } } - + [Fact] public void TextBox_CaretIndex_Persists_When_Focus_Lost() { @@ -768,7 +768,7 @@ namespace Avalonia.Controls.UnitTests target1.ApplyTemplate(); target2.ApplyTemplate(); - + var root = new TestRoot { Child = sp }; target2.Focus(); @@ -777,11 +777,11 @@ namespace Avalonia.Controls.UnitTests Assert.True(target2.IsFocused); target1.Focus(); - + Assert.Equal(2, target2.CaretIndex); } } - + [Fact] public void TextBox_Reveal_Password_Reset_When_Lost_Focus() { @@ -804,14 +804,14 @@ namespace Avalonia.Controls.UnitTests target1.ApplyTemplate(); target2.ApplyTemplate(); - + var root = new TestRoot { Child = sp }; target1.Focus(); target1.RevealPassword = true; - + target2.Focus(); - + Assert.False(target1.RevealPassword); } } @@ -833,7 +833,7 @@ namespace Avalonia.Controls.UnitTests Assert.Null(target.Text); } } - + [Theory] [InlineData("abc", "d", 3, 0, 0, false, "abc")] [InlineData("abc", "dd", 4, 3, 3, false, "abcd")] @@ -870,7 +870,7 @@ namespace Avalonia.Controls.UnitTests topLevel.LayoutManager.ExecuteInitialLayoutPass(); target.Measure(Size.Infinity); - + if (fromClipboard) { await topLevel.Clipboard!.SetTextAsync(textInput); @@ -882,7 +882,7 @@ namespace Avalonia.Controls.UnitTests { RaiseTextEvent(target, textInput); } - + Assert.Equal(expected, target.Text); } } @@ -1246,7 +1246,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal((minLines * target.LineHeight) + textPresenterMargin.Top + textPresenterMargin.Bottom, scrollViewer.MinHeight); } } - + [Theory] [InlineData(null, 1)] [InlineData("", 1)] @@ -1285,7 +1285,7 @@ namespace Avalonia.Controls.UnitTests var b = new TextBox(); Assert.Equal(-1, b.GetLineCount()); } - + [Fact] public void LineCount_Is_Correct_After_Text_Change() { @@ -1309,7 +1309,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); target.Measure(Size.Infinity); - + Assert.Equal(1, target.GetLineCount()); target.Text = "Hello\r\nWorld"; @@ -1505,7 +1505,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal("ABCDEF123", tb.Text); // Undo will take us back one step - tb.Undo(); + tb.Undo(); Assert.Equal("ABCDEF", tb.Text); // Undo again @@ -2050,7 +2050,7 @@ namespace Avalonia.Controls.UnitTests Assert.NotNull(client); Assert.Equal(string.Empty, client.SurroundingText); } - + [Fact] public void Backspace_Should_Delete_Last_Character_In_Line_And_Keep_Caret_On_Same_Line() { @@ -2122,7 +2122,7 @@ namespace Avalonia.Controls.UnitTests Assert.True(target1.IsFocused); - Assert.Equal("1234", target1.SelectedText); + Assert.Equal("1234", target1.SelectedText); target2.Focus(); @@ -2149,6 +2149,61 @@ namespace Avalonia.Controls.UnitTests Assert.Equal("FirstSecond", target.Text); } + [Fact] + public void PlaceholderForeground_Can_Be_Set() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + PlaceholderText = "Enter text", + PlaceholderForeground = Brushes.Red + }; + + target.ApplyTemplate(); + + Assert.Equal(Brushes.Red, target.PlaceholderForeground); + } + } + + [Fact] + public void PlaceholderForeground_Defaults_To_Null() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + PlaceholderText = "Enter text" + }; + + target.ApplyTemplate(); + + Assert.Null(target.PlaceholderForeground); + } + } + + [Fact] + public void PlaceholderForeground_Can_Be_Set_To_Null() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + PlaceholderText = "Enter text", + PlaceholderForeground = Brushes.Blue + }; + + target.ApplyTemplate(); + + target.PlaceholderForeground = null; + + Assert.Null(target.PlaceholderForeground); + } + } + private static TestServices FocusServices => TestServices.MockThreadingInterface.With( keyboardDevice: () => new KeyboardDevice(), keyboardNavigation: () => new KeyboardNavigationHandler(), @@ -2160,7 +2215,7 @@ namespace Avalonia.Controls.UnitTests private static TestServices Services => TestServices.MockThreadingInterface.With( standardCursorFactory: Mock.Of(), renderInterface: new HeadlessPlatformRenderInterface(), - textShaperImpl: new HarfBuzzTextShaper(), + textShaperImpl: new HarfBuzzTextShaper(), fontManagerImpl: new TestFontManager(), assetLoader: new StandardAssetLoader()); diff --git a/tests/Avalonia.Generators.Tests/Views/AttachedProps.xml b/tests/Avalonia.Generators.Tests/Views/AttachedProps.xml index 209b7ca9f1..ccd199040e 100644 --- a/tests/Avalonia.Generators.Tests/Views/AttachedProps.xml +++ b/tests/Avalonia.Generators.Tests/Views/AttachedProps.xml @@ -3,6 +3,6 @@ x:Class="Sample.App.AttachedProps" Design.Width="300"> + Placeholder="Username input" + UseFloatingPlaceholder="True" /> diff --git a/tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml b/tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml index 485fe93e4c..54e7f156e1 100644 --- a/tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml +++ b/tests/Avalonia.Generators.Tests/Views/ControlWithoutWindow.xml @@ -3,6 +3,6 @@ x:Class="Sample.App.ControlWithoutWindow" Design.Width="300"> + Placeholder="Username input" + UseFloatingPlaceholder="True" /> diff --git a/tests/Avalonia.Generators.Tests/Views/DataTemplates.xml b/tests/Avalonia.Generators.Tests/Views/DataTemplates.xml index f7e15644aa..c4d587baf2 100644 --- a/tests/Avalonia.Generators.Tests/Views/DataTemplates.xml +++ b/tests/Avalonia.Generators.Tests/Views/DataTemplates.xml @@ -3,14 +3,14 @@ x:Class="Sample.App.DataTemplates"> + Placeholder="Username input" + UseFloatingPlaceholder="True" /> + Placeholder="Templated input" + UseFloatingPlaceholder="True" /> diff --git a/tests/Avalonia.Generators.Tests/Views/FieldModifier.xml b/tests/Avalonia.Generators.Tests/Views/FieldModifier.xml index 3ee5e51466..65fafb414e 100644 --- a/tests/Avalonia.Generators.Tests/Views/FieldModifier.xml +++ b/tests/Avalonia.Generators.Tests/Views/FieldModifier.xml @@ -4,20 +4,20 @@ + Placeholder="Username input" + UseFloatingPlaceholder="True" /> + Placeholder="Username input" + UseFloatingPlaceholder="True" /> + Placeholder="Password input" + UseFloatingPlaceholder="True" /> + Placeholder="Password input" + UseFloatingPlaceholder="True" />