diff --git a/.editorconfig b/.editorconfig index 19d51c0037d7..dbe6fc83173f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -67,6 +67,10 @@ csharp_style_var_for_built_in_types = true:none csharp_style_var_when_type_is_apparent = true:none csharp_style_var_elsewhere = false:none +# Namespace preference (prefer file-scoped namespaces) +csharp_style_namespace_declarations = file_scoped +dotnet_diagnostic.IDE0161.severity = suggestion + # use language keywords instead of BCL types dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion diff --git a/.github/DEVELOPMENT.md b/.github/DEVELOPMENT.md index 23d75616bce1..899b91b47126 100644 --- a/.github/DEVELOPMENT.md +++ b/.github/DEVELOPMENT.md @@ -3,6 +3,7 @@ This page contains steps to build and run the .NET MAUI repository from source. If you are looking to build apps with .NET MAUI please head over to the links in the [README](https://github.com/dotnet/maui/blob/main/README.md) to get started. ## Initial setup + ### Windows - Install VS 17.12 or newer - Follow [these steps](https://learn.microsoft.com/dotnet/maui/get-started/installation?tabs=vswin) to include MAUI @@ -17,6 +18,8 @@ This page contains steps to build and run the .NET MAUI repository from source. ## Building the Build Tasks Before opening the solution in Visual Studio / VS Code you **MUST** build the build tasks. +Note: the main branch is always [pinned to the latest stable release](https://github.com/dotnet/maui/blob/main/global.json) of the .NET SDK. This is regardless of said release being a long-term support (LTS) release or not. This means that you will need to have that version of the .NET SDK installed and configured on your machine in order to be able to execute the commands below and build the .NET MAUI codebase. + 1. Open a command prompt/terminal/shell window 1. Navigate to the location of your cloned `dotnet/maui` repo, for example: ```shell diff --git a/.github/workflows/dotnet-autoformat-pr-push.yml b/.github/workflows/dotnet-autoformat-pr-push.yml index 6730e30e8ec9..37545d6f8d06 100644 --- a/.github/workflows/dotnet-autoformat-pr-push.yml +++ b/.github/workflows/dotnet-autoformat-pr-push.yml @@ -18,7 +18,7 @@ jobs: github.event.workflow_run.conclusion == 'success' steps: - name: 'Push autoformatted patch' - uses: rolfbjarne/autoformat-push@v0.2 + uses: rolfbjarne/autoformat-push@v0.5 with: githubToken: ${{ secrets.GITHUB_TOKEN }} commentContents: 'Thank you for your pull request. We are auto-formatting your source code to follow our code guidelines.' diff --git a/.github/workflows/dotnet-autoformat-pr.yml b/.github/workflows/dotnet-autoformat-pr.yml index dcb055c50335..40ea9cb49d1c 100644 --- a/.github/workflows/dotnet-autoformat-pr.yml +++ b/.github/workflows/dotnet-autoformat-pr.yml @@ -12,7 +12,7 @@ jobs: steps: - name: 'Autoformat' - uses: rolfbjarne/autoformat@v0.2 + uses: rolfbjarne/autoformat@v0.5 with: script: 'dotnet format Microsoft.Maui.sln --no-restore --exclude Templates/src BlazorWebView/src/SharedSource/BlazorWebViewDeveloperTools.cs BlazorWebView/src/SharedSource/BlazorWebViewServiceCollectionExtensions.cs Graphics/src/Graphics.Win2D/W2DCanvas.cs Graphics/src/Graphics.Win2D/W2DExtensions.cs' onlyFilesModifiedInPullRequest: true diff --git a/Directory.Build.props b/Directory.Build.props index d257c3f41848..46e842c3ba34 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -42,6 +42,7 @@ all moderate + false diff --git a/eng/Publishing.props b/eng/Publishing.props index c48d13ceaa23..b31e222bdfda 100644 --- a/eng/Publishing.props +++ b/eng/Publishing.props @@ -2,6 +2,7 @@ 3 true + $(PublishDependsOnTargets);_PublishBlobItems diff --git a/eng/Versions.props b/eng/Versions.props index f0f7f15ca21b..0edc6b2c473f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -120,16 +120,16 @@ - Feed URI in the nuget.config - Native assets build and sha --> - <_SkiaSharpVersion>2.88.8 - <_SkiaSharpExtendedVersion>2.0.0 - <_HarfBuzzSharpVersion>7.3.0.2 - <_SkiaSharpNativeAssetsVersion>0.0.0-commit.7af1d0840a381c0ce7ef2877454a88dbb2949686.1086 + <_SkiaSharpVersion>3.116.1 + <_SkiaSharpExtendedVersion>3.0.0-preview.13 + <_HarfBuzzSharpVersion>8.3.0.1 + <_SkiaSharpNativeAssetsVersion>0.0.0-commit.e57e2a11dac4ccc72bea52939dede49816842005.1728 7.0.120 9.0.0-prerelease.25113.3 9.0.0-prerelease.25113.3 9.0.0-prerelease.25113.3 0.9.2 - 1.0.0.16 + 2.0.0.4 1.3.0 0.9.0 4.2.3 diff --git a/eng/cake/dotnet.cake b/eng/cake/dotnet.cake index 2258e584d4ca..acb58ac00ad2 100644 --- a/eng/cake/dotnet.cake +++ b/eng/cake/dotnet.cake @@ -588,7 +588,6 @@ void UseLocalNuGetCacheFolder(bool reset = false) void StartVisualStudioCodeForDotNet() { - string workspace = "./maui.code-workspace"; if (IsCIBuild()) { Error("This target should not run on CI."); @@ -600,7 +599,7 @@ void StartVisualStudioCodeForDotNet() SetDotNetEnvironmentVariables(); } - StartProcess("code", new ProcessSettings{ Arguments = workspace, EnvironmentVariables = GetDotNetEnvironmentVariables() }); + StartProcess("code", new ProcessSettings{ EnvironmentVariables = GetDotNetEnvironmentVariables() }); } void StartVisualStudioForDotNet() diff --git a/eng/pipelines/common/maui-templates.yml b/eng/pipelines/common/maui-templates.yml index f913bdeb92ea..ad706222f9d9 100644 --- a/eng/pipelines/common/maui-templates.yml +++ b/eng/pipelines/common/maui-templates.yml @@ -90,7 +90,7 @@ jobs: name: $(POOL_NAME) vmImage: $(POOL_VIMAGE) demands: - - macOS.Name -equals Sonoma + - macOS.Name -equals Sequoia - macOS.Architecture -equals arm64 steps: - template: maui-templates-steps.yml @@ -157,15 +157,13 @@ jobs: DOTNET_TOKEN: $(dotnetbuilds-internal-container-read-token) PRIVATE_BUILD: $(PrivateBuild) - # - script: dotnet tool update Microsoft.DotNet.XHarness.CLI --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json --version "9.0.0-prerelease*" -g - # displayName: install xharness - ${{ if eq(RunPlatform.testName, 'RunOniOS') }}: - pwsh: ./build.ps1 -Script eng/devices/ios.cake --target=Cleanup --verbosity=diagnostic displayName: Reset iOS simulators # TODO: pass properly device type/version from top-level yml env: - IOS_TEST_DEVICE: ios-simulator-64_17.2 + IOS_TEST_DEVICE: ios-simulator-64_18.0 - pwsh: ./build.ps1 --target=dotnet-integration-test --filter="Category=${{ RunPlatform.testName }}" --resultsfilename="integration-run-${{ RunPlatform.testName }}" --verbosity=diagnostic displayName: Run ${{ RunPlatform.testName }} templates run tests @@ -173,7 +171,7 @@ jobs: # TODO: pass properly device type/version from top-level yml ${{ if eq(RunPlatform.testName, 'RunOniOS') }}: env: - IOS_TEST_DEVICE: ios-simulator-64_17.2 + IOS_TEST_DEVICE: ios-simulator-64_18.0 - ${{ if eq(RunPlatform.testName, 'RunOniOS') }}: - pwsh: ./build.ps1 --target=Cleanup -Script eng/devices/ios.cake ---results="$(TestResultsDirectory)" ${{ parameters.cakeArgs }} diff --git a/eng/pipelines/device-tests.yml b/eng/pipelines/device-tests.yml index 33b657706e1e..30bbbec7998d 100644 --- a/eng/pipelines/device-tests.yml +++ b/eng/pipelines/device-tests.yml @@ -67,7 +67,7 @@ parameters: name: $(androidTestsVmPool) vmImage: $(androidTestsVmImage) demands: - - macOS.Name -equals Sonoma + - macOS.Name -equals Sequoia - name: androidPoolX64 type: object @@ -84,7 +84,7 @@ parameters: name: $(iosDeviceTestsVmPool) vmImage: $(iosDeviceTestsVmImage) demands: - - macOS.Name -equals Sonoma + - macOS.Name -equals Sequoia - name: catalystPool type: object @@ -92,7 +92,7 @@ parameters: name: $(iosDeviceTestsVmPool) vmImage: $(iosDeviceTestsVmImage) demands: - - macOS.Name -equals Sonoma + - macOS.Name -equals Sequoia - name: windowsPool type: object diff --git a/eng/pipelines/ui-tests.yml b/eng/pipelines/ui-tests.yml index 078a6442d7e1..b528434b5bc8 100644 --- a/eng/pipelines/ui-tests.yml +++ b/eng/pipelines/ui-tests.yml @@ -148,7 +148,7 @@ stages: provisionatorChannel: ${{ parameters.provisionatorChannel }} ${{ if parameters.CompatibilityTests }}: runCompatibilityTests: true - ${{ if or(eq(parameters.UseProvisionator, true), eq(variables['internalProvisioning'],'True') ) }}: + ${{ if or(parameters.UseProvisionator, eq(variables['internalProvisioning'],'true') ) }}: skipProvisioning: false gitHubToken: $(github--pat--vs-mobiletools-engineering-service2) ${{ else }}: diff --git a/src/Controls/src/BindingSourceGen/InvocationParser.cs b/src/Controls/src/BindingSourceGen/InvocationParser.cs index bba633ce3f3d..006103c28042 100644 --- a/src/Controls/src/BindingSourceGen/InvocationParser.cs +++ b/src/Controls/src/BindingSourceGen/InvocationParser.cs @@ -30,32 +30,31 @@ internal Result ParseInvocation(InvocationExpressionSynta private Result VerifyCorrectOverloadBindingCreate(InvocationExpressionSyntax invocation, CancellationToken t) { - var argumentList = invocation.ArgumentList.Arguments; - var symbol = _context.SemanticModel.GetSymbolInfo(invocation.Expression).Symbol; - if ((symbol?.ContainingType?.Name != "Binding" && symbol?.ContainingType?.Name != "BindingBase") - || symbol?.ContainingType?.ContainingNamespace.ToDisplayString() is not "Microsoft.Maui.Controls") + if (symbol is not null) { - return Result.Failure(DiagnosticsFactory.SuboptimalSetBindingOverload(invocation.GetLocation())); + // If the symbol is available, ensure it is the expected method. + if (symbol is not IMethodSymbol methodSymbol + || methodSymbol.Name != "Create" + || (methodSymbol.ContainingType?.Name != "Binding" && methodSymbol.ContainingType?.Name != "BindingBase") + || methodSymbol.ContainingType?.ContainingNamespace.ToDisplayString() is not "Microsoft.Maui.Controls") + { + return Result.Failure(DiagnosticsFactory.SuboptimalSetBindingOverload(invocation.GetLocation())); + } } - - if (argumentList.Count == 0) + else { - throw new ArgumentOutOfRangeException(nameof(invocation)); - } + // It is not possible to resolve the method symbol when the bindable object (the first argument or the object that the extension method + // is called on) is referenced by a field that will be generated via XamlG based on the x:Name attributes. In that case, this source generator + // cannot see the outputs of the other source generator and we have incomplete information about the method invocation and we can only work with + // the syntax tree and not the semantic model. + var argumentsList = invocation.ArgumentList.Arguments; - var firstArgument = argumentList[0].Expression; - if (firstArgument is IdentifierNameSyntax) - { - var type = _context.SemanticModel.GetTypeInfo(firstArgument, cancellationToken: t).Type; - if (type != null && type.Name == "Func") + var firstArgument = argumentsList[0].Expression; // Guaranteed to have at least one argument (checked by the caller) + if (firstArgument is not LambdaExpressionSyntax) { return Result.Failure(DiagnosticsFactory.GetterIsNotLambda(firstArgument.GetLocation())); } - else // String and Binding - { - return Result.Failure(DiagnosticsFactory.SuboptimalSetBindingOverload(firstArgument.GetLocation())); - } } return Result.Success(InterceptedMethodType.Create); diff --git a/src/Controls/src/Core/Brush/Brush.cs b/src/Controls/src/Core/Brush/Brush.cs index d15666be22d4..256afd86c389 100644 --- a/src/Controls/src/Core/Brush/Brush.cs +++ b/src/Controls/src/Core/Brush/Brush.cs @@ -65,7 +65,9 @@ public static implicit operator Paint(Brush brush) for (int i = 0; i < gradientStopCollection.Count; i++) { var gs = gradientStopCollection[i]; - gradientStops[i] = new GraphicsGradientStop(gs.Offset, gs.Color); + + if (gs is not null) + gradientStops[i] = new GraphicsGradientStop(gs.Offset, gs.Color); } if (gradientBrush is LinearGradientBrush linearGradientBrush) diff --git a/src/Controls/src/Core/CheckBox/CheckBox.cs b/src/Controls/src/Core/CheckBox/CheckBox.cs index e622647662b0..2953a89a3c10 100644 --- a/src/Controls/src/Core/CheckBox/CheckBox.cs +++ b/src/Controls/src/Core/CheckBox/CheckBox.cs @@ -46,7 +46,37 @@ public bool IsChecked protected internal override void ChangeVisualState() { if (IsEnabled && IsChecked) - VisualStateManager.GoToState(this, IsCheckedVisualState); + { + bool isCheckedStateAvailable = false; + var visualStates = VisualStateManager.GetVisualStateGroups(this); + foreach (var group in visualStates) + { + if (group.Name is not "CommonStates") + { + continue; + } + + foreach (var state in group.States) + { + if (state.Name is IsCheckedVisualState) + { + isCheckedStateAvailable = true; + break; + } + } + + break; + } + + if (isCheckedStateAvailable) + { + VisualStateManager.GoToState(this, IsCheckedVisualState); + } + else + { + VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Normal); + } + } else base.ChangeVisualState(); } diff --git a/src/Controls/src/Core/Compatibility/Handlers/ListView/Windows/ListViewRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/ListView/Windows/ListViewRenderer.cs index e52a974164a2..f2348570def0 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/ListView/Windows/ListViewRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/ListView/Windows/ListViewRenderer.cs @@ -807,6 +807,11 @@ void OnListItemClicked(object sender, ItemClickEventArgs e) private protected override void DisconnectHandlerCore() { + if (Element is ITemplatedItemsView templatedItemsView) + { + templatedItemsView.TemplatedItems.CollectionChanged -= OnCollectionChanged; + } + CleanUpResources(); base.DisconnectHandlerCore(); } diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRenderer.cs index d969e5691f27..e75fdf86a5ce 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRenderer.cs @@ -54,7 +54,6 @@ void IAppearanceObserver.OnAppearanceChanged(ShellAppearance appearance) IShellBottomNavViewAppearanceTracker _appearanceTracker; BottomNavigationViewTracker _bottomNavigationTracker; BottomSheetDialog _bottomSheetDialog; - bool _disposed; bool _menuSetup; ShellAppearance _shellAppearance; bool _appearanceSet; @@ -126,18 +125,6 @@ void Destroy() } - protected override void Dispose(bool disposing) - { - if (_disposed) - return; - - _disposed = true; - if (disposing) - Destroy(); - - base.Dispose(disposing); - } - // Use OnDestory become OnDestroyView may fire before events are completed. public override void OnDestroy() { diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRendererBase.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRendererBase.cs index fac13a0b7936..e69d6bacd67d 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRendererBase.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRendererBase.cs @@ -33,7 +33,6 @@ ShellItem IShellItemRenderer.ShellItem IShellObservableFragment _currentFragment; ShellSection _shellSection; Page _displayedPage; - bool _disposed; protected ShellItemRendererBase(IShellContext shellContext) { @@ -114,19 +113,6 @@ public override void OnDestroy() Destroy(); } - protected override void Dispose(bool disposing) - { - if (_disposed) - return; - - _disposed = true; - - if (disposing) - Destroy(); - - base.Dispose(disposing); - } - protected abstract ViewGroup GetNavigationTarget(); protected virtual IShellObservableFragment GetOrCreateFragmentForTab(ShellSection shellSection) diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellRenderer.cs index 46ca471ab82d..6c0572bd564f 100644 --- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellRenderer.cs +++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellRenderer.cs @@ -235,8 +235,7 @@ protected virtual void SwitchFragment(FragmentManager manager, AView targetView, // Don't force the commit if this is our first load if (previousView == null) { - transaction - .SetReorderingAllowedEx(true); + transaction.SetReorderingAllowedEx(true); } transaction.CommitAllowingStateLossEx(); @@ -245,7 +244,6 @@ void OnDestroyed(object sender, EventArgs args) { previousView.Destroyed -= OnDestroyed; - previousView.Dispose(); previousView = null; } diff --git a/src/Controls/src/Core/GradientBrush.cs b/src/Controls/src/Core/GradientBrush.cs index a83e07655d2d..22b9b77cc7b4 100644 --- a/src/Controls/src/Core/GradientBrush.cs +++ b/src/Controls/src/Core/GradientBrush.cs @@ -65,8 +65,11 @@ void UpdateGradientStops(GradientStopCollection oldCollection, GradientStopColle foreach (var newStop in newCollection) { - newStop.Parent = this; - newStop.PropertyChanged += OnGradientStopPropertyChanged; + if (newStop is not null) + { + newStop.Parent = this; + newStop.PropertyChanged += OnGradientStopPropertyChanged; + } } } diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs index c42f5f3a4aad..59f98cda53a7 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/ObservableGroupedSource.cs @@ -315,6 +315,11 @@ void Move(NotifyCollectionChangedEventArgs args) int GetGroupCount(int groupIndex) { + if (groupIndex < 0 || groupIndex >= _groupSource.Count) + { + return 0; + } + switch (_groupSource[groupIndex]) { case IList list: @@ -334,6 +339,11 @@ int GetGroupCount(int groupIndex) object GetGroupItemAt(int groupIndex, int index) { + if (groupIndex < 0 || groupIndex >= _groupSource.Count) + { + return -1; + } + switch (_groupSource[groupIndex]) { case IList list: diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ObservableItemsSource.cs b/src/Controls/src/Core/Handlers/Items/iOS/ObservableItemsSource.cs index 1a8d7e7a76ac..e7faa936fce3 100644 --- a/src/Controls/src/Core/Handlers/Items/iOS/ObservableItemsSource.cs +++ b/src/Controls/src/Core/Handlers/Items/iOS/ObservableItemsSource.cs @@ -314,7 +314,7 @@ void Update(Action update, NotifyCollectionChangedEventArgs ar return; var collectionView = controller.CollectionView; - if (collectionView.Hidden) + if (collectionView.Hidden || collectionView.Window == null) { return; } diff --git a/src/Controls/src/Core/Handlers/Shell/Windows/ShellFlyoutItemView.cs b/src/Controls/src/Core/Handlers/Shell/Windows/ShellFlyoutItemView.cs index d771a9ed4920..58874bedcd52 100644 --- a/src/Controls/src/Core/Handlers/Shell/Windows/ShellFlyoutItemView.cs +++ b/src/Controls/src/Core/Handlers/Shell/Windows/ShellFlyoutItemView.cs @@ -128,15 +128,16 @@ void ShellElementPropertyChanged(object sender, PropertyChangedEventArgs e) protected override global::Windows.Foundation.Size ArrangeOverride(global::Windows.Foundation.Size finalSize) { + base.ArrangeOverride(finalSize); + // Replaced ActualWidth with finalSize.Width since ActualWidth updates only after ArrangeOverride completes, // ensuring accurate layout during the initial arrangement phase. if (finalSize.Width > 0 && _content is IView view) { view.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); - return finalSize; } - return base.ArrangeOverride(finalSize); + return finalSize; } void UpdateVisualState() diff --git a/src/Controls/src/Core/ImageButton/ImageButton.iOS.cs b/src/Controls/src/Core/ImageButton/ImageButton.iOS.cs new file mode 100644 index 000000000000..3b3e930c9dbe --- /dev/null +++ b/src/Controls/src/Core/ImageButton/ImageButton.iOS.cs @@ -0,0 +1,44 @@ +using System; +using CoreGraphics; +using Microsoft.Maui.Graphics; +using UIKit; + +namespace Microsoft.Maui.Controls +{ + public partial class ImageButton : ICrossPlatformLayout + { + /// + /// Measure the desired size of the ImageButton based on the image size taking into account the padding. + /// + /// + /// + /// Returns a representing the width and height of the ImageButton. + /// This method is used to override the SizeThatFitsImage() on wrapper view. + Size ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, double heightConstraint) + { + if (Handler?.PlatformView is not UIButton platformButton || platformButton.ImageView is null) + { + return Size.Zero; + } + + CGSize constraintSize = new CGSize(widthConstraint, heightConstraint); + if (platformButton.ImageView.Image is not null) + { + return platformButton.ImageView + .SizeThatFitsImage(constraintSize, Padding.IsNaN ? null : Padding).ToSize(); + } + + return platformButton.SizeThatFits(constraintSize).ToSize(); + } + + /// + /// Returns the size of the ImageButton as a Size based on the specified bounds. + /// + /// + /// Returns a representing the width and height of the ImageButton. + Size ICrossPlatformLayout.CrossPlatformArrange(Rect bounds) + { + return bounds.Size; + } + } +} diff --git a/src/Controls/src/Core/Page/Page.cs b/src/Controls/src/Core/Page/Page.cs index 39ff6d2c2eaf..2c23d673d1d5 100644 --- a/src/Controls/src/Core/Page/Page.cs +++ b/src/Controls/src/Core/Page/Page.cs @@ -65,7 +65,7 @@ public partial class Page : VisualElement, ILayout, IPageController, IElementCon public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Title), typeof(string), typeof(Page), null); /// Bindable property for . - public static readonly BindableProperty IconImageSourceProperty = BindableProperty.Create(nameof(IconImageSource), typeof(ImageSource), typeof(Page), default(ImageSource)); + public static readonly BindableProperty IconImageSourceProperty = BindableProperty.Create(nameof(IconImageSource), typeof(ImageSource), typeof(Page), default(ImageSource), propertyChanged: OnImageSourceChanged); readonly Lazy> _platformConfigurationRegistry; @@ -902,6 +902,20 @@ internal void SendNavigatedFrom(NavigatedFromEventArgs args, bool disconnectHand } } + static void OnImageSourceChanged(BindableObject bindable, object oldvalue, object newValue) + { + if (oldvalue is ImageSource oldImageSource) + oldImageSource.SourceChanged -= ((Page)bindable).OnImageSourceSourceChanged; + + if (newValue is ImageSource newImageSource) + newImageSource.SourceChanged += ((Page)bindable).OnImageSourceSourceChanged; + } + + void OnImageSourceSourceChanged(object sender, EventArgs e) + { + OnPropertyChanged(IconImageSourceProperty.PropertyName); + } + /// /// Raised after the page was navigated to. /// diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/FormattedStringExtensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/FormattedStringExtensions.cs index b4f81b11761a..dab407c8c40c 100644 --- a/src/Controls/src/Core/Platform/Windows/Extensions/FormattedStringExtensions.cs +++ b/src/Controls/src/Core/Platform/Windows/Extensions/FormattedStringExtensions.cs @@ -41,7 +41,8 @@ public static void UpdateInlines( Color? defaultColor = null, TextTransform defaultTextTransform = TextTransform.Default) { - textBlock.Inlines.Clear(); + var textBlockInlines = textBlock.Inlines; + textBlockInlines.Clear(); // Have to implement a measure here, otherwise inline.ContentStart and ContentEnd will be null, when used in RecalculatePositions textBlock.Measure(new global::Windows.Foundation.Size(double.MaxValue, double.MaxValue)); @@ -68,7 +69,7 @@ public static void UpdateInlines( SetMeasuredLineHeight(run, lineHeights[i]); - textBlock.Inlines.Add(run); + textBlockInlines.Add(run); if (background is not null || textColor is not null) { @@ -152,11 +153,13 @@ internal static void RecalculateSpanPositions(this TextBlock control, FormattedS return; } + // Span count is larger than 0, so we can always assign as the variable will be always used. + var controlInlines = control.Inlines; + for (int i = 0; i < spans.Count; i++) { var span = spans[i]; - - var inline = control.Inlines.ElementAt(i); + var inline = controlInlines.ElementAt(i); var startRect = inline.ContentStart.GetCharacterRect(LogicalDirection.Forward); var endRect = inline.ContentEnd.GetCharacterRect(LogicalDirection.Forward); diff --git a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Shipped.txt b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Shipped.txt index bc6e0632fd8e..ce68bc5c9d50 100644 --- a/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Shipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Shipped.txt @@ -7648,10 +7648,8 @@ override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutTemplatedCont override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutTemplatedContentRenderer.HeaderContainer.Dispose(bool disposing) -> void override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutTemplatedContentRenderer.HeaderContainer.OnLayout(bool changed, int l, int t, int r, int b) -> void override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutTemplatedContentRenderer.HeaderContainer.OnMeasure(int widthMeasureSpec, int heightMeasureSpec) -> void -override Microsoft.Maui.Controls.Platform.Compatibility.ShellItemRenderer.Dispose(bool disposing) -> void override Microsoft.Maui.Controls.Platform.Compatibility.ShellItemRenderer.OnDestroy() -> void override Microsoft.Maui.Controls.Platform.Compatibility.ShellItemRenderer.OnShellSectionChanged() -> void -override Microsoft.Maui.Controls.Platform.Compatibility.ShellItemRendererBase.Dispose(bool disposing) -> void override Microsoft.Maui.Controls.Platform.Compatibility.ShellItemRendererBase.OnDestroy() -> void override Microsoft.Maui.Controls.Platform.Compatibility.ShellSearchView.Dispose(bool disposing) -> void override Microsoft.Maui.Controls.Platform.Compatibility.ShellSearchView.OnAttachedToWindow() -> void diff --git a/src/Controls/src/Core/Shell/BaseShellItem.cs b/src/Controls/src/Core/Shell/BaseShellItem.cs index 4427cb7b54b4..085139ef7a25 100644 --- a/src/Controls/src/Core/Shell/BaseShellItem.cs +++ b/src/Controls/src/Core/Shell/BaseShellItem.cs @@ -14,6 +14,7 @@ namespace Microsoft.Maui.Controls { /// [DebuggerDisplay("Title = {Title}, Route = {Route}")] + [DebuggerTypeProxy(typeof(BaseShellItemDebugView))] public class BaseShellItem : NavigableElement, IPropertyPropagationController, IVisualController, IFlowDirectionController, IWindowController { public event EventHandler Appearing; @@ -558,6 +559,22 @@ internal static DataTemplate CreateDefaultFlyoutItemCell(BindableObject bo) }); } + + /// + /// Provides a debug view for the class. + /// + /// The instance to debug. + private protected class BaseShellItemDebugView(BaseShellItem baseShellItem) + { + public ImageSource Icon => baseShellItem.Icon; + + public ImageSource FlyoutIcon => baseShellItem.FlyoutIcon; + + public bool FlyoutItemIsVisible => baseShellItem.FlyoutItemIsVisible; + + public string Route => baseShellItem.Route; + } + #if NETSTANDARD sealed class OperatingSystem { diff --git a/src/Controls/src/Core/Shell/Shell.cs b/src/Controls/src/Core/Shell/Shell.cs index 7d12f00d3882..3e59dde42b40 100644 --- a/src/Controls/src/Core/Shell/Shell.cs +++ b/src/Controls/src/Core/Shell/Shell.cs @@ -6,6 +6,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -21,6 +22,7 @@ namespace Microsoft.Maui.Controls { /// [ContentProperty(nameof(Items))] + [DebuggerTypeProxy(typeof(ShellDebugView))] public partial class Shell : Page, IShellController, IPropertyPropagationController, IPageContainer, IFlyoutView { /// @@ -2150,5 +2152,25 @@ public NavigationImplWrapper(INavigation proxy, INavigation shellProxy) protected override Task OnPushModal(Page modal, bool animated) => _shellProxy.PushModalAsync(modal, animated); } } + + + /// + /// Provides a debug view for the Shell class. + /// + /// The Shell instance to debug. + private sealed class ShellDebugView(Shell shell) + { + public Page CurrentPage => shell.CurrentPage; + + public ShellNavigationState CurrentState => shell.CurrentState; + + public Uri AbsoluteUrl => shell.CurrentState.FullLocation; + + public FlyoutBehavior FlyoutBehavior => shell.FlyoutBehavior; + + public IEnumerable FlyoutItems => shell.FlyoutItems; + + public Window Windows => shell.Window; + } } } diff --git a/src/Controls/src/Core/Shell/ShellContentCollection.cs b/src/Controls/src/Core/Shell/ShellContentCollection.cs index 6f4c1a0e70da..7eab2faa7b4b 100644 --- a/src/Controls/src/Core/Shell/ShellContentCollection.cs +++ b/src/Controls/src/Core/Shell/ShellContentCollection.cs @@ -1,12 +1,16 @@ #nullable disable using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; namespace Microsoft.Maui.Controls { + [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] + [DebuggerTypeProxy(typeof(ShellContentCollectionDebugView))] internal sealed class ShellContentCollection : ShellElementCollection { public ShellContentCollection() : base() @@ -37,5 +41,16 @@ protected override void OnElementControllerRemoving(IElementController element) if (element is IShellContentController controller) controller.IsPageVisibleChanged -= OnIsPageVisibleChanged; } + + string GetDebuggerDisplay() => $"Count = {Count}"; + + /// + /// Provides a debug view for the class. + /// + /// The instance to debug. + private sealed class ShellContentCollectionDebugView(ShellContentCollection collection) + { + public IReadOnlyCollection VisibleItemsReadOnly => collection.VisibleItemsReadOnly; + } } } \ No newline at end of file diff --git a/src/Controls/src/Core/Shell/ShellItem.cs b/src/Controls/src/Core/Shell/ShellItem.cs index 72db9bf96516..494bb5c3a7c3 100644 --- a/src/Controls/src/Core/Shell/ShellItem.cs +++ b/src/Controls/src/Core/Shell/ShellItem.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -51,6 +52,7 @@ public TabBar() [ContentProperty(nameof(Items))] [EditorBrowsable(EditorBrowsableState.Never)] [TypeConverter(typeof(ShellItemConverter))] + [DebuggerTypeProxy(typeof(ShellItemDebugView))] public class ShellItem : ShellGroupItem, IShellItemController, IElementConfiguration, IPropertyPropagationController, IVisualTreeElement { #region PropertyKeys @@ -368,5 +370,18 @@ public override object ConvertFrom(ITypeDescriptorContext context, System.Global public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) => throw new NotSupportedException(); } + + /// + /// Provides a debug view for the class. + /// + /// The instance to debug. + private sealed class ShellItemDebugView(ShellItem shellItem) : BaseShellItemDebugView(shellItem) + { + public ShellSection CurrentItem => shellItem.CurrentItem; + + public IList Items => shellItem.Items; + + public Window Window => shellItem.Window; + } } } diff --git a/src/Controls/src/Core/Shell/ShellSection.cs b/src/Controls/src/Core/Shell/ShellSection.cs index e4548c48ce9a..82fd8e125c56 100644 --- a/src/Controls/src/Core/Shell/ShellSection.cs +++ b/src/Controls/src/Core/Shell/ShellSection.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Threading.Tasks; @@ -22,6 +23,7 @@ public class Tab : ShellSection [ContentProperty(nameof(Items))] [EditorBrowsable(EditorBrowsableState.Never)] [TypeConverter(typeof(ShellSectionTypeConverter))] + [DebuggerTypeProxy(typeof(ShellSectionDebugView))] public partial class ShellSection : ShellGroupItem, IShellSectionController, IPropertyPropagationController, IVisualTreeElement, IStackNavigation { #region PropertyKeys @@ -1270,5 +1272,18 @@ public override object ConvertFrom(ITypeDescriptorContext context, System.Global _ => throw new NotSupportedException(), }; } + + /// + /// Provides a debug view for the class. + /// + /// The instance to debug. + private sealed class ShellSectionDebugView(ShellSection section) + { + public ShellSection CurrentItem => section.CurrentItem; + + public IList Items => section.Items; + + public IReadOnlyList Stack => section.Stack; + } } } diff --git a/src/Controls/src/Core/Shell/ShellSectionCollection.cs b/src/Controls/src/Core/Shell/ShellSectionCollection.cs index 268105a90ee3..37a9ac74c819 100644 --- a/src/Controls/src/Core/Shell/ShellSectionCollection.cs +++ b/src/Controls/src/Core/Shell/ShellSectionCollection.cs @@ -1,10 +1,28 @@ using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; namespace Microsoft.Maui.Controls { + + [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] + [DebuggerTypeProxy(typeof(ShellSectionCollectionDebugView))] internal sealed class ShellSectionCollection : ShellElementCollection { public ShellSectionCollection() : base() { } + + string GetDebuggerDisplay() => $"Count = {Count}"; + + /// + /// Provides a debug view for the class. + /// + /// The instance to debug. + private sealed class ShellSectionCollectionDebugView(ShellSectionCollection collection) + { + public IList VisibleItems => collection.VisibleItems; + + public IReadOnlyCollection VisibleItemsReadOnly => collection.VisibleItemsReadOnly; + } } } \ No newline at end of file diff --git a/src/Controls/src/Core/VisualElement/VisualElement.cs b/src/Controls/src/Core/VisualElement/VisualElement.cs index 91b5f310a724..6161fcd8a8c1 100644 --- a/src/Controls/src/Core/VisualElement/VisualElement.cs +++ b/src/Controls/src/Core/VisualElement/VisualElement.cs @@ -269,9 +269,12 @@ static void OnTransformChanged(BindableObject bindable, object oldValue, object BindableProperty.Create("TransformOrigin", typeof(Point), typeof(VisualElement), new Point(.5d, .5d), propertyChanged: (b, o, n) => { (((VisualElement)b).AnchorX, ((VisualElement)b).AnchorY) = (Point)n; }); + bool _isVisibleExplicit = (bool)IsVisibleProperty.DefaultValue; + /// Bindable property for . public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(VisualElement), true, - propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable).OnIsVisibleChanged((bool)oldvalue, (bool)newvalue)); + propertyChanged: (bindable, oldvalue, newvalue) => ((VisualElement)bindable).OnIsVisibleChanged((bool)oldvalue, (bool)newvalue), + coerceValue: CoerceIsVisibleProperty); /// Bindable property for . public static readonly BindableProperty OpacityProperty = BindableProperty.Create(nameof(Opacity), typeof(double), typeof(VisualElement), 1d, coerceValue: (bindable, value) => ((double)value).Clamp(0, 1)); @@ -694,6 +697,36 @@ private protected bool InputTransparentCore } } + /// + /// This value represents the cumulative IsVisible value. + /// All types that override this property need to also invoke + /// the RefreshIsVisibleProperty() method if the value will change. + /// + private protected bool IsVisibleCore + { + get + { + if (_isVisibleExplicit == false) + { + // If the explicitly set value is false, then nothing else matters + // And we can save the effort of a Parent check + return false; + } + + var parent = Parent as VisualElement; + while (parent is not null) + { + if (!parent.IsVisible) + { + return false; + } + parent = parent.Parent as VisualElement; + } + + return _isVisibleExplicit; + } + } + /// /// Gets a value indicating whether this element is focused currently. This is a bindable property. /// @@ -1499,6 +1532,7 @@ internal virtual void OnIsVisibleChanged(bool oldValue, bool newValue) fe.Handler?.UpdateValue(nameof(IView.Visibility)); } + (this as IPropertyPropagationController)?.PropagatePropertyChanged(IsVisibleProperty.PropertyName); InvalidateMeasureInternal(InvalidationTrigger.Undefined); } @@ -1663,6 +1697,17 @@ static object CoerceInputTransparentProperty(BindableObject bindable, object val return false; } + static object CoerceIsVisibleProperty(BindableObject bindable, object value) + { + if (bindable is VisualElement visualElement) + { + visualElement._isVisibleExplicit = (bool)value; + return visualElement.IsVisibleCore; + } + + return false; + } + static void OnInputTransparentPropertyChanged(BindableObject bindable, object oldValue, object newValue) { (bindable as IPropertyPropagationController)?.PropagatePropertyChanged(VisualElement.InputTransparentProperty.PropertyName); @@ -1730,6 +1775,9 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName if (propertyName == null || propertyName == InputTransparentProperty.PropertyName) this.RefreshPropertyValue(InputTransparentProperty, _inputTransparentExplicit); + if (propertyName == null || propertyName == IsVisibleProperty.PropertyName) + this.RefreshPropertyValue(IsVisibleProperty, _isVisibleExplicit); + PropertyPropagationExtensions.PropagatePropertyChanged(propertyName, this, ((IVisualTreeElement)this).GetVisualChildren()); } @@ -1740,6 +1788,13 @@ void IPropertyPropagationController.PropagatePropertyChanged(string propertyName protected void RefreshIsEnabledProperty() => this.RefreshPropertyValue(IsEnabledProperty, _isEnabledExplicit); + /// + /// This method must always be called if some event occurs and the value of + /// the property will change. + /// + internal void RefreshIsVisibleProperty() => + this.RefreshPropertyValue(IsVisibleProperty, _isVisibleExplicit); + /// /// This method must always be called if some event occurs and the value of /// the InputTransparentCore property will change. diff --git a/src/Controls/tests/BindingSourceGen.UnitTests/IntegrationTests.cs b/src/Controls/tests/BindingSourceGen.UnitTests/IntegrationTests.cs index 3cb6580c6724..86994faf78d3 100644 --- a/src/Controls/tests/BindingSourceGen.UnitTests/IntegrationTests.cs +++ b/src/Controls/tests/BindingSourceGen.UnitTests/IntegrationTests.cs @@ -1,3 +1,6 @@ +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; using Microsoft.Maui.Controls.BindingSourceGen; using Xunit; @@ -1995,4 +1998,257 @@ internal static partial class GeneratedBindingInterceptors } """, actual); } + + [Fact] + public void GenerateCreateBindingWithXamlGeneratedSource() + { + var source = """ + using Microsoft.Maui.Controls; + using MyNamespace; + + var myPartialClass = new MyPartialClass(); + myPartialClass.CreateBinding(); + + namespace MyNamespace + { + public partial class MyPartialClass + { + public void CreateBinding() + { + Binding.Create(static (Slider s) => s.Value, source: slider); + } + } + } + """; + + var result = SourceGenHelpers.Run(source, [new BindingSourceGenerator(), new IncrementalGeneratorSlider()]); + var id = Math.Abs(result.Binding!.SimpleLocation.GetHashCode()); + AssertExtensions.AssertNoDiagnostics(result.SourceGeneratorDiagnostics, "Source generator"); + AssertExtensions.AssertNoDiagnostics(result.GeneratedCodeCompilationDiagnostics, "Generated code compilation"); + + AssertExtensions.CodeIsEqual( + $$""" + //------------------------------------------------------------------------------ + // + // This code was generated by a .NET MAUI source generator. + // + // Changes to this file may cause incorrect behavior and will be lost if + // the code is regenerated. + // + //------------------------------------------------------------------------------ + #nullable enable + + namespace System.Runtime.CompilerServices + { + using System; + using System.CodeDom.Compiler; + + + {{BindingCodeWriter.GeneratedCodeAttribute}} + [global::System.Diagnostics.Conditional("DEBUG")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + _ = version; + _ = data; + } + } + } + + namespace Microsoft.Maui.Controls.Generated + { + using System; + using System.CodeDom.Compiler; + using System.Runtime.CompilerServices; + using Microsoft.Maui.Controls.Internals; + + internal static partial class GeneratedBindingInterceptors + { + + {{BindingCodeWriter.GeneratedCodeAttribute}} + [InterceptsLocationAttribute({{result.Binding.InterceptableLocation.Version}}, @"{{result.Binding.InterceptableLocation.Data}}")] + public static BindingBase Create{{id}}( + Func getter, + BindingMode mode = BindingMode.Default, + IValueConverter? converter = null, + object? converterParameter = null, + string? stringFormat = null, + object? source = null, + object? fallbackValue = null, + object? targetNullValue = null) + { + Action? setter = null; + if (ShouldUseSetter(mode)) + { + setter = static (source, value) => + { + source.Value = value; + }; + } + + var binding = new TypedBinding( + getter: source => (getter(source), true), + setter, + handlers: new Tuple, string>[] + { + new(static source => source, "Value"), + }) + { + Mode = mode, + Converter = converter, + ConverterParameter = converterParameter, + StringFormat = stringFormat, + Source = source, + FallbackValue = fallbackValue, + TargetNullValue = targetNullValue + }; + return binding; + } + } + } + """, + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-13-13.g.cs"]); + } + + [Fact] + public void GenerateSetBindingWithXamlGeneratedSource() + { + var source = """ + using Microsoft.Maui.Controls; + using MyNamespace; + + var myPartialClass = new MyPartialClass(); + myPartialClass.CreateBinding(); + + namespace MyNamespace + { + public partial class MyPartialClass + { + public Label label { get; set; } = new Label(); + public void CreateBinding() + { + label.SetBinding(Label.TextProperty, static (Slider s) => s.Value, source: slider); + } + } + } + """; + + var result = SourceGenHelpers.Run(source, [new BindingSourceGenerator(), new IncrementalGeneratorSlider()]); + var id = Math.Abs(result.Binding!.SimpleLocation.GetHashCode()); + AssertExtensions.AssertNoDiagnostics(result.SourceGeneratorDiagnostics, "Source generator"); + AssertExtensions.AssertNoDiagnostics(result.GeneratedCodeCompilationDiagnostics, "Generated code compilation"); + + AssertExtensions.CodeIsEqual( + $$""" + //------------------------------------------------------------------------------ + // + // This code was generated by a .NET MAUI source generator. + // + // Changes to this file may cause incorrect behavior and will be lost if + // the code is regenerated. + // + //------------------------------------------------------------------------------ + #nullable enable + + namespace System.Runtime.CompilerServices + { + using System; + using System.CodeDom.Compiler; + + + {{BindingCodeWriter.GeneratedCodeAttribute}} + [global::System.Diagnostics.Conditional("DEBUG")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + _ = version; + _ = data; + } + } + } + + namespace Microsoft.Maui.Controls.Generated + { + using System; + using System.CodeDom.Compiler; + using System.Runtime.CompilerServices; + using Microsoft.Maui.Controls.Internals; + + internal static partial class GeneratedBindingInterceptors + { + + {{BindingCodeWriter.GeneratedCodeAttribute}} + [InterceptsLocationAttribute({{result.Binding.InterceptableLocation.Version}}, @"{{result.Binding.InterceptableLocation.Data}}")] + public static void SetBinding{{id}}( + this BindableObject bindableObject, + BindableProperty bindableProperty, + Func getter, + BindingMode mode = BindingMode.Default, + IValueConverter? converter = null, + object? converterParameter = null, + string? stringFormat = null, + object? source = null, + object? fallbackValue = null, + object? targetNullValue = null) + { + Action? setter = null; + if (ShouldUseSetter(mode, bindableProperty)) + { + setter = static (source, value) => + { + source.Value = value; + }; + } + + var binding = new TypedBinding( + getter: source => (getter(source), true), + setter, + handlers: new Tuple, string>[] + { + new(static source => source, "Value"), + }) + { + Mode = mode, + Converter = converter, + ConverterParameter = converterParameter, + StringFormat = stringFormat, + Source = source, + FallbackValue = fallbackValue, + TargetNullValue = targetNullValue + }; + bindableObject.SetBinding(bindableProperty, binding); + } + } + } + """, + result.GeneratedFiles["Path-To-Program.cs-GeneratedBindingInterceptors-14-11.g.cs"]); + } +} + + +internal class IncrementalGeneratorSlider : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterSourceOutput( + context.CompilationProvider, + (ctx, compilation) => + { + var source = """ + using Microsoft.Maui.Controls; + namespace MyNamespace + { + public partial class MyPartialClass + { + public Slider slider; + } + } + """; + ctx.AddSource("SampleSourceGeneratorOutput.g.cs", SourceText.From(source, Encoding.UTF8)); + }); + } } diff --git a/src/Controls/tests/Core.UnitTests/LinearGradientBrushTests.cs b/src/Controls/tests/Core.UnitTests/LinearGradientBrushTests.cs index 768d7710d05c..d60028723411 100644 --- a/src/Controls/tests/Core.UnitTests/LinearGradientBrushTests.cs +++ b/src/Controls/tests/Core.UnitTests/LinearGradientBrushTests.cs @@ -1,4 +1,5 @@ -using Microsoft.Maui.Graphics; +using Microsoft.Maui.Graphics; +using Microsoft.Maui.Platform; using Xunit; namespace Microsoft.Maui.Controls.Core.UnitTests @@ -73,6 +74,64 @@ public void TestNullOrEmptyLinearGradientBrush() Assert.False(Brush.IsNullOrEmpty(linearGradientBrush)); } + [Fact] + public void TestNullOrEmptyLinearGradientPaintWithEmptyGradientStop() + { + LinearGradientBrush linearGradientBrush = new LinearGradientBrush + { + StartPoint = new Point(0, 0), + EndPoint = new Point(1, 0), + GradientStops = new GradientStopCollection + { + new GradientStop(), + new GradientStop() + } + }; + + Paint linearGradientPaint = linearGradientBrush; + + Assert.True(linearGradientPaint.IsNullOrEmpty()); + } + + [Fact] + public void TestNullOrEmptyLinearGradientPaintWithNullGradientStop() + { + LinearGradientBrush linearGradientBrush = new LinearGradientBrush + { + StartPoint = new Point(0, 0), + EndPoint = new Point(1, 0), + GradientStops = new GradientStopCollection + { + null, + null + } + }; + + Paint linearGradientPaint = linearGradientBrush; + + Assert.True(linearGradientPaint.IsNullOrEmpty()); + } + + [Fact] + public void TestNullGradientStopLinearGradientPaint() + { + LinearGradientBrush linearGradientBrush = new LinearGradientBrush + { + StartPoint = new Point(0, 0), + EndPoint = new Point(1, 0), + GradientStops = new GradientStopCollection + { + new GradientStop { Color = Colors.Red, Offset = 0.1f }, + null, + new GradientStop { Color = Colors.Blue, Offset = 1.0f } + } + }; + + Paint linearGradientPaint = linearGradientBrush; + + Assert.False(linearGradientPaint.IsNullOrEmpty()); + } + [Fact] public void TestLinearGradientBrushPoints() { diff --git a/src/Controls/tests/Core.UnitTests/RadialGradientBrushTests.cs b/src/Controls/tests/Core.UnitTests/RadialGradientBrushTests.cs index 8348afe73592..d26e30391544 100644 --- a/src/Controls/tests/Core.UnitTests/RadialGradientBrushTests.cs +++ b/src/Controls/tests/Core.UnitTests/RadialGradientBrushTests.cs @@ -15,6 +15,44 @@ public void TestConstructor() Assert.Equal(0, gradientStops); } + [Fact] + public void TestNullOrEmptyRadialGradientPaintWithEmptyGradientStop() + { + RadialGradientBrush radialGradientBrush = new RadialGradientBrush + { + Center = new Point(0, 0), + Radius = 10, + GradientStops = new GradientStopCollection + { + new GradientStop(), + new GradientStop() + } + }; + + Paint radialGradientPaint = radialGradientBrush; + + Assert.True(radialGradientPaint.IsNullOrEmpty()); + } + + [Fact] + public void TestNullOrEmptyRadialGradientPaintWithNullGradientStop() + { + RadialGradientBrush radialGradientBrush = new RadialGradientBrush + { + Center = new Point(0, 0), + Radius = 10, + GradientStops = new GradientStopCollection + { + null, + null + } + }; + + Paint radialGradientPaint = radialGradientBrush; + + Assert.True(radialGradientPaint.IsNullOrEmpty()); + } + [Fact] public void TestConstructorUsingGradientStopCollection() { diff --git a/src/Controls/tests/Core.UnitTests/VisualElementTests.cs b/src/Controls/tests/Core.UnitTests/VisualElementTests.cs index 8ddabfbc1d12..05374344f645 100644 --- a/src/Controls/tests/Core.UnitTests/VisualElementTests.cs +++ b/src/Controls/tests/Core.UnitTests/VisualElementTests.cs @@ -317,5 +317,79 @@ public void WidthAndHeightRequestPropagateToHandler() Assert.Equal(2, heightMapperCalled); Assert.Equal(2, widthMapperCalled); } + + [Fact] + public void ShouldPropagateVisibilityToChildren() + { + var grid = new Grid() { IsVisible = false }; + var label = new Label() { IsVisible = true }; + grid.Add(label); + + Assert.False(label.IsVisible); + Assert.Equal(grid.IsVisible, label.IsVisible); + } + + [Theory] + [InlineData(false, true, true, false, false, false)] + [InlineData(true, false, true, true, false, false)] + public void IsVisiblePropagates(bool rootIsVisible, bool nestedIsVisible, bool childIsVisible, bool expectedRootVisibility, bool expectedNestedVisibility, bool expectedChildVisibility) + { + var root = new Grid() { IsVisible = rootIsVisible }; + var nested = new Grid() { IsVisible = nestedIsVisible }; + var child = new Button() { IsVisible = childIsVisible }; + + nested.Add(child); + root.Add(nested); + + Assert.Equal(root.IsVisible, expectedRootVisibility); + Assert.Equal(nested.IsVisible, expectedNestedVisibility); + Assert.Equal(child.IsVisible, expectedChildVisibility); + } + + [Fact] + public void IsVisibleParentCorrectlyUnsetsPropagatedChange() + { + var button = new Button(); + var grid = new Grid { button }; + + grid.IsVisible = false; + Assert.False(button.IsVisible); + + grid.IsVisible = true; + Assert.True(button.IsVisible); + } + + [Fact] + public void ButtonShouldStayHiddenIfExplicitlySet() + { + var button = new Button { IsVisible = false }; + var grid = new Grid { button }; + + grid.IsVisible = false; + Assert.False(button.IsVisible); + + // button stays hidden if it was explicitly set + grid.IsVisible = true; + Assert.False(button.IsVisible); + } + + [Fact] + public void ButtonShouldBeVisibleWhenExplicitlySetWhenParentIsVisible() + { + var button = new Button { IsVisible = false }; + var grid = new Grid { button }; + + // everything is hidden + grid.IsVisible = false; + Assert.False(button.IsVisible); + + // make button visible, but it should not appear + button.IsVisible = true; + Assert.False(button.IsVisible); + + // button appears when parent appears + grid.IsVisible = true; + Assert.True(button.IsVisible); + } } } diff --git a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTabBarTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTabBarTests.iOS.cs index 5b9fdce40d18..109d30a17d73 100644 --- a/src/Controls/tests/DeviceTests/Elements/Shell/ShellTabBarTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Shell/ShellTabBarTests.iOS.cs @@ -20,7 +20,18 @@ UITabBar GetTabBar(ShellSection item) var pagerParent = (shell.CurrentPage.Handler as IPlatformViewHandler) .PlatformView.FindParent(x => x.NextResponder is UITabBarController); - return pagerParent.Subviews.FirstOrDefault(v => v.GetType() == typeof(UITabBar)) as UITabBar; + // In macOS 15 Sequoia, the UITabBar is nested within the second subview (index 1) of the pagerParent. + if (OperatingSystem.IsMacCatalystVersionAtLeast(15, 0) || OperatingSystem.IsMacOSVersionAtLeast(15, 0)) + { + var subview = pagerParent.Subviews.ElementAtOrDefault(1); + + if (subview?.Subviews is null) + return null; + + return subview.Subviews.OfType().FirstOrDefault(); + } + + return pagerParent.Subviews.OfType().FirstOrDefault(); } async Task ValidateTabBarIconColor( diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/EntryClearButtonColorShouldMatchTextColor.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/EntryClearButtonColorShouldMatchTextColor.png index 3ba4cb3d026a..9db68c726ec4 100644 Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/EntryClearButtonColorShouldMatchTextColor.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/EntryClearButtonColorShouldMatchTextColor.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlyoutItemTextShouldDisplayProperly.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlyoutItemTextShouldDisplayProperly.png new file mode 100644 index 000000000000..6bed0269af05 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/FlyoutItemTextShouldDisplayProperly.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeBackgroundColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeBackgroundColor_VerifyVisualState.png new file mode 100644 index 000000000000..51a2dc8a3e6e Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeBackgroundColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeFlowDirection_RTL_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeFlowDirection_RTL_VerifyVisualState.png new file mode 100644 index 000000000000..1bd65197c8b4 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeFlowDirection_RTL_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeMaxTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeMaxTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..5c0ce0ecc9f3 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeMaxTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeMinTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeMinTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..59fde738e547 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeMinTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeThumbColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeThumbColor_VerifyVisualState.png new file mode 100644 index 000000000000..4da5c16cc9d1 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeThumbColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeThumbImageSource_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeThumbImageSource_VerifyVisualState.png new file mode 100644 index 000000000000..12f2706e2e25 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_ChangeThumbImageSource_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_FlowDirection_RTL_SetMaximumValue_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_FlowDirection_RTL_SetMaximumValue_VerifyVisualState.png new file mode 100644 index 000000000000..52c6c83a21f9 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_FlowDirection_RTL_SetMaximumValue_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_FlowDirection_RTL_SetMinimumValue_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_FlowDirection_RTL_SetMinimumValue_VerifyVisualState.png new file mode 100644 index 000000000000..518079bc5be5 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_FlowDirection_RTL_SetMinimumValue_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_FlowDirection_RTL_SetValue_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_FlowDirection_RTL_SetValue_VerifyVisualState.png new file mode 100644 index 000000000000..bd621b390283 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_FlowDirection_RTL_SetValue_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndIsEnable_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndIsEnable_VerifyVisualState.png new file mode 100644 index 000000000000..1753364c8b1b Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndIsEnable_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndMaxTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndMaxTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..9dceaafb2aa4 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndMaxTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndMinTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndMinTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..ec2b09dad9ce Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndMinTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndThumbColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndThumbColor_VerifyVisualState.png new file mode 100644 index 000000000000..1524760734cb Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetBackgroundColorAndThumbColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetEnabledStateToFalse_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetEnabledStateToFalse_VerifyVisualState.png new file mode 100644 index 000000000000..9754e154a0ed Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetEnabledStateToFalse_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetFlowDirectionAndMaxTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetFlowDirectionAndMaxTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..7d0d1c0cfc45 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetFlowDirectionAndMaxTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetFlowDirectionAndMinTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetFlowDirectionAndMinTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..59fc482dc223 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetFlowDirectionAndMinTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndBackgroundColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndBackgroundColor_VerifyVisualState.png new file mode 100644 index 000000000000..c4262655cc78 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndBackgroundColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndMaxTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndMaxTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..0cc145f6baf4 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndMaxTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndMinTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndMinTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..ce8911e2d19e Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndMinTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndThumbColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndThumbColor_VerifyVisualState.png new file mode 100644 index 000000000000..b579bf61ce7a Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsEnableAndThumbColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndBackgroundColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndBackgroundColor_VerifyVisualState.png new file mode 100644 index 000000000000..b1b7aa38f340 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndBackgroundColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndMaxTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndMaxTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..605908a128c0 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndMaxTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndMinTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndMinTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..649fe7d0a4aa Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndMinTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndThumbColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndThumbColor_VerifyVisualState.png new file mode 100644 index 000000000000..2bc51df4a370 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetIsVisibleAndThumbColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackAndBackgroundColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackAndBackgroundColor_VerifyVisualState.png new file mode 100644 index 000000000000..84bfac231f77 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackAndBackgroundColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackAndMinTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackAndMinTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..cd805274e866 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackAndMinTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackAndThumbColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackAndThumbColor_VerifyVisualState.png new file mode 100644 index 000000000000..530db95d189f Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackAndThumbColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackColorAndValue_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackColorAndValue_VerifyVisualState.png new file mode 100644 index 000000000000..23fa86490139 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackColorAndValue_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackColorTestFlowDirection_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackColorTestFlowDirection_VerifyVisualState.png new file mode 100644 index 000000000000..d3db3394380f Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaxTrackColorTestFlowDirection_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaximumAndChangeFlowDirection_RTL.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaximumAndChangeFlowDirection_RTL.png new file mode 100644 index 000000000000..42c16a659f7e Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMaximumAndChangeFlowDirection_RTL.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackAndBackgroundColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackAndBackgroundColor_VerifyVisualState.png new file mode 100644 index 000000000000..d8b57d9fb2d4 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackAndBackgroundColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackAndMaxTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackAndMaxTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..f3db18b9cce8 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackAndMaxTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackAndThumbColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackAndThumbColor_VerifyVisualState.png new file mode 100644 index 000000000000..6ce4eb08dd30 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackAndThumbColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackColorAndValue_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackColorAndValue_VerifyVisualState.png new file mode 100644 index 000000000000..e6a24540d2d8 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackColorAndValue_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackColorTestFlowDirection_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackColorTestFlowDirection_VerifyVisualState.png new file mode 100644 index 000000000000..d70632c52fda Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinTrackColorTestFlowDirection_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinimumAndChangeFlowDirection_RTL.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinimumAndChangeFlowDirection_RTL.png new file mode 100644 index 000000000000..5781bdae716a Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetMinimumAndChangeFlowDirection_RTL.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbAndBackgroundColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbAndBackgroundColor_VerifyVisualState.png new file mode 100644 index 000000000000..3ff405e0a179 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbAndBackgroundColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbAndMaxTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbAndMaxTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..d74d725050a4 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbAndMaxTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbAndMinTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbAndMinTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..45c7fcf75053 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbAndMinTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbColorAndThumbImageSource_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbColorAndThumbImageSource_VerifyVisualState.png new file mode 100644 index 000000000000..d2304f47a58c Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbColorAndThumbImageSource_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbImageSourceAndThumbColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbImageSourceAndThumbColor_VerifyVisualState.png new file mode 100644 index 000000000000..76afb10e9b2b Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetThumbImageSourceAndThumbColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndFlowDirection_RTL_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndFlowDirection_RTL_VerifyVisualState.png new file mode 100644 index 000000000000..174ef946a809 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndFlowDirection_RTL_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndMaxTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndMaxTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..544a7ffc9586 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndMaxTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndMinTrackColor_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndMinTrackColor_VerifyVisualState.png new file mode 100644 index 000000000000..f1d5415a8de9 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndMinTrackColor_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndThumbImageSource_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndThumbImageSource_VerifyVisualState.png new file mode 100644 index 000000000000..9c716cf138d7 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetValueAndThumbImageSource_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetVisibilityToFalse_VerifyVisualState.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetVisibilityToFalse_VerifyVisualState.png new file mode 100644 index 000000000000..a5a2257aa841 Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/Slider_SetVisibilityToFalse_VerifyVisualState.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/UpdatedIsEnabledProperty.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/UpdatedIsEnabledProperty.png new file mode 100644 index 000000000000..81c896f844fd Binary files /dev/null and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/UpdatedIsEnabledProperty.png differ diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyTimePickerApperance.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyTimePickerAppearance.png similarity index 100% rename from src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyTimePickerApperance.png rename to src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerifyTimePickerAppearance.png diff --git a/src/Controls/tests/TestCases.HostApp/CoreViews/CorePageView.cs b/src/Controls/tests/TestCases.HostApp/CoreViews/CorePageView.cs index 28f3bce0b956..9f2fc6c54f1a 100644 --- a/src/Controls/tests/TestCases.HostApp/CoreViews/CorePageView.cs +++ b/src/Controls/tests/TestCases.HostApp/CoreViews/CorePageView.cs @@ -76,6 +76,7 @@ public override string ToString() new GalleryPageFactory(() => new SwipeViewCoreGalleryPage(), "SwipeView Gallery"), new GalleryPageFactory(() => new TimePickerCoreGalleryPage(), "Time Picker Gallery"), new GalleryPageFactory(() => new WebViewCoreGalleryPage(), "WebView Gallery"), + new GalleryPageFactory(() => new SliderControlPage(), "Slider Feature Matrix"), }; public CorePageView(Page rootPage) diff --git a/src/Controls/tests/TestCases.HostApp/FeatureMatrix/Slider/SliderControlPage.xaml b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/Slider/SliderControlPage.xaml new file mode 100644 index 000000000000..5c438612d183 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/Slider/SliderControlPage.xaml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + diff --git a/src/Controls/tests/TestCases.HostApp/FeatureMatrix/Slider/SliderControlPage.xaml.cs b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/Slider/SliderControlPage.xaml.cs new file mode 100644 index 000000000000..f09914927612 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/Slider/SliderControlPage.xaml.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.Maui.Controls; + +namespace Maui.Controls.Sample +{ + public class SliderControlPage : NavigationPage + { + private SliderViewModel _viewModel; + + public SliderControlPage() + { + _viewModel = new SliderViewModel(); +#if ANDROID + BarTextColor = Colors.White; +#endif + PushAsync(new SliderControlMainPage(_viewModel)); + } + } + + public partial class SliderControlMainPage : ContentPage + { + private SliderViewModel _viewModel; + + public SliderControlMainPage(SliderViewModel viewModel) + { + InitializeComponent(); + _viewModel = viewModel; + BindingContext = _viewModel; + } + + private async void NavigateToOptionsPage_Clicked(object sender, EventArgs e) + { + BindingContext = _viewModel = new SliderViewModel(); + await Navigation.PushAsync(new SliderOptionsPage(_viewModel)); + } + } +} diff --git a/src/Controls/tests/TestCases.HostApp/FeatureMatrix/Slider/SliderOptionsPage.xaml b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/Slider/SliderOptionsPage.xaml new file mode 100644 index 000000000000..08ee64982ede --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/FeatureMatrix/Slider/SliderOptionsPage.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + +