Skip to content

Commit ea135a7

Browse files
authored
August 25th, 2025 Candidate (#31507)
This pull request introduces several improvements and bug fixes across the controls, handlers, and platform gesture management code. The most significant changes include enhanced gesture event handling for Windows, improved CarouselView initialization logic, updates to navigation page stack management, and additional test cases for recent issues. There are also project configuration updates to improve compatibility with AOT and trimming. **Gesture Handling and Event Management (Windows):** * Refactored gesture event handling in `GesturePlatformManager.Windows.cs` to use modern C# pattern matching (`is not View view`) and simplified finger tracking logic, ensuring fingers are cleared appropriately on pointer and manipulation events. This improves reliability and code clarity for touch/gesture interactions. [[1]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13R539-R545) [[2]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13L562-L595) [[3]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13L609-R600) [[4]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13L856-R837) [[5]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13L875-R855) [[6]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13L899-R878) [[7]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13L711-R694) [[8]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13L779-R761) * Fixed drag-and-drop event handling and improved pinch gesture state management by moving `_isPinching = true` to the correct event handler. [[1]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13L264-R275) [[2]](diffhunk://#diff-779a3cbf8053213a2331cbf6a85a82af5c41cd858702c4fa4c4efdf16d7a6b13L512-L513) **CarouselView and ItemsView Improvements:** * Updated `CarouselViewController2.cs` so initial position is only set if the view is visible, preventing incorrect initialization when hidden. * Updated test case `CarouselViewUpdatePosition.xaml` to use the correct `CarouselView` control instead of a custom version, reflecting resolved issues and improved reliability. **Navigation and Modal Management:** * Refactored navigation stack updates in `NavigationPage.Legacy.cs` to ensure the correct page appears after removal, improving stack consistency and event firing. [[1]](diffhunk://#diff-fd62b9e1be65f45c35e4e7f56828a936fd9ce3052357cf7715da6d8618a9992bL46-L48) [[2]](diffhunk://#diff-fd62b9e1be65f45c35e4e7f56828a936fd9ce3052357cf7715da6d8618a9992bR62-R69) * Simplified modal navigation back press handling on Android by removing complex event handler logic, preventing stack overflows and improving maintainability. **Project Configuration and Compatibility:** * Added `<IsAotCompatible>true</IsAotCompatible>` and `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>` to project files, improving support for AOT compilation and code trimming. [[1]](diffhunk://#diff-6f43a8754586232decf1ed0dd2c2951a61355c52c728638934ff8e3fe1d53171R26-R32) [[2]](diffhunk://#diff-33f0c4961f9a73122ccc2856023d4a2563b877e16d774eed3618e793780f3e5dR27) **Test Cases and Issue Coverage:** * Added new test cases for issues #23575, #28414, and #28811, improving coverage for gradient color restoration, navigation stack updates, and modal back button handling. [[1]](diffhunk://#diff-886e8faf51027512a38656a0dbf4aa3bd77bb39358b20a7a6e4305a23e496b68R1-R38) [[2]](diffhunk://#diff-e9cf17d3db81c16d2ef83ed94500fd099aab1fcd89952dd6bb3878ef3a7f2b92R1-R111) [[3]](diffhunk://#diff-9ac61aced2af41a8341d76ff5eb0b9d4b695d46a63905010537024a3198e8365R1-R32) Let me know if you want to dive deeper into any specific change or area!
2 parents e838b50 + 0998488 commit ea135a7

File tree

79 files changed

+867
-130
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+867
-130
lines changed

src/Controls/src/Core/Application/Application.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,9 @@ void TriggerThemeChangedActual()
261261
_themeChangedFiring = true;
262262
_lastAppTheme = newTheme;
263263

264+
OnPropertyChanged(nameof(UserAppTheme));
264265
OnParentResourcesChanged([new KeyValuePair<string, object>(AppThemeBinding.AppThemeResource, newTheme)]);
265266
_weakEventManager.HandleEvent(this, new AppThemeChangedEventArgs(newTheme), nameof(RequestedThemeChanged));
266-
OnPropertyChanged(nameof(UserAppTheme));
267267
}
268268
finally
269269
{

src/Controls/src/Core/Controls.Core.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323
</PropertyGroup>
2424

2525
<PropertyGroup Condition="!$(TargetFramework.StartsWith('netstandard'))">
26+
<IsAotCompatible>true</IsAotCompatible>
2627
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
2728
<EnableAotAnalyzer>true</EnableAotAnalyzer>
2829
<EnableSingleFileAnalyzer>true</EnableSingleFileAnalyzer>
30+
31+
<!-- This project implements generic WinRT interfaces which requires generated code using unsafe for trimming and AOT compatibility if passed across the WinRT ABI. -->
32+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
2933
</PropertyGroup>
3034

3135
<Import Project="$(MauiSrcDirectory)MultiTargeting.targets" />

src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,13 @@ async Task UpdateInitialPosition()
499499
return;
500500
}
501501

