Skip to content

Commit

Permalink
Use [AppendTo|PrependTo|Replace]Mapping for Focus commands (#15040)
Browse files Browse the repository at this point in the history
* Use AppendToMapping for Focus commands

* undo this

* and this

* different

* Fix focus methods

* this
  • Loading branch information
mattleibow authored and rmarinho committed May 30, 2023
1 parent 576a5cd commit 2e25b85
Show file tree
Hide file tree
Showing 14 changed files with 72 additions and 68 deletions.
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.PrependToMapping(nameof(IEntry.Focus), MapFocus);
#endif
}
}
}
1 change: 0 additions & 1 deletion src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs
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.PrependToMapping(nameof(IEditor.Focus), MapFocus);
#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.PrependToMapping(nameof(ISearchBar.Focus), MapFocus);
#endif
}
}
}
12 changes: 4 additions & 8 deletions src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs
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.ModifyMapping<VisualElement, IViewHandler>(nameof(IView.Focus), MapFocus);
}

public static void MapBackgroundColor(IViewHandler handler, IView view)
Expand Down Expand Up @@ -70,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<IElementHandler, IElement, object> baseMethod)
{
if (args is not FocusRequest fr)
return;

view.MapFocus(handler, fr);
view.MapFocus(fr, baseMethod is null ? null : () => baseMethod?.Invoke(handler, view, args));
}
}
}
35 changes: 16 additions & 19 deletions src/Controls/src/Core/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,35 +452,32 @@ 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);
view.MapFocus(focusRequest);
return focusRequest.Result;
}

static internal void MapFocus(this IView view, IViewHandler? handler, FocusRequest focusRequest)
static internal void MapFocus(this VisualElement view, FocusRequest focusRequest, Action? baseMethod = null)
{
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;
}
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;
}
// if there are legacy events, then use that
if (view.HasFocusChangeRequestedEvent)
{
var arg = new VisualElement.FocusRequestArgs { Focus = true };
view.InvokeFocusChangeRequested(arg);
focusRequest.TrySetResult(arg.Result);
return;
}

// otherwise, fall back to "base"
if (handler is not null)
if (baseMethod is not null)
{
ViewHandler.MapFocus(handler, view, focusRequest);
baseMethod.Invoke();
return;
}

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)
{
if (args is FocusRequest request)
handler.QueryEditor?.Focus(request);
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
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 ToBase64String(this Bitmap bitmap)
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
24 changes: 12 additions & 12 deletions src/TestUtils/src/DeviceTests/AssertionExtensions.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextBox>();

if (queryEditor is null)
throw new Exception("Unable to locate TextBox on AutoSuggestBox");

view = queryEditor;
}

TaskCompletionSource focusSource = new TaskCompletionSource();
view.GotFocus += OnFocused;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -120,6 +108,9 @@ public static Task HideKeyboardForView(this FrameworkElement view, int timeout =
public static async Task<string> CreateEqualError(this CanvasBitmap bitmap, CanvasBitmap other, string message) =>
$"{message} This is what it looked like: <img>{await bitmap.ToBase64StringAsync()}</img> and <img>{await other.ToBase64StringAsync()}</img>";

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

public static async Task<string> ToBase64StringAsync(this CanvasBitmap bitmap)
{
using var ms = new InMemoryRandomAccessStream();
Expand Down Expand Up @@ -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
{
Expand Down
12 changes: 12 additions & 0 deletions src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ public static Task HideKeyboardForView(this UIView view, int timeout = 1000, str
public static string CreateEqualError(this UIImage bitmap, UIImage other, string message) =>
$"{message} This is what it looked like: <img>{bitmap.ToBase64String()}</img> and <img>{other.ToBase64String()}</img>";

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

public static string ToBase64String(this UIImage bitmap)
{
var data = bitmap.AsPNG();
Expand Down Expand Up @@ -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
{
Expand Down

0 comments on commit 2e25b85

Please sign in to comment.