Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use [AppendTo|PrependTo|Replace]Mapping for Focus commands #15040

Merged
merged 9 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions eng/pipelines/common/device-tests-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/Controls/src/Core/Entry/Entry.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
12 changes: 4 additions & 8 deletions src/Controls/src/Core/Entry/Entry.Mapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,14 @@ public partial class Entry
[nameof(TextTransform)] = MapText,
};

static CommandMapper<IEntry, IEntryHandler> 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);
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
12 changes: 4 additions & 8 deletions src/Controls/src/Core/HandlerImpl/Editor/Editor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,14 @@ public partial class Editor
[nameof(TextTransform)] = MapText,
};

static CommandMapper<IEditor, IEditorHandler> 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);
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
12 changes: 4 additions & 8 deletions src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,14 @@ public partial class SearchBar
[nameof(TextTransform)] = MapText,
};

static CommandMapper<ISearchBar, ISearchBarHandler> 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);
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,11 @@ public partial class VisualElement
[nameof(IViewHandler.ContainerView)] = MapContainerView,
};

static CommandMapper<IView, IViewHandler> ControlsViewCommandMapper = new(ViewHandler.ViewCommandMapper)
{
[nameof(IView.Focus)] = MapFocus,
};

internal static void RemapForControls()
{
ViewHandler.ViewMapper = ControlsVisualElementMapper;
ViewHandler.ViewCommandMapper = ControlsViewCommandMapper;

ViewHandler.ViewCommandMapper.AppendToMapping<VisualElement, IViewHandler>(nameof(IView.Focus), MapFocus);
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
}

public static void MapBackgroundColor(IViewHandler handler, IView view)
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
}
}

public static void UpdateText(this UITextField textField, InputView inputView)
Expand All @@ -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);
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
}
}

public static void UpdateLineBreakMode(this UILabel platformLabel, Label label)
Expand Down
32 changes: 11 additions & 21 deletions src/Controls/src/Core/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
40 changes: 40 additions & 0 deletions src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,26 @@ await ValidatePropertyInitValue<int, EditorHandler>(
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<int, EditorHandler>(
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. " +
Expand Down Expand Up @@ -268,6 +288,26 @@ await ValidatePropertyInitValue<int, EditorHandler>(
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<int, EditorHandler>(
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. " +
Expand Down
40 changes: 40 additions & 0 deletions src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,26 @@ await ValidatePropertyInitValue<int, EntryHandler>(
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<int, EntryHandler>(
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. " +
Expand Down Expand Up @@ -290,6 +310,26 @@ await ValidatePropertyInitValue<int, EntryHandler>(
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<int, EntryHandler>(
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. " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
PureWeen marked this conversation as resolved.
Show resolved Hide resolved
{
if (args is FocusRequest request)
handler.QueryEditor?.Focus(request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,9 @@ public class ButtonTextStyleTests : TextStyleHandlerTests<ButtonHandler, ButtonS
{
}

// TODO: only windows button focus tests are working
#if WINDOWS
// TODO: buttons are not focusable on Android without FocusableInTouchMode=true and iOS is having issues
// https://github.com/dotnet/maui/issues/6482
[Category(TestCategory.Button)]
public class ButtonFocusTests : FocusHandlerTests<ButtonHandler, ButtonStub, VerticalStackLayoutStub>
{
Expand Down
12 changes: 12 additions & 0 deletions src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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: <img>{bitmap.ToBase64String()}</img> and <img>{other.ToBase64String()}</img>";

public static string CreateScreenshotError(this Bitmap bitmap, string message) =>
$"{message} This is what it looked like:<img>{bitmap.ToBase64String()}</img>";

public static AColor ColorAtPoint(this Bitmap bitmap, int x, int y, bool includeAlpha = false)
{
int pixel = bitmap.GetPixel(x, y);
Expand Down Expand Up @@ -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
{
Expand Down
Loading