502+
if(!ItemsView.IsVisible)
503+
{
504+
// If the CarouselView is not visible we don't want to set the initial position
505+
// since it will be set when the CarouselView becomes visible
506+
return;
507+
}
508+
502509
if (ItemsSource is null)
503510
{
504511
return;

src/Controls/src/Core/InputView/InputView.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,26 @@ internal InputView()
7171
{
7272
}
7373

74+
private protected override void OnHandlerChangingCore(HandlerChangingEventArgs args)
75+
{
76+
base.OnHandlerChangingCore(args);
77+
78+
if (Application.Current is null)
79+
return;
80+
81+
if (args.NewHandler is null || args.OldHandler is not null)
82+
Application.Current.RequestedThemeChanged -= OnRequestedThemeChanged;
83+
84+
if (args.NewHandler is not null && args.OldHandler is null)
85+
Application.Current.RequestedThemeChanged += OnRequestedThemeChanged;
86+
}
87+
88+
private void OnRequestedThemeChanged(object sender, AppThemeChangedEventArgs e)
89+
{
90+
OnPropertyChanged(nameof(PlaceholderColor));
91+
OnPropertyChanged(nameof(TextColor));
92+
}
93+
7494
/// <include file="../../docs/Microsoft.Maui.Controls/InputView.xml" path="//Member[@MemberName='Text']/Docs/*" />
7595
public string Text
7696
{

src/Controls/src/Core/NavigationPage/NavigationPage.Legacy.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ async Task<Page> RemoveAsyncInner(
4343

4444
FireDisappearing(page);
4545

46-
if (InternalChildren.Last() == page)
47-
FireAppearing((Page)InternalChildren[NavigationPageController.StackDepth - 2]);
48-
4946
var args = new NavigationRequestedEventArgs(page, animated);
5047

5148
var removed = true;
@@ -62,8 +59,14 @@ async Task<Page> RemoveAsyncInner(
6259
if (!removed && !fast)
6360
return CurrentPage;
6461

62+
bool isLastPage = InternalChildren.Last() == page;
6563
RemoveFromInnerChildren(page);
6664

65+
if (isLastPage && InternalChildren.Count > 0)
66+
{
67+
FireAppearing((Page)InternalChildren.Last());
68+
}
69+
6770
CurrentPage = (Page)InternalChildren.Last();
6871

6972
if (Popped != null)

src/Controls/src/Core/Platform/GestureManager/GesturePlatformManager.Windows.cs

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ void HandleDragLeave(object sender, Microsoft.UI.Xaml.DragEventArgs e)
191191
// for example
192192
// e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Copy;
193193
// Even if AcceptedOperation is already set to Copy it will cause the copy animation
194-
// to remain even after the the dragged element has left
194+
// to remain even after the dragged element has left
195195
if (!dragEventArgs.PlatformArgs?.Handled ?? true && operationPriorToSend != dragEventArgs.AcceptedOperation)
196196
{
197197
var result = (int)dragEventArgs.AcceptedOperation;
@@ -261,21 +261,18 @@ void HandleDragStarting(UIElement sender, Microsoft.UI.Xaml.DragStartingEventArg
261261
{
262262
SendEventArgs<DragGestureRecognizer>(rec =>
263263
{
264-
var view = Element as View;
265-
266-
if (!rec.CanDrag || view is null)
264+
if (!rec.CanDrag || Element is not View view)
267265
{
268266
e.Cancel = true;
269267
return;
270268
}
271269

272-
var handler = sender as IViewHandler;
273270
var args = rec.SendDragStarting(view, (relativeTo) => GetPosition(relativeTo, e), new PlatformDragStartingEventArgs(sender, e));
274271

275272
e.Data.Properties[_doNotUsePropertyString] = args.Data;
276273

277274
#pragma warning disable CS0618 // Type or member is obsolete
278-
if ((!args.Handled || (!args.PlatformArgs?.Handled ?? true)) && handler != null)
275+
if ((!args.Handled || (!args.PlatformArgs?.Handled ?? true)) && sender is IViewHandler handler)
279276
#pragma warning restore CS0618 // Type or member is obsolete
280277
{
281278
if (handler?.PlatformView is UI.Xaml.Controls.Image nativeImage &&
@@ -509,8 +506,6 @@ void HandlePinch(ManipulationDeltaRoutedEventArgs e, View view)
509506
return;
510507
}
511508

512-
_isPinching = true;
513-
514509
if (e.OriginalSource is UIElement container)
515510
{
516511
global::Windows.Foundation.Point translationPoint = container.TransformToVisual(Container).TransformPoint(e.Position);
@@ -541,13 +536,13 @@ void OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs
541536
SwipeComplete(true);
542537
PinchComplete(true);
543538
PanComplete(true);
539+
540+
_fingers.Clear();
544541
}
545542

546543
void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
547544
{
548-
var view = Element as View;
549-
550-
if (view == null)
545+
if (Element is not View view)
551546
{
552547
return;
553548
}
@@ -559,40 +554,33 @@ void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
559554

560555
void OnManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
561556
{
562-
var view = Element as View;
563-
if (view == null)
557+
if (Element is not View view)
564558
{
565559
return;
566560
}
567561

562+
_isPinching = true;
568563
_wasPinchGestureStartedSent = false;
569564
_wasPanGestureStartedSent = false;
570565
}
571566

572567
void OnPointerCanceled(object sender, PointerRoutedEventArgs e)
573568
{
574-
uint id = e.Pointer.PointerId;
575-
if (_fingers.Contains(id))
576-
{
577-
_fingers.Remove(id);
578-
}
579-
580569
SwipeComplete(false);
581570
PinchComplete(false);
582571
PanComplete(false);
572+
573+
_fingers.Clear();
583574
}
584575

585576
void OnPointerExited(object sender, PointerRoutedEventArgs e)
586577
{
578+
SwipeComplete(true);
579+
587580
if (!_isPanning)
588581
{
589-
uint id = e.Pointer.PointerId;
590-
if (_fingers.Contains(id))
591-
_fingers.Remove(id);
582+
_fingers.Remove(e.Pointer.PointerId);
592583
}
593-
594-
SwipeComplete(true);
595-
PinchComplete(true);
596584
}
597585

598586
void OnPointerPressed(object sender, PointerRoutedEventArgs e)
@@ -606,15 +594,10 @@ void OnPointerPressed(object sender, PointerRoutedEventArgs e)
606594

607595
void OnPointerReleased(object sender, PointerRoutedEventArgs e)
608596
{
609-
uint id = e.Pointer.PointerId;
610-
if (_fingers.Contains(id))
611-
{
612-
_fingers.Remove(id);
613-
}
614-
615597
SwipeComplete(true);
616-
PinchComplete(true);
617598
PanComplete(true);
599+
600+
_fingers.Remove(e.Pointer.PointerId);
618601
}
619602

620603
void OnPgrPointerEntered(object sender, PointerRoutedEventArgs e)
@@ -708,8 +691,7 @@ void OnPgrPointerReleased(object sender, PointerRoutedEventArgs e)
708691

709692
private void HandlePgrPointerEvent(PointerRoutedEventArgs e, Action<View, PointerGestureRecognizer> SendPointerEvent)
710693
{
711-
var view = Element as View;
712-
if (view == null)
694+
if (Element is not View view)
713695
{
714696
return;
715697
}
@@ -776,8 +758,7 @@ bool IsPointerEventRelevantToCurrentElement(PointerRoutedEventArgs e)
776758

777759
void OnTap(object sender, RoutedEventArgs e)
778760
{
779-
var view = Element as View;
780-
if (view == null)
761+
if (Element is not View view)
781762
{
782763
return;
783764
}
@@ -853,8 +834,7 @@ bool ValidateGesture(TapGestureRecognizer g)
853834

854835
void SwipeComplete(bool success)
855836
{
856-
var view = Element as View;
857-
if (view == null || !_isSwiping)
837+
if (Element is not View view || !_isSwiping)
858838
{
859839
return;
860840
}
@@ -872,8 +852,7 @@ void SwipeComplete(bool success)
872852

873853
void PanComplete(bool success)
874854
{
875-
var view = Element as View;
876-
if (view == null || !_isPanning)
855+
if (Element is not View view || !_isPanning)
877856
{
878857
return;
879858
}
@@ -896,8 +875,7 @@ void PanComplete(bool success)
896875

897876
void PinchComplete(bool success)
898877
{
899-
var view = Element as View;
900-
if (view is null || !_isPinching)
878+
if (Element is not View view || !_isPinching)
901879
{
902880
return;
903881
}

src/Controls/src/Core/Platform/ModalNavigationManager/ModalNavigationManager.Android.cs

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -415,45 +415,14 @@ public override void HandleOnBackPressed()
415415
return;
416416
}
417417

418-
Window? window = activity.GetWindow() as Window;
419-
EventHandler? eventHandler = null;
420-
eventHandler = OnPopCanceled;
421-
if (window is not null)
422-
{
423-
window.PopCanceled += eventHandler;
424-
}
425-
426-
var preventBackPropagation = false;
427-
428418
try
429419
{
430420
IPlatformApplication.Current?.Services?.InvokeLifecycleEvents<AndroidLifecycle.OnBackPressed>(del =>
431421
{
432-
preventBackPropagation = del(activity) || preventBackPropagation;
422+
del(activity);
433423
});
434424
}
435-
finally
436-
{
437-
if (window is not null && eventHandler is not null)
438-
{
439-
window.PopCanceled -= eventHandler;
440-
}
441-
}
442-
443-
if (!preventBackPropagation)
444-
{
445-
customComponentDialog.OnBackPressedDispatcher.OnBackPressed();
446-
}
447-
448-
eventHandler = null;
449-
void OnPopCanceled(object? sender, EventArgs e)
450-
{
451-
preventBackPropagation = true;
452-
if (window is not null && eventHandler is not null)
453-
{
454-
window.PopCanceled -= eventHandler;
455-
}
456-
}
425+
finally { }
457426
}
458427
}
459428
}

src/Controls/src/Xaml/Controls.Xaml.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
</PropertyGroup>
2525

2626
<PropertyGroup Condition="!$(TargetFramework.StartsWith('netstandard'))">
27+
<IsAotCompatible>true</IsAotCompatible>
2728
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
2829
<EnableAotAnalyzer>true</EnableAotAnalyzer>
2930
<EnableSingleFileAnalyzer>true</EnableSingleFileAnalyzer>
42.6 KB
Loading
101 KB
Loading

0 commit comments

Comments
 (0)