Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
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)
Comment thread
PureWeen marked this conversation as resolved.
{
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 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
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 async Task<string> CreateColorError(this CanvasBitmap bitmap, stri
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 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: <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