From c9c218f0237a622530f3fb0a11c31ff491bc8a9c Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Fri, 12 May 2023 02:39:24 +0800 Subject: [PATCH 1/6] Use AppendToMapping for Focus commands --- eng/pipelines/common/device-tests-steps.yml | 4 +- src/Controls/src/Core/Entry/Entry.Android.cs | 1 - src/Controls/src/Core/Entry/Entry.Mapper.cs | 12 ++---- .../Core/HandlerImpl/Editor/Editor.Android.cs | 1 - .../src/Core/HandlerImpl/Editor/Editor.cs | 12 ++---- .../SearchBar/SearchBar.Android.cs | 1 - .../Core/HandlerImpl/SearchBar/SearchBar.cs | 12 ++---- .../VisualElement/VisualElement.cs | 12 ++---- .../Platform/iOS/Extensions/TextExtensions.cs | 8 +++- src/Controls/src/Core/ViewExtensions.cs | 32 +++++---------- .../Elements/Editor/EditorTests.cs | 40 +++++++++++++++++++ .../DeviceTests/Elements/Entry/EntryTests.cs | 40 +++++++++++++++++++ .../SearchBar/SearchBarHandler.Android.cs | 2 +- .../net-android/PublicAPI.Unshipped.txt | 1 + .../Handlers/Button/ButtonHandlerTests.cs | 3 +- .../AssertionExtensions.Android.cs | 12 ++++++ .../AssertionExtensions.Windows.cs | 24 +++++------ .../DeviceTests/AssertionExtensions.iOS.cs | 12 ++++++ 18 files changed, 155 insertions(+), 74 deletions(-) diff --git a/eng/pipelines/common/device-tests-steps.yml b/eng/pipelines/common/device-tests-steps.yml index 4f05da05138e..b6ef7d5e8dcf 100644 --- a/eng/pipelines/common/device-tests-steps.yml +++ b/eng/pipelines/common/device-tests-steps.yml @@ -62,13 +62,13 @@ steps: inputs: testResultsFormat: xUnit testResultsFiles: '$(TestResultsDirectory)/**/TestResults.xml' - testRunTitle: '$(System.PhaseName)' + testRunTitle: '$(System.PhaseName) (attempt: $(System.JobAttempt))' - task: PublishBuildArtifacts@1 displayName: Publish Artifacts condition: always() inputs: - artifactName: $(System.PhaseName) + artifactName: $(System.PhaseName)_attempt_$(System.JobAttempt) # This must always be placed as the last step in the job - template: agent-rebooter/mac.v1.yml@yaml-templates diff --git a/src/Controls/src/Core/Entry/Entry.Android.cs b/src/Controls/src/Core/Entry/Entry.Android.cs index 7c670e609020..267969957b3b 100644 --- a/src/Controls/src/Core/Entry/Entry.Android.cs +++ b/src/Controls/src/Core/Entry/Entry.Android.cs @@ -35,7 +35,6 @@ public static void MapText(IEntryHandler handler, Entry entry) static void MapFocus(IViewHandler handler, IView view, object args) { handler.ShowKeyboardIfFocused(view); - EntryHandler.CommandMapper.Chained?.Invoke(handler, view, nameof(IView.Focus), args); } } } diff --git a/src/Controls/src/Core/Entry/Entry.Mapper.cs b/src/Controls/src/Core/Entry/Entry.Mapper.cs index 5f5328ff8fdb..c6e9fcd66d91 100644 --- a/src/Controls/src/Core/Entry/Entry.Mapper.cs +++ b/src/Controls/src/Core/Entry/Entry.Mapper.cs @@ -18,18 +18,14 @@ public partial class Entry [nameof(TextTransform)] = MapText, }; - static CommandMapper ControlsCommandMapper = new(EntryHandler.CommandMapper) - { -#if ANDROID - [nameof(IEntry.Focus)] = MapFocus -#endif - }; - internal static new void RemapForControls() { // Adjust the mappings to preserve Controls.Entry legacy behaviors EntryHandler.Mapper = ControlsEntryMapper; - EntryHandler.CommandMapper = ControlsCommandMapper; + +#if ANDROID + EntryHandler.CommandMapper.AppendToMapping(nameof(IEntry.Focus), MapFocus); +#endif } } } diff --git a/src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs b/src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs index aee3ae678a95..d67d92fd03d0 100644 --- a/src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs +++ b/src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs @@ -24,7 +24,6 @@ public static void MapText(IEditorHandler handler, Editor editor) static void MapFocus(IViewHandler handler, IView view, object args) { handler.ShowKeyboardIfFocused(view); - EditorHandler.CommandMapper.Chained?.Invoke(handler, view, nameof(IView.Focus), args); } } } diff --git a/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs b/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs index 221cc31be702..688c6f800e52 100644 --- a/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs +++ b/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs @@ -13,18 +13,14 @@ public partial class Editor [nameof(TextTransform)] = MapText, }; - static CommandMapper ControlsCommandMapper = new(EditorHandler.CommandMapper) - { -#if ANDROID - [nameof(IEditor.Focus)] = MapFocus -#endif - }; - internal static new void RemapForControls() { // Adjust the mappings to preserve Controls.Editor legacy behaviors EditorHandler.Mapper = ControlsEditorMapper; - EditorHandler.CommandMapper = ControlsCommandMapper; + +#if ANDROID + EditorHandler.CommandMapper.AppendToMapping(nameof(IEditor.Focus), MapFocus); +#endif } } } \ No newline at end of file diff --git a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.Android.cs b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.Android.cs index e9b47703f741..f83afc734b9b 100644 --- a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.Android.cs +++ b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.Android.cs @@ -16,7 +16,6 @@ public static void MapText(ISearchBarHandler handler, SearchBar searchBar) static void MapFocus(IViewHandler handler, IView view, object args) { handler.ShowKeyboardIfFocused(view); - SearchBarHandler.CommandMapper.Chained?.Invoke(handler, view, nameof(IView.Focus), args); } } } diff --git a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs index 56d6ba28a0eb..9c2555c489af 100644 --- a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs +++ b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs @@ -15,18 +15,14 @@ public partial class SearchBar [nameof(TextTransform)] = MapText, }; - static CommandMapper ControlsCommandMapper = new(SearchBarHandler.CommandMapper) - { -#if ANDROID - [nameof(ISearchBar.Focus)] = MapFocus -#endif - }; - internal static new void RemapForControls() { // Adjust the mappings to preserve Controls.SearchBar legacy behaviors SearchBarHandler.Mapper = ControlsSearchBarMapper; - SearchBarHandler.CommandMapper = ControlsCommandMapper; + +#if ANDROID + SearchBarHandler.CommandMapper.AppendToMapping(nameof(ISearchBar.Focus), MapFocus); +#endif } } } diff --git a/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs b/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs index b8533ca4c86c..82cb1be6e4a6 100644 --- a/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs +++ b/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs @@ -24,15 +24,11 @@ public partial class VisualElement [nameof(IViewHandler.ContainerView)] = MapContainerView, }; - static CommandMapper ControlsViewCommandMapper = new(ViewHandler.ViewCommandMapper) - { - [nameof(IView.Focus)] = MapFocus, - }; - internal static void RemapForControls() { ViewHandler.ViewMapper = ControlsVisualElementMapper; - ViewHandler.ViewCommandMapper = ControlsViewCommandMapper; + + ViewHandler.ViewCommandMapper.AppendToMapping(nameof(IView.Focus), MapFocus); } public static void MapBackgroundColor(IViewHandler handler, IView view) @@ -72,10 +68,10 @@ static void MapContainerView(IViewHandler arg1, IView arg2) static void MapFocus(IViewHandler handler, IView view, object args) { - if (args is not FocusRequest fr) + if (args is not FocusRequest fr || view is not VisualElement element) return; - view.MapFocus(handler, fr); + element.MapFocus(fr); } } } diff --git a/src/Controls/src/Core/Platform/iOS/Extensions/TextExtensions.cs b/src/Controls/src/Core/Platform/iOS/Extensions/TextExtensions.cs index 5a38647b190e..c0139e8ce529 100644 --- a/src/Controls/src/Core/Platform/iOS/Extensions/TextExtensions.cs +++ b/src/Controls/src/Core/Platform/iOS/Extensions/TextExtensions.cs @@ -45,9 +45,11 @@ public static void UpdateText(this UITextView textView, InputView inputView) var cursorPosition = textView.IsFirstResponder ? textView.GetCursorPosition(cursorOffset) : newText.Length; if (oldText != newText) + { textView.Text = newText; - textView.SetTextRange(cursorPosition, 0); + textView.SetTextRange(cursorPosition, 0); + } } public static void UpdateText(this UITextField textField, InputView inputView) @@ -67,9 +69,11 @@ public static void UpdateText(this UITextField textField, InputView inputView) var cursorPosition = textField.IsEditing ? textField.GetCursorPosition(cursorOffset) : newText.Length; if (oldText != newText) + { textField.Text = newText; - textField.SetTextRange(cursorPosition, 0); + textField.SetTextRange(cursorPosition, 0); + } } public static void UpdateLineBreakMode(this UILabel platformLabel, Label label) diff --git a/src/Controls/src/Core/ViewExtensions.cs b/src/Controls/src/Core/ViewExtensions.cs index c5c0cc9e5439..d9cd7eac55c5 100644 --- a/src/Controls/src/Core/ViewExtensions.cs +++ b/src/Controls/src/Core/ViewExtensions.cs @@ -452,35 +452,25 @@ static internal bool RequestFocus(this VisualElement view) // if there is no handler, we need to still run some code var focusRequest = new FocusRequest(); - MapFocus(view, null, focusRequest); + MapFocus(view, focusRequest); return focusRequest.Result; } - static internal void MapFocus(this IView view, IViewHandler? handler, FocusRequest focusRequest) + static internal void MapFocus(this VisualElement view, FocusRequest focusRequest) { - if (view is VisualElement ve) + // the virtual view is already focused + if (view.IsFocused) { - // the virtual view is already focused - if (ve.IsFocused) - { - focusRequest.TrySetResult(true); - return; - } - - // if there are legacy events, then use that - if (ve.HasFocusChangeRequestedEvent) - { - var arg = new VisualElement.FocusRequestArgs { Focus = true }; - ve.InvokeFocusChangeRequested(arg); - focusRequest.TrySetResult(arg.Result); - return; - } + focusRequest.TrySetResult(true); + return; } - // otherwise, fall back to "base" - if (handler is not null) + // if there are legacy events, then use that + if (view.HasFocusChangeRequestedEvent) { - ViewHandler.MapFocus(handler, view, focusRequest); + var arg = new VisualElement.FocusRequestArgs { Focus = true }; + view.InvokeFocusChangeRequested(arg); + focusRequest.TrySetResult(arg.Result); return; } diff --git a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs index 87e1943a286b..d303c2b94805 100644 --- a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs @@ -171,6 +171,26 @@ await ValidatePropertyInitValue( 0); } + [Theory(DisplayName = "Unset CursorPosition is kept at zero at initialization with TextTransform")] + [InlineData("This is a test!!!")] + [InlineData("a")] + [InlineData("")] + [InlineData(" ")] + public async Task UnsetCursorPositionKeepsToZeroOnInitializationWithTextTransform(string text) + { + var editor = new Editor + { + Text = text, + TextTransform = TextTransform.Uppercase + }; + + await ValidatePropertyInitValue( + editor, + () => editor.CursorPosition, + GetPlatformCursorPosition, + 0); + } + [Theory(DisplayName = "CursorPosition moves to the end on text change after initialization" #if WINDOWS , Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " + @@ -268,6 +288,26 @@ await ValidatePropertyInitValue( 0); } + [Theory(DisplayName = "Unset SelectionLength is kept at zero at initialization with TextTransform")] + [InlineData("This is a test!!!")] + [InlineData("a")] + [InlineData("")] + [InlineData(" ")] + public async Task UnsetSelectionLengthKeepsToZeroOnInitializationWithTextTransform(string text) + { + var editor = new Editor + { + Text = text, + TextTransform = TextTransform.Uppercase + }; + + await ValidatePropertyInitValue( + editor, + () => editor.SelectionLength, + GetPlatformSelectionLength, + 0); + } + [Theory(DisplayName = "SelectionLength is kept at zero on text change after initialization" #if WINDOWS , Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " + diff --git a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs index c9bad6489962..030a8bd56cd0 100644 --- a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs @@ -193,6 +193,26 @@ await ValidatePropertyInitValue( 0); } + [Theory(DisplayName = "Unset CursorPosition is kept at zero at initialization with TextTransform")] + [InlineData("This is a test!!!")] + [InlineData("a")] + [InlineData("")] + [InlineData(" ")] + public async Task UnsetCursorPositionIsKeptAtZeroAtInitializationWithTextTransform(string text) + { + var entry = new Entry + { + Text = text, + TextTransform = TextTransform.Uppercase + }; + + await ValidatePropertyInitValue( + entry, + () => entry.CursorPosition, + GetPlatformCursorPosition, + 0); + } + [Theory(DisplayName = "CursorPosition moves to the end on text change by code after initialization" #if WINDOWS , Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " + @@ -290,6 +310,26 @@ await ValidatePropertyInitValue( 0); } + [Theory(DisplayName = "Unset SelectionLength is kept at zero at initialization with TextTransform")] + [InlineData("This is a test!!!")] + [InlineData("a")] + [InlineData("")] + [InlineData(" ")] + public async Task UnsetSelectionLengthIsKeptAtZeroAtInitializationWithTextTransform(string text) + { + var entry = new Entry + { + Text = text, + TextTransform = TextTransform.Uppercase + }; + + await ValidatePropertyInitValue( + entry, + () => entry.SelectionLength, + GetPlatformSelectionLength, + 0); + } + [Theory(DisplayName = "SelectionLength is kept at zero on text change by code after initialization" #if WINDOWS , Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " + diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs index b6637ebbe66e..48f982b050f4 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs @@ -119,7 +119,7 @@ public static void MapKeyboard(ISearchBarHandler handler, ISearchBar searchBar) handler.PlatformView?.UpdateKeyboard(searchBar); } - static void MapFocus(ISearchBarHandler handler, ISearchBar searchBar, object? args) + public static void MapFocus(ISearchBarHandler handler, ISearchBar searchBar, object? args) { if (args is FocusRequest request) handler.QueryEditor?.Focus(request); diff --git a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt index db9619a849ba..4528ab650bd9 100644 --- a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -56,6 +56,7 @@ static Microsoft.Maui.FontSize.operator ==(Microsoft.Maui.FontSize left, Microso static Microsoft.Maui.Graphics.PaintExtensions.ToDrawable(this Microsoft.Maui.Graphics.Paint? paint, Android.Content.Context? context) -> Android.Graphics.Drawables.Drawable? static Microsoft.Maui.GridLength.operator !=(Microsoft.Maui.GridLength left, Microsoft.Maui.GridLength right) -> bool static Microsoft.Maui.GridLength.operator ==(Microsoft.Maui.GridLength left, Microsoft.Maui.GridLength right) -> bool +static Microsoft.Maui.Handlers.SearchBarHandler.MapFocus(Microsoft.Maui.Handlers.ISearchBarHandler! handler, Microsoft.Maui.ISearchBar! searchBar, object? args) -> void static Microsoft.Maui.Handlers.StepperHandler.MapIsEnabled(Microsoft.Maui.Handlers.IStepperHandler! handler, Microsoft.Maui.IStepper! stepper) -> void static Microsoft.Maui.Layouts.FlexBasis.operator !=(Microsoft.Maui.Layouts.FlexBasis left, Microsoft.Maui.Layouts.FlexBasis right) -> bool static Microsoft.Maui.Layouts.FlexBasis.operator ==(Microsoft.Maui.Layouts.FlexBasis left, Microsoft.Maui.Layouts.FlexBasis right) -> bool diff --git a/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs index 86b10a5c3011..5255ad411038 100644 --- a/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Button/ButtonHandlerTests.cs @@ -180,8 +180,9 @@ public class ButtonTextStyleTests : TextStyleHandlerTests { diff --git a/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs b/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs index 1a20a26eb930..515d878f95c7 100644 --- a/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs +++ b/src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs @@ -284,6 +284,9 @@ public static string CreateColorError(this Bitmap bitmap, string message) => public static string CreateEqualError(this Bitmap bitmap, Bitmap other, string message) => $"{message} This is what it looked like: {bitmap.ToBase64String()} and {other.ToBase64String()}"; + public static string CreateScreenshotError(this Bitmap bitmap, string message) => + $"{message} This is what it looked like:{bitmap.ToBase64String()}"; + public static AColor ColorAtPoint(this Bitmap bitmap, int x, int y, bool includeAlpha = false) { int pixel = bitmap.GetPixel(x, y); @@ -594,6 +597,15 @@ public static Task AssertNotEqualAsync(this Bitmap bitmap, Bitmap other) return Task.CompletedTask; } + public static async Task ThrowScreenshot(this AView view, IMauiContext mauiContext, string? message = null, Exception? ex = null) + { + var bitmap = await view.ToBitmap(mauiContext); + if (ex is null) + throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error.")); + else + throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error: " + ex.Message), ex); + } + public static TextUtils.TruncateAt? ToPlatform(this LineBreakMode mode) => mode switch { diff --git a/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs b/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs index 5a93d55328f4..ebcb6b9fc2fc 100644 --- a/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs +++ b/src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs @@ -44,16 +44,6 @@ public static Task SendKeyboardReturnType(this FrameworkElement view, ReturnType public static async Task WaitForFocused(this FrameworkElement view, int timeout = 1000) { - if (view is AutoSuggestBox searchView) - { - var queryEditor = searchView.GetFirstDescendant(); - - if (queryEditor is null) - throw new Exception("Unable to locate TextBox on AutoSuggestBox"); - - view = queryEditor; - } - TaskCompletionSource focusSource = new TaskCompletionSource(); view.GotFocus += OnFocused; @@ -87,8 +77,6 @@ public static async Task WaitForUnFocused(this FrameworkElement view, int timeou view.LostFocus -= OnUnFocused; } - await focusSource.Task.WaitAsync(TimeSpan.FromMilliseconds(timeout)); - void OnUnFocused(object? sender, RoutedEventArgs e) { view.LostFocus -= OnUnFocused; @@ -120,6 +108,9 @@ public static async Task CreateColorError(this CanvasBitmap bitmap, stri public static async Task CreateEqualError(this CanvasBitmap bitmap, CanvasBitmap other, string message) => $"{message} This is what it looked like: {await bitmap.ToBase64StringAsync()} and {await other.ToBase64StringAsync()}"; + public static async Task CreateScreenshotError(this CanvasBitmap bitmap, string message) => + $"{message} This is what it looked like:{await bitmap.ToBase64StringAsync()}"; + public static async Task ToBase64StringAsync(this CanvasBitmap bitmap) { using var ms = new InMemoryRandomAccessStream(); @@ -455,6 +446,15 @@ bool IsMatching() } } + public static async Task ThrowScreenshot(this FrameworkElement view, IMauiContext mauiContext, string? message = null, Exception? ex = null) + { + var bitmap = await view.ToBitmap(mauiContext); + if (ex is null) + throw new XunitException(await CreateScreenshotError(bitmap, message ?? "There was an error.")); + else + throw new XunitException(await CreateScreenshotError(bitmap, message ?? "There was an error: " + ex.Message), ex); + } + public static TextTrimming ToPlatform(this LineBreakMode mode) => mode switch { diff --git a/src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs b/src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs index 9acc9c957444..dc64fe9a6aae 100644 --- a/src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs +++ b/src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs @@ -82,6 +82,9 @@ public static string CreateColorError(this UIImage bitmap, string message) => public static string CreateEqualError(this UIImage bitmap, UIImage other, string message) => $"{message} This is what it looked like: {bitmap.ToBase64String()} and {other.ToBase64String()}"; + public static string CreateScreenshotError(this UIImage bitmap, string message) => + $"{message} This is what it looked like:{bitmap.ToBase64String()}"; + public static string ToBase64String(this UIImage bitmap) { var data = bitmap.AsPNG(); @@ -415,6 +418,15 @@ bool IsMatching() } } + public static async Task ThrowScreenshot(this UIView view, IMauiContext mauiContext, string? message = null, Exception? ex = null) + { + var bitmap = await view.ToBitmap(mauiContext); + if (ex is null) + throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error.")); + else + throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error: " + ex.Message), ex); + } + public static UILineBreakMode ToPlatform(this LineBreakMode mode) => mode switch { From 8c4c9c681230819caa219c36e02acb9c1ca828d6 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 16 May 2023 12:16:13 +0800 Subject: [PATCH 2/6] undo this --- .../src/Core/Platform/iOS/Extensions/TextExtensions.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Controls/src/Core/Platform/iOS/Extensions/TextExtensions.cs b/src/Controls/src/Core/Platform/iOS/Extensions/TextExtensions.cs index c0139e8ce529..5a38647b190e 100644 --- a/src/Controls/src/Core/Platform/iOS/Extensions/TextExtensions.cs +++ b/src/Controls/src/Core/Platform/iOS/Extensions/TextExtensions.cs @@ -45,11 +45,9 @@ public static void UpdateText(this UITextView textView, InputView inputView) var cursorPosition = textView.IsFirstResponder ? textView.GetCursorPosition(cursorOffset) : newText.Length; if (oldText != newText) - { textView.Text = newText; - textView.SetTextRange(cursorPosition, 0); - } + textView.SetTextRange(cursorPosition, 0); } public static void UpdateText(this UITextField textField, InputView inputView) @@ -69,11 +67,9 @@ public static void UpdateText(this UITextField textField, InputView inputView) var cursorPosition = textField.IsEditing ? textField.GetCursorPosition(cursorOffset) : newText.Length; if (oldText != newText) - { textField.Text = newText; - textField.SetTextRange(cursorPosition, 0); - } + textField.SetTextRange(cursorPosition, 0); } public static void UpdateLineBreakMode(this UILabel platformLabel, Label label) From 345fb0498233ddf6b3064d118aa096eccb71532f Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 16 May 2023 12:17:52 +0800 Subject: [PATCH 3/6] and this --- .../Elements/Editor/EditorTests.cs | 40 ------------------- .../DeviceTests/Elements/Entry/EntryTests.cs | 40 ------------------- 2 files changed, 80 deletions(-) diff --git a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs index d303c2b94805..87e1943a286b 100644 --- a/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs @@ -171,26 +171,6 @@ await ValidatePropertyInitValue( 0); } - [Theory(DisplayName = "Unset CursorPosition is kept at zero at initialization with TextTransform")] - [InlineData("This is a test!!!")] - [InlineData("a")] - [InlineData("")] - [InlineData(" ")] - public async Task UnsetCursorPositionKeepsToZeroOnInitializationWithTextTransform(string text) - { - var editor = new Editor - { - Text = text, - TextTransform = TextTransform.Uppercase - }; - - await ValidatePropertyInitValue( - editor, - () => editor.CursorPosition, - GetPlatformCursorPosition, - 0); - } - [Theory(DisplayName = "CursorPosition moves to the end on text change after initialization" #if WINDOWS , Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " + @@ -288,26 +268,6 @@ await ValidatePropertyInitValue( 0); } - [Theory(DisplayName = "Unset SelectionLength is kept at zero at initialization with TextTransform")] - [InlineData("This is a test!!!")] - [InlineData("a")] - [InlineData("")] - [InlineData(" ")] - public async Task UnsetSelectionLengthKeepsToZeroOnInitializationWithTextTransform(string text) - { - var editor = new Editor - { - Text = text, - TextTransform = TextTransform.Uppercase - }; - - await ValidatePropertyInitValue( - editor, - () => editor.SelectionLength, - GetPlatformSelectionLength, - 0); - } - [Theory(DisplayName = "SelectionLength is kept at zero on text change after initialization" #if WINDOWS , Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " + diff --git a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs index 030a8bd56cd0..c9bad6489962 100644 --- a/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs +++ b/src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs @@ -193,26 +193,6 @@ await ValidatePropertyInitValue( 0); } - [Theory(DisplayName = "Unset CursorPosition is kept at zero at initialization with TextTransform")] - [InlineData("This is a test!!!")] - [InlineData("a")] - [InlineData("")] - [InlineData(" ")] - public async Task UnsetCursorPositionIsKeptAtZeroAtInitializationWithTextTransform(string text) - { - var entry = new Entry - { - Text = text, - TextTransform = TextTransform.Uppercase - }; - - await ValidatePropertyInitValue( - entry, - () => entry.CursorPosition, - GetPlatformCursorPosition, - 0); - } - [Theory(DisplayName = "CursorPosition moves to the end on text change by code after initialization" #if WINDOWS , Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " + @@ -310,26 +290,6 @@ await ValidatePropertyInitValue( 0); } - [Theory(DisplayName = "Unset SelectionLength is kept at zero at initialization with TextTransform")] - [InlineData("This is a test!!!")] - [InlineData("a")] - [InlineData("")] - [InlineData(" ")] - public async Task UnsetSelectionLengthIsKeptAtZeroAtInitializationWithTextTransform(string text) - { - var entry = new Entry - { - Text = text, - TextTransform = TextTransform.Uppercase - }; - - await ValidatePropertyInitValue( - entry, - () => entry.SelectionLength, - GetPlatformSelectionLength, - 0); - } - [Theory(DisplayName = "SelectionLength is kept at zero on text change by code after initialization" #if WINDOWS , Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " + From d8544d00e81836c6c0a8082b484c7524821a2d0d Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 24 May 2023 23:50:10 +0800 Subject: [PATCH 4/6] different --- src/Controls/src/Core/Entry/Entry.Mapper.cs | 2 +- src/Controls/src/Core/HandlerImpl/Editor/Editor.cs | 2 +- src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs | 2 +- .../src/Core/HandlerImpl/VisualElement/VisualElement.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Controls/src/Core/Entry/Entry.Mapper.cs b/src/Controls/src/Core/Entry/Entry.Mapper.cs index c6e9fcd66d91..6d6d2224257c 100644 --- a/src/Controls/src/Core/Entry/Entry.Mapper.cs +++ b/src/Controls/src/Core/Entry/Entry.Mapper.cs @@ -24,7 +24,7 @@ public partial class Entry EntryHandler.Mapper = ControlsEntryMapper; #if ANDROID - EntryHandler.CommandMapper.AppendToMapping(nameof(IEntry.Focus), MapFocus); + EntryHandler.CommandMapper.PrependToMapping(nameof(IEntry.Focus), MapFocus); #endif } } diff --git a/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs b/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs index 688c6f800e52..2eb66320065c 100644 --- a/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs +++ b/src/Controls/src/Core/HandlerImpl/Editor/Editor.cs @@ -19,7 +19,7 @@ public partial class Editor EditorHandler.Mapper = ControlsEditorMapper; #if ANDROID - EditorHandler.CommandMapper.AppendToMapping(nameof(IEditor.Focus), MapFocus); + EditorHandler.CommandMapper.PrependToMapping(nameof(IEditor.Focus), MapFocus); #endif } } diff --git a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs index 9c2555c489af..d68c8e451a52 100644 --- a/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs +++ b/src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs @@ -21,7 +21,7 @@ public partial class SearchBar SearchBarHandler.Mapper = ControlsSearchBarMapper; #if ANDROID - SearchBarHandler.CommandMapper.AppendToMapping(nameof(ISearchBar.Focus), MapFocus); + SearchBarHandler.CommandMapper.PrependToMapping(nameof(ISearchBar.Focus), MapFocus); #endif } } diff --git a/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs b/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs index 82cb1be6e4a6..dbb8a0314fc5 100644 --- a/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs +++ b/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs @@ -28,7 +28,7 @@ internal static void RemapForControls() { ViewHandler.ViewMapper = ControlsVisualElementMapper; - ViewHandler.ViewCommandMapper.AppendToMapping(nameof(IView.Focus), MapFocus); + ViewHandler.ViewCommandMapper.ReplaceMapping(nameof(IView.Focus), MapFocus); } public static void MapBackgroundColor(IViewHandler handler, IView view) From 5245475bf71823d5e0cde7decad0a70ab93965c9 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Sat, 27 May 2023 00:40:02 +0800 Subject: [PATCH 5/6] Fix focus methods --- .../src/Core/HandlerImpl/VisualElement/VisualElement.cs | 8 ++++---- src/Controls/src/Core/ViewExtensions.cs | 7 +++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs b/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs index dbb8a0314fc5..fa98561e72d0 100644 --- a/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs +++ b/src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs @@ -28,7 +28,7 @@ internal static void RemapForControls() { ViewHandler.ViewMapper = ControlsVisualElementMapper; - ViewHandler.ViewCommandMapper.ReplaceMapping(nameof(IView.Focus), MapFocus); + ViewHandler.ViewCommandMapper.ModifyMapping(nameof(IView.Focus), MapFocus); } public static void MapBackgroundColor(IViewHandler handler, IView view) @@ -66,12 +66,12 @@ static void MapContainerView(IViewHandler arg1, IView arg2) } } - static void MapFocus(IViewHandler handler, IView view, object args) + static void MapFocus(IViewHandler handler, VisualElement view, object args, Action baseMethod) { - if (args is not FocusRequest fr || view is not VisualElement element) + if (args is not FocusRequest fr) return; - element.MapFocus(fr); + view.MapFocus(fr, baseMethod is null ? null : () => baseMethod?.Invoke(handler, view, args)); } } } diff --git a/src/Controls/src/Core/ViewExtensions.cs b/src/Controls/src/Core/ViewExtensions.cs index d9cd7eac55c5..f4f312b71827 100644 --- a/src/Controls/src/Core/ViewExtensions.cs +++ b/src/Controls/src/Core/ViewExtensions.cs @@ -452,11 +452,11 @@ static internal bool RequestFocus(this VisualElement view) // if there is no handler, we need to still run some code var focusRequest = new FocusRequest(); - MapFocus(view, focusRequest); + view.MapFocus(focusRequest); return focusRequest.Result; } - static internal void MapFocus(this VisualElement view, FocusRequest focusRequest) + static internal void MapFocus(this VisualElement view, FocusRequest focusRequest, Action? baseMethod = null) { // the virtual view is already focused if (view.IsFocused) @@ -474,6 +474,9 @@ static internal void MapFocus(this VisualElement view, FocusRequest focusRequest return; } + // otherwise, fall back to "base" + baseMethod?.Invoke(); + // if there was nothing that handles this, then nothing changed focusRequest.TrySetResult(false); } From c6ad0110309d67abc4b16a2c90a72af4160925ac Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Sat, 27 May 2023 00:42:44 +0800 Subject: [PATCH 6/6] this --- src/Controls/src/Core/ViewExtensions.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Controls/src/Core/ViewExtensions.cs b/src/Controls/src/Core/ViewExtensions.cs index f4f312b71827..18b75f78bc48 100644 --- a/src/Controls/src/Core/ViewExtensions.cs +++ b/src/Controls/src/Core/ViewExtensions.cs @@ -475,7 +475,11 @@ static internal void MapFocus(this VisualElement view, FocusRequest focusRequest } // otherwise, fall back to "base" - baseMethod?.Invoke(); + if (baseMethod is not null) + { + baseMethod.Invoke(); + return; + } // if there was nothing that handles this, then nothing changed focusRequest.TrySetResult(false);