diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 7fe404670d4e..a742909b2a9f 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -154,6 +154,8 @@ public class IssueXXXXX : _IssuesUITest
- Verify AutomationId references match between XAML and test code
- Ensure tests follow the established naming and inheritance patterns
+IMPORTANT NOTE: When a new UI test category is added to `UITestCategories.cs`, we need to also update the `ui-tests.yml` to include this new category. Make sure to detect this in your reviews.
+
### Code Formatting
Before committing any changes, format the codebase using the following command to ensure consistent code style:
diff --git a/Microsoft.Maui-dev.sln b/Microsoft.Maui-dev.sln
index 50107d813ac8..a66f4363aff9 100644
--- a/Microsoft.Maui-dev.sln
+++ b/Microsoft.Maui-dev.sln
@@ -130,7 +130,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Controls.Foldable", "src\Co
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{A9C514B9-1EE2-4A12-8E8A-CE16D87545C3}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTests", "src\BlazorWebView\tests\MauiDeviceTests\MauiBlazorWebView.DeviceTests.csproj", "{5FEA7500-0ACE-4C26-9A7B-2EB3958CBBC6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTests", "src\BlazorWebView\tests\DeviceTests\MauiBlazorWebView.DeviceTests.csproj", "{5FEA7500-0ACE-4C26-9A7B-2EB3958CBBC6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SharedSource", "SharedSource", "{4F2926C8-43AB-4328-A735-D9EAD699F81D}"
ProjectSection(SolutionItems) = preProject
diff --git a/Microsoft.Maui-mac.slnf b/Microsoft.Maui-mac.slnf
index 928aae767e7e..eab1a15d3e6d 100644
--- a/Microsoft.Maui-mac.slnf
+++ b/Microsoft.Maui-mac.slnf
@@ -5,7 +5,7 @@
"src\\BlazorWebView\\samples\\MauiRazorClassLibrarySample\\MauiRazorClassLibrarySample.csproj",
"src\\BlazorWebView\\samples\\WebViewAppShared\\WebViewAppShared.csproj",
"src\\BlazorWebView\\src\\Maui\\Microsoft.AspNetCore.Components.WebView.Maui.csproj",
- "src\\BlazorWebView\\tests\\MauiDeviceTests\\MauiBlazorWebView.DeviceTests.csproj",
+ "src\\BlazorWebView\\tests\\DeviceTests\\MauiBlazorWebView.DeviceTests.csproj",
"src\\Controls\\Foldable\\src\\Controls.Foldable.csproj",
"src\\Controls\\Maps\\src\\Controls.Maps.csproj",
"src\\Controls\\samples\\Controls.Sample.Profiling\\Maui.Controls.Sample.Profiling.csproj",
diff --git a/Microsoft.Maui-vscode.sln b/Microsoft.Maui-vscode.sln
index d74ad80a1185..9ea2c6ec754e 100644
--- a/Microsoft.Maui-vscode.sln
+++ b/Microsoft.Maui-vscode.sln
@@ -123,7 +123,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Controls.Foldable", "src\Co
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{A9C514B9-1EE2-4A12-8E8A-CE16D87545C3}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTests", "src\BlazorWebView\tests\MauiDeviceTests\MauiBlazorWebView.DeviceTests.csproj", "{5FEA7500-0ACE-4C26-9A7B-2EB3958CBBC6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTests", "src\BlazorWebView\tests\DeviceTests\MauiBlazorWebView.DeviceTests.csproj", "{5FEA7500-0ACE-4C26-9A7B-2EB3958CBBC6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SharedSource", "SharedSource", "{4F2926C8-43AB-4328-A735-D9EAD699F81D}"
ProjectSection(SolutionItems) = preProject
diff --git a/Microsoft.Maui-windows.slnf b/Microsoft.Maui-windows.slnf
index ce7648da24a5..0fc4bc16c1bb 100644
--- a/Microsoft.Maui-windows.slnf
+++ b/Microsoft.Maui-windows.slnf
@@ -9,7 +9,7 @@
"src\\BlazorWebView\\src\\Maui\\Microsoft.AspNetCore.Components.WebView.Maui.csproj",
"src\\BlazorWebView\\src\\WindowsForms\\Microsoft.AspNetCore.Components.WebView.WindowsForms.csproj",
"src\\BlazorWebView\\src\\Wpf\\Microsoft.AspNetCore.Components.WebView.Wpf.csproj",
- "src\\BlazorWebView\\tests\\MauiDeviceTests\\MauiBlazorWebView.DeviceTests.csproj",
+ "src\\BlazorWebView\\tests\\DeviceTests\\MauiBlazorWebView.DeviceTests.csproj",
"src\\Controls\\Foldable\\src\\Controls.Foldable.csproj",
"src\\Controls\\Maps\\src\\Controls.Maps.csproj",
"src\\Controls\\samples\\Controls.Sample.Profiling\\Maui.Controls.Sample.Profiling.csproj",
diff --git a/Microsoft.Maui.LegacyControlGallery.sln b/Microsoft.Maui.LegacyControlGallery.sln
index 570c40a5f558..e555b8990ce5 100644
--- a/Microsoft.Maui.LegacyControlGallery.sln
+++ b/Microsoft.Maui.LegacyControlGallery.sln
@@ -130,7 +130,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Controls.Foldable", "src\Co
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{A9C514B9-1EE2-4A12-8E8A-CE16D87545C3}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTests", "src\BlazorWebView\tests\MauiDeviceTests\MauiBlazorWebView.DeviceTests.csproj", "{5FEA7500-0ACE-4C26-9A7B-2EB3958CBBC6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTests", "src\BlazorWebView\tests\DeviceTests\MauiBlazorWebView.DeviceTests.csproj", "{5FEA7500-0ACE-4C26-9A7B-2EB3958CBBC6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SharedSource", "SharedSource", "{4F2926C8-43AB-4328-A735-D9EAD699F81D}"
ProjectSection(SolutionItems) = preProject
diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln
index b4fd18748316..6edfb32d2ff4 100644
--- a/Microsoft.Maui.sln
+++ b/Microsoft.Maui.sln
@@ -138,7 +138,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Controls.Foldable", "src\Co
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{A9C514B9-1EE2-4A12-8E8A-CE16D87545C3}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTests", "src\BlazorWebView\tests\MauiDeviceTests\MauiBlazorWebView.DeviceTests.csproj", "{5FEA7500-0ACE-4C26-9A7B-2EB3958CBBC6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MauiBlazorWebView.DeviceTests", "src\BlazorWebView\tests\DeviceTests\MauiBlazorWebView.DeviceTests.csproj", "{5FEA7500-0ACE-4C26-9A7B-2EB3958CBBC6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SharedSource", "SharedSource", "{4F2926C8-43AB-4328-A735-D9EAD699F81D}"
ProjectSection(SolutionItems) = preProject
diff --git a/docs/DevelopmentTips.md b/docs/DevelopmentTips.md
index a915b4a0607c..cb4495e2dc2f 100644
--- a/docs/DevelopmentTips.md
+++ b/docs/DevelopmentTips.md
@@ -178,3 +178,116 @@ These tests can be run using the Test Explorer in VS, or from the command line w
```bash
dotnet test src/TestUtils/src/Microsoft.Maui.IntegrationTests --logger "console;verbosity=diagnostic" --filter "Name=Build\(%22maui%22,%22net7.0%22,%22Debug%22,False\)"
```
+
+## Running Device Tests on Helix
+
+.NET MAUI now supports running device tests on [.NET Engineering Services Helix](https://helix.dot.net) using XHarness. Helix provides cloud-based device testing infrastructure that enables running tests across multiple platforms and devices in parallel.
+
+### Overview
+
+Device tests can be run on the following platforms via Helix:
+
+
+The device test projects include:
+- `Controls.DeviceTests` - UI control tests
+- `Core.DeviceTests` - Core framework tests
+- `Graphics.DeviceTests` - Graphics and drawing tests
+- `Essentials.DeviceTests` - Platform API tests
+- `MauiBlazorWebView.DeviceTests` - Blazor WebView tests
+
+
+### Available Helix Queues
+
+Check available queues at [helix.dot.net](https://helix.dot.net). The current configuration uses:
+
+- **iOS**: `osx.15.arm64.Open`
+- **Mac Catalyst**: `osx.15.arm64.Open`
+- **Android**: `ubuntu.2204.amd64.android.33.open`
+
+### Running Device Tests Locally
+
+#### Step 1: Build Build Tasks
+First, restore tools and build the required MSBuild tasks:
+
+```bash
+# Restore dotnet tools
+dotnet tool restore
+
+# Build the Build tasks (required)
+./build.sh -restore -build -configuration Release -projects './Microsoft.Maui.BuildTasks.slnf' /bl:BuildBuildTasks.binlog -warnAsError false
+```
+
+#### Step 2: Build Device Tests
+Build the device test projects:
+
+```bash
+# Build device tests for all platforms
+./build.sh -restore -build -configuration Release /p:BuildDeviceTests=true /bl:BuildDeviceTests.binlog -warnAsError false
+```
+
+#### Step 3: Send to Helix
+Submit the tests to Helix for execution:
+
+```bash
+# Send to Helix for Android
+./eng/common/msbuild.sh ./eng/helix_xharness.proj /restore /p:TreatWarningsAsErrors=false /t:Test /p:TargetOS=android /bl:sendhelix.binlog -verbosity:diag
+
+# Send to Helix for iOS
+./eng/common/msbuild.sh ./eng/helix_xharness.proj /restore /p:TreatWarningsAsErrors=false /t:Test /p:TargetOS=ios /bl:sendhelix.binlog -verbosity:diag
+
+# Send to Helix for Mac Catalyst
+./eng/common/msbuild.sh ./eng/helix_xharness.proj /restore /p:TreatWarningsAsErrors=false /t:Test /p:TargetOS=maccatalyst /bl:sendhelix.binlog -verbosity:diag
+```
+
+### Windows Commands
+
+For Windows development, use the corresponding `.cmd` files:
+
+```cmd
+REM Build Build tasks
+.\build.cmd -restore -build -configuration Release -projects ".\Microsoft.Maui.BuildTasks.slnf" /bl:BuildBuildTasks.binlog -warnAsError false
+
+REM Build device tests
+.\build.cmd -restore -build -configuration Release /p:BuildDeviceTests=true /bl:BuildDeviceTests.binlog -warnAsError false
+
+REM Send to Helix (Android example)
+.\eng\common\msbuild.cmd .\eng\helix_xharness.proj /restore /p:TreatWarningsAsErrors=false /t:Test /p:TargetOS=android /bl:sendhelix.binlog -verbosity:diag
+```
+
+### Configuration Details
+
+The Helix configuration is defined in `eng/helix_xharness.proj` and includes:
+
+- **Timeouts**: 2-hour work item timeout, 1-hour test timeout
+- **Test Discovery**: Automatically discovers test bundles for each scenario
+- **Platform Targeting**: Specific target frameworks per platform
+- **Queue Selection**: Platform-appropriate Helix queues
+- **XHarness Integration**: Uses XHarness for device orchestration
+
+### Troubleshooting
+
+#### Common Issues
+
+1. **Build failures**: Ensure you've built the BuildTasks first
+2. **Missing devices**: Check queue availability at [helix.dot.net](https://helix.dot.net)
+3. **Authentication**: For CI scenarios, ensure proper Azure DevOps access tokens
+4. **Timeouts**: Tests have generous timeouts but may need adjustment for complex scenarios
+
+#### Logging and Diagnostics
+
+- Use `/bl:filename.binlog` for detailed MSBuild logs
+- Add `-verbosity:diag` for maximum diagnostic output
+- Check Helix job results at the provided URL after submission
+
+### CI Integration
+
+The device tests are integrated into the CI pipeline via:
+- `eng/pipelines/common/stage-device-tests.yml` - Pipeline template
+- `eng/test-configuration.json` - Test retry configuration
+- Automatic execution on PR builds for qualifying changes
+
+### Additional Resources
+
+- [XHarness on Helix Documentation](https://github.com/dotnet/arcade/blob/main/src/Microsoft.DotNet.Helix/Sdk/tools/xharness-runner/Readme.md#android-apk-payloads)
+- [Helix Documentation](https://github.com/dotnet/arcade/tree/main/src/Microsoft.DotNet.Helix)
+- [Example Helix Run](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1115383&view=results)
diff --git a/docs/README.md b/docs/README.md
index 22fc15184845..5abf72ea0224 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -12,6 +12,7 @@ The table below outlines the different docs in this folder and what they are hel
|----------------------|---------------------|---------------------|
| [CG Manifest](CgManifest.md) | Guide to Component Governance manifest generation and management | Contributors who need to manage third-party dependencies or include CG manifest in packages |
| [Code Documentation Guidelines](CodeDocumentationGuidelines.md) | Overview of the guidelines for the inline code documentation | Community members and collaborators looking to understand how to add good inline code comments that fuel our IntelliSense and online API docs |
+| [Development Tips](DevelopmentTips.md) | Development tips including debugging, building, and device testing on Helix | Contributors who need guidance on development workflows, including running device tests on cloud infrastructure |
| [Issue management](IssueManagementPolicies.md) | Overview of policies in place to manage issues| Community members and collaborators looking to understand how we handle closed issues, issues that need author feedback, etc. |
| [Release Schedule](ReleaseSchedule.md) | Overview of .NET MAUI version releases | Anyone who is interested in .NET MAUI versions and release dates |
| [Triage process](TriageProcess.md)| Overview of the issue triage process used in the repo | Anyone looking to understand the triage process on the repo |
diff --git a/eng/Build.props b/eng/Build.props
index 9a58ebc2ba11..3caad25a1c42 100644
--- a/eng/Build.props
+++ b/eng/Build.props
@@ -1,7 +1,14 @@
-
-
-
+
+
+
+
+
+
+
+ CodesignRequireProvisioningProfile=false
+
+
\ No newline at end of file
diff --git a/eng/Versions.props b/eng/Versions.props
index e03b87bda02a..334fb960542d 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -38,17 +38,17 @@
$(MicrosoftNETCoreAppRefPackageVersion)$(MicrosoftNETCoreAppRefPackageVersion)
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.935.0.61
@@ -59,27 +59,27 @@
8.0.148
- 9.0.8
+ 9.0.9$(MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportVersion)
- 1.7.250606001
+ 1.7.25090900310.0.22621.7561.3.21.0.3179.45
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
- 9.0.8
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.9
+ 9.0.98.0.19$(MicrosoftAspNetCorePackageVersion)
diff --git a/eng/devices/devices-shared.cake b/eng/devices/devices-shared.cake
index bbe08fa13cb8..bdaa4c1c640f 100644
--- a/eng/devices/devices-shared.cake
+++ b/eng/devices/devices-shared.cake
@@ -214,19 +214,19 @@ void HandleTestResults(string resultsDir, bool testsFailed, bool needsNameFix, s
if (FileExists(resultsFile))
{
Information($"Test results found on {resultsDir}");
- MoveFile(resultsFile, resultsFile.GetDirectory().CombineWithFilePath($"TestResults{suffix}.xml"));
+ MoveFile(resultsFile, resultsFile.GetDirectory().CombineWithFilePath($"testResults{suffix}.xml"));
var logFiles = GetFiles($"{resultsDir}/*.log");
foreach (var logFile in logFiles)
{
- if (logFile.GetFilename().ToString().StartsWith("TestResults"))
+ if (logFile.GetFilename().ToString().StartsWith("testResults"))
{
// These are log files that have already been renamed
continue;
}
Information($"Log file found: {logFile.GetFilename().ToString()}");
- MoveFile(logFile, resultsFile.GetDirectory().CombineWithFilePath($"TestResults{suffix}-{logFile.GetFilename()}"));
+ MoveFile(logFile, resultsFile.GetDirectory().CombineWithFilePath($"testResults{suffix}-{logFile.GetFilename()}"));
}
}
}
@@ -249,9 +249,9 @@ void HandleTestResults(string resultsDir, bool testsFailed, bool needsNameFix, s
CopyFiles($"{resultsDir}/{searchQuery}", failurePath);
// We don't want these to upload
- MoveFile($"{failurePath}/TestResults{suffix}.xml", $"{failurePath}/Results{suffix}.xml");
+ MoveFile($"{failurePath}/testResults{suffix}.xml", $"{failurePath}/Results{suffix}.xml");
}
- FailRunOnOnlyInconclusiveTests($"{resultsDir}/TestResults{suffix}.xml");
+ FailRunOnOnlyInconclusiveTests($"{resultsDir}/testResults{suffix}.xml");
}
DirectoryPath DetermineBinlogDirectory(string projectPath, string binlogArg)
diff --git a/eng/devices/windows.cake b/eng/devices/windows.cake
index 15896a3190cd..aba1333694c7 100644
--- a/eng/devices/windows.cake
+++ b/eng/devices/windows.cake
@@ -5,6 +5,13 @@ using System.Security.Cryptography.X509Certificates;
const string defaultVersion = "10.0.19041.0";
+// Test category filtering:
+// Use --test-filter to run only specific categories. Examples:
+// --test-filter="Button" # Run only Button category
+// --test-filter="Button,Label" # Run Button and Label categories
+// --test-filter="Button,Layout" # Run any category containing "Button" or "Layout"
+// Categories are matched case-insensitively using substring matching
+
// required
string DEFAULT_WINDOWS_PROJECT = "../../src/Controls/tests/TestCases.WinUI.Tests/Controls.TestCases.WinUI.Tests.csproj";
FilePath PROJECT = Argument("project", EnvironmentVariable("WINDOWS_TEST_PROJECT") ?? DEFAULT_WINDOWS_PROJECT);
@@ -181,20 +188,80 @@ Task("buildOnly")
DotNetPublish(PROJECT.FullPath, s);
});
+// Helper function to wait for a specific test result file with timeout
+Func WaitForCategoryTestResult = (string expectedFile, string categoryName) => {
+ var timeoutInSeconds = 480; // 8 minutes per category
+ var waited = 0;
+
+ Information($"Waiting for test results file: {expectedFile}");
+
+ while (!FileExists(expectedFile) && waited < timeoutInSeconds) {
+ System.Threading.Thread.Sleep(1000);
+ waited++;
+
+ if (waited % 10 == 0) { // Log every 10 seconds
+ Information($"Still waiting for {categoryName} test results... ({waited}s)");
+ }
+ }
+
+ if (FileExists(expectedFile)) {
+ Information($"✓ Found test results for {categoryName} after {waited} seconds");
+ return true;
+ } else {
+ Warning($"✗ Timeout waiting for {categoryName} test results after {waited} seconds");
+ return false;
+ }
+};
+
+// Helper function to filter categories based on testFilter parameter
+Func FilterCategories = (string[] allCategories) => {
+ if (string.IsNullOrWhiteSpace(testFilter)) {
+ Information("No test filter specified, running all categories");
+ return allCategories;
+ }
+
+ var filters = testFilter.Split(',', StringSplitOptions.RemoveEmptyEntries)
+ .Select(f => f.Trim())
+ .ToArray();
+
+ var filteredCategories = allCategories.Where(category =>
+ filters.Any(filter =>
+ category.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0
+ )
+ ).ToArray();
+
+ Information($"Test filter '{testFilter}' applied:");
+ Information($" - Total categories available: {allCategories.Length}");
+ Information($" - Filtered categories to run: {filteredCategories.Length}");
+
+ if (filteredCategories.Length > 0) {
+ Information(" - Categories that will run:");
+ foreach (var category in filteredCategories) {
+ Information($" * {category}");
+ }
+ } else {
+ Warning($"No categories matched the filter '{testFilter}'. Available categories:");
+ foreach (var category in allCategories) {
+ Information($" * {category}");
+ }
+ }
+
+ return filteredCategories;
+};
+
Task("testOnly")
.IsDependentOn("SetupTestPaths")
.Does(() =>
{
- var waitForResultTimeoutInSeconds = 240;
-
CleanDirectories(TEST_RESULTS);
-
Information("Cleaned directories");
var testResultsPath = MakeAbsolute((DirectoryPath)TEST_RESULTS).FullPath.Replace("/", "\\");
var testResultsFile = testResultsPath + $"\\TestResults-{PACKAGEID.Replace(".", "_")}";
- if (!string.IsNullOrWhiteSpace(testFilter))
+ // For Controls project tests, each category gets its own result file, so don't append testFilter here
+ // For non-Controls tests, append testFilter to the single result file
+ if (!isControlsProjectTestRun && !string.IsNullOrWhiteSpace(testFilter))
{
testResultsFile += SanitizeTestResultsFilename($"-{testFilter}");
}
@@ -216,6 +283,10 @@ Task("testOnly")
DeleteFile(testsToRunFile);
}
+ // Track completed and failed test categories
+ var completedCategories = new List();
+ var failedCategories = new List();
+
if (isPackagedTestRun)
{
Information("isPackagedTestRuns");
@@ -286,22 +357,46 @@ Task("testOnly")
var startArgsInitial = "Start-Process shell:AppsFolder\\$((Get-AppxPackage -Name \"" + PACKAGEID + "\").PackageFamilyName)!App -ArgumentList \"" + testResultsFile + "\", \"-1\"";
StartProcess("powershell", startArgsInitial);
- Information($"Waiting 10 seconds for process to finish...");
+ Information($"Waiting 10 seconds for category discovery to finish...");
System.Threading.Thread.Sleep(10000);
- var testCategoriesToRun = System.IO.File.ReadAllLines(testsToRunFile).Length;
+ if (!FileExists(testsToRunFile)) {
+ throw new Exception("Test categories file was not created during discovery phase");
+ }
+
+ var expectedCategories = System.IO.File.ReadAllLines(testsToRunFile);
+ var filteredCategories = FilterCategories(expectedCategories);
+
+ if (filteredCategories.Length == 0) {
+ Information("No categories to run after applying filter. Skipping test execution.");
+ return;
+ }
+
+ Information($"Will run {filteredCategories.Length} filtered categories out of {expectedCategories.Length} total");
- for (int i = 0; i <= testCategoriesToRun; i++)
+ for (int i = 0; i < filteredCategories.Length; i++)
{
- var startArgs = "Start-Process shell:AppsFolder\\$((Get-AppxPackage -Name \"" + PACKAGEID + "\").PackageFamilyName)!App -ArgumentList \"" + testResultsFile + "\", \"" + i + "\"";
-
+ var categoryName = filteredCategories[i];
+ // Find the original index of this category in the expected categories
+ var originalIndex = Array.IndexOf(expectedCategories, categoryName);
+ var expectedResultFile = testResultsPath + $"\\TestResults-{PACKAGEID.Replace(".", "_")}_{categoryName}.xml";
+
+ Information($"Running category {originalIndex}: {categoryName} (filtered {i + 1}/{filteredCategories.Length})");
+
+ var startArgs = "Start-Process shell:AppsFolder\\$((Get-AppxPackage -Name \"" + PACKAGEID + "\").PackageFamilyName)!App -ArgumentList \"" + testResultsFile + "\", \"" + originalIndex + "\"";
Information(startArgs);
// Start the DeviceTests app for packaged
StartProcess("powershell", startArgs);
- Information($"Waiting 10 seconds for the next...");
- System.Threading.Thread.Sleep(10000);
+ // Wait for this specific category's results
+ if (WaitForCategoryTestResult(expectedResultFile, categoryName)) {
+ completedCategories.Add(categoryName);
+ Information($"✓ Category {categoryName} completed successfully");
+ } else {
+ failedCategories.Add(categoryName);
+ Error($"✗ Category {categoryName} failed or timed out");
+ }
}
}
else
@@ -312,99 +407,122 @@ Task("testOnly")
// Start the DeviceTests app for packaged
StartProcess("powershell", startArgs);
+
+ // Wait for the single test result file
+ if (WaitForCategoryTestResult(testResultsFile, "All Tests")) {
+ completedCategories.Add("All Tests");
+ } else {
+ failedCategories.Add("All Tests");
+ }
}
}
else
{
- // Unpackaged process blocks the thread, so we can wait shorter for the results
- waitForResultTimeoutInSeconds = 30;
-
if (isControlsProjectTestRun)
{
// Start the app once, this will trigger the discovery of the test categories
StartProcess(TEST_APP, testResultsFile + " -1");
+
+ Information($"Waiting 10 seconds for category discovery to finish...");
+ System.Threading.Thread.Sleep(10000);
+
+ if (!FileExists(testsToRunFile)) {
+ throw new Exception("Test categories file was not created during discovery phase");
+ }
+
+ var expectedCategories = System.IO.File.ReadAllLines(testsToRunFile);
+ var filteredCategories = FilterCategories(expectedCategories);
+
+ if (filteredCategories.Length == 0) {
+ Information("No categories to run after applying filter. Skipping test execution.");
+ return;
+ }
- var testCategoriesToRun = System.IO.File.ReadAllLines(testsToRunFile).Length;
+ Information($"Will run {filteredCategories.Length} filtered categories out of {expectedCategories.Length} total");
- for (int i = 0; i <= testCategoriesToRun; i++)
+ for (int i = 0; i < filteredCategories.Length; i++)
{
+ var categoryName = filteredCategories[i];
+ // Find the original index of this category in the expected categories
+ var originalIndex = Array.IndexOf(expectedCategories, categoryName);
+ var expectedResultFile = testResultsPath + $"\\TestResults-{PACKAGEID.Replace(".", "_")}_{categoryName}.xml";
+
+ Information($"Running category {originalIndex}: {categoryName} (filtered {i + 1}/{filteredCategories.Length})");
+
// Start the DeviceTests app for unpackaged
- StartProcess(TEST_APP, testResultsFile + " " + i);
+ StartProcess(TEST_APP, testResultsFile + " " + originalIndex);
+
+ // Wait for this specific category's results
+ if (WaitForCategoryTestResult(expectedResultFile, categoryName)) {
+ completedCategories.Add(categoryName);
+ Information($"✓ Category {categoryName} completed successfully");
+ } else {
+ failedCategories.Add(categoryName);
+ Error($"✗ Category {categoryName} failed or timed out");
+ }
}
}
else
{
StartProcess(TEST_APP, testResultsFile);
+
+ // Wait for the single test result file
+ if (WaitForCategoryTestResult(testResultsFile, "All Tests")) {
+ completedCategories.Add("All Tests");
+ } else {
+ failedCategories.Add("All Tests");
+ }
}
}
- var waited = 0;
- while (System.IO.Directory.GetFiles(testResultsPath, "TestResults-*.xml").Length == 0) {
- System.Threading.Thread.Sleep(1000);
- waited++;
+ // Final validation and reporting
+ Information($"=== Test Execution Summary ===");
+ Information($"Completed categories: {completedCategories.Count}");
+ Information($"Failed categories: {failedCategories.Count}");
- Information($"Waiting {waited} second(s) for tests to finish...");
- if (waited >= waitForResultTimeoutInSeconds)
- break;
+ if (completedCategories.Any()) {
+ Information("✓ Successfully completed categories:");
+ foreach (var category in completedCategories) {
+ Information($" - {category}");
+ }
}
- // If we're running the Controls project, double-check if we have all test result files
- // and if the categories we expected to run match the test result files
- if (isControlsProjectTestRun)
- {
- var expectedCategories = System.IO.File.ReadAllLines(testsToRunFile);
- var expectedCategoriesRanCount = expectedCategories.Length;
- var actualResultFileCount = System.IO.Directory.GetFiles(testResultsPath, "TestResults-*.xml").Length;
-
- while (actualResultFileCount < expectedCategoriesRanCount) {
- actualResultFileCount = System.IO.Directory.GetFiles(testResultsPath, "TestResults-*.xml").Length;
- System.Threading.Thread.Sleep(1000);
- waited++;
-
- Information($"Waiting {waited} additional second(s) for tests to finish...");
- if (waited >= 30)
- break;
+ if (failedCategories.Any()) {
+ Error("✗ Failed or timed out categories:");
+ foreach (var category in failedCategories) {
+ Error($" - {category}");
}
-
- if (FileExists(testsToRunFile))
- {
- DeleteFile(testsToRunFile);
- }
-
- // While the count should match exactly, if we get more files somehow we'll allow it
- // If it's less, throw an exception to fail the pipeline.
- if (actualResultFileCount < expectedCategoriesRanCount)
- {
- // Grab the category name from the file name
- // Ex: "TestResults-com_microsoft_maui_controls_devicetests_Frame.xml" -> "Frame"
- var actualFiles = System.IO.Directory.GetFiles(testResultsPath, "TestResults-*.xml");
- var actualCategories = actualFiles.Select(x => x.Substring(0, x.Length - 4) // Remove ".xml"
- .Split('_').Last()).ToList();
+ }
- foreach (var category in expectedCategories)
- {
- if (!actualCategories.Contains(category))
- {
- Error($"Error: missing test file result for {category}");
- }
- }
+ // Clean up the test categories file
+ if (FileExists(testsToRunFile))
+ {
+ DeleteFile(testsToRunFile);
+ }
- throw new Exception($"Expected test result files: {expectedCategoriesRanCount}, actual files: {actualResultFileCount}, some process(es) might have crashed.");
- }
+ // Check if we have any test result files at all
+ var actualResultFiles = System.IO.Directory.GetFiles(testResultsPath, "TestResults-*.xml");
+ if (actualResultFiles.Length == 0)
+ {
+ throw new Exception($"No test result files found. All test processes may have crashed or failed to start.");
}
- if(System.IO.Directory.GetFiles(testResultsPath, "TestResults-*.xml").Length == 0)
+ // If we're running Controls tests, validate we have results for expected categories
+ if (isControlsProjectTestRun && failedCategories.Any())
{
- throw new Exception($"Test result file(s) not found after {waited} seconds, process might have crashed or not completed yet.");
+ throw new Exception($"Some test categories failed to complete: {string.Join(", ", failedCategories)}. Expected {completedCategories.Count + failedCategories.Count} categories, but {failedCategories.Count} failed.");
}
- foreach(var file in System.IO.Directory.GetFiles(testResultsPath, "TestResults-*.xml"))
+ // Check for test failures in the result files
+ foreach(var file in actualResultFiles)
{
var failed = XmlPeek(file, "/assemblies/assembly[@failed > 0 or @errors > 0]/@failed");
if (!string.IsNullOrEmpty(failed)) {
- throw new Exception($"At least {failed} test(s) failed.");
+ throw new Exception($"At least {failed} test(s) failed in {System.IO.Path.GetFileName(file)}.");
}
}
+
+ Information($"✓ All test executions completed successfully with {actualResultFiles.Length} result file(s)");
});
Task("build")
diff --git a/eng/helix.proj b/eng/helix.proj
index e588f10bbfa1..5c3d8bd40280 100644
--- a/eng/helix.proj
+++ b/eng/helix.proj
@@ -2,18 +2,17 @@
test/product/$(BUILD_BUILDNUMBER)
+ defaultWindows.10.Amd64.Open;OSX.15.ARM64.Openmaui
-
truesdk
-
truetrue
+
-
Windows.10.Amd64.Open;OSX.1200.Amd64.Open;OSX.1200.ARM64.Open;Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64false
@@ -22,10 +21,10 @@
-
-
-
-
+
+
+
+
diff --git a/eng/helix_xharness.proj b/eng/helix_xharness.proj
new file mode 100644
index 000000000000..d7408f0b512d
--- /dev/null
+++ b/eng/helix_xharness.proj
@@ -0,0 +1,127 @@
+
+
+ test/devices/
+ $(BUILD_BUILDNUMBER)
+ default
+ osx.15.arm64.Open
+ osx.15.arm64.Open
+ ubuntu.2204.amd64.android.33.open
+ maui
+ true
+ sdk
+ true
+ true
+ $(_MauiDotNetTfm)
+
+
+
+
+ $(RepoRoot)artifacts/bin/
+
+ Windows.10.Amd64.Open;OSX.1200.Amd64.Open;OSX.1200.ARM64.Open;Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
+ false
+ maui/localbuild/
+ t001
+
+
+
+ $(BUILD_SOURCESDIRECTORY)/artifacts/bin/
+ true
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ Controls.DeviceTests
+ $(ScenariosDir)Controls.DeviceTests
+ Microsoft.Maui.Controls.DeviceTests
+ com.microsoft.maui.controls.devicetests
+
+
+
+ Core.DeviceTests
+ $(ScenariosDir)Core.DeviceTests
+ Microsoft.Maui.Core.DeviceTests
+ com.microsoft.maui.core.devicetests
+
+
+
+ Graphics.DeviceTests
+ $(ScenariosDir)Graphics.DeviceTests
+ Microsoft.Maui.Graphics.DeviceTests
+ com.microsoft.maui.graphics.devicetests
+
+
+
+ Essentials.DeviceTests
+ $(ScenariosDir)Essentials.DeviceTests
+ Microsoft.Maui.Essentials.DeviceTests
+ com.microsoft.maui.essentials.devicetests
+
+
+
+ MauiBlazorWebView.DeviceTests
+ $(ScenariosDir)MauiBlazorWebView.DeviceTests
+ Microsoft.Maui.MauiBlazorWebView.DeviceTests
+ com.microsoft.maui.mauiblazorwebview.devicetests
+
+
+
+
+
+
+
+
+
+
+ <_MAUIScenarioSearch Include="@(MAUIScenario)" />
+
+
+
+
+
+ ios-simulator-64
+ 02:00:00
+ 01:00:00
+ %(_MAUIScenarioSearch.ScenarioDirectoryName)
+
+
+
+
+
+ maccatalyst
+ 02:00:00
+ 01:00:00
+ %(_MAUIScenarioSearch.ScenarioDirectoryName)
+
+
+
+
+ <_apks Include="%(_MAUIScenarioSearch.PayloadDirectory)/Release/$(TargetFrameworkToTest)-android/**/*Signed.apk" />
+
+ 02:00:00
+ 01:00:00
+ %(Filename)
+
+
+
+
+ $([System.String]::Copy('%(AndroidPackageName)').Replace('-Signed',''))
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eng/pipelines/arcade/stage-device-tests.yml b/eng/pipelines/arcade/stage-device-tests.yml
new file mode 100644
index 000000000000..bc439ce3c98b
--- /dev/null
+++ b/eng/pipelines/arcade/stage-device-tests.yml
@@ -0,0 +1,196 @@
+# Template for devicetests on dnceng
+parameters:
+- name: prepareSteps
+ type: stepList
+ default: []
+- name: postSteps
+ type: stepList
+ default: []
+- name: pool
+ type: object
+- name: enableSourceBuild
+ type: boolean
+ default: false
+- name: enableSourceIndex
+ type: boolean
+ default: false
+- name: sourceIndexParams
+ type: object
+ default: []
+- name: runAsPublic
+ type: boolean
+ default: true
+- name: helixProject
+ type: string
+ default: 'eng/helix_xharness.proj'
+- name: extraHelixArguments
+ type: string
+ default: ''
+- name: BuildConfiguration
+ type: string
+ default: 'Release'
+- name: TargetFrameworkVersion
+ type: string
+ default: 'net9.0'
+- name: appArtifactName
+ type: string
+ default: 'device_tests_app'
+- name: checkoutDirectory
+ type: string
+ default: $(System.DefaultWorkingDirectory)
+
+stages:
+- stage: deviceTests_ios_android
+ displayName: ${{ parameters.TargetFrameworkVersion }} ios/catalyst/android Helix Tests
+ dependsOn: []
+ jobs:
+ - template: ${{ iif(eq(parameters.runAsPublic, 'true'), '/eng/common/templates/jobs/jobs.yml', '/eng/common/templates-official/jobs/jobs.yml@self') }}
+ parameters:
+ helixRepo: dotnet/maui
+ pool: ${{ parameters.pool }}
+ enableMicrobuild: true
+ enablePublishUsingPipelines: true
+ enablePublishBuildAssets: true
+ enableTelemetry: true
+ enableSourceBuild: ${{ parameters.enableSourceBuild }}
+ enableSourceIndex: ${{ parameters.enableSourceIndex }}
+ sourceIndexParams: ${{ parameters.sourceIndexParams }}
+ publishAssetsImmediately: true
+ enablePublishBuildArtifacts: true
+ enablePublishTestResults: true
+ workspace:
+ clean: all
+ jobs:
+ - job: builddevice_tests
+ displayName: Build Device Tests
+ timeoutInMinutes: 240
+ variables:
+ - name: _BuildConfig
+ value: ${{ parameters.BuildConfiguration }}
+ preSteps:
+ - checkout: self
+ fetchDepth: 1
+ clean: true
+
+ steps:
+ - ${{ each step in parameters.prepareSteps }}:
+ - ${{ each pair in step }}:
+ ${{ pair.key }}: ${{ pair.value }}
+
+ # Run on public pipeline
+ - script: $(_buildScriptMacOS) -restore -build -configuration $(_BuildConfig) -projects '$(Build.SourcesDirectory)/Microsoft.Maui.BuildTasks.slnf' /bl:BuildBuildTasks.binlog $(_OfficialBuildIdArgs)
+ displayName: Build BuildTasks
+
+ - script: $(_buildScriptMacOS) -restore -build -configuration $(_BuildConfig) /p:BuildDeviceTests=true /bl:BuildDeviceTests.binlog
+ displayName: Build DeviceTests
+
+ - publish: ${{ parameters.checkoutDirectory }}/artifacts/bin
+ displayName: Publish Succeeded Artifacts Directory
+ condition: succeeded()
+ artifact: ${{ parameters.appArtifactName }}
+
+ - publish: ${{ parameters.checkoutDirectory }}/artifacts/bin
+ displayName: Publish Failed Artifacts Directory
+ condition: not(succeeded())
+ artifact: ${{ parameters.appArtifactName }}_failed_$(System.JobAttempt)
+
+ - template: ${{ iif(eq(parameters.runAsPublic, 'true'), '/eng/common/templates/jobs/jobs.yml', '/eng/common/templates-official/jobs/jobs.yml@self') }}
+ parameters:
+ helixRepo: dotnet/maui
+ pool: ${{ parameters.pool }}
+ enableMicrobuild: false
+ enablePublishUsingPipelines: true
+ enablePublishBuildAssets: true
+ enableTelemetry: true
+ enableSourceBuild: ${{ parameters.enableSourceBuild }}
+ enableSourceIndex: ${{ parameters.enableSourceIndex }}
+ sourceIndexParams: ${{ parameters.sourceIndexParams }}
+ publishAssetsImmediately: true
+ enablePublishBuildArtifacts: true
+ enablePublishTestResults: false
+ workspace:
+ clean: all
+ jobs:
+ - job: device_tests_ios
+ dependsOn:
+ - builddevice_tests
+ displayName: Run DeviceTests iOS
+ timeoutInMinutes: 240
+ variables:
+ - name: _BuildConfig
+ value: ${{ parameters.BuildConfiguration }}
+ preSteps:
+ - checkout: self
+ fetchDepth: 1
+ clean: true
+
+ - task: DownloadPipelineArtifact@2
+ displayName: 'Download Build'
+ condition: succeeded()
+ inputs:
+ artifactName: ${{ parameters.appArtifactName }}
+ targetPath: ${{ parameters.checkoutDirectory }}/artifacts/bin
+
+ - template: /eng/common/templates-official/steps/send-to-helix.yml
+ parameters:
+ HelixProjectPath: ${{ parameters.helixProject }}
+ HelixProjectArguments: /p:TargetOS=ios /p:TestRunNameSuffix="_$(_BuildConfig)" /p:TestRunNamePrefix="DeviceTestsIOS_"
+ HelixConfiguration: $(_BuildConfig)
+ IncludeDotNetCli: true
+ DisplayNamePrefix: DeviceTestsIOS
+
+ - job: device_tests_maccatalyst
+ dependsOn:
+ - builddevice_tests
+ displayName: Run DeviceTests MacCatalyst
+ timeoutInMinutes: 240
+ variables:
+ - name: _BuildConfig
+ value: ${{ parameters.BuildConfiguration }}
+ preSteps:
+ - checkout: self
+ fetchDepth: 1
+ clean: true
+
+ - task: DownloadPipelineArtifact@2
+ displayName: 'Download Build'
+ condition: succeeded()
+ inputs:
+ artifactName: ${{ parameters.appArtifactName }}
+ targetPath: ${{ parameters.checkoutDirectory }}/artifacts/bin
+
+ - template: /eng/common/templates-official/steps/send-to-helix.yml
+ parameters:
+ HelixProjectPath: ${{ parameters.helixProject }}
+ HelixProjectArguments: /p:TargetOS=maccatalyst /p:TestRunNameSuffix="_$(_BuildConfig)" /p:TestRunNamePrefix="DeviceTestsMacCatalyst_"
+ HelixConfiguration: $(_BuildConfig)
+ IncludeDotNetCli: true
+ DisplayNamePrefix: DeviceTestsMacCatalyst
+
+ - job: device_tests_android
+ dependsOn:
+ - builddevice_tests
+ displayName: Run DeviceTests Android
+ timeoutInMinutes: 240
+ variables:
+ - name: _BuildConfig
+ value: ${{ parameters.BuildConfiguration }}
+ preSteps:
+ - checkout: self
+ fetchDepth: 1
+ clean: true
+
+ - task: DownloadPipelineArtifact@2
+ displayName: 'Download Build'
+ condition: succeeded()
+ inputs:
+ artifactName: ${{ parameters.appArtifactName }}
+ targetPath: ${{ parameters.checkoutDirectory }}/artifacts/bin
+
+ - template: /eng/common/templates-official/steps/send-to-helix.yml
+ parameters:
+ HelixProjectPath: ${{ parameters.helixProject }}
+ HelixProjectArguments: /p:TargetOS=android /p:TestRunNameSuffix="_$(_BuildConfig)" /p:TestRunNamePrefix="DeviceTestsAndroid_"
+ HelixConfiguration: $(_BuildConfig)
+ IncludeDotNetCli: true
+ DisplayNamePrefix: DeviceTestsAndroid
diff --git a/eng/pipelines/arcade/variables.yml b/eng/pipelines/arcade/variables.yml
index a9e968312f69..ebcc51444080 100644
--- a/eng/pipelines/arcade/variables.yml
+++ b/eng/pipelines/arcade/variables.yml
@@ -11,7 +11,7 @@ variables:
- name: _buildScript
value: $(Build.SourcesDirectory)\build.cmd -ci
- name: _buildScriptMacOS
- value: $(Build.SourcesDirectory)/build.sh -ci -warnAsError 0
+ value: $(Build.SourcesDirectory)/build.sh -ci -warnAsError false
- name: _helixScriptMacOS
value: $(Build.SourcesDirectory)/helix.sh
- name: _helixScript
diff --git a/eng/pipelines/common/device-tests-steps.yml b/eng/pipelines/common/device-tests-steps.yml
index fb39b3fe531f..61b19e924fd5 100644
--- a/eng/pipelines/common/device-tests-steps.yml
+++ b/eng/pipelines/common/device-tests-steps.yml
@@ -171,7 +171,7 @@ steps:
condition: always()
inputs:
testResultsFormat: xUnit
- testResultsFiles: '$(TestResultsDirectory)/**/TestResults*(-*).xml'
+ testResultsFiles: '$(TestResultsDirectory)/**/*Results*(-*).xml'
testRunTitle: '$(Agent.JobName) (attempt $(System.JobAttempt))'
# Publish the job artifacts
diff --git a/eng/pipelines/common/variables.yml b/eng/pipelines/common/variables.yml
index d98ed6b6f0cf..356321466fdf 100644
--- a/eng/pipelines/common/variables.yml
+++ b/eng/pipelines/common/variables.yml
@@ -51,7 +51,7 @@ variables:
value: ''
# Variable groups required for all builds
-- ${{ if and(ne(variables['Build.DefinitionName'], 'maui-pr'), ne(variables['Build.DefinitionName'], 'dotnet-maui')) }}:
+- ${{ if and(ne(variables['Build.DefinitionName'], 'maui-pr'), ne(variables['Build.DefinitionName'], 'dotnet-maui'), ne(variables['Build.DefinitionName'], 'maui-pr-devicetests')) }}:
- group: maui-provisionator # This is just needed for the provisionator
- group: MAUI # This is the main MAUI variable group that contains secret for the apple certificate
diff --git a/eng/pipelines/device-tests.yml b/eng/pipelines/device-tests.yml
index 6359911c2199..65d442bbe383 100644
--- a/eng/pipelines/device-tests.yml
+++ b/eng/pipelines/device-tests.yml
@@ -44,7 +44,9 @@ pr:
- THIRD-PARTY-NOTICES.TXT
variables:
+- template: /eng/common/templates/variables/pool-providers.yml@self
- template: /eng/pipelines/common/variables.yml@self
+- template: /eng/pipelines/arcade/variables.yml@self
parameters:
@@ -52,6 +54,11 @@ parameters:
type: boolean
default: false
+- name: provisionatorChannel
+ displayName: 'Provisionator channel'
+ type: string
+ default: 'latest'
+
- name: BuildEverything
type: boolean
default: false
@@ -83,8 +90,14 @@ parameters:
- name: windowsPool
type: object
default:
- name: $(windowsTestsVmPool)
- vmImage: $(windowsTestsVmImage)
+ name: Azure Pipelines
+ vmImage: windows-2022
+
+- name: macOSPool
+ type: object
+ default:
+ name: Azure Pipelines
+ vmImage: macOS-15
- name: targetFrameworkVersions
type: object
@@ -93,81 +106,142 @@ parameters:
stages:
- ${{ each targetFrameworkVersion in parameters.targetFrameworkVersions }}:
- - template: common/device-tests.yml
- parameters:
- androidPool: ${{ parameters.androidPool }}
- iosPool: ${{ parameters.iosPool }}
- catalystPool: ${{ parameters.catalystPool }}
- windowsPool: ${{ parameters.windowsPool }}
- # agentPoolAccessToken: $(AgentPoolAccessToken)
- targetFrameworkVersion: ${{ targetFrameworkVersion }}
- ${{ if or(parameters.BuildEverything, and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['System.TeamProject'], 'devdiv'))) }}:
- androidApiLevels: [ 33, 30, 29, 28, 27, 26, 25, 24, 23 ]
- iosVersions: [ 'simulator-18.0' ]
- catalystVersions: [ 'latest' ]
- windowsVersions: [ 'packaged', 'unpackaged' ]
- provisionatorChannel: ${{ parameters.provisionatorChannel }}
- skipProvisioning: ${{ or(not(parameters.UseProvisionator), false) }}
- ${{ else }}:
- androidApiLevels: [ 33, 23 ]
- iosVersions: [ 'simulator-18.0' ]
- catalystVersions: [ 'latest' ]
+
+ # Run on dnceng-public (Helix)
+ - ${{ if eq(variables['Build.DefinitionName'], 'maui-pr-devicetests') }}:
+
+ # Use Helix for iOS / Android and MacCatalyst Device Tests
+ - template: /eng/pipelines/arcade/stage-device-tests.yml@self
+ parameters:
+ pool: ${{ parameters.macOSPool }}
+ runAsPublic: true
+ prepareSteps:
+ - template: /eng/pipelines/common/provision.yml@self
+ parameters:
+ checkoutDirectory: '$(System.DefaultWorkingDirectory)'
+ skipJdk: false
+ skipAndroidCommonSdks: false
+ skipAndroidPlatformApis: false
+ onlyAndroidPlatformDefaultApis: true
+ skipAndroidEmulatorImages: true
+ skipAndroidCreateAvds: true
+ skipProvisioning: true
+ skipXcode: true
+
+ # Just use the old way for Windows Device Tests
+ - template: common/device-tests.yml
+ parameters:
+ windowsPool: ${{ parameters.windowsPool }}
+ targetFrameworkVersion: ${{ targetFrameworkVersion }}
windowsVersions: [ 'packaged', 'unpackaged' ]
- provisionatorChannel: ${{ parameters.provisionatorChannel }}
- skipProvisioning: ${{ not(parameters.UseProvisionator) }}
- projects:
- - name: essentials
- desc: Essentials
- androidApiLevelsExclude: [ 25, 27 ] # Ignore for now API25 since the runs's are not stable
- androidConfiguration: 'Release'
- iOSConfiguration: 'Debug'
- windowsConfiguration: 'Debug'
- windowsPackageId: 'com.microsoft.maui.essentials.devicetests'
- android: $(System.DefaultWorkingDirectory)/src/Essentials/test/DeviceTests/Essentials.DeviceTests.csproj
- ios: $(System.DefaultWorkingDirectory)/src/Essentials/test/DeviceTests/Essentials.DeviceTests.csproj
- catalyst: $(System.DefaultWorkingDirectory)/src/Essentials/test/DeviceTests/Essentials.DeviceTests.csproj
- windows: $(System.DefaultWorkingDirectory)/src/Essentials/test/DeviceTests/Essentials.DeviceTests.csproj
- - name: graphics
- desc: Graphics
- androidApiLevelsExclude: [ 25, 28 ] # Ignore for now API25 and API28 since the runs's are not stable
- androidConfiguration: 'Release'
- iOSConfiguration: 'Debug'
- windowsConfiguration: 'Debug'
- windowsPackageId: 'com.microsoft.maui.graphics.devicetests'
- android: $(System.DefaultWorkingDirectory)/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
- ios: $(System.DefaultWorkingDirectory)/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
- catalyst: $(System.DefaultWorkingDirectory)/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
- windows: $(System.DefaultWorkingDirectory)/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
- - name: core
- desc: Core
- androidApiLevelsExclude: [ 25 ] # Ignore for now API25 since the runs's are not stable
- androidConfiguration: 'Release'
- iOSConfiguration: 'Debug'
- windowsConfiguration: 'Debug'
- windowsPackageId: 'com.microsoft.maui.core.devicetests'
- android: $(System.DefaultWorkingDirectory)/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
- ios: $(System.DefaultWorkingDirectory)/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
- catalyst: $(System.DefaultWorkingDirectory)/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
- windows: $(System.DefaultWorkingDirectory)/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
- - name: controls
- desc: Controls
- androidApiLevelsExclude: [ 27, 25 ] # Ignore for now API25 since the runs's are not stable
- androidConfiguration: 'Debug'
- iOSConfiguration: 'Debug'
- windowsConfiguration: 'Debug'
- windowsPackageId: 'com.microsoft.maui.controls.devicetests'
- android: $(System.DefaultWorkingDirectory)/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
- ios: $(System.DefaultWorkingDirectory)/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
- catalyst: $(System.DefaultWorkingDirectory)/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
- windows: $(System.DefaultWorkingDirectory)/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
- - name: blazorwebview
- desc: BlazorWebView
- androidApiLevelsExclude: [ 30, 29, 28, 27, 26, 25, 24, 23, 22, 21 ] # BlazorWebView requires a recent version of Chrome
- androidConfiguration: 'Release'
- iOSConfiguration: 'Debug'
- windowsConfiguration: 'Debug'
- windowsPackageId: 'Microsoft.Maui.MauiBlazorWebView.DeviceTests'
- android: $(System.DefaultWorkingDirectory)/src/BlazorWebView/tests/MauiDeviceTests/MauiBlazorWebView.DeviceTests.csproj
- ios: $(System.DefaultWorkingDirectory)/src/BlazorWebView/tests/MauiDeviceTests/MauiBlazorWebView.DeviceTests.csproj
- catalyst: $(System.DefaultWorkingDirectory)/src/BlazorWebView/tests/MauiDeviceTests/MauiBlazorWebView.DeviceTests.csproj
- windows: $(System.DefaultWorkingDirectory)/src/BlazorWebView/tests/MauiDeviceTests/MauiBlazorWebView.DeviceTests.csproj
+ skipProvisioning: true
+ projects:
+ - name: essentials
+ desc: Essentials
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'com.microsoft.maui.essentials.devicetests'
+ windows: $(System.DefaultWorkingDirectory)/src/Essentials/test/DeviceTests/Essentials.DeviceTests.csproj
+ - name: graphics
+ desc: Graphics
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'com.microsoft.maui.graphics.devicetests'
+ windows: $(System.DefaultWorkingDirectory)/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
+ - name: core
+ desc: Core
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'com.microsoft.maui.core.devicetests'
+ windows: $(System.DefaultWorkingDirectory)/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
+ - name: controls
+ desc: Controls
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'com.microsoft.maui.controls.devicetests'
+ windows: $(System.DefaultWorkingDirectory)/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
+ - name: blazorwebview
+ desc: BlazorWebView
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'Microsoft.Maui.MauiBlazorWebView.DeviceTests'
+ windows: $(System.DefaultWorkingDirectory)/src/BlazorWebView/tests/DeviceTests/MauiBlazorWebView.DeviceTests.csproj
+ platforms:
+ - windows
+
+ # Run on xamarin public instance
+ - ${{ else }}:
+
+ - template: common/device-tests.yml
+ parameters:
+ androidPool: ${{ parameters.androidPool }}
+ iosPool: ${{ parameters.iosPool }}
+ catalystPool: ${{ parameters.catalystPool }}
+ windowsPool: ${{ parameters.windowsPool }}
+ agentPoolAccessToken: $(AgentPoolAccessToken)
+ targetFrameworkVersion: ${{ targetFrameworkVersion }}
+ ${{ if or(parameters.BuildEverything, and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['System.TeamProject'], 'devdiv'))) }}:
+ androidApiLevels: [ 33, 30, 29, 28, 27, 26, 25, 24, 23 ]
+ iosVersions: [ 'simulator-18.0' ]
+ catalystVersions: [ 'latest' ]
+ windowsVersions: [ 'packaged', 'unpackaged' ]
+ provisionatorChannel: ${{ parameters.provisionatorChannel }}
+ skipProvisioning: ${{ or(not(parameters.UseProvisionator), false) }}
+ ${{ else }}:
+ androidApiLevels: [ 33, 23 ]
+ iosVersions: [ 'simulator-18.0' ]
+ catalystVersions: [ 'latest' ]
+ windowsVersions: [ 'packaged', 'unpackaged' ]
+ provisionatorChannel: ${{ parameters.provisionatorChannel }}
+ skipProvisioning: ${{ not(parameters.UseProvisionator) }}
+ projects:
+ - name: essentials
+ desc: Essentials
+ androidApiLevelsExclude: [ 25, 27 ] # Ignore for now API25 since the runs's are not stable
+ androidConfiguration: 'Release'
+ iOSConfiguration: 'Debug'
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'com.microsoft.maui.essentials.devicetests'
+ android: $(System.DefaultWorkingDirectory)/src/Essentials/test/DeviceTests/Essentials.DeviceTests.csproj
+ ios: $(System.DefaultWorkingDirectory)/src/Essentials/test/DeviceTests/Essentials.DeviceTests.csproj
+ catalyst: $(System.DefaultWorkingDirectory)/src/Essentials/test/DeviceTests/Essentials.DeviceTests.csproj
+ windows: $(System.DefaultWorkingDirectory)/src/Essentials/test/DeviceTests/Essentials.DeviceTests.csproj
+ - name: graphics
+ desc: Graphics
+ androidApiLevelsExclude: [ 25 ] # Ignore for now API25 since the runs's are not stable
+ androidConfiguration: 'Release'
+ iOSConfiguration: 'Debug'
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'com.microsoft.maui.graphics.devicetests'
+ android: $(System.DefaultWorkingDirectory)/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
+ ios: $(System.DefaultWorkingDirectory)/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
+ catalyst: $(System.DefaultWorkingDirectory)/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
+ windows: $(System.DefaultWorkingDirectory)/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
+ - name: core
+ desc: Core
+ androidApiLevelsExclude: [ 25 ] # Ignore for now API25 since the runs's are not stable
+ androidConfiguration: 'Release'
+ iOSConfiguration: 'Debug'
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'com.microsoft.maui.core.devicetests'
+ android: $(System.DefaultWorkingDirectory)/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
+ ios: $(System.DefaultWorkingDirectory)/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
+ catalyst: $(System.DefaultWorkingDirectory)/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
+ windows: $(System.DefaultWorkingDirectory)/src/Core/tests/DeviceTests/Core.DeviceTests.csproj
+ - name: controls
+ desc: Controls
+ androidApiLevelsExclude: [ 27, 25 ] # Ignore for now API25 since the runs's are not stable
+ androidConfiguration: 'Debug'
+ iOSConfiguration: 'Debug'
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'com.microsoft.maui.controls.devicetests'
+ android: $(System.DefaultWorkingDirectory)/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
+ ios: $(System.DefaultWorkingDirectory)/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
+ catalyst: $(System.DefaultWorkingDirectory)/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
+ windows: $(System.DefaultWorkingDirectory)/src/Controls/tests/DeviceTests/Controls.DeviceTests.csproj
+ - name: blazorwebview
+ desc: BlazorWebView
+ androidApiLevelsExclude: [ 30, 29, 28, 27, 26, 25, 24, 23, 22, 21 ] # BlazorWebView requires a recent version of Chrome
+ androidConfiguration: 'Release'
+ iOSConfiguration: 'Debug'
+ windowsConfiguration: 'Debug'
+ windowsPackageId: 'Microsoft.Maui.MauiBlazorWebView.DeviceTests'
+ android: $(System.DefaultWorkingDirectory)/src/BlazorWebView/tests/DeviceTests/MauiBlazorWebView.DeviceTests.csproj
+ ios: $(System.DefaultWorkingDirectory)/src/BlazorWebView/tests/DeviceTests/MauiBlazorWebView.DeviceTests.csproj
+ catalyst: $(System.DefaultWorkingDirectory)/src/BlazorWebView/tests/DeviceTests/MauiBlazorWebView.DeviceTests.csproj
+ windows: $(System.DefaultWorkingDirectory)/src/BlazorWebView/tests/DeviceTests/MauiBlazorWebView.DeviceTests.csproj
diff --git a/eng/test-configuration.json b/eng/test-configuration.json
new file mode 100644
index 000000000000..4ef6d9cf4a76
--- /dev/null
+++ b/eng/test-configuration.json
@@ -0,0 +1,12 @@
+{
+ "version" : 1,
+ "defaultOnFailure": "fail",
+ "localRerunCount" : 2,
+ "retryOnRules": [
+ {"testAssembly": {"wildcard": "Microsoft.Maui.Controls.*" }}
+ ],
+ "failOnRules": [
+ ],
+ "quarantineRules": [
+ ]
+}
\ No newline at end of file
diff --git a/es-metadata.yml b/es-metadata.yml
new file mode 100644
index 000000000000..46d81d275952
--- /dev/null
+++ b/es-metadata.yml
@@ -0,0 +1,8 @@
+schemaVersion: 0.0.1
+isProduction: true
+accountableOwners:
+ service: 9d770e15-6208-4284-b347-b2762803623b
+routing:
+ defaultAreaPath:
+ org: devdiv
+ path: DevDiv\.NET MAUI
diff --git a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs
index d8d4f11f78da..052a96f5586f 100644
--- a/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs
+++ b/src/BlazorWebView/src/Maui/BlazorWebViewHandler.cs
@@ -25,6 +25,10 @@ public partial class BlazorWebViewHandler
{
[nameof(IBlazorWebView.HostPage)] = MapHostPage,
[nameof(IBlazorWebView.RootComponents)] = MapRootComponents,
+#if WINDOWS
+ [nameof(IView.FlowDirection)] = MapFlowDirection,
+#endif
+
};
///
diff --git a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs
index 17839d87cf49..0ed8171e6d02 100644
--- a/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs
+++ b/src/BlazorWebView/src/Maui/Windows/BlazorWebViewHandler.Windows.cs
@@ -114,6 +114,12 @@ private void StartWebViewCoreIfPossible()
_webviewManager.Navigate(VirtualView.StartPath);
}
+ internal static void MapFlowDirection(BlazorWebViewHandler handler, IView view)
+ {
+ // Explicitly do nothing here to override the base ViewHandler.MapFlowDirection behavior
+ // This prevents the WebView2.FlowDirection from being set, avoiding content mirroring
+ }
+
internal IFileProvider CreateFileProvider(string contentRootDir)
{
// On WinUI we override HandleWebResourceRequest in WinUIWebViewManager so that loading static assets is done entirely there in an async manner.
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/ControlsDeviceTestExtensions.cs b/src/BlazorWebView/tests/DeviceTests/ControlsDeviceTestExtensions.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/ControlsDeviceTestExtensions.cs
rename to src/BlazorWebView/tests/DeviceTests/ControlsDeviceTestExtensions.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/ControlsHandlerTestBase.cs b/src/BlazorWebView/tests/DeviceTests/ControlsHandlerTestBase.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/ControlsHandlerTestBase.cs
rename to src/BlazorWebView/tests/DeviceTests/ControlsHandlerTestBase.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Directory.Build.targets b/src/BlazorWebView/tests/DeviceTests/Directory.Build.targets
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Directory.Build.targets
rename to src/BlazorWebView/tests/DeviceTests/Directory.Build.targets
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.cs b/src/BlazorWebView/tests/DeviceTests/Elements/BlazorWebViewTests.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Elements/BlazorWebViewTests.cs
rename to src/BlazorWebView/tests/DeviceTests/Elements/BlazorWebViewTests.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/MauiAppNewWindowStub.Windows.cs b/src/BlazorWebView/tests/DeviceTests/MauiAppNewWindowStub.Windows.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/MauiAppNewWindowStub.Windows.cs
rename to src/BlazorWebView/tests/DeviceTests/MauiAppNewWindowStub.Windows.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/MauiBlazorWebView.DeviceTests.csproj b/src/BlazorWebView/tests/DeviceTests/MauiBlazorWebView.DeviceTests.csproj
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/MauiBlazorWebView.DeviceTests.csproj
rename to src/BlazorWebView/tests/DeviceTests/MauiBlazorWebView.DeviceTests.csproj
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/MauiProgram.cs b/src/BlazorWebView/tests/DeviceTests/MauiProgram.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/MauiProgram.cs
rename to src/BlazorWebView/tests/DeviceTests/MauiProgram.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Platforms/Android/AndroidManifest.xml b/src/BlazorWebView/tests/DeviceTests/Platforms/Android/AndroidManifest.xml
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Platforms/Android/AndroidManifest.xml
rename to src/BlazorWebView/tests/DeviceTests/Platforms/Android/AndroidManifest.xml
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Platforms/MacCatalyst/Info.plist b/src/BlazorWebView/tests/DeviceTests/Platforms/MacCatalyst/Info.plist
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Platforms/MacCatalyst/Info.plist
rename to src/BlazorWebView/tests/DeviceTests/Platforms/MacCatalyst/Info.plist
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Platforms/Windows/App.xaml b/src/BlazorWebView/tests/DeviceTests/Platforms/Windows/App.xaml
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Platforms/Windows/App.xaml
rename to src/BlazorWebView/tests/DeviceTests/Platforms/Windows/App.xaml
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Platforms/Windows/App.xaml.cs b/src/BlazorWebView/tests/DeviceTests/Platforms/Windows/App.xaml.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Platforms/Windows/App.xaml.cs
rename to src/BlazorWebView/tests/DeviceTests/Platforms/Windows/App.xaml.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Platforms/Windows/Package.appxmanifest b/src/BlazorWebView/tests/DeviceTests/Platforms/Windows/Package.appxmanifest
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Platforms/Windows/Package.appxmanifest
rename to src/BlazorWebView/tests/DeviceTests/Platforms/Windows/Package.appxmanifest
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Platforms/Windows/app.manifest b/src/BlazorWebView/tests/DeviceTests/Platforms/Windows/app.manifest
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Platforms/Windows/app.manifest
rename to src/BlazorWebView/tests/DeviceTests/Platforms/Windows/app.manifest
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Platforms/iOS/Info.plist b/src/BlazorWebView/tests/DeviceTests/Platforms/iOS/Info.plist
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Platforms/iOS/Info.plist
rename to src/BlazorWebView/tests/DeviceTests/Platforms/iOS/Info.plist
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Properties/launchSettings.json b/src/BlazorWebView/tests/DeviceTests/Properties/launchSettings.json
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Properties/launchSettings.json
rename to src/BlazorWebView/tests/DeviceTests/Properties/launchSettings.json
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/black.png b/src/BlazorWebView/tests/DeviceTests/Resources/Images/black.png
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/black.png
rename to src/BlazorWebView/tests/DeviceTests/Resources/Images/black.png
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/blue.png b/src/BlazorWebView/tests/DeviceTests/Resources/Images/blue.png
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/blue.png
rename to src/BlazorWebView/tests/DeviceTests/Resources/Images/blue.png
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/green.png b/src/BlazorWebView/tests/DeviceTests/Resources/Images/green.png
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/green.png
rename to src/BlazorWebView/tests/DeviceTests/Resources/Images/green.png
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/red.png b/src/BlazorWebView/tests/DeviceTests/Resources/Images/red.png
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/red.png
rename to src/BlazorWebView/tests/DeviceTests/Resources/Images/red.png
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/white.png b/src/BlazorWebView/tests/DeviceTests/Resources/Images/white.png
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Resources/Images/white.png
rename to src/BlazorWebView/tests/DeviceTests/Resources/Images/white.png
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Resources/appicon.svg b/src/BlazorWebView/tests/DeviceTests/Resources/appicon.svg
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Resources/appicon.svg
rename to src/BlazorWebView/tests/DeviceTests/Resources/appicon.svg
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/Resources/appiconfg.svg b/src/BlazorWebView/tests/DeviceTests/Resources/appiconfg.svg
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/Resources/appiconfg.svg
rename to src/BlazorWebView/tests/DeviceTests/Resources/appiconfg.svg
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/TestCategory.cs b/src/BlazorWebView/tests/DeviceTests/TestCategory.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/TestCategory.cs
rename to src/BlazorWebView/tests/DeviceTests/TestCategory.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/TestLogger.cs b/src/BlazorWebView/tests/DeviceTests/TestLogger.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/TestLogger.cs
rename to src/BlazorWebView/tests/DeviceTests/TestLogger.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Android.cs b/src/BlazorWebView/tests/DeviceTests/WebViewHelpers.Android.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Android.cs
rename to src/BlazorWebView/tests/DeviceTests/WebViewHelpers.Android.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.NetStandard.cs b/src/BlazorWebView/tests/DeviceTests/WebViewHelpers.NetStandard.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.NetStandard.cs
rename to src/BlazorWebView/tests/DeviceTests/WebViewHelpers.NetStandard.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Shared.cs b/src/BlazorWebView/tests/DeviceTests/WebViewHelpers.Shared.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Shared.cs
rename to src/BlazorWebView/tests/DeviceTests/WebViewHelpers.Shared.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Windows.cs b/src/BlazorWebView/tests/DeviceTests/WebViewHelpers.Windows.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.Windows.cs
rename to src/BlazorWebView/tests/DeviceTests/WebViewHelpers.Windows.cs
diff --git a/src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.iOS.cs b/src/BlazorWebView/tests/DeviceTests/WebViewHelpers.iOS.cs
similarity index 100%
rename from src/BlazorWebView/tests/MauiDeviceTests/WebViewHelpers.iOS.cs
rename to src/BlazorWebView/tests/DeviceTests/WebViewHelpers.iOS.cs
diff --git a/src/Controls/docs/Microsoft.Maui.Controls/BindingBase.xml b/src/Controls/docs/Microsoft.Maui.Controls/BindingBase.xml
deleted file mode 100644
index b409e7826204..000000000000
--- a/src/Controls/docs/Microsoft.Maui.Controls/BindingBase.xml
+++ /dev/null
@@ -1,230 +0,0 @@
-
-
-
-
-
-
- Microsoft.Maui.Controls.Core
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
-
-
- System.Object
-
-
-
- An abstract class that provides a and a formatting option.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
- The collection on which to stop synchronization.
- Stops synchronization on the .
- See for more information on enabling and disabling synchronization of collections in multithreaded environments.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
-
-
- The collection that will be read or updated.
- The context or lock object that will be passed to . May be .
- The synchronization callback.
- Starts synchronization on the by using and .
-
- Application developers implement and pass it to the method to enable correct multithreaded access to . After synchronization is enabled, the Microsoft.Maui.Controls framework passes an access method, , and a that indicates whether write access is needed, to the application developer's implementation of each time that the framework needs to modify the collection in a multithreaded environment. The application developer's implementation should decide, based on the object (which may be merely a locking object or the object on which the collection lives) and the value of the parameter, whether or not to lock while calling .
- Because Microsoft.Maui.Controls maintains a weak reference to , application developers do not need to call to aid in garbage collection.
-
-
-
-
-
-
-
-
- Property
-
- Microsoft.Maui.Controls.Core
- 0.0.0.0
- 2.0.0.0
-
-
- System.Object
-
-
- Gets or sets the value to use instead of the default value for the property, if no specified value exists.
- The value to use instead of the default value for the property, if no specified value exists
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindingMode
-
-
- Gets or sets the mode for this binding.
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.String
-
-
- Gets or sets the string format for this binding.
-
- A string specifying the format for the value of this binding.
-
-
-
- Used for providing a display format for the binding value or compositing the value with other
- text. Implementors of decide how the string format is utilized, but
- all support standard conventions.
-
-
- allows for one argument for its singular value.
-
-
-
- A simple example showing compositing text and determining the display format for the value with a
-
-
-
-
-
-
-
-
-
-
-
-
- Property
-
- Microsoft.Maui.Controls.Core
- 0.0.0.0
- 2.0.0.0
-
-
- System.Object
-
-
- Gets or sets the value to supply for a bound property when the target of the binding is .
- The value to supply for a bound property when the target of the binding is .
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
- Throws an if the binding has been applied.
-
- Use this method in property setters as bindings cannot be changed once applied.
-
-
-
-
-
diff --git a/src/Controls/docs/Microsoft.Maui.Controls/Grid.xml b/src/Controls/docs/Microsoft.Maui.Controls/Grid.xml
deleted file mode 100644
index 39789ea2ebee..000000000000
--- a/src/Controls/docs/Microsoft.Maui.Controls/Grid.xml
+++ /dev/null
@@ -1,986 +0,0 @@
-
-
-
-
-
-
- Microsoft.Maui.Controls.Core
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
-
-
- Microsoft.Maui.Controls.Layout<Microsoft.Maui.Controls.View>
-
- Microsoft.Maui.Controls.View
-
-
-
-
- Microsoft.Maui.Controls.IElementConfiguration<Microsoft.Maui.Controls.Grid>
-
-
- Microsoft.Maui.Controls.IGridController
-
-
-
- A layout that arranges views in rows and columns.
-
-
-
-
-
-
- Constructor
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- Initializes a new instance of the Grid class.
-
-
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.Grid+IGridList<Microsoft.Maui.Controls.View>
-
-
- Gets the collection of child elements of the Grid.
- The collection of child elements.
-
- Application developers can use implicit collection syntax in XAML to add items to this collection, because this property is the ContentPropertyAttribute for the Grid class.
-
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.TypeConverter(typeof(Microsoft.Maui.Controls.ColumnDefinitionCollectionTypeConverter))
-
-
-
- Microsoft.Maui.Controls.ColumnDefinitionCollection
-
-
- Provides the interface for the bound property that gets or sets the ordered collection of objects that control the layout of columns in the .
- A ColumnDefinitionCollection for the Grid instance.
-
- ColumnDefinitions is an ordered set of ColumnDefinition objects that determine the width of each column. Each successive ColumnDefintion controls the width of each successive column. If ColumnDefinitions is empty, or if there are more columns than definitions, then columns for which there is no definition are rendered as if they were controlled by a ColumnDefinition object that has its property set to .
- The property has XAML syntax support. The syntax for this operation is shown below.
-
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Implements the property, and allows the class to bind it to properties on other objects at run time.
-
-
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Implements the attached property that represents the zero-based column index of a child element. See Remarks.
-
- The interface for this property is defined by the and methods.
-
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Double
-
-
- Gets or sets the amount of space between columns in the Grid. This is a bindable property.
- The space between columns in this layout. The default is 0.
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Implements the property, and allows the class to bind it to properties on other objects at run time.
-
-
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Implements the attached property that represents the number of columns that a child element spans. See Remarks.
-
- The interface for this property is defined by the and methods.
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Int32
-
-
-
-
-
- An element that belongs to the Grid layout.
- Gets the column of the child element.
- The column that the child element is in.
-
- The method corresponds to the value that is set by the following XAML attached property.
-
-
- Attached Property
- Value
-
-
- Column
-
- An integer that represents the Column in which the item will appear.
-
-
-
- The remarks for the method contain syntax for and information about the Column attached property.
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Int32
-
-
-
-
-
- An element that belongs to the Grid layout.
- Gets the column span of the child element.
- The column that the child element is in.
-
- The method corresponds to the value that is set by the following XAML attached property.
-
-
- Attached Property
- Value
-
-
- ColumnSpan
-
- An integer that represents the number of Columns that the item will span.
-
-
-
- The remarks for the method contain syntax for and information about the ColumnSpan attached property.
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Int32
-
-
-
-
-
- An element that belongs to the Grid layout.
- Gets the row of the child element.
- The row that the child element is in.
-
- The method corresponds to the following XAML attached property:
-
-
- Attached Property
- Value
-
-
- Row
-
- An integer that represents the row in which the item will appear.
-
-
-
- The remarks for the method contain syntax for and information about the Row attached property.
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Int32
-
-
-
-
-
- An element that belongs to the Grid layout.
- Gets the row span of the child element.
- The row that the child element is in.
-
- The method corresponds to the following XAML attached properties:
-
-
- Attached Property
- Value
-
-
- RowSpan
- An integer that represents the number of rows that the item will span.
-
-
- The remarks for the method contain syntax for and information about the RowSpan attached property.
-
-
-
-
-
-
-
-
- Method
-
- M:Microsoft.Maui.Controls.IGridController.InvalidateMeasureInernalNonVirtual(Microsoft.Maui.Controls.Internals.InvalidationTrigger)
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- System.Void
-
-
-
-
-
- For internal use by the Microsoft.Maui.Controls platform.
- For internal use by the Microsoft.Maui.Controls platform.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
-
-
-
- X-coordinate of the top left corner of the bounding rectangle.
- Y-coordinate of the top left corner of the bounding rectangle.
- Width of the bounding rectangle.
- Height of the bounding rectangle.
-
- Lays out the child elements when the layout is invalidated.
-
-
-
-
-
-
-
-
- Method
-
- M:Microsoft.Maui.Controls.IElementConfiguration`1.On``1
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.IPlatformElementConfiguration<T,Microsoft.Maui.Controls.Grid>
-
-
-
-
- Microsoft.Maui.Controls.IConfigPlatform
-
-
-
-
-
- The platform configuration that selects the platform specific to use.
- Returns the configuration object that the developer can use to call platform-specific methods for the grid control.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
- The element that was added.
- Method that is called when a child is added to this element.
-
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
- Application developers override this to respond when the binding context changes.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
- The element that was removed.
- Method that is called when a child is removed from this element.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
- System.Obsolete("OnSizeRequest is obsolete as of version 2.2.0. Please use OnMeasure instead.")
-
-
-
- Microsoft.Maui.SizeRequest
-
-
-
-
-
-
- The requested width.
- The requested height.
- Method that is called when an attempt is made to resize this element.
-
- The new requested size.
-
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.TypeConverter(typeof(Microsoft.Maui.Controls.RowDefinitionCollectionTypeConverter))
-
-
-
- Microsoft.Maui.Controls.RowDefinitionCollection
-
-
- Provides the interface for the bound property that gets or sets the collection of RowDefinition objects that control the heights of each row.
- A RowDefinitionCollection for the Grid instance.
-
- RowDefinitions is an ordered set of objects that determine the height of each row. Each successive RowDefintion controls the width of each successive row. If RowDefinitions is empty, or if there are more rows than definitions, then rows for which there is no definition are rendered as if they were controlled by a RowDefinition object that has its property set to .
-
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Implements the property, and allows the class to bind it to properties on other objects at run time.
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Implements the attached property that represents the zero-based row index of a child element. See Remarks.
-
- The interface for this property is defined by the and methods.
-
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Double
-
-
- Gets or sets the amount of space between rows in the Grid. This is a bindable property.
- The space between rows in this layout. The default is 0.
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Implements the property, and allows the class to bind it to properties on other objects at run time.
-
-
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Implements the attached property that represents the number of rows that a child element spans, and allows the class to bind it to properties on other objects at run time.
-
- The interface for this property is defined by the and methods.
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
-
- A child element of this Grid to move to a different column.
- The column in which to place the child element.
- Changes the column in which a child element will be placed.
-
- The method corresponds to the value that is set by the following XAML attached property.
-
-
- Attached Property
- Value
-
-
- ColumnSpan
-
- An integer that represents the Column in which the item will appear.
-
-
-
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
-
- A child element of this Grid on which to assign a new column span.
- The new column span.
- Changes the column span of the specified child element.
-
- The method corresponds to the value that is set by the following XAML attached property.
-
-
- Attached Property
- Value
-
-
- Column
-
- An integer that represents the number of Columns that the item will span.
-
-
-
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
-
- A child element of this Grid to move to a different row.
- The row in which to place the child element.
- Changes the row in which a child element will be placed.
-
- The method corresponds to the following XAML attached property:
-
-
- Attached Property
- Value
-
-
- Row
-
- An integer that represents the row in which the item will appear.
-
-
-
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
-
- A child element of this Grid on which to assign a new row span.
- The new row span.
- Changes the row span of the specified child element.
-
- The method corresponds to the following XAML attached property:
-
-
- Attached Property
- Value
-
-
- RowSpan
- An integer that represents the number of rows that the item will span.
-
-
-
-
-
-
-
diff --git a/src/Controls/docs/Microsoft.Maui.Controls/MultiTrigger.xml b/src/Controls/docs/Microsoft.Maui.Controls/MultiTrigger.xml
deleted file mode 100644
index d70f68814f35..000000000000
--- a/src/Controls/docs/Microsoft.Maui.Controls/MultiTrigger.xml
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
-
-
-
- Microsoft.Maui.Controls.Core
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
-
-
- Microsoft.Maui.Controls.TriggerBase
-
-
-
-
- Microsoft.Maui.Controls.ContentProperty("Setters")
-
-
-
- Class that represents a list of property and binding conditions, and a list of setters that are applied when all of the conditions in the list are met.
-
- Developers can use a to compare against property values on the control that contains it by using objects, or on any bound property (including those on the enclosing control) by using objects. These can be mixed in the same list.
-
- The XML example below, when added to a Microsoft.Maui.Controls app with the correct project namespace, creates a UI that suggests that the user type in a secret and toggle a switch to check if the secret is correct. If the user enters "The text color is green", and toggles the to its On position, then the text that the user typed into the turns green. If either the text is altered to something other than the secret or the Switch is toggled to its Off position, the text returns to the default color
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-]]>
-
-
-
-
-
-
-
-
-
-
-
- Constructor
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
-
-
- System.ComponentModel.TypeConverter(typeof(Microsoft.Maui.Controls.TypeTypeConverter))
-
-
-
-
-
- The type of the trigger target.
- Initializes a new instance.
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Collections.Generic.IList<Microsoft.Maui.Controls.Condition>
-
-
- Gets the list of conditions that must be satisfied in ordeer for the setters in the list to be invoked.
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Collections.Generic.IList<Microsoft.Maui.Controls.Setter>
-
-
- Gets the list of objects that will be applied when the list of conditions in the property are all met.
-
-
-
-
diff --git a/src/Controls/docs/Microsoft.Maui.Controls/WebView.xml b/src/Controls/docs/Microsoft.Maui.Controls/WebView.xml
deleted file mode 100644
index e292f31d6202..000000000000
--- a/src/Controls/docs/Microsoft.Maui.Controls/WebView.xml
+++ /dev/null
@@ -1,842 +0,0 @@
-
-
-
-
-
-
- Microsoft.Maui.Controls.Core
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
-
-
- Microsoft.Maui.Controls.View
-
-
-
- Microsoft.Maui.Controls.IElementConfiguration<Microsoft.Maui.Controls.WebView>
-
-
- Microsoft.Maui.Controls.IElementController
-
-
- Microsoft.Maui.Controls.IViewController
-
-
- Microsoft.Maui.Controls.IVisualElementController
-
-
- Microsoft.Maui.Controls.IWebViewController
-
-
-
-
- Microsoft.Maui.Controls.RenderWith(typeof(Microsoft.Maui.Controls.Platform._WebViewRenderer))
-
-
-
- A that presents HTML content.
-
- The following example shows a basic use.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Constructor
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- Creates a new element with default values.
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Boolean
-
-
- Gets a value that indicates whether the user can navigate to previous pages.
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Backing store for the property.
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Boolean
-
-
- Gets a value that indicates whether the user can navigate forward.
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Backing store for the property.
-
-
-
-
-
-
-
- Property
-
- Microsoft.Maui.Controls.Core
- 2.0.0.0
-
-
- System.Net.CookieContainer
-
-
- When set this will act as a sync for cookies.
-
-
-
-
-
-
-
- Field
-
- Microsoft.Maui.Controls.Core
- 2.0.0.0
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
- A script to evaluate.
- Evaluates the script that is specified by .
-
-
-
-
-
-
-
- Event
-
- E:Microsoft.Maui.Controls.IWebViewController.EvalRequested
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- System.EventHandler<Microsoft.Maui.Controls.Internals.EvalRequested>
-
-
- For internal use by the Microsoft.Maui.Controls platform.
-
-
-
-
-
-
-
- Method
-
- Microsoft.Maui.Controls.Core
- 0.0.0.0
- 2.0.0.0
-
-
- System.Threading.Tasks.Task<System.String>
-
-
-
-
-
- The script to evaluate.
- On platforms that support JavaScript evaluation, evaluates .
- A task that contains the result of the evaluation as a string.
- Native JavaScript evaluation is supported neither on Tizen nor GTK (Linux).
-
-
-
-
-
-
-
- Event
-
- E:Microsoft.Maui.Controls.IWebViewController.EvaluateJavaScriptRequested
-
-
- Microsoft.Maui.Controls.Core
- 0.0.0.0
- 2.0.0.0
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- Microsoft.Maui.Controls.Internals.EvaluateJavaScriptDelegate
-
-
- For internal use by the Microsoft.Maui.Controls platform.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
- Navigates to the previous page.
-
-
-
-
-
-
-
- Event
-
- E:Microsoft.Maui.Controls.IWebViewController.GoBackRequested
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- System.EventHandler
-
-
- For internal use by the Microsoft.Maui.Controls platform.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
- Navigates to the next page in the list of visited pages.
-
-
-
-
-
-
-
- Event
-
- E:Microsoft.Maui.Controls.IWebViewController.GoForwardRequested
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- System.EventHandler
-
-
- For internal use by the Microsoft.Maui.Controls platform.
-
-
-
-
-
-
-
- Event
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.EventHandler<Microsoft.Maui.Controls.WebNavigatedEventArgs>
-
-
- Event that is raised after navigation completes.
-
-
-
-
-
-
-
- Event
-
- 0.0.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.EventHandler<Microsoft.Maui.Controls.WebNavigatingEventArgs>
-
-
- Event that is raised when navigation starts.
-
-
-
-
-
-
-
- Method
-
- M:Microsoft.Maui.Controls.IElementConfiguration`1.On``1
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.IPlatformElementConfiguration<T,Microsoft.Maui.Controls.WebView>
-
-
-
-
- Microsoft.Maui.Controls.IConfigPlatform
-
-
-
-
-
- To be added.
- Returns the platform-specific instance of this , on which a platform-specific method may be called.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
- Method that is called when the binding context is changed.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
- The name of the property that was changed.
- Method that is called when is changed.
-
-
-
-
-
-
-
- Method
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.Void
-
-
-
-
-
-
- The object that raised the event.
- The event arguments.
- Method that is called when the view source that is specified by the parameter is changed.
-
-
-
-
-
-
-
- Method
-
- Microsoft.Maui.Controls.Core
- 2.0.0.0
-
-
- System.Void
-
-
-
-
-
-
-
-
-
-
- Event
-
- E:Microsoft.Maui.Controls.IWebViewController.ReloadRequested
-
-
- Microsoft.Maui.Controls.Core
- 2.0.0.0
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- System.EventHandler
-
-
-
-
-
-
-
-
-
- Method
-
- M:Microsoft.Maui.Controls.IWebViewController.SendNavigated(Microsoft.Maui.Controls.WebNavigatedEventArgs)
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- System.Void
-
-
-
-
-
- For internal use by the Microsoft.Maui.Controls platform.
- For internal use by the Microsoft.Maui.Controls platform.
-
-
-
-
-
-
-
- Method
-
- M:Microsoft.Maui.Controls.IWebViewController.SendNavigating(Microsoft.Maui.Controls.WebNavigatingEventArgs)
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- System.Void
-
-
-
-
-
- For internal use by the Microsoft.Maui.Controls platform.
- For internal use by the Microsoft.Maui.Controls platform.
-
-
-
-
-
-
-
- Property
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.TypeConverter(typeof(Microsoft.Maui.Controls.WebViewSourceTypeConverter))
-
-
-
- Microsoft.Maui.Controls.WebViewSource
-
-
- Gets or sets the object that represents the location that this object displays.
-
-
-
-
-
-
-
- Field
-
- 0.0.0.0
- 1.0.0.0
- 1.1.0.0
- 1.2.0.0
- 1.3.0.0
- 1.4.0.0
- 1.5.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Backing store for the property.
-
-
-
-
-
-
-
- Property
-
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- System.String
-
-
- Gets or sets the user agent string that this object uses.
-
- The default value is the default User Agent of the underlying platform browser, or if it cannot be determined.
- If the parameter is the User Agent will not be updated and the current User Agent will remain.
-
-
-
-
-
-
-
-
- Field
-
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
- Microsoft.Maui.Controls.BindableProperty
-
-
- Backing store for the property.
-
-
-
-
-
-
-
- Property
-
- P:Microsoft.Maui.Controls.IWebViewController.CanGoBack
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- System.Boolean
-
-
- For internal use by the Microsoft.Maui.Controls platform.
-
-
-
-
-
-
-
- Property
-
- P:Microsoft.Maui.Controls.IWebViewController.CanGoForward
-
-
- 0.0.0.0
- 2.0.0.0
- Microsoft.Maui.Controls.Core
-
-
-
- System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)
-
-
-
- System.Boolean
-
-
- For internal use by the Microsoft.Maui.Controls platform.
-
-
-
-
-
-
-
- Event
-
- E:Microsoft.Maui.Controls.IWebViewController.EvalRequested
-
-
- Microsoft.Maui.Controls.Core
- 2.0.0.0
-
-
- System.EventHandler<Microsoft.Maui.Controls.Internals.EvalRequested>
-
-
-
-
-
-
diff --git a/src/Controls/docs/Microsoft.Maui.Controls/_images/WebView.TripleScreenShot.png b/src/Controls/docs/Microsoft.Maui.Controls/_images/WebView.TripleScreenShot.png
deleted file mode 100644
index 5e7b6a4be8c8..000000000000
Binary files a/src/Controls/docs/Microsoft.Maui.Controls/_images/WebView.TripleScreenShot.png and /dev/null differ
diff --git a/src/Controls/src/Core/Application/Application.cs b/src/Controls/src/Core/Application/Application.cs
index da5d038e7f0d..5921d2fe15d3 100644
--- a/src/Controls/src/Core/Application/Application.cs
+++ b/src/Controls/src/Core/Application/Application.cs
@@ -43,7 +43,7 @@ internal Application(bool setCurrentApplication)
#pragma warning disable CS0612 // Type or member is obsolete
_systemResources = new Lazy(() =>
{
- var systemResources = DependencyService.Get().GetSystemResources();
+ var systemResources = DependencyService.Get()?.GetSystemResources();
if (systemResources is not null)
{
systemResources.ValuesChanged += OnParentResourcesChanged;
diff --git a/src/Controls/src/Core/BindableProperty.cs b/src/Controls/src/Core/BindableProperty.cs
index 0f58a074403e..311836c64621 100644
--- a/src/Controls/src/Core/BindableProperty.cs
+++ b/src/Controls/src/Core/BindableProperty.cs
@@ -20,24 +20,127 @@ public sealed class BindableProperty
internal const DynamicallyAccessedMemberTypes DeclaringTypeMembers = DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicMethods;
internal const DynamicallyAccessedMemberTypes ReturnTypeMembers = DynamicallyAccessedMemberTypes.PublicParameterlessConstructor;
+ ///
+ /// Represents a delegate that is called when a bindable property value has changed.
+ ///
+ /// The instance that owns the property.
+ /// The previous value of the property.
+ /// The new value of the property.
+ ///
+ /// This delegate does not provide information about which specific
+ /// triggered the change. If multiple properties share the same callback and need to be distinguished,
+ /// consider using separate callbacks or the event.
+ ///
public delegate void BindingPropertyChangedDelegate(BindableObject bindable, object oldValue, object newValue);
+ ///
+ /// Represents a strongly-typed delegate that is called when a bindable property value has changed.
+ ///
+ /// The type of the property value.
+ /// The instance that owns the property.
+ /// The previous value of the property.
+ /// The new value of the property.
+ ///
+ /// This delegate does not provide information about which specific
+ /// triggered the change. See for workaround strategies.
+ ///
public delegate void BindingPropertyChangedDelegate(BindableObject bindable, TPropertyType oldValue, TPropertyType newValue);
+ ///
+ /// Represents a delegate that is called when a bindable property value is about to change.
+ ///
+ /// The instance that owns the property.
+ /// The current value of the property before the change.
+ /// The new value that the property will be set to.
+ ///
+ ///
+ /// This delegate is invoked before a property value is changed on a .
+ /// Like , this delegate does not include information
+ /// about which specific is changing when multiple properties share the same callback.
+ ///
+ ///
public delegate void BindingPropertyChangingDelegate(BindableObject bindable, object oldValue, object newValue);
+ ///
+ /// Represents a strongly-typed delegate that is called when a bindable property value is about to change.
+ ///
+ /// The type of the property value.
+ /// The instance that owns the property.
+ /// The current value of the property before the change.
+ /// The new value that the property will be set to.
+ ///
+ ///
+ /// This strongly-typed delegate is invoked before a property value is changed on a .
+ /// Like , this delegate does not include information
+ /// about which specific is changing when multiple properties share the same callback.
+ ///
+ ///
public delegate void BindingPropertyChangingDelegate(BindableObject bindable, TPropertyType oldValue, TPropertyType newValue);
+ ///
+ /// Represents a delegate that is called to coerce a property value to a valid range or state.
+ ///
+ /// The instance that owns the property.
+ /// The value to be coerced.
+ /// The coerced value.
public delegate object CoerceValueDelegate(BindableObject bindable, object value);
+ ///
+ /// Represents a strongly-typed delegate that is called to coerce a property value to a valid range or state.
+ ///
+ /// The type of the property value.
+ /// The instance that owns the property.
+ /// The value to be coerced.
+ /// The coerced value.
public delegate TPropertyType CoerceValueDelegate(BindableObject bindable, TPropertyType value);
+ ///
+ /// Represents a delegate that creates a default value for a bindable property.
+ ///
+ /// The instance that owns the property.
+ /// The default value for the property.
+ ///
+ /// This delegate is useful for creating unique default instances for reference types,
+ /// avoiding shared references between different bindable object instances.
+ ///
public delegate object CreateDefaultValueDelegate(BindableObject bindable);
+ ///
+ /// Represents a strongly-typed delegate that creates a default value for a bindable property.
+ ///
+ /// The type of the declaring object.
+ /// The type of the property value.
+ /// The declaring object instance that owns the property.
+ /// The default value for the property.
+ ///
+ /// This strongly-typed delegate is useful for creating unique default instances for reference types,
+ /// avoiding shared references between different bindable object instances.
+ ///
public delegate TPropertyType CreateDefaultValueDelegate(TDeclarer bindable);
+ ///
+ /// Represents a delegate that validates whether a value is acceptable for a bindable property.
+ ///
+ /// The instance that owns the property.
+ /// The value to validate.
+ /// if the value is valid; otherwise, .
+ ///
+ /// If this delegate returns , an will be thrown
+ /// when attempting to set the property to the invalid value.
+ ///
public delegate bool ValidateValueDelegate(BindableObject bindable, object value);
+ ///
+ /// Represents a strongly-typed delegate that validates whether a value is acceptable for a bindable property.
+ ///
+ /// The type of the property value.
+ /// The instance that owns the property.
+ /// The strongly-typed value to validate.
+ /// if the value is valid; otherwise, .
+ ///
+ /// If this delegate returns , an will be thrown
+ /// when attempting to set the property to the invalid value.
+ ///
public delegate bool ValidateValueDelegate(BindableObject bindable, TPropertyType value);
internal static readonly Dictionary KnownTypeConverters = new Dictionary
@@ -157,6 +260,15 @@ public sealed class BindableProperty
/// A delegate used to coerce the range of a value. This parameter is optional. Default is null.
/// A Func used to initialize default value for reference types.
/// A newly created BindableProperty.
+ ///
+ ///
+ /// When using the callback, note that if multiple
+ /// instances share the same , the callback cannot determine which
+ /// specific property triggered the change. Consider using separate callback methods for properties that require
+ /// different handling, or use alternative approaches such as monitoring the
+ /// event which includes the property name.
+ ///
+ ///
public static BindableProperty Create(string propertyName, [DynamicallyAccessedMembers(ReturnTypeMembers)] Type returnType, [DynamicallyAccessedMembers(DeclaringTypeMembers)] Type declaringType, object defaultValue = null, BindingMode defaultBindingMode = BindingMode.OneWay,
ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
@@ -177,6 +289,14 @@ public static BindableProperty Create(string propertyName, [DynamicallyAccessedM
/// A delegate used to coerce the range of a value. This parameter is optional. Default is null.
/// A Func used to initialize default value for reference types.
/// A newly created attached BindableProperty.
+ ///
+ ///
+ /// When using the callback, note that if multiple attached
+ /// instances share the same , the callback cannot determine which
+ /// specific property triggered the change. Consider using separate callback methods for properties that require
+ /// different handling.
+ ///
+ ///
public static BindableProperty CreateAttached(string propertyName, [DynamicallyAccessedMembers(ReturnTypeMembers)] Type returnType, [DynamicallyAccessedMembers(DeclaringTypeMembers)] Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWay,
ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
@@ -196,7 +316,15 @@ public static BindableProperty CreateAttached(string propertyName, [DynamicallyA
/// A delegate used to coerce the range of a value. This parameter is optional. Default is null.
/// A Func used to initialize default value for reference types.
/// A newly created attached read-only BindableProperty.
- /// Attached properties are bindable properties that are bound to an object other than their parent. Often, they are used for child items in tables and grids, where data about the location of an item is maintained by its parent, but must be accessed from the child item itself.
+ ///
+ /// Attached properties are bindable properties that are bound to an object other than their parent. Often, they are used for child items in tables and grids, where data about the location of an item is maintained by its parent, but must be accessed from the child item itself.
+ ///
+ /// When using the callback, note that if multiple attached
+ /// instances share the same , the callback cannot determine which
+ /// specific property triggered the change. Consider using separate callback methods for properties that require
+ /// different handling.
+ ///
+ ///
public static BindablePropertyKey CreateAttachedReadOnly(string propertyName, [DynamicallyAccessedMembers(ReturnTypeMembers)] Type returnType, [DynamicallyAccessedMembers(DeclaringTypeMembers)] Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWayToSource,
ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
@@ -217,6 +345,14 @@ public static BindablePropertyKey CreateAttachedReadOnly(string propertyName, [D
/// A delegate to be run when the value will change. This parameter is optional. Default is null.
/// A delegate used to coerce the range of a value. This parameter is optional. Default is null.
/// A Func used to initialize default value for reference types.
+ ///
+ ///
+ /// When using the callback, note that if multiple
+ /// instances share the same , the callback cannot determine which
+ /// specific property triggered the change. Consider using separate callback methods for properties that require
+ /// different handling.
+ ///
+ ///
public static BindablePropertyKey CreateReadOnly(string propertyName, [DynamicallyAccessedMembers(ReturnTypeMembers)] Type returnType, [DynamicallyAccessedMembers(DeclaringTypeMembers)] Type declaringType, object defaultValue, BindingMode defaultBindingMode = BindingMode.OneWayToSource,
ValidateValueDelegate validateValue = null, BindingPropertyChangedDelegate propertyChanged = null, BindingPropertyChangingDelegate propertyChanging = null,
CoerceValueDelegate coerceValue = null, CreateDefaultValueDelegate defaultValueCreator = null)
diff --git a/src/Controls/src/Core/BindingBase.cs b/src/Controls/src/Core/BindingBase.cs
index e0d00af93bba..cefb63920392 100644
--- a/src/Controls/src/Core/BindingBase.cs
+++ b/src/Controls/src/Core/BindingBase.cs
@@ -6,7 +6,13 @@
namespace Microsoft.Maui.Controls
{
- ///
+ ///
+ /// An abstract base class for all bindings providing selection, fallback/target null values, and formatting support.
+ ///
+ ///
+ /// This class underlies concrete binding implementations (e.g., , ) and supplies common features such as
+ /// binding mode control, string formatting and thread-safe collection synchronization helpers.
+ ///
public abstract partial class BindingBase
{
static readonly ConditionalWeakTable SynchronizedCollections = new ConditionalWeakTable();
@@ -21,7 +27,7 @@ internal BindingBase()
{
}
- ///
+ /// Gets or sets the mode for this binding.
public BindingMode Mode
{
get { return _mode; }
@@ -40,7 +46,11 @@ public BindingMode Mode
}
}
- ///
+ /// Gets or sets the string format applied to the bound value.
+ /// A standard composite format string. For single-value bindings use one placeholder (e.g., {0:C2}).
+ ///
+ /// Used to format or composite the resulting bound value for display. Implementations follow standard semantics.
+ ///
public string StringFormat
{
get { return _stringFormat; }
@@ -51,7 +61,16 @@ public string StringFormat
}
}
- ///
+ ///
+ /// Gets or sets the value to use when the binding successfully resolves the source path and the resulting source value is
+ /// .
+ ///
+ /// The value that will replace a resolved source value when updating the target.
+ ///
+ /// TargetNullValue acts like a null-coalescing value for the binding source result: if the binding path resolves and the value is
+ /// , the target receives TargetNullValue instead. It is not used when the binding cannot resolve (e.g. missing
+ /// property, conversion error) — in those cases is considered.
+ ///
public object TargetNullValue
{
get { return _targetNullValue; }
@@ -62,7 +81,13 @@ public object TargetNullValue
}
}
- ///
+ /// Gets or sets the value used when the binding cannot produce a source value (e.g. path not found, conversion failure).
+ /// The fallback value used instead of the target property's default value when source resolution fails entirely.
+ ///
+ /// FallbackValue is applied when the binding engine fails to obtain a value (e.g., missing source, unresolved path, or type conversion failure within the binding engine itself).
+ /// It is not used for errors that occur inside value converters; such errors may be handled by the converter or use different fallback mechanisms. If the source resolves to , is applied if set.
+ /// Together with this allows differentiating between a legitimate value and an unresolved binding.
+ ///
public object FallbackValue
{
get => _fallbackValue;
@@ -96,7 +121,9 @@ internal Element RelativeSourceTargetOverride
}
}
- ///
+ /// Stops collection synchronization previously enabled for .
+ /// The collection on which to disable synchronization.
+ /// See for details on thread-safe collection access.
public static void DisableCollectionSynchronization(IEnumerable collection)
{
if (collection == null)
@@ -105,9 +132,14 @@ public static void DisableCollectionSynchronization(IEnumerable collection)
SynchronizedCollections.Remove(collection);
}
-#pragma warning disable CS1734 // XML comment on 'BindingBase.EnableCollectionSynchronization(IEnumerable, object, CollectionSynchronizationCallback)' has a paramref tag for 'writeAccess', but there is no parameter by that name
- ///
-#pragma warning restore CS1734
+ /// Enables synchronized (thread-safe) access to using the supplied callback.
+ /// The collection that will be read or updated from multiple threads.
+ /// A context object (optionally a lock object) passed to ; may be .
+ /// Delegate invoked by the framework to perform collection access under synchronization.
+ ///
+ /// The framework holds only a weak reference to the collection. The callback receives parameters indicating whether write access is required;
+ /// implementers should perform appropriate locking (often on ) before invoking the supplied access delegate.
+ ///
public static void EnableCollectionSynchronization(IEnumerable collection, object context, CollectionSynchronizationCallback callback)
{
if (collection == null)
@@ -118,6 +150,9 @@ public static void EnableCollectionSynchronization(IEnumerable collection, objec
SynchronizedCollections.Add(collection, new CollectionSynchronizationContext(context, callback));
}
+ /// Throws if the binding has already been applied.
+ /// Used by property setters to prevent mutation after the binding has been attached to a target.
+ /// The binding has already been applied.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void ThrowIfApplied()
{
diff --git a/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
index b84444975463..53953d2e61d5 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
@@ -11,6 +11,7 @@
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
using Microsoft.Maui.Devices;
using Microsoft.Maui.Graphics;
+using Microsoft.Maui.Layouts;
using Microsoft.Maui.Platform;
using ObjCRuntime;
using UIKit;
@@ -1910,6 +1911,9 @@ class Container : UIView
UIImageView _icon;
bool _disposed;
+ //https://developer.apple.com/documentation/uikit/uiview/2865930-directionallayoutmargins
+ const int SystemMargin = 16;
+
public Container(View view, UINavigationBar bar) : base(bar.Bounds)
{
if (OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsMacCatalystVersionAtLeast(11))
@@ -1940,6 +1944,33 @@ public Container(View view, UINavigationBar bar) : base(bar.Bounds)
ClipsToBounds = true;
}
+ UIEdgeInsets CalculateUIEdgeInsets()
+ {
+ var type = UIBarButtonSystemItem.FixedSpace;
+ var spacer = new UIBarButtonItem(type, (_, _) => { });
+ spacer.Width = SystemMargin + (OperatingSystem.IsIOSVersionAtLeast(11) ? 8 : -16);
+
+ nfloat screenWidth = UIScreen.MainScreen.Bounds.Size.Width;
+
+ if (!OperatingSystem.IsIOSVersionAtLeast(11) && screenWidth < 375)
+ {
+ // 3.5 and 4 inch
+ spacer.Width += 8;
+ }
+ else if (screenWidth >= 414)
+ {
+ // 5.5 inch
+ spacer.Width -= 4;
+ }
+
+ return new UIEdgeInsets(0, spacer.Width, 0, spacer.Width);
+ }
+
+ public override UIEdgeInsets AlignmentRectInsets
+ {
+ get => CalculateUIEdgeInsets();
+ }
+
void OnTitleViewParentSet(object sender, EventArgs e)
{
if (sender is View view)
@@ -2036,10 +2067,16 @@ public override void LayoutSubviews()
if (_icon != null)
_icon.Frame = new RectangleF(0, 0, IconWidth, Math.Min(toolbarHeight, IconHeight));
- if (_child?.VirtualView != null)
+ if (_child?.VirtualView is IView view)
{
Rect layoutBounds = new Rect(IconWidth, 0, Bounds.Width - IconWidth, height);
+ if (view.HorizontalLayoutAlignment != Primitives.LayoutAlignment.Fill ||
+ view.VerticalLayoutAlignment != Primitives.LayoutAlignment.Fill)
+ {
+ view.Measure(Bounds.Width, Bounds.Height);
+ layoutBounds = view.ComputeFrame(new Rect(0, 0, Bounds.Width, Bounds.Height));
+ }
_child.PlatformArrangeHandler(layoutBounds);
}
else if (_icon != null && Superview != null)
diff --git a/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs
index 96907a84703c..449e5c9f11a2 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/TabbedPage/iOS/TabbedRenderer.cs
@@ -51,6 +51,11 @@ public override UIViewController SelectedViewController
set
{
base.SelectedViewController = value;
+ // If the selected view controller is the "More" navigation controller, do not update the current page
+ // because it is a special case where the user is navigating to a different set of tabs
+ if (value == MoreNavigationController)
+ return;
+
UpdateCurrentPage();
}
}
diff --git a/src/Controls/src/Core/Element/Element.cs b/src/Controls/src/Core/Element/Element.cs
index dfba62840f72..60b36fbdda51 100644
--- a/src/Controls/src/Core/Element/Element.cs
+++ b/src/Controls/src/Core/Element/Element.cs
@@ -517,8 +517,27 @@ public bool EffectIsAttached(string name)
/// Returns the element that has the specified name.
/// The name of the element to be found.
- /// The element that has the specified name.
+ /// The element that has the specified name, or if no element with the specified name is found.
/// Thrown if the element's namescope couldn't be found.
+ ///
+ ///
+ /// This method searches for named elements within the current namescope. The search scope is determined by
+ /// traversing up the visual tree from the current element until a namescope is found. Typically, each
+ /// page, content view, or data template defines its own namescope.
+ ///
+ ///
+ /// The search is limited to elements that have been registered in the same namescope, which includes:
+ ///
+ ///
+ /// Elements with x:Name attributes defined in XAML within the same namescope
+ /// Elements manually registered using
+ /// Child elements and their descendants within the same namescope boundary
+ ///
+ ///
+ /// Elements in different namescopes (such as different pages or data templates) are not accessible
+ /// from each other through this method.
+ ///
+ ///
public object FindByName(string name)
{
var namescope = GetNameScope() ?? transientNamescope;
diff --git a/src/Controls/src/Core/Handlers/Items/Android/Adapters/SelectableItemsViewAdapter.cs b/src/Controls/src/Core/Handlers/Items/Android/Adapters/SelectableItemsViewAdapter.cs
index 1841d2d82900..41f5619c3ea7 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/Adapters/SelectableItemsViewAdapter.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/Adapters/SelectableItemsViewAdapter.cs
@@ -12,6 +12,7 @@ public class SelectableItemsViewAdapter : StructuredIt
where TItemsSource : IItemsViewSource
{
List _currentViewHolders = new List();
+ HashSet
/// Optional name for the screenshot. If not provided, a default name will be used.
/// Optional delay between retry attempts when verification fails.
+ /// Number of pixels to crop from the left of the screenshot.
+ /// Number of pixels to crop from the right of the screenshot.
/// Number of pixels to crop from the top of the screenshot.
/// Number of pixels to crop from the bottom of the screenshot.
/// Tolerance level for image comparison as a percentage from 0 to 100.
@@ -179,6 +183,8 @@ public void VerifyScreenshotOrSetException(
public void VerifyScreenshot(
string? name = null,
TimeSpan? retryDelay = null,
+ int cropLeft = 0,
+ int cropRight = 0,
int cropTop = 0,
int cropBottom = 0,
double tolerance = 0.0 // Add tolerance parameter (0.05 = 5%)
@@ -316,16 +322,24 @@ but both can happen.
TestDevice.iOS => 40,
_ => 0,
};
-
+
+ // Cropping from the left or right can be applied for any platform using the user-specified crop values.
+ // The default values are set based on the platform, but the final cropping is determined by the parameters passed in.
+ // This allows cropping of UI elements (such as navigation bars or home indicators) for any platform as needed.
+ int cropFromLeft = 0;
+ int cropFromRight = 0;
+
+ cropFromLeft = cropLeft > 0 ? cropLeft : cropFromLeft;
+ cropFromRight = cropRight > 0 ? cropRight : cropFromRight;
cropFromTop = cropTop > 0 ? cropTop : cropFromTop;
cropFromBottom = cropBottom > 0 ? cropBottom : cropFromBottom;
- if (cropFromTop > 0 || cropFromBottom > 0)
+ if (cropFromLeft > 0 || cropFromRight > 0 || cropFromTop > 0 || cropFromBottom > 0)
{
IImageEditor imageEditor = _imageEditorFactory.CreateImageEditor(actualImage);
(int width, int height) = imageEditor.GetSize();
- imageEditor.Crop(0, cropFromTop, width, height - cropFromTop - cropFromBottom);
+ imageEditor.Crop(cropFromLeft, cropFromTop, width - cropFromLeft - cropFromRight, height - cropFromTop - cropFromBottom);
actualImage = imageEditor.GetUpdatedImage();
}
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ButtonsLayoutResolveWhenParentSizeChangesSizeButtonsDownPortrait.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ButtonsLayoutResolveWhenParentSizeChangesSizeButtonsDownPortrait.png
index 0dc14fee6a57..7f6881497e95 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ButtonsLayoutResolveWhenParentSizeChangesSizeButtonsDownPortrait.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ButtonsLayoutResolveWhenParentSizeChangesSizeButtonsDownPortrait.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/DownSizeImageAppearProperly.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/DownSizeImageAppearProperly.png
new file mode 100644
index 000000000000..4c2597ff9952
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/DownSizeImageAppearProperly.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/DynamicFontImageSourceColorShouldApplyOnTabIcon.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/DynamicFontImageSourceColorShouldApplyOnTabIcon.png
new file mode 100644
index 000000000000..e02d4f1d3237
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/DynamicFontImageSourceColorShouldApplyOnTabIcon.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/EntryClearButtonColorShouldUpdateOnThemeChange.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/EntryClearButtonColorShouldUpdateOnThemeChange.png
new file mode 100644
index 000000000000..7e05cd60da58
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/EntryClearButtonColorShouldUpdateOnThemeChange.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/FontImageSourceColorShouldApplyOnTabIcon.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/FontImageSourceColorShouldApplyOnTabIcon.png
new file mode 100644
index 000000000000..1c9b3664c1d3
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/FontImageSourceColorShouldApplyOnTabIcon.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue15330Test.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue15330Test.png
index 09b56a6bc1a9..42eb147602c3 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue15330Test.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue15330Test.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue23834FlyoutMisbehavior.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue23834FlyoutMisbehavior.png
new file mode 100644
index 000000000000..6d44dab294cf
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue23834FlyoutMisbehavior.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test.png
index ce34198d8db2..cde79199dba5 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_1.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_1.png
index e08752071342..778960f6a5ca 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_1.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_1.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_2.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_2.png
index 04cd284815b2..a4f2ab6e2e77 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_2.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_2.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_3.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_3.png
index d1e2a05207aa..917442071f8b 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_3.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_3.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_4.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_4.png
index 12b1e3f77595..f126c18a2e5a 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_4.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_4.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_5.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_5.png
index dd3e12c0ba7c..b92a3b3e3a17 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_5.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue24414Test_5.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue2775Test.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue2775Test.png
index 5e73e13e53f0..00ebce85cba8 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue2775Test.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Issue2775Test.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SelectItem_VerifySelectedItem.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SelectItem_VerifySelectedItem.png
new file mode 100644
index 000000000000..7dcafc6c35db
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SelectItem_VerifySelectedItem.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFlowDirectionRTLAndTitle_VerifyFlowDirectionAndTitle.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFlowDirectionRTLAndTitle_VerifyFlowDirectionAndTitle.png
new file mode 100644
index 000000000000..1173266b0244
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFlowDirectionRTLAndTitle_VerifyFlowDirectionAndTitle.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFlowDirectionRTL_VerifyFlowDirection.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFlowDirectionRTL_VerifyFlowDirection.png
new file mode 100644
index 000000000000..152cdbb5aade
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFlowDirectionRTL_VerifyFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFontAttributesItalicAndFontFamilyDokdo_VerifyFontAttributesAndFontFamily.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFontAttributesItalicAndFontFamilyDokdo_VerifyFontAttributesAndFontFamily.png
new file mode 100644
index 000000000000..b90fadb407ad
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFontAttributesItalicAndFontFamilyDokdo_VerifyFontAttributesAndFontFamily.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFontSizeAndFontAttributesBold_VerifyFontSizeAndAttributes.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFontSizeAndFontAttributesBold_VerifyFontSizeAndAttributes.png
new file mode 100644
index 000000000000..c68c23ababfb
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFontSizeAndFontAttributesBold_VerifyFontSizeAndAttributes.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFontSizeAndFontFamilyDokdo_VerifyFontSizeAndFontFamily.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFontSizeAndFontFamilyDokdo_VerifyFontSizeAndFontFamily.png
new file mode 100644
index 000000000000..3f475aa887af
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetFontSizeAndFontFamilyDokdo_VerifyFontSizeAndFontFamily.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetHorizontalTextAlignmentAndSelectedItem_VerifySelectedItem.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetHorizontalTextAlignmentAndSelectedItem_VerifySelectedItem.png
new file mode 100644
index 000000000000..39740a40a34b
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetHorizontalTextAlignmentAndSelectedItem_VerifySelectedItem.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetHorizontalTextAlignmentEndAndTitle_VerifyTitleAlignment.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetHorizontalTextAlignmentEndAndTitle_VerifyTitleAlignment.png
new file mode 100644
index 000000000000..f13e01e3c120
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetHorizontalTextAlignmentEndAndTitle_VerifyTitleAlignment.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetIsEnabledFalse_VerifyPickerDisabled.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetIsEnabledFalse_VerifyPickerDisabled.png
new file mode 100644
index 000000000000..a9d7718cf094
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetIsEnabledFalse_VerifyPickerDisabled.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetSelectedIndex_VerifySelectedIndexAndItem.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetSelectedIndex_VerifySelectedIndexAndItem.png
new file mode 100644
index 000000000000..c449d8a44e0e
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetSelectedIndex_VerifySelectedIndexAndItem.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetSelectedItem_VerifySelectedItemLabel.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetSelectedItem_VerifySelectedItemLabel.png
new file mode 100644
index 000000000000..19b076d496af
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetSelectedItem_VerifySelectedItemLabel.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTextColorRed_VerifyTextColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTextColorRed_VerifyTextColor.png
new file mode 100644
index 000000000000..6d0fe67a71a2
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTextColorRed_VerifyTextColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleColorOrange_VerifyTitleColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleColorOrange_VerifyTitleColor.png
new file mode 100644
index 000000000000..c55e9f4ecc23
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleColorOrange_VerifyTitleColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleWithFontAttributeBold_VerifyTitleAndFontAttribute.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleWithFontAttributeBold_VerifyTitleAndFontAttribute.png
new file mode 100644
index 000000000000..2c7f37a4812e
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleWithFontAttributeBold_VerifyTitleAndFontAttribute.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleWithFontFamilyDokdo_VerifyTitleAndFontFamily.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleWithFontFamilyDokdo_VerifyTitleAndFontFamily.png
new file mode 100644
index 000000000000..d54391459ae9
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleWithFontFamilyDokdo_VerifyTitleAndFontFamily.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleWithFontSize_VerifyTitleAndFontSize.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleWithFontSize_VerifyTitleAndFontSize.png
new file mode 100644
index 000000000000..d4f58c63f2b7
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitleWithFontSize_VerifyTitleAndFontSize.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitle_VerifyTitleLabel.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitle_VerifyTitleLabel.png
new file mode 100644
index 000000000000..17516161b29b
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetTitle_VerifyTitleLabel.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetVerticalTextAlignmentAndSelectedItem_VerifySelectedItem.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetVerticalTextAlignmentAndSelectedItem_VerifySelectedItem.png
new file mode 100644
index 000000000000..4b75eb789599
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetVerticalTextAlignmentAndSelectedItem_VerifySelectedItem.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetVerticalTextAlignmentEndAndTitle_VerifyTitleAlignment.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetVerticalTextAlignmentEndAndTitle_VerifyTitleAlignment.png
new file mode 100644
index 000000000000..3fd580f50cfc
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_SetVerticalTextAlignmentEndAndTitle_VerifyTitleAlignment.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_TapPicker_TakeScreenshot.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_TapPicker_TakeScreenshot.png
new file mode 100644
index 000000000000..07ad907cc186
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_TapPicker_TakeScreenshot.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_Validate_VerifyLabels.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_Validate_VerifyLabels.png
new file mode 100644
index 000000000000..fd83fc468c6a
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Picker_Validate_VerifyLabels.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/PlaceholderColorShouldChange.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/PlaceholderColorShouldChange.png
index c1b39131dbd4..b79711069da8 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/PlaceholderColorShouldChange.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/PlaceholderColorShouldChange.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ChangeFlowDirection_RTL_VerifyLabel.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ChangeFlowDirection_RTL_VerifyLabel.png
index 0b4762be6ad5..cc8f536ad361 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ChangeFlowDirection_RTL_VerifyLabel.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ChangeFlowDirection_RTL_VerifyLabel.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ProgressToMethod_VerifyVisualState.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ProgressToMethod_VerifyVisualState.png
index b261d579b66f..92e4c39459c0 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ProgressToMethod_VerifyVisualState.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ProgressToMethod_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetIsVisibleFalse_VerifyLabel.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetIsVisibleFalse_VerifyLabel.png
index 71ca09b1b93c..708ff9645bbe 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetIsVisibleFalse_VerifyLabel.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetIsVisibleFalse_VerifyLabel.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetProgressColorAndBackgroundColor_VerifyVisualState.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetProgressColorAndBackgroundColor_VerifyVisualState.png
index 5561ee67878a..4191502838a9 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetProgressColorAndBackgroundColor_VerifyVisualState.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetProgressColorAndBackgroundColor_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetProgressNegativeValue.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetProgressNegativeValue.png
index 7b8127ee3f9c..d612f7236739 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetProgressNegativeValue.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_SetProgressNegativeValue.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ToggleShadow_VerifyVisualState.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ToggleShadow_VerifyVisualState.png
index 4af293477769..9761cf847bf7 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ToggleShadow_VerifyVisualState.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ProgressBar_ToggleShadow_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SearchbarColorsShouldUpdate.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SearchbarColorsShouldUpdate.png
new file mode 100644
index 000000000000..df6bf62b627f
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SearchbarColorsShouldUpdate.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SelectedItemsShowSelected_multiple.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SelectedItemsShowSelected_multiple.png
new file mode 100644
index 000000000000..a5042cb11c30
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SelectedItemsShowSelected_multiple.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SelectedItemsShowSelected_none.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SelectedItemsShowSelected_none.png
new file mode 100644
index 000000000000..7c51e889dcb8
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SelectedItemsShowSelected_none.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SelectedItemsShowSelected_single.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SelectedItemsShowSelected_single.png
new file mode 100644
index 000000000000..ca7c6ad59a38
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/SelectedItemsShowSelected_single.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShadowUpdateColor1.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShadowUpdateColor1.png
index 68d3416139ae..3d9cfd13b687 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShadowUpdateColor1.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShadowUpdateColor1.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShadowUpdateColor2.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShadowUpdateColor2.png
index bde49628af61..339df55124b3 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShadowUpdateColor2.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShadowUpdateColor2.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_ChangeFlowDirection_RTL_VerifyScreenshot.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_ChangeFlowDirection_RTL_VerifyScreenshot.png
index 745a644f9455..8c02c91e5160 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_ChangeFlowDirection_RTL_VerifyScreenshot.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_ChangeFlowDirection_RTL_VerifyScreenshot.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetColor.png
index b6a860c428b0..21bd35aa2b9d 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetColor.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetEnabledStateToFalse_VerifyScreenshot.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetEnabledStateToFalse_VerifyScreenshot.png
index 4485dac793c0..2263b30a05a9 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetEnabledStateToFalse_VerifyScreenshot.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetEnabledStateToFalse_VerifyScreenshot.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOffset_PositiveValues.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOffset_PositiveValues.png
index 98f788a880a0..e1d1d0dfe820 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOffset_PositiveValues.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOffset_PositiveValues.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOffset_Zero.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOffset_Zero.png
index 313d18ab324b..acaec6bfd648 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOffset_Zero.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOffset_Zero.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOpacity.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOpacity.png
index 851baba2dea1..2649e60fee90 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOpacity.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOpacity.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOpacity_Zero.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOpacity_Zero.png
index 7c49ab22eb96..f9d0f4898d19 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOpacity_Zero.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetOpacity_Zero.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetRadius.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetRadius.png
index 1e04749829f4..038c713158c9 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetRadius.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetRadius.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetRadius_Zero.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetRadius_Zero.png
index 416ea487fc59..0072526c0a34 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetRadius_Zero.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetRadius_Zero.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetVisibilityToFalse_VerifyScreenshot.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetVisibilityToFalse_VerifyScreenshot.png
index da029b5b7ee2..f2dd893c526a 100644
Binary files a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetVisibilityToFalse_VerifyScreenshot.png and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/Shadow_SetVisibilityToFalse_VerifyScreenshot.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShouldDisplayLabelWithoutBeingCroppedInsideBorder.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShouldDisplayLabelWithoutBeingCroppedInsideBorder.png
new file mode 100644
index 000000000000..3bb63e0473d2
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/ShouldDisplayLabelWithoutBeingCroppedInsideBorder.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_BackgroundColor_WithGrid.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_BackgroundColor_WithGrid.png
new file mode 100644
index 000000000000..61326ca38eae
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_BackgroundColor_WithGrid.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_ForegroundColor_WithBackgroundColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_ForegroundColor_WithBackgroundColor.png
new file mode 100644
index 000000000000..ac868d72ce49
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_ForegroundColor_WithBackgroundColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_ForegroundColor_WithGrid.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_ForegroundColor_WithGrid.png
new file mode 100644
index 000000000000..d094b1b2e28b
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_ForegroundColor_WithGrid.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithBackgroundColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithBackgroundColor.png
new file mode 100644
index 000000000000..58254c1be202
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithBackgroundColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithForegroundColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithForegroundColor.png
new file mode 100644
index 000000000000..fa6182e0c9bc
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithForegroundColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithSearchBar.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithSearchBar.png
new file mode 100644
index 000000000000..adcd91940b49
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithSearchBar.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithTrailingContentAndLeadingContent.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithTrailingContentAndLeadingContent.png
new file mode 100644
index 000000000000..e27a046dd92b
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Icon_WithTrailingContentAndLeadingContent.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_IsVisible.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_IsVisible.png
new file mode 100644
index 000000000000..dcbc95c4c2ab
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_IsVisible.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_Entry.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_Entry.png
new file mode 100644
index 000000000000..df90abcf2e73
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_Entry.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithBackgroundColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithBackgroundColor.png
new file mode 100644
index 000000000000..c08922abca48
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithBackgroundColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithGrid.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithGrid.png
new file mode 100644
index 000000000000..4489cd1e1a93
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithGrid.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithHorizontalStackLayout.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithHorizontalStackLayout.png
new file mode 100644
index 000000000000..891d84105dac
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithHorizontalStackLayout.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithSearchBar.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithSearchBar.png
new file mode 100644
index 000000000000..c9f58b8a4246
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TitleAndSubTitle_WithSearchBar.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithBackgroundColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithBackgroundColor.png
new file mode 100644
index 000000000000..f90014bf5a1b
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithBackgroundColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithGrid.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithGrid.png
new file mode 100644
index 000000000000..052b4306332d
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithGrid.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithHorizontalStackLayout.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithHorizontalStackLayout.png
new file mode 100644
index 000000000000..b17dfd894cd9
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithHorizontalStackLayout.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithSearchBar.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithSearchBar.png
new file mode 100644
index 000000000000..6083042982ac
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithSearchBar.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithTitleAndSubtitle.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithTitleAndSubtitle.png
new file mode 100644
index 000000000000..08d73ed9fab4
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_TrailingContentAndLeadingContent_WithTitleAndSubtitle.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Window.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Window.png
new file mode 100644
index 000000000000..5f724da15022
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/TitleBar_Window.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyDataTemplateParentIsNotNull.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyDataTemplateParentIsNotNull.png
new file mode 100644
index 000000000000..ded903c9b9f8
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyDataTemplateParentIsNotNull.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColor.png
new file mode 100644
index 000000000000..9b179caac6e9
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWhenItemsAdded.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWhenItemsAdded.png
new file mode 100644
index 000000000000..193cce016177
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWhenItemsAdded.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWithFlowDirection.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWithFlowDirection.png
new file mode 100644
index 000000000000..f1dff83c9c90
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWithFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWithIndicatorShape.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWithIndicatorShape.png
new file mode 100644
index 000000000000..8c97fe493aca
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWithIndicatorShape.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWithIndicatorSize.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWithIndicatorSize.png
new file mode 100644
index 000000000000..bfdaaccc49d2
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorColorWithIndicatorSize.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorHideSingleIsFalse.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorHideSingleIsFalse.png
new file mode 100644
index 000000000000..92718b34eddb
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorHideSingleIsFalse.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorHideSingleIsFalseWithSelectedIndicatorColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorHideSingleIsFalseWithSelectedIndicatorColor.png
new file mode 100644
index 000000000000..369cb64b6de0
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorHideSingleIsFalseWithSelectedIndicatorColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorHideSingleWithIndicatorSize.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorHideSingleWithIndicatorSize.png
new file mode 100644
index 000000000000..d676c1a00c05
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorHideSingleWithIndicatorSize.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShape.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShape.png
new file mode 100644
index 000000000000..281ef9b529ba
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShape.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShapeWithFlowDirection.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShapeWithFlowDirection.png
new file mode 100644
index 000000000000..98964a730bc0
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShapeWithFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShapeWithHideSingle.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShapeWithHideSingle.png
new file mode 100644
index 000000000000..4a7b2990ce65
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShapeWithHideSingle.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShapeWithMaximumVisible.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShapeWithMaximumVisible.png
new file mode 100644
index 000000000000..68530d815ef3
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorShapeWithMaximumVisible.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorSizeWithFlowDirection.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorSizeWithFlowDirection.png
new file mode 100644
index 000000000000..a4a4f75a0269
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorSizeWithFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorSizeWithIndicatorShape.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorSizeWithIndicatorShape.png
new file mode 100644
index 000000000000..a768502e215f
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorSizeWithIndicatorShape.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorSizeWithMaximumVisible.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorSizeWithMaximumVisible.png
new file mode 100644
index 000000000000..b08317373417
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorSizeWithMaximumVisible.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_FlowDirection.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_FlowDirection.png
new file mode 100644
index 000000000000..9dc7fcbd88d8
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_FlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_HideSingle.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_HideSingle.png
new file mode 100644
index 000000000000..ead4ccbb3093
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_HideSingle.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_IsVisible.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_IsVisible.png
new file mode 100644
index 000000000000..b5e5ea7e1f7a
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_IsVisible.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_MaximumVisible.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_MaximumVisible.png
new file mode 100644
index 000000000000..577b435c2c85
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifyIndicatorView_MaximumVisible.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySearchBarFlowDirection.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySearchBarFlowDirection.png
new file mode 100644
index 000000000000..f864a9007b3f
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySearchBarFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColor.png
new file mode 100644
index 000000000000..a9f2668639e4
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithFlowDirection.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithFlowDirection.png
new file mode 100644
index 000000000000..83dbcf22cdde
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithIndicatorColor.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithIndicatorColor.png
new file mode 100644
index 000000000000..3e094838a780
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithIndicatorColor.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithIndicatorShape.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithIndicatorShape.png
new file mode 100644
index 000000000000..3253ca21164f
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithIndicatorShape.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithIndicatorSize.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithIndicatorSize.png
new file mode 100644
index 000000000000..02f1ca5fc1b1
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorColorWithIndicatorSize.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorSize.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorSize.png
new file mode 100644
index 000000000000..b482f930060a
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/VerifySelectedIndicatorSize.png differ
diff --git a/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/WebViewShouldNotMirrored.png b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/WebViewShouldNotMirrored.png
new file mode 100644
index 000000000000..bf6c65012972
Binary files /dev/null and b/src/Controls/tests/TestCases.WinUI.Tests/snapshots/windows/WebViewShouldNotMirrored.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/DownSizeImageAppearProperly.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/DownSizeImageAppearProperly.png
new file mode 100644
index 000000000000..79d08e141924
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/DownSizeImageAppearProperly.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldUpdateOnThemeChange.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldUpdateOnThemeChange.png
new file mode 100644
index 000000000000..5723dcdc4e4f
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldUpdateOnThemeChange.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue23834FlyoutMisbehavior.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue23834FlyoutMisbehavior.png
new file mode 100644
index 000000000000..19c683cd87f9
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue23834FlyoutMisbehavior.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/PageShouldNotScroll.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/PageShouldNotScroll.png
index 20a088003f6a..99824db0acb0 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/PageShouldNotScroll.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/PageShouldNotScroll.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/PickerNewKeyboardIsAboveKeyboard_Entry7.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/PickerNewKeyboardIsAboveKeyboard_Entry7.png
index f6a2ba16e58c..70eb442b2ad0 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/PickerNewKeyboardIsAboveKeyboard_Entry7.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/PickerNewKeyboardIsAboveKeyboard_Entry7.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetCharacterSpacing_VerifyCharacterSpacingLabel.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetCharacterSpacing_VerifyCharacterSpacingLabel.png
new file mode 100644
index 000000000000..bf7fe12fdea9
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetCharacterSpacing_VerifyCharacterSpacingLabel.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFlowDirectionRTL_VerifyFlowDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFlowDirectionRTL_VerifyFlowDirection.png
new file mode 100644
index 000000000000..0df5476a67ae
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFlowDirectionRTL_VerifyFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFontAttributesItalicAndFontFamilyDokdo_VerifyFontAttributesAndFontFamily.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFontAttributesItalicAndFontFamilyDokdo_VerifyFontAttributesAndFontFamily.png
new file mode 100644
index 000000000000..b3687aac5f77
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFontAttributesItalicAndFontFamilyDokdo_VerifyFontAttributesAndFontFamily.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFontSizeAndFontAttributesBold_VerifyFontSizeAndAttributes.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFontSizeAndFontAttributesBold_VerifyFontSizeAndAttributes.png
new file mode 100644
index 000000000000..7c968f74647e
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFontSizeAndFontAttributesBold_VerifyFontSizeAndAttributes.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFontSizeAndFontFamilyDokdo_VerifyFontSizeAndFontFamily.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFontSizeAndFontFamilyDokdo_VerifyFontSizeAndFontFamily.png
new file mode 100644
index 000000000000..2ab36ed86b78
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetFontSizeAndFontFamilyDokdo_VerifyFontSizeAndFontFamily.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetHorizontalTextAlignmentAndSelectedItem_VerifySelectedItem.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetHorizontalTextAlignmentAndSelectedItem_VerifySelectedItem.png
new file mode 100644
index 000000000000..e44915fa33f6
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetHorizontalTextAlignmentAndSelectedItem_VerifySelectedItem.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetIsEnabledFalse_VerifyPickerDisabled.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetIsEnabledFalse_VerifyPickerDisabled.png
new file mode 100644
index 000000000000..0376586ded22
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetIsEnabledFalse_VerifyPickerDisabled.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetSelectedIndex_VerifySelectedIndexAndItem.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetSelectedIndex_VerifySelectedIndexAndItem.png
new file mode 100644
index 000000000000..ae334acf51c5
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetSelectedIndex_VerifySelectedIndexAndItem.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetSelectedItem_VerifySelectedItemLabel.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetSelectedItem_VerifySelectedItemLabel.png
new file mode 100644
index 000000000000..5fb1ff882e4d
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetSelectedItem_VerifySelectedItemLabel.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetShadow_VerifyShadow.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetShadow_VerifyShadow.png
new file mode 100644
index 000000000000..1cec547211ef
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetShadow_VerifyShadow.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetTextColorRed_VerifyTextColor.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetTextColorRed_VerifyTextColor.png
new file mode 100644
index 000000000000..aafb1990fc64
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetTextColorRed_VerifyTextColor.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetVerticalTextAlignmentAndSelectedItem_VerifySelectedItem.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetVerticalTextAlignmentAndSelectedItem_VerifySelectedItem.png
new file mode 100644
index 000000000000..c8cc7658ad91
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_SetVerticalTextAlignmentAndSelectedItem_VerifySelectedItem.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_TapPicker_TakeScreenshot.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_TapPicker_TakeScreenshot.png
new file mode 100644
index 000000000000..63cb70566611
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_TapPicker_TakeScreenshot.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_Validate_VerifyLabels.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_Validate_VerifyLabels.png
new file mode 100644
index 000000000000..470de3e5454f
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Picker_Validate_VerifyLabels.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/RightToLeftFlowDirectionShouldWork.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/RightToLeftFlowDirectionShouldWork.png
new file mode 100644
index 000000000000..8118ea02e5ec
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/RightToLeftFlowDirectionShouldWork.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetCancelButtonAndTextColor_VerifyVisualState.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetCancelButtonAndTextColor_VerifyVisualState.png
index 94ccef85eb02..11a0e1929e50 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetCancelButtonAndTextColor_VerifyVisualState.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetCancelButtonAndTextColor_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFlowDirection_VerifyVisualState.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFlowDirection_VerifyVisualState.png
index a198efe333de..1bc49d75d917 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFlowDirection_VerifyVisualState.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFlowDirection_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontFamilyAndFontSize_VerifyVisualState.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontFamilyAndFontSize_VerifyVisualState.png
index 975fd6a6b075..c177f3507ede 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontFamilyAndFontSize_VerifyVisualState.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontFamilyAndFontSize_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontFamilyAndText_VerifyVisualState.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontFamilyAndText_VerifyVisualState.png
index e92e58568d51..ca50aa48dae9 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontFamilyAndText_VerifyVisualState.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontFamilyAndText_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontSizeAndText_VerifyVisualState.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontSizeAndText_VerifyVisualState.png
index 11e04607e137..4f30472c92c1 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontSizeAndText_VerifyVisualState.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetFontSizeAndText_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetHorizontalTextAlignmentAndText_VerifyVisualState.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetHorizontalTextAlignmentAndText_VerifyVisualState.png
index 32361eef3a69..1440996ac236 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetHorizontalTextAlignmentAndText_VerifyVisualState.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetHorizontalTextAlignmentAndText_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetShadow_VerifyVisualState.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetShadow_VerifyVisualState.png
index 7f3e55ece2f3..36d358275d7c 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetShadow_VerifyVisualState.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchBar_SetShadow_VerifyVisualState.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchbarColorsShouldUpdate.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchbarColorsShouldUpdate.png
new file mode 100644
index 000000000000..4300b3756f63
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/SearchbarColorsShouldUpdate.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShadowAddClip.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShadowAddClip.png
index 7e857aaa8d2a..451240246401 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShadowAddClip.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShadowAddClip.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShadowRemoveClip.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShadowRemoveClip.png
index 2be73b534c7b..30ebdd709e87 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShadowRemoveClip.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShadowRemoveClip.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_AddClip_VerifyShadow.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_AddClip_VerifyShadow.png
index 87f93da31763..f7bef7a7961f 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_AddClip_VerifyShadow.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_AddClip_VerifyShadow.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_ChangeFlowDirection_RTL_VerifyScreenshot.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_ChangeFlowDirection_RTL_VerifyScreenshot.png
index 38c02ae9484c..8c80b5e17945 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_ChangeFlowDirection_RTL_VerifyScreenshot.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_ChangeFlowDirection_RTL_VerifyScreenshot.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetEnabledStateToFalse_VerifyScreenshot.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetEnabledStateToFalse_VerifyScreenshot.png
index c8b6befc8d58..82de865a3fc5 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetEnabledStateToFalse_VerifyScreenshot.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetEnabledStateToFalse_VerifyScreenshot.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOffset_PositiveValues.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOffset_PositiveValues.png
index 3bc683915cce..83e52d091378 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOffset_PositiveValues.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOffset_PositiveValues.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOffset_Zero.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOffset_Zero.png
index 63761fe7dde7..be2a30fa197e 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOffset_Zero.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOffset_Zero.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOpacity.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOpacity.png
index 58226acfd196..452e1944ffa0 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOpacity.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOpacity.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOpacity_Zero.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOpacity_Zero.png
index e2a0fad09a61..190b6d76692a 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOpacity_Zero.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetOpacity_Zero.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetRadius.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetRadius.png
index 97e30bc07e18..194707b609be 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetRadius.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetRadius.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetRadius_Zero.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetRadius_Zero.png
index 537df2f30600..65684c1bdaf6 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetRadius_Zero.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetRadius_Zero.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetVisibilityToFalse_VerifyScreenshot.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetVisibilityToFalse_VerifyScreenshot.png
index 9931f585c792..b18a64ccd696 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetVisibilityToFalse_VerifyScreenshot.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Shadow_SetVisibilityToFalse_VerifyScreenshot.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShouldDisplayLabelWithoutBeingCroppedInsideBorder.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShouldDisplayLabelWithoutBeingCroppedInsideBorder.png
new file mode 100644
index 000000000000..169f774f9a84
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ShouldDisplayLabelWithoutBeingCroppedInsideBorder.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ValidateEntryClearButtonVisibilityBehavior.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ValidateEntryClearButtonVisibilityBehavior.png
index 3ed29868cddc..95ab55257a45 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ValidateEntryClearButtonVisibilityBehavior.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ValidateEntryClearButtonVisibilityBehavior.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearVisiblityButtonWhenTextColorChanged.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearVisiblityButtonWhenTextColorChanged.png
index f4c1863b853f..3399eb244c1e 100644
Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearVisiblityButtonWhenTextColorChanged.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearVisiblityButtonWhenTextColorChanged.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyDataTemplateParentIsNotNull.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyDataTemplateParentIsNotNull.png
new file mode 100644
index 000000000000..788c38b197a5
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyDataTemplateParentIsNotNull.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColor.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColor.png
new file mode 100644
index 000000000000..e87aa65935a0
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColor.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColorWhenItemsAdded.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColorWhenItemsAdded.png
new file mode 100644
index 000000000000..f886224c23fc
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColorWhenItemsAdded.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColorWithFlowDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColorWithFlowDirection.png
new file mode 100644
index 000000000000..dae98004484a
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColorWithFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColorWithIndicatorSize.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColorWithIndicatorSize.png
new file mode 100644
index 000000000000..5551e58234cf
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorColorWithIndicatorSize.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorHideSingleIsFalse.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorHideSingleIsFalse.png
new file mode 100644
index 000000000000..e6244cfea2af
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorHideSingleIsFalse.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorHideSingleIsFalseWithSelectedIndicatorColor.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorHideSingleIsFalseWithSelectedIndicatorColor.png
new file mode 100644
index 000000000000..b78af4aa26db
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorHideSingleIsFalseWithSelectedIndicatorColor.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorHideSingleWithIndicatorSize.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorHideSingleWithIndicatorSize.png
new file mode 100644
index 000000000000..a2896a69e93c
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorHideSingleWithIndicatorSize.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorSizeWithFlowDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorSizeWithFlowDirection.png
new file mode 100644
index 000000000000..7cca2225ee79
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorSizeWithFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorSizeWithMaximumVisible.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorSizeWithMaximumVisible.png
new file mode 100644
index 000000000000..a667e093b91f
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorSizeWithMaximumVisible.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_FlowDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_FlowDirection.png
new file mode 100644
index 000000000000..67f0f9d42801
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_FlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_HideSingle.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_HideSingle.png
new file mode 100644
index 000000000000..baf4a6a6f787
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_HideSingle.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_IsVisible.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_IsVisible.png
new file mode 100644
index 000000000000..b8e8b2e3fe4d
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_IsVisible.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_MaximumVisible.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_MaximumVisible.png
new file mode 100644
index 000000000000..3eebde4f04f5
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_MaximumVisible.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_Shadow.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_Shadow.png
new file mode 100644
index 000000000000..536d102ae636
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyIndicatorView_Shadow.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySearchBarFlowDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySearchBarFlowDirection.png
new file mode 100644
index 000000000000..219e81d16253
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySearchBarFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColor.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColor.png
new file mode 100644
index 000000000000..e14fee440b56
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColor.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColorWithFlowDirection.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColorWithFlowDirection.png
new file mode 100644
index 000000000000..972d73c934bd
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColorWithFlowDirection.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColorWithIndicatorColor.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColorWithIndicatorColor.png
new file mode 100644
index 000000000000..0c8243175a28
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColorWithIndicatorColor.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColorWithIndicatorSize.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColorWithIndicatorSize.png
new file mode 100644
index 000000000000..7f1eac81679f
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifySelectedIndicatorColorWithIndicatorSize.png differ
diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/WebViewShouldNotMirrored.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/WebViewShouldNotMirrored.png
new file mode 100644
index 000000000000..2601c2ffb60b
Binary files /dev/null and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/WebViewShouldNotMirrored.png differ
diff --git a/src/Core/src/Core/Extensions/ITextInputExtensions.cs b/src/Core/src/Core/Extensions/ITextInputExtensions.cs
index d6c1fc125f0e..246404944b01 100644
--- a/src/Core/src/Core/Extensions/ITextInputExtensions.cs
+++ b/src/Core/src/Core/Extensions/ITextInputExtensions.cs
@@ -39,7 +39,13 @@ public static bool TextWithinMaxLength(this ITextInput textInput, string? text,
var newLength = currLength + addLength - remLength;
- return newLength <= textInput.MaxLength;
+ var shouldChange = newLength <= textInput.MaxLength;
+
+ // cut text when user is pasting a text longer that maxlength
+ if(!shouldChange && !string.IsNullOrWhiteSpace(replacementString) && replacementString!.Length >= textInput.MaxLength)
+ textInput.Text = replacementString!.Substring(0, textInput.MaxLength);
+
+ return shouldChange;
}
#endif
diff --git a/src/Core/src/Core/IPlatformApplication.cs b/src/Core/src/Core/IPlatformApplication.cs
index c3d1d785d688..08608854e28e 100644
--- a/src/Core/src/Core/IPlatformApplication.cs
+++ b/src/Core/src/Core/IPlatformApplication.cs
@@ -1,33 +1,99 @@
using System;
-namespace Microsoft.Maui
+namespace Microsoft.Maui;
+
+///
+/// Represents the platform-specific application instance that hosts a .NET MAUI application.
+///
+///
+/// This interface provides access to platform-specific services and the main application instance.
+/// Each platform (Android, iOS, Windows, etc.) provides its own implementation of this interface.
+/// Use the IPlatformApplication.Current property to access the current platform application instance.
+///
+public interface IPlatformApplication
{
- ///
- /// IPlatformApplication.
- /// Hosts the platform application.
- ///
- public interface IPlatformApplication
- {
#if !NETSTANDARD2_0
- ///
- /// Gets the current IPlatformApplication.
- /// This must be set in each implementation manually, as we can't
- /// have a true static be used in the implementation.
- ///
- public static IPlatformApplication? Current { get; set; }
+ ///
+ /// Gets or sets the current platform application instance.
+ ///
+ ///
+ /// The current instance, or if not set.
+ ///
+ ///
+ ///
+ /// This property provides access to the platform-specific application instance and its services.
+ /// It must be manually set by each platform implementation during application startup.
+ ///
+ ///
+ /// Common usage scenarios:
+ ///
+ ///
+ /// Accessing platform services: IPlatformApplication.Current?.Services
+ /// Getting the application instance: IPlatformApplication.Current?.Application
+ /// Platform-specific operations requiring the native application context
+ ///
+ ///
+ /// Always check for before using this property, especially during application startup
+ /// or in unit tests where the platform application may not be initialized.
+ ///
+ ///
+ ///
+ ///
+ /// // Accessing a service from the platform application
+ /// var platformApp = IPlatformApplication.Current;
+ /// if (platformApp != null)
+ /// {
+ /// var myService = platformApp.Services.GetService<IMyService>();
+ /// // Use the service...
+ /// }
+ ///
+ ///
+ public static IPlatformApplication? Current { get; set; }
#endif
- ///
- /// Gets the Service Provider.
- /// .
- ///
- public IServiceProvider Services { get; }
+ ///
+ /// Gets the dependency injection service provider for the platform application.
+ ///
+ ///
+ /// An instance containing platform-specific and application services.
+ ///
+ ///
+ /// Use this service provider to resolve services that have been registered with the platform's
+ /// dependency injection container. This includes both framework services and custom services
+ /// registered during application configuration.
+ ///
+ ///
+ ///
+ /// // Getting a service from the platform application
+ /// var logger = platformApp.Services.GetService<ILogger>();
+ /// var httpClient = platformApp.Services.GetRequiredService<HttpClient>();
+ ///
+ ///
+ public IServiceProvider Services { get; }
- ///
- /// Gets the Application.
- /// .
- ///
- public IApplication Application { get; }
- }
+ ///
+ /// Gets the .NET MAUI application instance.
+ ///
+ ///
+ /// An instance representing the current MAUI application.
+ ///
+ ///
+ /// This property provides access to the main MAUI application instance, which contains
+ /// application-level configuration, the main page, and application lifecycle methods.
+ /// Use this to access application-wide properties and methods.
+ ///
+ ///
+ ///
+ /// // Accessing the main page from the application
+ /// var mainPage = platformApp.Application.MainPage;
+ ///
+ /// // Triggering application lifecycle events
+ /// if (platformApp.Application is Application app)
+ /// {
+ /// app.OnStart();
+ /// }
+ ///
+ ///
+ public IApplication Application { get; }
}
diff --git a/src/Core/src/Handlers/Editor/EditorHandler.iOS.cs b/src/Core/src/Handlers/Editor/EditorHandler.iOS.cs
index 843d962dc3ea..aaf1a2ec7eda 100644
--- a/src/Core/src/Handlers/Editor/EditorHandler.iOS.cs
+++ b/src/Core/src/Handlers/Editor/EditorHandler.iOS.cs
@@ -14,28 +14,10 @@ public partial class EditorHandler : ViewHandler
protected override MauiTextView CreatePlatformView()
{
var platformEditor = new MauiTextView();
-
-#if !MACCATALYST
- var accessoryView = new MauiDoneAccessoryView();
- accessoryView.SetDataContext(this);
- accessoryView.SetDoneClicked(OnDoneClicked);
- platformEditor.InputAccessoryView = accessoryView;
-#endif
-
+ platformEditor.AddMauiDoneAccessoryView(this);
return platformEditor;
}
-#if !MACCATALYST
- static void OnDoneClicked(object sender)
- {
- if (sender is IEditorHandler handler)
- {
- handler.PlatformView.ResignFirstResponder();
- handler.VirtualView.Completed();
- }
- }
-#endif
-
public override void SetVirtualView(IView view)
{
base.SetVirtualView(view);
diff --git a/src/Core/src/Handlers/Entry/EntryHandler.Android.cs b/src/Core/src/Handlers/Entry/EntryHandler.Android.cs
index d88351d432e0..167d5fef404e 100644
--- a/src/Core/src/Handlers/Entry/EntryHandler.Android.cs
+++ b/src/Core/src/Handlers/Entry/EntryHandler.Android.cs
@@ -80,8 +80,15 @@ public static void MapBackground(IEntryHandler handler, IEntry entry) =>
public static void MapText(IEntryHandler handler, IEntry entry) =>
handler.PlatformView?.UpdateText(entry);
- public static void MapTextColor(IEntryHandler handler, IEntry entry) =>
+ public static void MapTextColor(IEntryHandler handler, IEntry entry)
+ {
handler.PlatformView?.UpdateTextColor(entry);
+ if (handler is EntryHandler platformHandler && platformHandler._clearButtonVisible)
+ {
+ // Update the clear button color to match the text color
+ handler.PlatformView?.UpdateClearButtonColor(entry.TextColor, platformHandler.GetClearButtonDrawable());
+ }
+ }
public static void MapIsPassword(IEntryHandler handler, IEntry entry)
{
@@ -256,10 +263,7 @@ internal void ShowClearButton()
var drawable = GetClearButtonDrawable();
- if (VirtualView?.TextColor is not null)
- drawable?.SetColorFilter(VirtualView.TextColor.ToPlatform(), FilterMode.SrcIn);
- else
- drawable?.ClearColorFilter();
+ PlatformView.UpdateClearButtonColor(VirtualView.TextColor, drawable);
if (PlatformView.LayoutDirection == LayoutDirection.Rtl)
PlatformView.SetCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
diff --git a/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs b/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs
index 20c31183773a..e6e2030716c8 100644
--- a/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs
+++ b/src/Core/src/Handlers/Entry/EntryHandler.iOS.cs
@@ -11,13 +11,18 @@ public partial class EntryHandler : ViewHandler
{
readonly MauiTextFieldProxy _proxy = new();
- protected override MauiTextField CreatePlatformView() =>
- new MauiTextField
+ protected override MauiTextField CreatePlatformView()
+ {
+ var platformEntry = new MauiTextField
{
BorderStyle = UITextBorderStyle.RoundedRect,
ClipsToBounds = true
};
+ platformEntry.AddMauiDoneAccessoryView(this);
+ return platformEntry;
+ }
+
public override void SetVirtualView(IView view)
{
base.SetVirtualView(view);
@@ -43,8 +48,14 @@ public static void MapText(IEntryHandler handler, IEntry entry)
MapFormatting(handler, entry);
}
- public static void MapTextColor(IEntryHandler handler, IEntry entry) =>
+ public static void MapTextColor(IEntryHandler handler, IEntry entry)
+ {
handler.PlatformView?.UpdateTextColor(entry);
+ if (entry.ClearButtonVisibility == ClearButtonVisibility.WhileEditing)
+ {
+ handler.PlatformView?.UpdateClearButtonColor(entry);
+ }
+ }
public static void MapIsPassword(IEntryHandler handler, IEntry entry) =>
handler.PlatformView?.UpdateIsPassword(entry);
diff --git a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
index 020e93cf4e61..8967777b2234 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.Windows.cs
@@ -102,6 +102,12 @@ private void OnWebMessageReceived(WebView2 sender, CoreWebView2WebMessageReceive
MessageReceived(args.TryGetWebMessageAsString());
}
+ internal static void MapFlowDirection(IHybridWebViewHandler handler, IHybridWebView hybridWebView)
+ {
+ // Explicitly do nothing here to override the base ViewHandler.MapFlowDirection behavior
+ // This prevents the WebView2.FlowDirection from being set, avoiding content mirroring
+ }
+
private async void OnWebResourceRequested(CoreWebView2 sender, CoreWebView2WebResourceRequestedEventArgs eventArgs)
{
// Get a deferral object so that WebView2 knows there's some async stuff going on. We call Complete() at the end of this method.
diff --git a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs
index 20b7014d4f39..ae52ea6e7a42 100644
--- a/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs
+++ b/src/Core/src/Handlers/HybridWebView/HybridWebViewHandler.cs
@@ -69,6 +69,9 @@ public partial class HybridWebViewHandler : IHybridWebViewHandler
public static IPropertyMapper Mapper = new PropertyMapper(ViewHandler.ViewMapper)
{
+#if WINDOWS
+ [nameof(IView.FlowDirection)] = MapFlowDirection,
+#endif
};
public static CommandMapper CommandMapper = new(ViewCommandMapper)
diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs
index 1d21a1dc52fc..aef4b346caf2 100644
--- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs
+++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs
@@ -4,6 +4,7 @@
using Android.Widget;
using AndroidX.AppCompat.Widget;
using static AndroidX.AppCompat.Widget.SearchView;
+using AView = Android.Views.View;
using SearchView = AndroidX.AppCompat.Widget.SearchView;
namespace Microsoft.Maui.Handlers
@@ -68,6 +69,25 @@ public static void MapPlaceholderColor(ISearchBarHandler handler, ISearchBar sea
handler.PlatformView?.UpdatePlaceholderColor(searchBar, DefaultPlaceholderTextColors, handler.QueryEditor);
}
+ internal static void MapFlowDirection(ISearchBarHandler handler, ISearchBar searchBar)
+ {
+ if (searchBar.FlowDirection == FlowDirection.MatchParent && searchBar.Parent != null && searchBar.Parent is IView parentView)
+ {
+ // When FlowDirection is MatchParent, respect the parent's FlowDirection
+ if (handler.PlatformView is AView platformView)
+ Microsoft.Maui.Platform.ViewExtensions.UpdateFlowDirection(platformView, parentView);
+
+ if (handler.QueryEditor is TextView textView)
+ Microsoft.Maui.Platform.TextViewExtensions.UpdateFlowDirection(textView, parentView);
+ }
+ else
+ {
+ // Otherwise, use the SearchBar's own FlowDirection
+ handler.PlatformView?.UpdateFlowDirection(searchBar);
+ handler.QueryEditor?.UpdateFlowDirection(searchBar);
+ }
+ }
+
public static void MapFont(ISearchBarHandler handler, ISearchBar searchBar)
{
var fontManager = handler.GetRequiredService();
@@ -92,6 +112,7 @@ public static void MapCharacterSpacing(ISearchBarHandler handler, ISearchBar sea
public static void MapTextColor(ISearchBarHandler handler, ISearchBar searchBar)
{
handler.QueryEditor?.UpdateTextColor(searchBar);
+ handler.PlatformView?.UpdateTextColor(searchBar);
}
public static void MapIsTextPredictionEnabled(ISearchBarHandler handler, ISearchBar searchBar)
diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs
index ef6615dbbc30..e102cce53de1 100644
--- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs
+++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.cs
@@ -34,6 +34,9 @@ public partial class SearchBarHandler : ISearchBarHandler
[nameof(ISearchBar.Text)] = MapText,
[nameof(ISearchBar.TextColor)] = MapTextColor,
[nameof(ISearchBar.CancelButtonColor)] = MapCancelButtonColor,
+#if ANDROID
+ [nameof(ISearchBar.FlowDirection)] = MapFlowDirection,
+#endif
[nameof(ISearchBar.Keyboard)] = MapKeyboard
};
diff --git a/src/Core/src/Handlers/View/ViewHandler.Windows.cs b/src/Core/src/Handlers/View/ViewHandler.Windows.cs
index 1c760bd798a4..efe1009bb60b 100644
--- a/src/Core/src/Handlers/View/ViewHandler.Windows.cs
+++ b/src/Core/src/Handlers/View/ViewHandler.Windows.cs
@@ -6,171 +6,218 @@
using Microsoft.UI.Xaml.Input;
using PlatformView = Microsoft.UI.Xaml.FrameworkElement;
-namespace Microsoft.Maui.Handlers
+namespace Microsoft.Maui.Handlers;
+
+public partial class ViewHandler
{
- public partial class ViewHandler
+ readonly static ConditionalWeakTable FocusManagerMapping = new();
+
+ static ViewHandler()
{
- readonly static ConditionalWeakTable FocusManagerMapping = new();
+ FocusManager.GotFocus += FocusManager_GotFocus;
+ FocusManager.LostFocus += FocusManager_LostFocus;
+ }
- static ViewHandler()
+ partial void ConnectingHandler(PlatformView? platformView)
+ {
+ if (platformView is not null)
{
- FocusManager.GotFocus += FocusManager_GotFocus;
- FocusManager.LostFocus += FocusManager_LostFocus;
+ FocusManagerMapping.Add(platformView, this);
}
+ }
- partial void ConnectingHandler(PlatformView? platformView)
- {
- if (platformView is not null)
- {
- FocusManagerMapping.Add(platformView, this);
- }
- }
+ partial void DisconnectingHandler(PlatformView platformView)
+ {
+ FocusManagerMapping.Remove(platformView);
+ UpdateIsFocused(false);
+ }
- partial void DisconnectingHandler(PlatformView platformView)
- {
- FocusManagerMapping.Remove(platformView);
- UpdateIsFocused(false);
- }
+ static partial void MappingFrame(IViewHandler handler, IView view)
+ {
+ // Both Clip and Shadow depend on the Control size.
+ handler.ToPlatform().UpdateClip(view);
+ handler.ToPlatform().UpdateShadow(view);
+ }
- static partial void MappingFrame(IViewHandler handler, IView view)
- {
- // Both Clip and Shadow depend on the Control size.
- handler.ToPlatform().UpdateClip(view);
- handler.ToPlatform().UpdateShadow(view);
- }
+ public static void MapTranslationX(IViewHandler handler, IView view)
+ {
+ // When a handler is connected, all properties are initialized using this method. So we can skip other properties.
+ handler.ToPlatform().UpdateTransformation(view);
+ }
- public static void MapTranslationX(IViewHandler handler, IView view)
+ public static void MapTranslationY(IViewHandler handler, IView view)
+ {
+ if (view.IsConnectingHandler())
{
- handler.ToPlatform().UpdateTransformation(view);
+ return;
}
- public static void MapTranslationY(IViewHandler handler, IView view)
- {
- handler.ToPlatform().UpdateTransformation(view);
- }
+ handler.ToPlatform().UpdateTransformation(view);
+ }
- public static void MapScale(IViewHandler handler, IView view)
+ public static void MapScale(IViewHandler handler, IView view)
+ {
+ if (view.IsConnectingHandler())
{
- handler.ToPlatform().UpdateTransformation(view);
+ return;
}
- public static void MapScaleX(IViewHandler handler, IView view)
+ handler.ToPlatform().UpdateTransformation(view);
+ }
+
+ public static void MapScaleX(IViewHandler handler, IView view)
+ {
+ if (view.IsConnectingHandler())
{
- handler.ToPlatform().UpdateTransformation(view);
+ return;
}
- public static void MapScaleY(IViewHandler handler, IView view)
+ handler.ToPlatform().UpdateTransformation(view);
+ }
+
+ public static void MapScaleY(IViewHandler handler, IView view)
+ {
+ if (view.IsConnectingHandler())
{
- handler.ToPlatform().UpdateTransformation(view);
+ return;
}
- public static void MapRotation(IViewHandler handler, IView view)
+ handler.ToPlatform().UpdateTransformation(view);
+ }
+
+ public static void MapRotation(IViewHandler handler, IView view)
+ {
+ if (view.IsConnectingHandler())
{
- handler.ToPlatform().UpdateTransformation(view);
+ return;
}
- public static void MapRotationX(IViewHandler handler, IView view)
+ handler.ToPlatform().UpdateTransformation(view);
+ }
+
+ public static void MapRotationX(IViewHandler handler, IView view)
+ {
+ if (view.IsConnectingHandler())
{
- handler.ToPlatform().UpdateTransformation(view);
+ return;
}
- public static void MapRotationY(IViewHandler handler, IView view)
+ handler.ToPlatform().UpdateTransformation(view);
+ }
+
+ public static void MapRotationY(IViewHandler handler, IView view)
+ {
+ if (view.IsConnectingHandler())
{
- handler.ToPlatform().UpdateTransformation(view);
+ return;
}
- public static void MapAnchorX(IViewHandler handler, IView view)
+ handler.ToPlatform().UpdateTransformation(view);
+ }
+
+ public static void MapAnchorX(IViewHandler handler, IView view)
+ {
+ if (view.IsConnectingHandler())
{
- handler.ToPlatform().UpdateTransformation(view);
+ return;
}
- public static void MapAnchorY(IViewHandler handler, IView view)
+ handler.ToPlatform().UpdateTransformation(view);
+ }
+
+ public static void MapAnchorY(IViewHandler handler, IView view)
+ {
+ if (view.IsConnectingHandler())
{
- handler.ToPlatform().UpdateTransformation(view);
+ return;
}
- public static void MapToolbar(IViewHandler handler, IView view)
+ handler.ToPlatform().UpdateTransformation(view);
+ }
+
+ public static void MapToolbar(IViewHandler handler, IView view)
+ {
+ if (view is IToolbarElement tb)
{
- if (view is IToolbarElement tb)
- {
- MapToolbar(handler, tb);
- }
+ MapToolbar(handler, tb);
}
+ }
- internal static void MapToolbar(IElementHandler handler, IToolbarElement toolbarElement)
- {
- _ = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(handler.MauiContext)} null");
+ internal static void MapToolbar(IElementHandler handler, IToolbarElement toolbarElement)
+ {
+ _ = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(handler.MauiContext)} null");
- if (toolbarElement.Toolbar != null)
- {
- var toolBar = toolbarElement.Toolbar.ToPlatform(handler.MauiContext);
- handler.MauiContext.GetNavigationRootManager().SetToolbar(toolBar);
- }
+ if (toolbarElement.Toolbar != null)
+ {
+ var toolBar = toolbarElement.Toolbar.ToPlatform(handler.MauiContext);
+ handler.MauiContext.GetNavigationRootManager().SetToolbar(toolBar);
}
+ }
- public static void MapContextFlyout(IViewHandler handler, IView view)
+ public static void MapContextFlyout(IViewHandler handler, IView view)
+ {
+ if (view is IContextFlyoutElement contextFlyoutContainer)
{
- if (view is IContextFlyoutElement contextFlyoutContainer)
+ if (handler.IsConnectingHandler() && contextFlyoutContainer.ContextFlyout is null)
{
- if (handler.IsConnectingHandler() && contextFlyoutContainer.ContextFlyout is null)
- return;
-
- MapContextFlyout(handler, contextFlyoutContainer);
+ return;
}
+
+ MapContextFlyout(handler, contextFlyoutContainer);
}
+ }
- internal static void MapContextFlyout(IElementHandler handler, IContextFlyoutElement contextFlyoutContainer)
- {
- _ = handler.MauiContext ?? throw new InvalidOperationException($"The handler's {nameof(handler.MauiContext)} cannot be null.");
+ internal static void MapContextFlyout(IElementHandler handler, IContextFlyoutElement contextFlyoutContainer)
+ {
+ _ = handler.MauiContext ?? throw new InvalidOperationException($"The handler's {nameof(handler.MauiContext)} cannot be null.");
- if (handler.PlatformView is Microsoft.UI.Xaml.UIElement uiElement)
+ if (handler.PlatformView is Microsoft.UI.Xaml.UIElement uiElement)
+ {
+ if (contextFlyoutContainer.ContextFlyout != null)
{
- if (contextFlyoutContainer.ContextFlyout != null)
- {
- var contextFlyoutHandler = contextFlyoutContainer.ContextFlyout.ToHandler(handler.MauiContext);
- var contextFlyoutPlatformView = contextFlyoutHandler.PlatformView;
+ var contextFlyoutHandler = contextFlyoutContainer.ContextFlyout.ToHandler(handler.MauiContext);
+ var contextFlyoutPlatformView = contextFlyoutHandler.PlatformView;
- if (contextFlyoutPlatformView is FlyoutBase flyoutBase)
- {
- uiElement.ContextFlyout = flyoutBase;
- }
- }
- else
+ if (contextFlyoutPlatformView is FlyoutBase flyoutBase)
{
- uiElement.ClearValue(UIElement.ContextFlyoutProperty);
+ uiElement.ContextFlyout = flyoutBase;
}
}
+ else
+ {
+ uiElement.ClearValue(UIElement.ContextFlyoutProperty);
+ }
}
+ }
- static void FocusManager_GotFocus(object? sender, FocusManagerGotFocusEventArgs e)
+ static void FocusManager_GotFocus(object? sender, FocusManagerGotFocusEventArgs e)
+ {
+ if (e.NewFocusedElement is PlatformView platformView && FocusManagerMapping.TryGetValue(platformView, out ViewHandler? handler))
{
- if (e.NewFocusedElement is PlatformView platformView && FocusManagerMapping.TryGetValue(platformView, out ViewHandler? handler))
- {
- handler.UpdateIsFocused(true);
- }
+ handler.UpdateIsFocused(true);
}
+ }
- static void FocusManager_LostFocus(object? sender, FocusManagerLostFocusEventArgs e)
+ static void FocusManager_LostFocus(object? sender, FocusManagerLostFocusEventArgs e)
+ {
+ if (e.OldFocusedElement is PlatformView platformView && FocusManagerMapping.TryGetValue(platformView, out ViewHandler? handler))
{
- if (e.OldFocusedElement is PlatformView platformView && FocusManagerMapping.TryGetValue(platformView, out ViewHandler? handler))
- {
- handler.UpdateIsFocused(false);
- }
+ handler.UpdateIsFocused(false);
}
+ }
- private protected void UpdateIsFocused(bool isFocused)
+ private protected void UpdateIsFocused(bool isFocused)
+ {
+ if (VirtualView is not { } virtualView)
{
- if (VirtualView is not { } virtualView)
- {
- return;
- }
+ return;
+ }
- bool updateIsFocused = (isFocused && !virtualView.IsFocused) || (!isFocused && virtualView.IsFocused);
+ bool updateIsFocused = (isFocused && !virtualView.IsFocused) || (!isFocused && virtualView.IsFocused);
- if (updateIsFocused)
- {
- virtualView.IsFocused = isFocused;
- }
+ if (updateIsFocused)
+ {
+ virtualView.IsFocused = isFocused;
}
}
}
\ No newline at end of file
diff --git a/src/Core/src/Handlers/WebView/WebViewHandler.Windows.cs b/src/Core/src/Handlers/WebView/WebViewHandler.Windows.cs
index 76443edbfe28..2316ab18417f 100644
--- a/src/Core/src/Handlers/WebView/WebViewHandler.Windows.cs
+++ b/src/Core/src/Handlers/WebView/WebViewHandler.Windows.cs
@@ -320,6 +320,12 @@ public static void MapEvaluateJavaScriptAsync(IWebViewHandler handler, IWebView
}
}
+ internal static void MapFlowDirection(IWebViewHandler handler, IWebView webView)
+ {
+ // Explicitly do nothing here to override the base ViewHandler.MapFlowDirection behavior
+ // This prevents the WebView2.FlowDirection from being set, avoiding content mirroring
+ }
+
class WebView2Proxy
{
WeakReference? _window;
diff --git a/src/Core/src/Handlers/WebView/WebViewHandler.cs b/src/Core/src/Handlers/WebView/WebViewHandler.cs
index acd2be502103..b4dbad5fad5c 100644
--- a/src/Core/src/Handlers/WebView/WebViewHandler.cs
+++ b/src/Core/src/Handlers/WebView/WebViewHandler.cs
@@ -24,6 +24,9 @@ public partial class WebViewHandler : IWebViewHandler
{
[nameof(IWebView.Source)] = MapSource,
[nameof(IWebView.UserAgent)] = MapUserAgent,
+#if WINDOWS
+ [nameof(IView.FlowDirection)] = MapFlowDirection,
+#endif
#if __ANDROID__
[nameof(WebViewClient)] = MapWebViewClient,
[nameof(WebChromeClient)] = MapWebChromeClient,
diff --git a/src/Core/src/IViewWithWindow.cs b/src/Core/src/IViewWithWindow.cs
new file mode 100644
index 000000000000..c730208ef20b
--- /dev/null
+++ b/src/Core/src/IViewWithWindow.cs
@@ -0,0 +1,15 @@
+namespace Microsoft.Maui
+{
+ ///
+ /// Internal interface for views that can provide access to their window.
+ /// This enables dependency injection for testing scenarios.
+ ///
+ // TODO Delete this in NET10 and just add it with a default implementation to IView
+ internal interface IViewWithWindow
+ {
+ ///
+ /// Gets the window associated with this view.
+ ///
+ IWindow? Window { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Core/src/Layouts/DensityValue.cs b/src/Core/src/Layouts/DensityValue.cs
new file mode 100644
index 000000000000..28ce0a151b09
--- /dev/null
+++ b/src/Core/src/Layouts/DensityValue.cs
@@ -0,0 +1,271 @@
+using System;
+
+namespace Microsoft.Maui.Layouts
+{
+ ///
+ /// Represents a value that tracks both density-independent (dp) and physical pixel values
+ /// to enable precise pixel-aware layout calculations.
+ ///
+ internal readonly struct DensityValue : IEquatable
+ {
+ private const double Epsilon = 0.00001;
+
+ ///
+ /// Gets the raw pixel value without rounding.
+ ///
+ public double RawPx { get; }
+
+ ///
+ /// Gets the display density factor.
+ ///
+ public double Density { get; }
+
+ ///
+ /// Gets the value in density-independent pixels (dp).
+ ///
+ public double Dp
+ {
+ get
+ {
+ // Handle default case where Density is 0
+ if (Math.Abs(Density) < Epsilon)
+ {
+ return RawPx; // Treat as 1.0 density
+ }
+
+ return Math.Abs(Density - 1.0) < Epsilon ? RawPx : RawPx / Density;
+ }
+ }
+
+
+
+ ///
+ /// Initializes a new instance of the DensityValue struct.
+ ///
+ /// The value in density-independent pixels.
+ /// The display density factor.
+ public DensityValue(double dp, double density)
+ {
+ // When density is 1.0, store the dp value directly as RawPx to avoid any precision loss
+ if (Math.Abs(density - 1.0) < Epsilon)
+ {
+ RawPx = dp;
+ }
+ else
+ {
+ RawPx = dp * density;
+ }
+ Density = density;
+ }
+
+ ///
+ /// Initializes a new instance of the DensityValue struct with default density of 1.0.
+ ///
+ /// The value in density-independent pixels.
+ public DensityValue(double value) : this(value, 1.0)
+ {
+ }
+
+ ///
+ /// Private constructor for internal use.
+ ///
+ private DensityValue(double rawPx, double density, bool fromPixels)
+ {
+ RawPx = rawPx;
+ Density = density;
+ }
+
+ ///
+ /// Creates a DensityValue from a pixel value and density.
+ ///
+ /// The pixel value.
+ /// The display density factor.
+ /// A DensityValue representing the equivalent dp value.
+ public static DensityValue FromPixels(double pixels, double density)
+ {
+ return new DensityValue(pixels, density, true);
+ }
+
+ ///
+ /// Adds two DensityValue instances.
+ ///
+ public static DensityValue operator +(DensityValue left, DensityValue right)
+ {
+ // If both have density 1.0, we can safely add them
+ if (Math.Abs(left.Density - 1.0) < Epsilon && Math.Abs(right.Density - 1.0) < Epsilon)
+ {
+ return new DensityValue(left.RawPx + right.RawPx, 1.0);
+ }
+
+ // If densities are the same, add them
+ if (Math.Abs(left.Density - right.Density) < Epsilon)
+ {
+ return DensityValue.FromPixels(left.RawPx + right.RawPx, left.Density);
+ }
+
+ // If one has density 1.0 and the other doesn't, treat the 1.0 density value as having the same density as the other
+ if (Math.Abs(left.Density - 1.0) < Epsilon)
+ {
+ return DensityValue.FromPixels(left.RawPx + right.RawPx, right.Density);
+ }
+
+ if (Math.Abs(right.Density - 1.0) < Epsilon)
+ {
+ return DensityValue.FromPixels(left.RawPx + right.RawPx, left.Density);
+ }
+
+ throw new ArgumentException("Cannot add DensityValues with different densities.");
+ }
+
+ ///
+ /// Subtracts two DensityValue instances.
+ ///
+ public static DensityValue operator -(DensityValue left, DensityValue right)
+ {
+ // If both have density 1.0, we can safely subtract them
+ if (Math.Abs(left.Density - 1.0) < Epsilon && Math.Abs(right.Density - 1.0) < Epsilon)
+ {
+ return new DensityValue(left.RawPx - right.RawPx, 1.0);
+ }
+
+ // If densities are the same, subtract them
+ if (Math.Abs(left.Density - right.Density) < Epsilon)
+ {
+ return DensityValue.FromPixels(left.RawPx - right.RawPx, left.Density);
+ }
+
+ // If one has density 1.0 and the other doesn't, treat the 1.0 density value as having the same density as the other
+ if (Math.Abs(left.Density - 1.0) < Epsilon)
+ {
+ return DensityValue.FromPixels(left.RawPx - right.RawPx, right.Density);
+ }
+
+ if (Math.Abs(right.Density - 1.0) < Epsilon)
+ {
+ return DensityValue.FromPixels(left.RawPx - right.RawPx, left.Density);
+ }
+
+ throw new ArgumentException("Cannot subtract DensityValues with different densities.");
+ }
+
+ ///
+ /// Multiplies a DensityValue by a scalar.
+ ///
+ public static DensityValue operator *(DensityValue value, double scalar)
+ {
+ return DensityValue.FromPixels(value.RawPx * scalar, value.Density);
+ }
+
+ ///
+ /// Multiplies a DensityValue by a scalar.
+ ///
+ public static DensityValue operator *(double scalar, DensityValue value)
+ {
+ return value * scalar;
+ }
+
+ ///
+ /// Divides a DensityValue by a scalar.
+ ///
+ public static DensityValue operator /(DensityValue value, double scalar)
+ {
+ return DensityValue.FromPixels(value.RawPx / scalar, value.Density);
+ }
+
+ ///
+ /// Implicitly converts a DensityValue to its dp value.
+ ///
+ public static implicit operator double(DensityValue value)
+ {
+ return value.Dp;
+ }
+
+ ///
+ /// Implicitly converts a double to a DensityValue with density 1.0.
+ ///
+ public static implicit operator DensityValue(double value)
+ {
+ return new DensityValue(value, 1.0);
+ }
+
+ ///
+ /// Distributes a total pixel amount across multiple DensityValue instances,
+ /// accumulating rounding errors and applying them to the final elements.
+ /// This implements Android's approach of assigning remainder pixels to the last element.
+ ///
+ /// The total pixels to distribute.
+ /// The display density.
+ /// The relative portions for each element.
+ /// An array of pixel values that sum exactly to totalPixels.
+ public static int[] DistributePixels(double totalPixels, double density, double[] portions)
+ {
+ if (portions.Length == 0)
+ return Array.Empty();
+
+ var totalPortions = 0.0;
+ foreach (var portion in portions)
+ {
+ totalPortions += portion;
+ }
+
+ if (totalPortions <= 0)
+ return new int[portions.Length]; // All zeros
+
+ var result = new int[portions.Length];
+ var targetTotal = (int)Math.Floor(totalPixels);
+ var assignedTotal = 0;
+
+ // Calculate ideal pixels per portion
+ var idealPixelsPerUnit = totalPixels / totalPortions;
+
+ // Assign pixels to all elements using floor
+ for (int i = 0; i < portions.Length; i++)
+ {
+ var idealPixels = idealPixelsPerUnit * portions[i];
+ result[i] = (int)Math.Floor(idealPixels);
+ assignedTotal += result[i];
+ }
+
+ // Distribute remaining pixels from right to left (as requested in review)
+ var remainingPixels = targetTotal - assignedTotal;
+ for (int i = portions.Length - 1; i >= 0 && remainingPixels > 0; i--)
+ {
+ result[i]++;
+ remainingPixels--;
+ }
+
+ return result;
+ }
+
+ public bool Equals(DensityValue other)
+ {
+ return Math.Abs(RawPx - other.RawPx) < Epsilon &&
+ Math.Abs(Density - other.Density) < Epsilon;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is DensityValue other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return (RawPx, Density).GetHashCode();
+ }
+
+ public static bool operator ==(DensityValue left, DensityValue right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(DensityValue left, DensityValue right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override string ToString()
+ {
+ return $"{RawPx:F2}px ({Dp:F2}dp @ {Density:F2}x)";
+ }
+ }
+}
diff --git a/src/Core/src/Layouts/GridLayoutManager.cs b/src/Core/src/Layouts/GridLayoutManager.cs
index 22d9428f6f90..6f31d1383c29 100644
--- a/src/Core/src/Layouts/GridLayoutManager.cs
+++ b/src/Core/src/Layouts/GridLayoutManager.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
+using System.Linq;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Primitives;
@@ -265,12 +266,12 @@ public Rect GetCellBoundsFor(IView view, double xOffset, double yOffset)
for (int n = firstColumn; n < lastColumn; n++)
{
- width += _columns[n].Size;
+ width += _columns[n].Size.Dp;
}
for (int n = firstRow; n < lastRow; n++)
{
- height += _rows[n].Size;
+ height += _rows[n].Size.Dp;
}
// Account for any space between spanned rows/columns
@@ -340,7 +341,7 @@ static double SumDefinitions(Definition[] definitions, double spacing, bool mini
for (int n = 0; n < definitions.Length; n++)
{
- sum += minimize ? definitions[n].MinimumSize : definitions[n].Size;
+ sum += minimize ? definitions[n].MinimumSize.Dp : definitions[n].Size.Dp;
if (n > 0)
{
@@ -393,7 +394,7 @@ void FirstMeasurePass()
bool treatCellHeightAsAuto = TreatCellHeightAsAuto(cell);
bool treatCellWidthAsAuto = TreatCellWidthAsAuto(cell);
- if (double.IsNaN(cell.MeasureHeight) || double.IsNaN(cell.MeasureWidth))
+ if (double.IsNaN(cell.MeasureHeight.Dp) || double.IsNaN(cell.MeasureWidth.Dp))
{
// We still have some unknown measure constraints (* rows/columns that need to have
// the Auto measurements settled before we can measure them). So mark this cell for the
@@ -403,7 +404,7 @@ void FirstMeasurePass()
continue;
}
- var measure = MeasureCell(cell, cell.MeasureWidth, cell.MeasureHeight);
+ var measure = MeasureCell(cell, cell.MeasureWidth.Dp, cell.MeasureHeight.Dp);
if (treatCellWidthAsAuto)
{
@@ -443,7 +444,7 @@ void SecondMeasurePass()
double width = 0;
double height = 0;
- if (double.IsInfinity(cell.MeasureHeight))
+ if (double.IsInfinity(cell.MeasureHeight.Dp))
{
height = double.PositiveInfinity;
}
@@ -451,11 +452,11 @@ void SecondMeasurePass()
{
for (int n = cell.Row; n < cell.Row + cell.RowSpan; n++)
{
- height += _rows[n].Size;
+ height += _rows[n].Size.Dp;
}
}
- if (double.IsInfinity(cell.MeasureWidth))
+ if (double.IsInfinity(cell.MeasureWidth.Dp))
{
width = double.PositiveInfinity;
}
@@ -463,7 +464,7 @@ void SecondMeasurePass()
{
for (int n = cell.Column; n < cell.Column + cell.ColumnSpan; n++)
{
- width += _columns[n].Size;
+ width += _columns[n].Size.Dp;
}
}
@@ -539,7 +540,7 @@ static void ResolveSpan(Definition[] definitions, int start, int length, double
// Determine how large the spanned area currently is
for (int n = start; n < end; n++)
{
- currentSize += definitions[n].Size;
+ currentSize += definitions[n].Size.Dp;
if (n > start)
{
@@ -590,7 +591,7 @@ double LeftEdgeOfColumn(int column)
for (int n = 0; n < column; n++)
{
- left += _columns[n].Size;
+ left += _columns[n].Size.Dp;
left += _columnSpacing;
}
@@ -603,7 +604,7 @@ double TopEdgeOfRow(int row)
for (int n = 0; n < row; n++)
{
- top += _rows[n].Size;
+ top += _rows[n].Size.Dp;
top += _rowSpacing;
}
@@ -692,7 +693,7 @@ void ResolveStarRows(double heightConstraint)
foreach (var cell in _cells)
{
- if (double.IsNaN(cell.MeasureHeight))
+ if (double.IsNaN(cell.MeasureHeight.Dp))
{
UpdateKnownMeasureHeight(cell);
}
@@ -760,7 +761,7 @@ static void DetermineMinimumStarSizesInSpan(double spaceNeeded, Definition[] def
{
if (definitions[n].IsAbsolute || definitions[n].IsAuto)
{
- spaceNeeded -= definitions[n].Size;
+ spaceNeeded -= definitions[n].Size.Dp;
}
}
@@ -841,7 +842,7 @@ static void MinimizeStars(Definition[] defs)
}
}
- static void ExpandStarDefinitions(Definition[] definitions, double targetSize, double currentSize, double spacing, double starCount, bool limitStarSizes)
+ void ExpandStarDefinitions(Definition[] definitions, double targetSize, double currentSize, double spacing, double starCount, bool limitStarSizes)
{
// Figure out what the star value should be at this size
var starSize = ComputeStarSizeForTarget(targetSize, definitions, spacing, starCount);
@@ -854,8 +855,11 @@ static void ExpandStarDefinitions(Definition[] definitions, double targetSize, d
EnsureSizeLimit(definitions, starSize);
}
+ // Get density for pixel-perfect distribution
+ var density = GetDensity();
+
// Inflate the stars so that we fill up the space at this size
- ExpandStars(targetSize, currentSize, definitions, starSize, starCount);
+ ExpandStars(targetSize, currentSize, definitions, starSize, starCount, density);
}
static void EnsureSizeLimit(Definition[] definitions, double starSize)
@@ -890,7 +894,7 @@ static double ComputeStarSizeForTarget(double targetSize, Definition[] defs, dou
return (targetSize - sum) / starCount;
}
- static void ExpandStars(double targetSize, double currentSize, Definition[] defs, double targetStarSize, double starCount)
+ static void ExpandStars(double targetSize, double currentSize, Definition[] defs, double targetStarSize, double starCount, double density)
{
Debug.Assert(starCount > 0, "Assume that the caller has already checked for the existence of star rows/columns before using this.");
@@ -923,12 +927,14 @@ static void ExpandStars(double targetSize, double currentSize, Definition[] defs
// targetStarSize, that means we have enough room to expand all of our star rows/columns
// to their full size.
- foreach (var definition in defs)
+ var starDefinitions = defs.Where(d => d.IsStar).ToArray();
+ var portions = starDefinitions.Select(d => targetStarSize * d.GridLength.Value).ToArray();
+ var totalPixels = portions.Sum() * density;
+ var pixelAllocations = DensityValue.DistributePixels(totalPixels, density, portions);
+
+ for (int i = 0; i < starDefinitions.Length; i++)
{
- if (definition.IsStar)
- {
- definition.Size = targetStarSize * definition.GridLength.Value;
- }
+ starDefinitions[i].Size = DensityValue.FromPixels(pixelAllocations[i], density);
}
return;
@@ -951,27 +957,50 @@ static void ExpandStars(double targetSize, double currentSize, Definition[] defs
}
}
- foreach (var definition in defs)
+ // Use density-aware distribution for pixel-perfect proportional allocation
+ var proportionalStarDefinitions = defs.Where(d => d.IsStar).ToArray();
+ var proportionalPortions = new double[proportionalStarDefinitions.Length];
+
+ for (int i = 0; i < proportionalStarDefinitions.Length; i++)
{
- if (definition.IsStar)
+ var definition = proportionalStarDefinitions[i];
+ double fullTargetSize = targetStarSize * definition.GridLength.Value;
+
+ if (definition.MinimumSize < fullTargetSize)
{
- // Skip the star rows/columns whose minimums are at or higher than the target sizes
- double fullTargetSize = targetStarSize * definition.GridLength.Value;
+ var scale = (fullTargetSize - definition.MinimumSize) / totaldiff;
+ var portion = scale * availableSpace;
+ proportionalPortions[i] = definition.MinimumSize + portion;
+ }
+ else
+ {
+ proportionalPortions[i] = definition.MinimumSize;
+ }
+ }
- if (definition.MinimumSize < fullTargetSize)
- {
- // Figure out how small this definition is relative to the total difference,
- // and use that to determine how much of the available space this definition gets
+ var proportionalTotalPixels = proportionalPortions.Sum() * density;
+ var proportionalPixelAllocations = DensityValue.DistributePixels(proportionalTotalPixels, density, proportionalPortions);
+
+ for (int i = 0; i < proportionalStarDefinitions.Length; i++)
+ {
+ proportionalStarDefinitions[i].Size = DensityValue.FromPixels(proportionalPixelAllocations[i], density);
+ }
+ }
- // The goal is to have the definitions expand proportionate to their deficit from
- // their full target sizes
- var scale = (fullTargetSize - definition.MinimumSize) / totaldiff;
- var portion = scale * availableSpace;
- definition.Size = definition.MinimumSize + portion;
- }
- }
+ ///
+ /// Gets the display density for density-aware calculations.
+ ///
+ /// The display density, or 1.0 if not available.
+ double GetDensity()
+ {
+ // Try to get density from the grid view if it implements IViewWithWindow
+ if (_grid is IViewWithWindow viewWithWindow && viewWithWindow.Window != null)
+ {
+ return viewWithWindow.Window.RequestDisplayDensity();
}
+
+ return 1.0;
}
static bool AnyAuto(Definition[] definitions)
@@ -1012,7 +1041,7 @@ void UpdateKnownMeasureWidth(Cell cell)
double measureWidth = 0;
for (int column = cell.Column; column < cell.Column + cell.ColumnSpan; column++)
{
- measureWidth += _columns[column].Size;
+ measureWidth += _columns[column].Size.Dp;
if (column > cell.Column)
{
@@ -1028,7 +1057,7 @@ void UpdateKnownMeasureHeight(Cell cell)
double measureHeight = 0;
for (int row = cell.Row; row < cell.Row + cell.RowSpan; row++)
{
- measureHeight += _rows[row].Size;
+ measureHeight += _rows[row].Size.Dp;
if (row > cell.Row)
{
@@ -1136,8 +1165,8 @@ class Cell
public int Column { get; }
public int RowSpan { get; }
public int ColumnSpan { get; }
- public double MeasureWidth { get; set; } = double.NaN;
- public double MeasureHeight { get; set; } = double.NaN;
+ public DensityValue MeasureWidth { get; set; } = new DensityValue(double.NaN);
+ public DensityValue MeasureHeight { get; set; } = new DensityValue(double.NaN);
public bool NeedsSecondPass { get; set; }
///
@@ -1197,12 +1226,12 @@ static GridLengthType ToGridLengthType(GridUnitType gridUnitType)
class Definition
{
readonly GridLength _gridLength;
- private double _size;
+ private DensityValue _size;
///
/// The current size of this definition
///
- public double Size
+ public DensityValue Size
{
get => _size;
set
@@ -1220,11 +1249,11 @@ public double Size
/// For absolute and auto definitions, this is the same as Size
/// For star definitions, this is the minimum size which can contain the contents of the row/column
///
- public double MinimumSize { get; set; }
+ public DensityValue MinimumSize { get; set; }
- public void Update(double size)
+ public void Update(DensityValue size)
{
- if (size > Size)
+ if (size.RawPx > Size.RawPx)
{
Size = size;
}
@@ -1238,13 +1267,19 @@ public void Update(double size)
public Definition(GridLength gridLength)
{
+ _gridLength = gridLength;
+
if (gridLength.IsAbsolute)
{
Size = gridLength.Value;
}
-
- _gridLength = gridLength;
+ else
+ {
+ // For auto and star, start with size 0
+ Size = new DensityValue(0.0);
+ MinimumSize = new DensityValue(0.0);
+ }
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Core/src/Platform/Android/ContextExtensions.cs b/src/Core/src/Platform/Android/ContextExtensions.cs
index a175c270619e..fba7ec65deb9 100644
--- a/src/Core/src/Platform/Android/ContextExtensions.cs
+++ b/src/Core/src/Platform/Android/ContextExtensions.cs
@@ -135,12 +135,14 @@ internal static (int left, int top, int right, int bottom) ToPixels(this View vi
public static (int left, int top, int right, int bottom) ToPixels(this Context context, Graphics.Rect rectangle)
{
+ var left = (int)context.ToPixels(rectangle.Left);
+ var top = (int)context.ToPixels(rectangle.Top);
return
(
- (int)context.ToPixels(rectangle.Left),
- (int)context.ToPixels(rectangle.Top),
- (int)context.ToPixels(rectangle.Right),
- (int)context.ToPixels(rectangle.Bottom)
+ left,
+ top,
+ left + (int)context.ToPixels(rectangle.Width),
+ top + (int)context.ToPixels(rectangle.Height)
);
}
diff --git a/src/Core/src/Platform/Android/EditTextExtensions.cs b/src/Core/src/Platform/Android/EditTextExtensions.cs
index 6e6ce269fdbf..853e665ea565 100644
--- a/src/Core/src/Platform/Android/EditTextExtensions.cs
+++ b/src/Core/src/Platform/Android/EditTextExtensions.cs
@@ -225,6 +225,18 @@ public static void UpdateClearButtonVisibility(this EditText editText, IEntry en
}
}
+ internal static void UpdateClearButtonColor(this EditText editText, Graphics.Color textColor, Drawable? clearButtonDrawable)
+ {
+ if (textColor is not null)
+ {
+ clearButtonDrawable?.SetColorFilter(textColor.ToPlatform(), FilterMode.SrcIn);
+ }
+ else
+ {
+ clearButtonDrawable?.ClearColorFilter();
+ }
+ }
+
public static void UpdateReturnType(this EditText editText, IEntry entry)
{
editText.ImeOptions = entry.ReturnType.ToPlatform();
diff --git a/src/Core/src/Platform/Android/SearchViewExtensions.cs b/src/Core/src/Platform/Android/SearchViewExtensions.cs
index cdfe739f80bc..5308599fab4c 100644
--- a/src/Core/src/Platform/Android/SearchViewExtensions.cs
+++ b/src/Core/src/Platform/Android/SearchViewExtensions.cs
@@ -1,6 +1,11 @@
-using Android.Content.Res;
+using System;
+using Android.Content.Res;
+using Android.Graphics;
+using Android.Graphics.Drawables;
using Android.Text;
+using Android.Util;
using Android.Widget;
+using static Android.Content.Res.Resources;
using SearchView = AndroidX.AppCompat.Widget.SearchView;
namespace Microsoft.Maui.Platform
@@ -21,22 +26,36 @@ public static void UpdatePlaceholderColor(this SearchView searchView, ISearchBar
{
editText ??= searchView.GetFirstChildOfType();
- if (editText == null)
+ if (editText is null)
return;
- var placeholderTextColor = searchBar.PlaceholderColor;
-
- if (placeholderTextColor == null)
- {
- editText.SetHintTextColor(defaultPlaceholderColor);
- }
- else
+ if (searchBar?.PlaceholderColor is Graphics.Color placeholderTextColor)
{
if (PlatformInterop.CreateEditTextColorStateList(editText.HintTextColors, placeholderTextColor.ToPlatform()) is ColorStateList c)
{
editText.SetHintTextColor(c);
}
}
+ else if (TryGetDefaultStateColor(searchView, Android.Resource.Attribute.TextColorHint, out var color))
+ {
+ editText.SetHintTextColor(color);
+
+ var searchMagIconImage = searchView.FindViewById(Resource.Id.search_mag_icon);
+ searchMagIconImage?.Drawable?.SetTint(color);
+ }
+ }
+
+ internal static void UpdateTextColor(this SearchView searchView, ITextStyle entry)
+ {
+ if (TryGetDefaultStateColor(searchView, Android.Resource.Attribute.TextColorPrimary, out var color) &&
+ searchView.GetFirstChildOfType() is EditText editText)
+ {
+ if (entry.TextColor is null)
+ editText.SetTextColor(color);
+
+ var searchMagIconImage = searchView.FindViewById(Resource.Id.search_mag_icon);
+ searchMagIconImage?.Drawable?.SetTint(color);
+ }
}
public static void UpdateFont(this SearchView searchView, ISearchBar searchBar, IFontManager fontManager, EditText? editText = null)
@@ -108,12 +127,12 @@ public static void UpdateCancelButtonColor(this SearchView searchView, ISearchBa
{
var image = searchView.FindViewById(searchCloseButtonIdentifier);
- if (image != null && image.Drawable != null)
+ if (image is not null && image.Drawable is Drawable drawable)
{
- if (searchBar.CancelButtonColor != null)
- image.Drawable.SetColorFilter(searchBar.CancelButtonColor, FilterMode.SrcIn);
- else
- image.Drawable.ClearColorFilter();
+ if (searchBar.CancelButtonColor is not null)
+ drawable.SetColorFilter(searchBar.CancelButtonColor, FilterMode.SrcIn);
+ else if (TryGetDefaultStateColor(searchView, Android.Resource.Attribute.TextColorPrimary, out var color))
+ drawable.SetColorFilter(color, FilterMode.SrcIn);
}
}
}
@@ -171,5 +190,28 @@ internal static void SetInputType(this SearchView searchView, ISearchBar searchB
editText.SetInputType(searchBar);
}
+
+ static bool TryGetDefaultStateColor(SearchView searchView, int attribute, out Color color)
+ {
+ color = default;
+
+ if (!OperatingSystem.IsAndroidVersionAtLeast(23))
+ return false;
+
+ if (searchView.Context?.Theme is not Theme theme)
+ return false;
+
+ int[] s_disabledState = [-Android.Resource.Attribute.StateEnabled];
+ int[] s_enabledState = [Android.Resource.Attribute.StateEnabled];
+
+ using var ta = theme.ObtainStyledAttributes([attribute]);
+ var cs = ta.GetColorStateList(0);
+ if (cs is null)
+ return false;
+
+ var state = searchView.Enabled ? s_enabledState : s_disabledState;
+ color = new Color(cs.GetColorForState(state, Color.Black));
+ return true;
+ }
}
}
\ No newline at end of file
diff --git a/src/Core/src/Platform/ElementExtensions.cs b/src/Core/src/Platform/ElementExtensions.cs
index a76988c5afba..afd06e83879d 100644
--- a/src/Core/src/Platform/ElementExtensions.cs
+++ b/src/Core/src/Platform/ElementExtensions.cs
@@ -167,6 +167,10 @@ public static void SetWindowHandler(this PlatformWindow platformWindow, IWindow
#if WINDOWS || IOS || ANDROID || TIZEN
internal static IWindow GetWindow(this IElement element) =>
+ (element as IViewWithWindow)?.Window ??
+#if !TIZEN
+ (element as IView)?.GetHostedWindow() ??
+#endif
element.Handler?.MauiContext?.GetPlatformWindow()?.GetWindow() ??
throw new InvalidOperationException("IWindow not found");
#endif
diff --git a/src/Core/src/Platform/Windows/NavigationViewItemViewModel.cs b/src/Core/src/Platform/Windows/NavigationViewItemViewModel.cs
index 1fa08a939541..aa0b0d9ba5c6 100644
--- a/src/Core/src/Platform/Windows/NavigationViewItemViewModel.cs
+++ b/src/Core/src/Platform/Windows/NavigationViewItemViewModel.cs
@@ -88,6 +88,7 @@ internal class NavigationViewItemViewModel : INotifyPropertyChanged
WBrush? _selectedTitleColor;
WBrush? _unselectedTitleColor;
WBrush? _unselectedForeground;
+ WBrush? _iconColor;
ObservableCollection? _menuItemsSource;
WIconElement? _icon;
WeakReference? _data;
@@ -106,7 +107,7 @@ public WIconElement? Icon
public WBrush? Foreground
{
- get => IsSelected ? SelectedForeground : UnselectedForeground;
+ get => IconColor ?? (IsSelected ? SelectedForeground : UnselectedForeground);
}
public WBrush? Background
@@ -171,6 +172,17 @@ public WBrush? UnselectedTitleColor
}
}
+ public WBrush? IconColor
+ {
+ get => _iconColor;
+ set
+ {
+ _iconColor = value;
+ OnPropertyChanged(nameof(IconColor));
+ UpdateForeground();
+ }
+ }
+
public WBrush? SelectedForeground
{
get => _selectedForeground;
diff --git a/src/Core/src/Platform/Windows/TransformationExtensions.cs b/src/Core/src/Platform/Windows/TransformationExtensions.cs
index d865a90f060f..2aa6b4bf7a06 100644
--- a/src/Core/src/Platform/Windows/TransformationExtensions.cs
+++ b/src/Core/src/Platform/Windows/TransformationExtensions.cs
@@ -2,63 +2,65 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
-namespace Microsoft.Maui.Platform
+namespace Microsoft.Maui.Platform;
+
+public static class TransformationExtensions
{
- public static class TransformationExtensions
+ public static void UpdateTransformation(this FrameworkElement frameworkElement, IView view)
{
- public static void UpdateTransformation(this FrameworkElement frameworkElement, IView view)
- {
- double rotationX = view.RotationX;
- double rotationY = view.RotationY;
- double rotation = view.Rotation;
- double translationX = view.TranslationX;
- double translationY = view.TranslationY;
- double scaleX = view.Scale * view.ScaleX;
- double scaleY = view.Scale * view.ScaleY;
+ double rotationX = view.RotationX;
+ double rotationY = view.RotationY;
+ double rotation = view.Rotation;
+ double translationX = view.TranslationX;
+ double translationY = view.TranslationY;
+ double scaleX = view.Scale * view.ScaleX;
+ double scaleY = view.Scale * view.ScaleY;
- if (rotationX % 360 == 0 && rotationY % 360 == 0 && rotation % 360 == 0 &&
- translationX == 0 && translationY == 0 && scaleX == 1 && scaleY == 1)
+ if (rotationX % 360 == 0 && rotationY % 360 == 0 && rotation % 360 == 0 &&
+ translationX == 0 && translationY == 0 && scaleX == 1 && scaleY == 1)
+ {
+ if (!view.IsConnectingHandler())
{
frameworkElement.Projection = null;
frameworkElement.RenderTransform = null;
}
- else
- {
- double anchorX = view.AnchorX;
- double anchorY = view.AnchorY;
+ }
+ else
+ {
+ double anchorX = view.AnchorX;
+ double anchorY = view.AnchorY;
- frameworkElement.RenderTransformOrigin = new global::Windows.Foundation.Point(anchorX, anchorY);
- frameworkElement.RenderTransform = new ScaleTransform { ScaleX = scaleX, ScaleY = scaleY };
+ frameworkElement.RenderTransformOrigin = new global::Windows.Foundation.Point(anchorX, anchorY);
+ frameworkElement.RenderTransform = new ScaleTransform { ScaleX = scaleX, ScaleY = scaleY };
- // PlaneProjection removes touch and scrollwheel functionality on scrollable views such
- // as ScrollView, ListView, and TableView. If neither RotationX or RotationY are set
- // (i.e. their absolute value is 0), a CompositeTransform is instead used to allow for
- // rotation of the control on a 2D plane, and the other values are set. Otherwise, the
- // rotation values are set, but the aforementioned functionality will be lost.
- if (Math.Abs(view.RotationX) != 0 || Math.Abs(view.RotationY) != 0)
+ // PlaneProjection removes touch and scrollwheel functionality on scrollable views such
+ // as ScrollView, ListView, and TableView. If neither RotationX or RotationY are set
+ // (i.e. their absolute value is 0), a CompositeTransform is instead used to allow for
+ // rotation of the control on a 2D plane, and the other values are set. Otherwise, the
+ // rotation values are set, but the aforementioned functionality will be lost.
+ if (Math.Abs(view.RotationX) != 0 || Math.Abs(view.RotationY) != 0)
+ {
+ frameworkElement.Projection = new PlaneProjection
{
- frameworkElement.Projection = new PlaneProjection
- {
- CenterOfRotationX = anchorX,
- CenterOfRotationY = anchorY,
- GlobalOffsetX = translationX,
- GlobalOffsetY = translationY,
- RotationX = -rotationX,
- RotationY = -rotationY,
- RotationZ = -rotation
- };
- }
- else
+ CenterOfRotationX = anchorX,
+ CenterOfRotationY = anchorY,
+ GlobalOffsetX = translationX,
+ GlobalOffsetY = translationY,
+ RotationX = -rotationX,
+ RotationY = -rotationY,
+ RotationZ = -rotation
+ };
+ }
+ else
+ {
+ frameworkElement.RenderTransform = new CompositeTransform
{
- frameworkElement.RenderTransform = new CompositeTransform
- {
- Rotation = rotation,
- ScaleX = scaleX,
- ScaleY = scaleY,
- TranslateX = translationX,
- TranslateY = translationY
- };
- }
+ Rotation = rotation,
+ ScaleX = scaleX,
+ ScaleY = scaleY,
+ TranslateX = translationX,
+ TranslateY = translationY
+ };
}
}
}
diff --git a/src/Core/src/Platform/Windows/WrapperView.cs b/src/Core/src/Platform/Windows/WrapperView.cs
index 7b00c734f6f5..78bada1d94a5 100644
--- a/src/Core/src/Platform/Windows/WrapperView.cs
+++ b/src/Core/src/Platform/Windows/WrapperView.cs
@@ -97,6 +97,7 @@ void UpdateClip()
if (clipGeometry is null)
{
+ DisposeClip();
return;
}
@@ -167,13 +168,20 @@ void UpdateBorder()
partial void ShadowChanged()
{
- if (HasShadow)
+ if (Shadow?.Paint is { })
{
- UpdateShadowAsync().FireAndForget(IPlatformApplication.Current?.Services?.CreateLogger(nameof(WrapperView)));
+ if (HasShadow)
+ {
+ UpdateShadowAsync().FireAndForget(IPlatformApplication.Current?.Services?.CreateLogger(nameof(WrapperView)));
+ }
+ else
+ {
+ CreateShadowAsync().FireAndForget(IPlatformApplication.Current?.Services?.CreateLogger(nameof(WrapperView)));
+ }
}
else
{
- CreateShadowAsync().FireAndForget(IPlatformApplication.Current?.Services?.CreateLogger(nameof(WrapperView)));
+ DisposeShadow();
}
}
@@ -238,7 +246,7 @@ async Task CreateShadowAsync()
var visual = ElementCompositionPreview.GetElementVisual(Child);
- if (Clip != null && visual.Clip == null)
+ if (Clip is not null && visual.Clip is null)
{
return;
}
@@ -325,8 +333,9 @@ void UpdateShadowSize()
_shadowHost.Width = width;
_shadowHost.Height = height;
- Canvas.SetLeft(_shadowHost, Child.ActualOffset.X);
- Canvas.SetTop(_shadowHost, Child.ActualOffset.Y);
+ Vector3 actualOffset = Child.ActualOffset;
+ Canvas.SetLeft(_shadowHost, actualOffset.X);
+ Canvas.SetTop(_shadowHost, actualOffset.Y);
}
}
}
@@ -338,7 +347,7 @@ async Task SetShadowPropertiesAsync(DropShadow dropShadow, IShadow? mauiShadow)
Graphics.Color? shadowColor = Colors.Black;
Graphics.Point offset = Graphics.Point.Zero;
- if (mauiShadow != null)
+ if (mauiShadow is not null)
{
blurRadius = mauiShadow.Radius * 2;
opacity = mauiShadow.Opacity;
@@ -349,7 +358,7 @@ async Task SetShadowPropertiesAsync(DropShadow dropShadow, IShadow? mauiShadow)
dropShadow.BlurRadius = blurRadius;
dropShadow.Opacity = opacity;
- if (shadowColor != null)
+ if (shadowColor is not null)
{
dropShadow.Color = shadowColor.ToWindowsColor();
}
diff --git a/src/Core/src/Platform/iOS/TextFieldExtensions.cs b/src/Core/src/Platform/iOS/TextFieldExtensions.cs
index 3b5211076a0c..886b48fc5f4a 100644
--- a/src/Core/src/Platform/iOS/TextFieldExtensions.cs
+++ b/src/Core/src/Platform/iOS/TextFieldExtensions.cs
@@ -246,5 +246,24 @@ internal static void UpdateClearButtonColor(this UITextField textField, IEntry e
context?.FillRect(rect, CGBlendMode.SourceIn);
});
}
+
+ internal static void AddMauiDoneAccessoryView(this UITextField textField, IViewHandler handler)
+ {
+#if !MACCATALYST
+ var accessoryView = new MauiDoneAccessoryView();
+ accessoryView.SetDataContext(handler);
+ accessoryView.SetDoneClicked(OnDoneClicked);
+ textField.InputAccessoryView = accessoryView;
+#endif
+ }
+
+ static void OnDoneClicked(object sender)
+ {
+ if (sender is IEntryHandler entryHandler)
+ {
+ entryHandler.PlatformView.ResignFirstResponder();
+ entryHandler.VirtualView.Completed();
+ }
+ }
}
}
diff --git a/src/Core/src/Platform/iOS/TextViewExtensions.cs b/src/Core/src/Platform/iOS/TextViewExtensions.cs
index 10518c47314a..55b8af9713f3 100644
--- a/src/Core/src/Platform/iOS/TextViewExtensions.cs
+++ b/src/Core/src/Platform/iOS/TextViewExtensions.cs
@@ -177,5 +177,24 @@ static UITextPosition GetSelectionEnd(UITextView textView, IEditor editor, UITex
return end;
}
+
+ internal static void AddMauiDoneAccessoryView(this UITextView textView, IViewHandler handler)
+ {
+#if !MACCATALYST
+ var accessoryView = new MauiDoneAccessoryView();
+ accessoryView.SetDataContext(handler);
+ accessoryView.SetDoneClicked(OnDoneClicked);
+ textView.InputAccessoryView = accessoryView;
+#endif
+ }
+
+ static void OnDoneClicked(object sender)
+ {
+ if (sender is IEditorHandler entryHandler)
+ {
+ entryHandler.PlatformView.ResignFirstResponder();
+ entryHandler.VirtualView.Completed();
+ }
+ }
}
}
diff --git a/src/Core/src/Platform/iOS/WindowViewController.cs b/src/Core/src/Platform/iOS/WindowViewController.cs
index fb966fe8ed61..1afa57af2771 100644
--- a/src/Core/src/Platform/iOS/WindowViewController.cs
+++ b/src/Core/src/Platform/iOS/WindowViewController.cs
@@ -70,6 +70,19 @@ public WindowViewController(UIViewController contentViewController, IWindow wind
contentViewController.DidMoveToParentViewController(this);
}
+
+ public override void TraitCollectionDidChange(UITraitCollection? previousTraitCollection)
+ {
+#pragma warning disable CA1422 // Validate platform compatibility
+ base.TraitCollectionDidChange(previousTraitCollection);
+#pragma warning restore CA1422 // Validate platform compatibility
+
+ if (_titleBar is not null && _iTitleBarRef.TryGetTarget(out IView? iTitleBar) && iTitleBar?.Background is null)
+ {
+ _titleBar.BackgroundColor = UIColor.SecondarySystemBackground;
+ }
+ }
+
public override void ViewWillLayoutSubviews()
{
LayoutTitleBar();
diff --git a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Tests.cs b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Tests.cs
index 541cf2568f5f..5c2ab41b30fe 100644
--- a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Tests.cs
+++ b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Tests.cs
@@ -45,7 +45,18 @@ public async Task SetFlowDirection(FlowDirection flowDirection)
};
var id = await GetValueAsync(view, handler => GetFlowDirection(handler));
+#if WINDOWS
+ if (typeof(THandler).Name.Contains("WebView", StringComparison.Ordinal))
+ {
+ Assert.Equal(FlowDirection.LeftToRight, id);
+ }
+ else
+ {
+ Assert.Equal(flowDirection, id);
+ }
+#else
Assert.Equal(flowDirection, id);
+#endif
}
[Theory(DisplayName = "Opacity is set correctly")]
diff --git a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs
index 4e348ebd553b..49ed16161ba0 100644
--- a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs
+++ b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs
@@ -102,6 +102,24 @@ await AttachAndRun(searchBar, async (searchBarHandler) =>
await ValidatePropertyInitValue(searchBar, () => searchBar.IsTextPredictionEnabled, GetNativeIsTextPredictionEnabled, isEnabled);
}
+ [Theory(DisplayName = "IsTextPredictionEnabled Initializes Correctly for SearchBar")]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task SearchBarIsTextPredictionEnabledInitializesCorrectly(bool isEnabled)
+ {
+ var searchBar = new Microsoft.Maui.Controls.SearchBar()
+ {
+ IsTextPredictionEnabled = isEnabled
+ };
+
+ await AttachAndRun(searchBar, async (searchBarHandler) =>
+ {
+ await AssertEventually(() => searchBarHandler.PlatformView.IsLoaded());
+ });
+
+ await ValidatePropertyInitValue(searchBar, () => searchBar.IsTextPredictionEnabled, GetNativeIsTextPredictionEnabled, isEnabled);
+ }
+
[Theory(DisplayName = "IsSpellCheckEnabled Initializes Correctly")]
[InlineData(true)]
[InlineData(false)]
diff --git a/src/Core/tests/UnitTests/Layouts/GridLayoutManagerDensityTest.cs b/src/Core/tests/UnitTests/Layouts/GridLayoutManagerDensityTest.cs
new file mode 100644
index 000000000000..d181683fee70
--- /dev/null
+++ b/src/Core/tests/UnitTests/Layouts/GridLayoutManagerDensityTest.cs
@@ -0,0 +1,173 @@
+using System;
+using Xunit;
+using Microsoft.Maui.Layouts;
+
+namespace Microsoft.Maui.UnitTests.Layouts
+{
+ [Category(TestCategory.Layout)]
+ public class GridLayoutManagerDensityTests
+ {
+ [Fact]
+ public void DensityValue_ThreeEqualColumns_WithRemainder()
+ {
+ // Scenario 1: 293.4dp across 3 columns at density 2.625
+ var totalDp = 293.4;
+ var density = 2.625;
+ var totalPixels = totalDp * density; // 770.175px
+ var portions = new double[] { 1.0, 1.0, 1.0 }; // Equal star sizing
+
+ var result = DensityValue.DistributePixels(totalPixels, density, portions);
+
+ // Expected with right-to-left distribution: [256, 257, 257] = 770px
+ Assert.Equal(3, result.Length);
+ Assert.Equal(256, result[0]);
+ Assert.Equal(257, result[1]);
+ Assert.Equal(257, result[2]);
+
+ var total = result[0] + result[1] + result[2];
+ Assert.Equal(770, total);
+
+ // Verify this is better than naive division
+ var naiveSize = (int)Math.Round(totalPixels / 3); // 257
+ var naiveTotal = naiveSize * 3; // 771 - too much!
+ Assert.True(total < naiveTotal, "DensityValue should provide more accurate allocation than naive rounding");
+ }
+
+ [Fact]
+ public void DensityValue_ThreeEqualColumns_PerfectDivision()
+ {
+ // Scenario 2: 290dp across 3 columns at density 3.0 (perfect case)
+ var totalDp = 290.0;
+ var density = 3.0;
+ var totalPixels = totalDp * density; // 870.0px
+ var portions = new double[] { 1.0, 1.0, 1.0 };
+
+ var result = DensityValue.DistributePixels(totalPixels, density, portions);
+
+ // Perfect division case
+ Assert.Equal(3, result.Length);
+ Assert.Equal(290, result[0]);
+ Assert.Equal(290, result[1]);
+ Assert.Equal(290, result[2]);
+
+ var total = result[0] + result[1] + result[2];
+ Assert.Equal(870, total);
+ }
+
+ [Fact]
+ public void DensityValue_FourEqualColumns_WithRemainder()
+ {
+ // Scenario 3: 300dp across 4 columns at density 2.625
+ var totalDp = 300.0;
+ var density = 2.625;
+ var totalPixels = totalDp * density; // 787.5px
+ var portions = new double[] { 1.0, 1.0, 1.0, 1.0 };
+
+ var result = DensityValue.DistributePixels(totalPixels, density, portions);
+
+ // Expected with right-to-left distribution: [196, 197, 197, 197] = 787px
+ Assert.Equal(4, result.Length);
+ Assert.Equal(196, result[0]);
+ Assert.Equal(197, result[1]);
+ Assert.Equal(197, result[2]);
+ Assert.Equal(197, result[3]);
+
+ var total = result[0] + result[1] + result[2] + result[3];
+ Assert.Equal(787, total);
+ }
+
+ [Fact]
+ public void DensityValue_WeightedStarSizing_PerfectDivision()
+ {
+ // Test weighted star sizing: 2*, 1*, 2* across 500 pixels
+ var totalPixels = 500.0;
+ var density = 2.0;
+ var portions = new double[] { 2.0, 1.0, 2.0 }; // 2*, 1*, 2*
+
+ var result = DensityValue.DistributePixels(totalPixels, density, portions);
+
+ // Total weight = 5, so distribution should be:
+ // First: 500 * (2/5) = 200 pixels
+ // Second: 500 * (1/5) = 100 pixels
+ // Third: 500 * (2/5) = 200 pixels
+ Assert.Equal(3, result.Length);
+ Assert.Equal(200, result[0]);
+ Assert.Equal(100, result[1]);
+ Assert.Equal(200, result[2]);
+
+ var total = result[0] + result[1] + result[2];
+ Assert.Equal(500, total);
+ }
+
+ [Fact]
+ public void DensityValue_WeightedStarSizing_WithRounding()
+ {
+ // Test weighted star sizing with rounding: 3*, 2*, 3* across 333 pixels
+ var totalPixels = 333.0;
+ var density = 1.5;
+ var portions = new double[] { 3.0, 2.0, 3.0 }; // 3*, 2*, 3*
+
+ var result = DensityValue.DistributePixels(totalPixels, density, portions);
+
+ // Total weight = 8, with right-to-left distribution:
+ // portions[0]=3: floor(333 * 3/8) = floor(124.875) = 124
+ // portions[1]=2: floor(333 * 2/8) = floor(83.25) = 83
+ // portions[2]=3: floor(333 * 3/8) = floor(124.875) = 124
+ // Total assigned: 124+83+124 = 331, remainder: 333-331 = 2
+ // Right-to-left: portions[2] gets +1, portions[1] gets +1
+ // Final: [124, 84, 125]
+ Assert.Equal(3, result.Length);
+ Assert.Equal(124, result[0]);
+ Assert.Equal(84, result[1]);
+ Assert.Equal(125, result[2]);
+
+ var total = result[0] + result[1] + result[2];
+ Assert.Equal(333, total);
+ }
+
+ [Theory]
+ [InlineData(100.0, 1.0, new double[] { 1, 1, 1, 1 }, 25)] // Perfect division
+ [InlineData(101.0, 1.0, new double[] { 1, 1, 1, 1 }, 25)] // 1 pixel remainder
+ [InlineData(103.0, 1.0, new double[] { 1, 1, 1, 1 }, 25)] // 3 pixel remainder
+ public void DensityValue_RemainderPixels_Distribution(double totalPixels, double density, double[] portions, int expectedBase)
+ {
+ var result = DensityValue.DistributePixels(totalPixels, density, portions);
+
+ // With right-to-left distribution, we expect different behavior:
+ if (totalPixels == 103.0)
+ {
+ // 103/4 = 25.75, floor=25 each, remainder=3
+ // Right-to-left: [25, 26, 26, 26]
+ Assert.Equal(25, result[0]);
+ Assert.Equal(26, result[1]);
+ Assert.Equal(26, result[2]);
+ Assert.Equal(26, result[3]);
+ }
+ else if (totalPixels == 101.0)
+ {
+ // 101/4 = 25.25, floor=25 each, remainder=1
+ // Right-to-left: [25, 25, 25, 26]
+ Assert.Equal(25, result[0]);
+ Assert.Equal(25, result[1]);
+ Assert.Equal(25, result[2]);
+ Assert.Equal(26, result[3]);
+ }
+ else
+ {
+ // 100/4 = 25 exactly, no remainder
+ for (int i = 0; i < result.Length; i++)
+ {
+ Assert.Equal(expectedBase, result[i]);
+ }
+ }
+
+ // Total should match exactly
+ var total = 0;
+ foreach (var value in result)
+ {
+ total += value;
+ }
+ Assert.Equal((int)Math.Round(totalPixels), total);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Essentials/src/FileSystem/FileSystemUtils.android.cs b/src/Essentials/src/FileSystem/FileSystemUtils.android.cs
index 5dad4645a4f3..e80be6cd853e 100644
--- a/src/Essentials/src/FileSystem/FileSystemUtils.android.cs
+++ b/src/Essentials/src/FileSystem/FileSystemUtils.android.cs
@@ -124,9 +124,7 @@ static string ResolveDocumentPath(AndroidUri uri)
// This is the internal "external" memory, NOT the SD Card
if (storageType.Equals(storageTypePrimary, StringComparison.OrdinalIgnoreCase))
{
-#pragma warning disable CS0618 // Type or member is obsolete
var root = global::Android.OS.Environment.ExternalStorageDirectory.Path;
-#pragma warning restore CS0618 // Type or member is obsolete
return Path.Combine(root, uriPath);
}
@@ -179,10 +177,8 @@ static string ResolveDocumentPath(AndroidUri uri)
contentUri = MediaStore.Video.Media.ExternalContentUri;
else if (storageType.Equals(storageTypeAudio, StringComparison.OrdinalIgnoreCase))
contentUri = MediaStore.Audio.Media.ExternalContentUri;
-#pragma warning disable CS0618
- if (contentUri != null && GetDataFilePath(contentUri, $"{MediaStore.MediaColumns.Id}=?", new[] { uriPath }) is string filePath)
+ if (contentUri != null && GetDataFilePath(contentUri, $"{IBaseColumns.Id}=?", new[] { uriPath }) is string filePath)
return filePath;
-#pragma warning restore CS0618
}
}
@@ -218,9 +214,7 @@ static string CacheContentFile(AndroidUri uri)
return null;
// resolve or generate a valid destination path
-#pragma warning disable CS0618
- var filename = GetColumnValue(uri, MediaStore.Files.FileColumns.DisplayName) ?? Guid.NewGuid().ToString("N");
-#pragma warning restore CS0618
+ var filename = GetColumnValue(uri, MediaStore.IMediaColumns.DisplayName) ?? Guid.NewGuid().ToString("N");
if (!Path.HasExtension(filename) && !string.IsNullOrEmpty(extension))
filename = Path.ChangeExtension(filename, extension);
@@ -261,9 +255,10 @@ static bool IsVirtualFile(AndroidUri uri)
if (!string.IsNullOrEmpty(value) && int.TryParse(value, out var flagsInt))
{
var flags = (DocumentContractFlags)flagsInt;
-#pragma warning disable CA1416 // Introduced in API 24: https://developer.android.com/reference/android/provider/DocumentsContract.Document#FLAG_VIRTUAL_DOCUMENT
- return flags.HasFlag(DocumentContractFlags.VirtualDocument);
-#pragma warning restore CA1416
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(24))
+ return flags.HasFlag(DocumentContractFlags.VirtualDocument);
+
}
return false;
@@ -307,13 +302,7 @@ static string GetColumnValue(AndroidUri contentUri, string column, string select
static string GetDataFilePath(AndroidUri contentUri, string selection = null, string[] selectionArgs = null)
{
-#pragma warning disable CS0618 // Type or member is obsolete
-#pragma warning disable CA1422 // Validate platform compatibility
-#pragma warning disable CA1416 // Validate platform compatibility
- const string column = MediaStore.Files.FileColumns.Data;
-#pragma warning restore CA1422 // Validate platform compatibility
-#pragma warning restore CA1416 // Validate platform compatibility
-#pragma warning restore CS0618 // Type or member is obsolete
+ const string column = MediaStore.IMediaColumns.Data;
// ask the content provider for the data column, which may contain the actual file path
var path = GetColumnValue(contentUri, column, selection, selectionArgs);
diff --git a/src/Essentials/src/Platform/IntermediateActivity.android.cs b/src/Essentials/src/Platform/IntermediateActivity.android.cs
index 46d44960fba3..51c742704ef4 100644
--- a/src/Essentials/src/Platform/IntermediateActivity.android.cs
+++ b/src/Essentials/src/Platform/IntermediateActivity.android.cs
@@ -32,13 +32,12 @@ protected override void OnCreate(Bundle? savedInstanceState)
// read the values
launched = extras?.GetBoolean(launchedExtra, false) ?? false;
-#pragma warning disable 618 // TODO: one day use the API 33+ version: https://developer.android.com/reference/android/os/Bundle#getParcelable(java.lang.String,%20java.lang.Class%3CT%3E)
-#pragma warning disable CA1422 // Validate platform compatibility
-#pragma warning disable CA1416 // Validate platform compatibility
- actualIntent = extras?.GetParcelable(actualIntentExtra) as Intent;
-#pragma warning restore CA1422 // Validate platform compatibility
-#pragma warning restore CA1416 // Validate platform compatibility
-#pragma warning restore 618
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(33))
+ actualIntent = extras?.GetParcelable(actualIntentExtra, Java.Lang.Class.FromType(typeof(Intent))) as Intent;
+ else
+ actualIntent = extras?.GetParcelable(actualIntentExtra) as Intent;
+
guid = extras?.GetString(guidExtra);
requestCode = extras?.GetInt(requestCodeExtra, -1) ?? -1;
diff --git a/src/Graphics/src/Graphics/CanvasExtensions.cs b/src/Graphics/src/Graphics/CanvasExtensions.cs
index 499ca674f18a..4fb06bba66be 100644
--- a/src/Graphics/src/Graphics/CanvasExtensions.cs
+++ b/src/Graphics/src/Graphics/CanvasExtensions.cs
@@ -152,11 +152,48 @@ public static void DrawPath(this ICanvas target, PathF path)
target.DrawPath(path);
}
+ ///
+ /// Fills the specified path using the current fill color and the non-zero winding rule.
+ ///
+ /// The canvas to draw on.
+ /// The path to fill.
+ ///
+ /// Thrown when the path is invalid for fill operations. This can occur when:
+ ///
+ /// The path is empty or contains no drawable segments
+ /// The path contains invalid coordinates or operations
+ /// The path is not properly formed for the underlying graphics implementation
+ ///
+ ///
+ ///
+ /// For best results with fill operations, ensure the path represents a closed shape by calling
+ /// or manually connecting the end point back to the start point.
+ /// Unclosed paths may produce unexpected results or exceptions depending on the graphics backend.
+ ///
public static void FillPath(this ICanvas target, PathF path)
{
target.FillPath(path, WindingMode.NonZero);
}
+ ///
+ /// Fills the specified path using the current fill color and the specified winding rule.
+ ///
+ /// The canvas to draw on.
+ /// The path to fill.
+ /// The winding rule to use for determining which areas are inside the path.
+ ///
+ /// Thrown when the path is invalid for fill operations. This can occur when:
+ ///
+ /// The path is empty or contains no drawable segments
+ /// The path contains invalid coordinates or operations
+ /// The path is not properly formed for the underlying graphics implementation
+ ///
+ ///
+ ///
+ /// For best results with fill operations, ensure the path represents a closed shape by calling
+ /// or manually connecting the end point back to the start point.
+ /// Unclosed paths may produce unexpected results or exceptions depending on the graphics backend.
+ ///
public static void FillPath(this ICanvas target, PathF path, WindingMode windingMode)
{
target.FillPath(path, windingMode);
diff --git a/src/Graphics/src/Graphics/PathF.cs b/src/Graphics/src/Graphics/PathF.cs
index 748ed21b1a5b..4c93697e9139 100644
--- a/src/Graphics/src/Graphics/PathF.cs
+++ b/src/Graphics/src/Graphics/PathF.cs
@@ -8,6 +8,17 @@ namespace Microsoft.Maui.Graphics
///
/// Represents a geometric path consisting of lines, curves, and shapes using single-precision floating-point coordinates.
///
+ ///
+ ///
+ /// A path is composed of one or more sub-paths, each beginning with a Move operation and consisting of connected
+ /// line segments, curves, and arcs. For fill operations to work reliably, paths should typically be closed using
+ /// the method or by explicitly connecting the end point back to the starting point.
+ ///
+ ///
+ /// When creating paths for filling, ensure proper path construction to avoid exceptions during rendering.
+ /// Paths that start with operations will automatically create an initial MoveTo operation.
+ ///
+ ///
public class PathF : IDisposable
{
private const float K_RATIO = 0.551784777779014f; // ideal ratio of cubic Bezier points for a quarter circle
@@ -47,6 +58,10 @@ private PathF(List points, List arcSizes, List arcClockwise
}
}
+ ///
+ /// Initializes a new path by copying the segments, points, and arc metadata of another .
+ ///
+ /// The path to copy.
public PathF(PathF path) : this()
{
_operations.AddRange(path._operations);
@@ -59,16 +74,28 @@ public PathF(PathF path) : this()
_subPathsClosed = new List(path._subPathsClosed);
}
+ ///
+ /// Initializes a new path whose first (move) point is the specified point.
+ ///
+ /// The starting point.
public PathF(PointF point) : this()
{
MoveTo(point.X, point.Y);
}
+ ///
+ /// Initializes a new path whose first (move) point is at the specified coordinates.
+ ///
+ /// X coordinate of the starting point.
+ /// Y coordinate of the starting point.
public PathF(float x, float y) : this()
{
MoveTo(x, y);
}
+ ///
+ /// Initializes an empty path with no segments.
+ ///
public PathF()
{
_subPathCount = 0;
@@ -79,8 +106,14 @@ public PathF()
_subPathsClosed = new List();
}
+ ///
+ /// Gets the number of sub-paths (contiguous sequences beginning with ) in the path.
+ ///
public int SubPathCount => _subPathCount;
+ ///
+ /// Gets a value indicating whether the last sub-path has been explicitly closed with .
+ ///
public bool Closed
{
get
@@ -92,6 +125,9 @@ public bool Closed
}
}
+ ///
+ /// Gets the first point in the path, or the default value if the path is empty.
+ ///
public PointF FirstPoint
{
get
@@ -103,6 +139,9 @@ public PointF FirstPoint
}
}
+ ///
+ /// Enumerates the sequence of segment operations composing the path.
+ ///
public IEnumerable SegmentTypes
{
get
@@ -112,6 +151,9 @@ public IEnumerable SegmentTypes
}
}
+ ///
+ /// Enumerates all points used by the path's segments in logical order.
+ ///
public IEnumerable Points
{
get
@@ -121,6 +163,9 @@ public IEnumerable Points
}
}
+ ///
+ /// Gets the last point in the path, or the default value if the path is empty.
+ ///
public PointF LastPoint
{
get
@@ -132,6 +177,9 @@ public PointF LastPoint
}
}
+ ///
+ /// Gets the index of the last point, or -1 if the path is empty.
+ ///
public int LastPointIndex
{
get
@@ -143,6 +191,10 @@ public int LastPointIndex
}
}
+ ///
+ /// Gets the point at the specified index, or the default value if the index is out of range.
+ ///
+ /// Index of the point.
public PointF this[int index]
{
get
@@ -155,22 +207,42 @@ public PointF this[int index]
//set { points[index] = value; }
}
+ ///
+ /// Sets the coordinates of the point at the specified index.
+ ///
+ /// Index of the point.
+ /// New X value.
+ /// New Y value.
public void SetPoint(int index, float x, float y)
{
_points[index] = new PointF(x, y);
Invalidate();
}
+ ///
+ /// Sets the point at the specified index.
+ ///
+ /// Index of the point.
+ /// The new point value.
public void SetPoint(int index, PointF point)
{
_points[index] = point;
Invalidate();
}
+ ///
+ /// Gets the total number of points in the path.
+ ///
public int Count => _points.Count;
+ ///
+ /// Gets the number of segment operations (including move and close) in the path.
+ ///
public int OperationCount => _operations.Count;
+ ///
+ /// Gets the count of segment operations excluding a leading and a trailing , if present.
+ ///
public int SegmentCountExcludingOpenAndClose
{
get
@@ -198,11 +270,21 @@ public int SegmentCountExcludingOpenAndClose
}
}
+ ///
+ /// Gets the segment operation type at the specified index.
+ ///
+ /// Segment index.
+ /// The value.
public PathOperation GetSegmentType(int aIndex)
{
return _operations[aIndex];
}
+ ///
+ /// Gets an arc angle value at the specified index (stored as degrees).
+ ///
+ /// Angle index.
+ /// The angle in degrees, or 0 if out of range.
public float GetArcAngle(int aIndex)
{
if (_arcAngles.Count > aIndex)
@@ -213,6 +295,11 @@ public float GetArcAngle(int aIndex)
return 0;
}
+ ///
+ /// Sets an arc angle value (degrees) at the specified index.
+ ///
+ /// Angle index.
+ /// New angle in degrees.
public void SetArcAngle(int aIndex, float aValue)
{
if (_arcAngles.Count > aIndex)
@@ -223,6 +310,11 @@ public void SetArcAngle(int aIndex, float aValue)
Invalidate();
}
+ ///
+ /// Gets the stored clockwise flag for an arc segment at the specified index.
+ ///
+ /// Arc flag index.
+ /// true if clockwise; otherwise false.
public bool GetArcClockwise(int aIndex)
{
if (_arcClockwise.Count > aIndex)
@@ -233,6 +325,11 @@ public bool GetArcClockwise(int aIndex)
return false;
}
+ ///
+ /// Sets the stored clockwise flag for an arc segment.
+ ///
+ /// Arc flag index.
+ /// New clockwise value.
public void SetArcClockwise(int aIndex, bool aValue)
{
if (_arcClockwise.Count > aIndex)
@@ -243,11 +340,22 @@ public void SetArcClockwise(int aIndex, bool aValue)
Invalidate();
}
+ ///
+ /// Starts a new sub-path at the specified coordinates.
+ ///
+ /// X coordinate of the starting point.
+ /// Y coordinate of the starting point.
+ /// The current path for chaining.
public PathF MoveTo(float x, float y)
{
return MoveTo(new PointF(x, y));
}
+ ///
+ /// Starts a new sub-path at the specified point.
+ ///
+ /// Starting point of the new sub-path.
+ /// The current path for chaining.
public PathF MoveTo(PointF point)
{
_subPathCount++;
@@ -258,6 +366,14 @@ public PathF MoveTo(PointF point)
return this;
}
+ ///
+ /// Closes the current sub-path by appending a close segment if it is not already closed.
+ ///
+ ///
+ /// Closing a path is typically required for fill operations to work correctly. Attempting to fill
+ /// an unclosed path may result in undefined behavior or exceptions in some graphics implementations.
+ /// A closed path ensures that the shape is properly defined for filling operations.
+ ///
public void Close()
{
if (!Closed)
@@ -270,6 +386,9 @@ public void Close()
Invalidate();
}
+ ///
+ /// Reopens a previously closed last sub-path by removing its closing segment.
+ ///
public void Open()
{
if (_operations[_operations.Count - 1] == PathOperation.Close)
@@ -282,11 +401,27 @@ public void Open()
Invalidate();
}
+ ///
+ /// Adds a straight line segment to the specified coordinates.
+ ///
+ /// The x-coordinate of the end point.
+ /// The y-coordinate of the end point.
+ /// The current path.
public PathF LineTo(float x, float y)
{
return LineTo(new PointF(x, y));
}
+ ///
+ /// Adds a straight line segment to the specified end point (starting a new sub-path if the path is empty).
+ ///
+ /// The end point.
+ /// The current path.
+ ///
+ /// If this is the first operation on an empty path, it will automatically create an initial MoveTo operation
+ /// to the specified point. For paths intended to be filled, ensure the path forms a closed shape by calling
+ /// or explicitly connecting back to the starting point.
+ ///
public PathF LineTo(PointF point)
{
if (_points.Count == 0)
@@ -307,6 +442,12 @@ public PathF LineTo(PointF point)
return this;
}
+ ///
+ /// Inserts a line segment at a specific segment index.
+ ///
+ /// Line end point.
+ /// Segment index at which to insert.
+ /// The current path.
public PathF InsertLineTo(PointF point, int index)
{
if (index == 0)
@@ -329,11 +470,40 @@ public PathF InsertLineTo(PointF point, int index)
return this;
}
+ ///
+ /// Adds an elliptical arc segment using coordinate values instead of points.
+ ///
+ /// The X coordinate of the top-left corner of the bounding rectangle of the ellipse.
+ /// The Y coordinate of the top-left corner of the bounding rectangle of the ellipse.
+ /// The X coordinate of the bottom-right corner of the bounding rectangle of the ellipse.
+ /// The Y coordinate of the bottom-right corner of the bounding rectangle of the ellipse.
+ /// Starting angle of the arc in degrees. 0° points to the right (along the positive X axis). Angles increase counter-clockwise.
+ /// Ending angle of the arc in degrees, measured with the same convention as .
+ /// If true, the arc is drawn in the clockwise direction from to ; otherwise it is drawn counter-clockwise (the positive angle direction).
+ /// The current path for chaining.
public PathF AddArc(float x1, float y1, float x2, float y2, float startAngle, float endAngle, bool clockwise)
{
return AddArc(new PointF(x1, y1), new PointF(x2, y2), startAngle, endAngle, clockwise);
}
+ ///
+ /// Adds an elliptical arc segment to the current sub-path.
+ ///
+ /// The top-left point of the rectangle that bounds the full ellipse from which the arc segment is taken.
+ /// The bottom-right point of the bounding rectangle of the ellipse.
+ /// Starting angle of the arc in degrees. 0° points to the right (along the positive X axis). Angles increase counter-clockwise.
+ /// Ending angle of the arc in degrees, measured with the same convention as .
+ /// If true, the arc is drawn in the clockwise direction from to ; otherwise it is drawn counter-clockwise (the positive angle direction).
+ ///
+ /// Angle values are specified in degrees (not radians). The angular coordinate system used by for arcs is:
+ ///
+ /// 0° is the point on the ellipse at the positive X axis (to the right of center).
+ /// Positive angles advance counter-clockwise.
+ /// The direction of increasing Y on the drawing surface (often downwards in device pixels) does not change the counter-clockwise convention used for angles.
+ ///
+ /// The current point is not implicitly connected to the start of the arc. If you need a straight line connection, call first.
+ ///
+ /// The current so that calls can be chained fluently.
public PathF AddArc(PointF topLeft, PointF bottomRight, float startAngle, float endAngle, bool clockwise)
{
if (Count == 0 || OperationCount == 0 || GetSegmentType(OperationCount - 1) == PathOperation.Close)
@@ -352,11 +522,25 @@ public PathF AddArc(PointF topLeft, PointF bottomRight, float startAngle, float
return this;
}
+ ///
+ /// Adds a quadratic Bézier curve segment using coordinate values.
+ ///
+ /// X-coordinate of the control point.
+ /// Y-coordinate of the control point.
+ /// X-coordinate of the end point.
+ /// Y-coordinate of the end point.
+ /// The current path.
public PathF QuadTo(float cx, float cy, float x, float y)
{
return QuadTo(new PointF(cx, cy), new PointF(x, y));
}
+ ///
+ /// Adds a quadratic Bézier curve segment defined by a control point and an end point.
+ ///
+ /// Quadratic control point.
+ /// End point of the curve.
+ /// The current path.
public PathF QuadTo(PointF controlPoint, PointF point)
{
_points.Add(controlPoint);
@@ -366,6 +550,13 @@ public PathF QuadTo(PointF controlPoint, PointF point)
return this;
}
+ ///
+ /// Inserts a quadratic Bézier segment at a specific segment index.
+ ///
+ /// Control point.
+ /// End point.
+ /// Insertion segment index.
+ /// The current path.
public PathF InsertQuadTo(PointF controlPoint, PointF point, int index)
{
if (index == 0)
@@ -389,11 +580,28 @@ public PathF InsertQuadTo(PointF controlPoint, PointF point, int index)
return this;
}
+ ///
+ /// Adds a cubic Bézier curve segment using coordinate values.
+ ///
+ /// X-coordinate of the first control point.
+ /// Y-coordinate of the first control point.
+ /// X-coordinate of the second control point.
+ /// Y-coordinate of the second control point.
+ /// X-coordinate of the end point.
+ /// Y-coordinate of the end point.
+ /// The current path.
public PathF CurveTo(float c1X, float c1Y, float c2X, float c2Y, float x, float y)
{
return CurveTo(new PointF(c1X, c1Y), new PointF(c2X, c2Y), new PointF(x, y));
}
+ ///
+ /// Adds a cubic Bézier curve segment defined by two control points and an end point.
+ ///
+ /// First control point.
+ /// Second control point.
+ /// End point of the curve.
+ /// The current path.
public PathF CurveTo(PointF controlPoint1, PointF controlPoint2, PointF point)
{
_points.Add(controlPoint1);
@@ -404,6 +612,14 @@ public PathF CurveTo(PointF controlPoint1, PointF controlPoint2, PointF point)
return this;
}
+ ///
+ /// Inserts a cubic Bézier segment at a specific segment index.
+ ///
+ /// First control point.
+ /// Second control point.
+ /// End point.
+ /// Insertion segment index.
+ /// The current path.
public PathF InsertCurveTo(PointF controlPoint1, PointF controlPoint2, PointF point, int index)
{
if (index == 0)
@@ -428,6 +644,11 @@ public PathF InsertCurveTo(PointF controlPoint1, PointF controlPoint2, PointF po
return this;
}
+ ///
+ /// Computes the starting point index in the internal point list for a given segment index.
+ ///
+ /// Segment index.
+ /// The point index, or -1 if not found.
public int GetSegmentPointIndex(int index)
{
if (index <= OperationCount)
@@ -482,6 +703,14 @@ public int GetSegmentPointIndex(int index)
return -1;
}
+ ///
+ /// Retrieves segment metadata, returning the segment type and output indices pointing into internal collections.
+ ///
+ /// Segment index.
+ /// Receives the starting point index.
+ /// Receives the starting arc angle index.
+ /// Receives the arc clockwise flag index.
+ /// The segment operation type, or if invalid.
public PathOperation GetSegmentInfo(int segmentIndex, out int pointIndex, out int arcAngleIndex, out int arcClockwiseIndex)
{
pointIndex = 0;
@@ -537,6 +766,11 @@ public PathOperation GetSegmentInfo(int segmentIndex, out int pointIndex, out in
return PathOperation.Close;
}
+ ///
+ /// Determines which segment uses the point at a specified index.
+ ///
+ /// Point index.
+ /// The segment index, or -1 if not found.
public int GetSegmentForPoint(int pointIndex)
{
if (pointIndex < _points.Count)
@@ -606,6 +840,11 @@ public int GetSegmentForPoint(int pointIndex)
return -1;
}
+ ///
+ /// Gets the points defining the segment at the specified index.
+ ///
+ /// Segment index.
+ /// An array of points (length varies by segment type), an empty array for close segments, or null if invalid.
public PointF[] GetPointsForSegment(int segmentIndex)
{
if (segmentIndex <= OperationCount)
@@ -710,6 +949,10 @@ private void RemoveAllAfter(int pointIndex, int segmentIndex, int arcIndex, int
Invalidate();
}
+ ///
+ /// Removes the specified segment and all segments that follow it.
+ ///
+ /// Segment index at which truncation begins.
public void RemoveAllSegmentsAfter(int segmentIndex)
{
if (segmentIndex <= OperationCount)
@@ -755,6 +998,10 @@ public void RemoveAllSegmentsAfter(int segmentIndex)
Invalidate();
}
+ ///
+ /// Removes a single segment, adjusting internal point and arc data accordingly.
+ ///
+ /// Segment index to remove.
public void RemoveSegment(int segmentIndex)
{
if (segmentIndex <= OperationCount)
@@ -873,6 +1120,16 @@ public void RemoveSegment(int segmentIndex)
}
}
+ ///
+ /// Creates a new representing this path rotated by the specified angle about a pivot point.
+ ///
+ /// The rotation angle in degrees. Positive angles rotate counter-clockwise; 0° keeps the path unchanged.
+ /// The pivot point about which all points (and ellipse bounding rectangles for arc segments) are rotated.
+ /// A new containing the rotated geometry. The original path is not modified.
+ ///
+ /// Rotation uses the same degree-based, counter-clockwise positive convention as arc angles (see ).
+ /// Arc segments preserve their original start and end angle values; only their bounding rectangle corner points are rotated.
+ ///
public PathF Rotate(float angleAsDegrees, PointF pivot)
{
var path = new PathF();
@@ -925,12 +1182,26 @@ public PathF Rotate(float angleAsDegrees, PointF pivot)
return path;
}
+ ///
+ /// Computes the position of a point in the path after rotation about a pivot.
+ ///
+ /// Index into the internal point list.
+ /// The pivot point for rotation.
+ /// Rotation angle in degrees (counter-clockwise positive).
+ /// The rotated point.
+ ///
+ /// This helper applies the same rotation semantics used by and does not cache results.
+ ///
public PointF GetRotatedPoint(int pointIndex, PointF pivotPoint, float angle)
{
var point = _points[pointIndex];
return GeometryUtil.RotatePoint(pivotPoint, point, angle);
}
+ ///
+ /// Applies a 2D affine transformation matrix to all points in the path in place.
+ ///
+ /// The transformation matrix.
public void Transform(Matrix3x2 transform)
{
for (var i = 0; i < _points.Count; i++)
@@ -939,6 +1210,10 @@ public void Transform(Matrix3x2 transform)
Invalidate();
}
+ ///
+ /// Splits the path into individual sub-path objects.
+ ///
+ /// A list of sub-paths.
public List Separate()
{
var paths = new List();
@@ -987,6 +1262,10 @@ public List Separate()
return paths;
}
+ ///
+ /// Creates a new path with the segment and point order reversed.
+ ///
+ /// A new reversed path.
public PathF Reverse()
{
var points = new List(_points);
@@ -1031,11 +1310,22 @@ public PathF Reverse()
return new PathF(points, arcSizes, arcClockwise, operations, _subPathCount);
}
+ ///
+ /// Appends an approximated ellipse path inside the specified rectangle.
+ ///
+ /// The bounding rectangle for the ellipse.
public void AppendEllipse(RectF rect)
{
AppendEllipse(rect.X, rect.Y, rect.Width, rect.Height);
}
+ ///
+ /// Appends an approximated ellipse path (using 4 cubic curves) inside the specified rectangle.
+ ///
+ /// Left.
+ /// Top.
+ /// Width.
+ /// Height.
public void AppendEllipse(float x, float y, float w, float h)
{
var minX = x;
@@ -1055,11 +1345,22 @@ public void AppendEllipse(float x, float y, float w, float h)
Close();
}
+ ///
+ /// Appends an approximated circle path centered at the specified point.
+ ///
+ /// Center point.
+ /// Radius.
public void AppendCircle(PointF center, float r)
{
AppendCircle(center.X, center.Y, r);
}
+ ///
+ /// Appends an approximated circle path centered at the specified point.
+ ///
+ /// Center X.
+ /// Center Y.
+ /// Radius.
public void AppendCircle(float cx, float cy, float r)
{
var minX = cx - r;
@@ -1079,11 +1380,24 @@ public void AppendCircle(float cx, float cy, float r)
Close();
}
+ ///
+ /// Appends a rectangle path using the specified rectangle bounds.
+ ///
+ /// The rectangle bounds.
+ /// Include a final duplicate line to the first point before closing.
public void AppendRectangle(RectF rect, bool includeLast = false)
{
AppendRectangle(rect.X, rect.Y, rect.Width, rect.Height, includeLast);
}
+ ///
+ /// Appends a rectangle path.
+ ///
+ /// Left.
+ /// Top.
+ /// Width.
+ /// Height.
+ /// Include a final duplicate line to the first point before closing.
public void AppendRectangle(float x, float y, float w, float h, bool includeLast = false)
{
var minX = x;
@@ -1104,11 +1418,26 @@ public void AppendRectangle(float x, float y, float w, float h, bool includeLast
Close();
}
+ ///
+ /// Appends a rounded rectangle using the specified rectangle bounds and uniform corner radius.
+ ///
+ /// The rectangle bounds.
+ /// Corner radius (clamped to half width/height).
+ /// Include a duplicate final line before closing.
public void AppendRoundedRectangle(RectF rect, float cornerRadius, bool includeLast = false)
{
AppendRoundedRectangle(rect.X, rect.Y, rect.Width, rect.Height, cornerRadius, includeLast);
}
+ ///
+ /// Appends a rounded rectangle where all four corners share the same radius.
+ ///
+ /// Left.
+ /// Top.
+ /// Width.
+ /// Height.
+ /// Corner radius (clamped to half width/height).
+ /// Include a duplicate final line before closing.
public void AppendRoundedRectangle(float x, float y, float w, float h, float cornerRadius, bool includeLast = false)
{
cornerRadius = ClampCornerRadius(cornerRadius, w, h);
@@ -1138,11 +1467,26 @@ public void AppendRoundedRectangle(float x, float y, float w, float h, float cor
Close();
}
+ ///
+ /// Appends a rounded rectangle using the specified rectangle bounds and individual corner radii.
+ ///
+ /// Bounding rectangle.
+ /// Top-left corner radius.
+ /// Top-right corner radius.
+ /// Bottom-left corner radius.
+ /// Bottom-right corner radius.
+ /// Include a duplicate final line before closing.
public void AppendRoundedRectangle(RectF rect, float topLeftCornerRadius, float topRightCornerRadius, float bottomLeftCornerRadius, float bottomRightCornerRadius, bool includeLast = false)
{
AppendRoundedRectangle(rect.X, rect.Y, rect.Width, rect.Height, topLeftCornerRadius, topRightCornerRadius, bottomLeftCornerRadius, bottomRightCornerRadius, includeLast);
}
+ ///
+ /// Appends a rounded rectangle using distinct horizontal and vertical radii (elliptical corners).
+ ///
+ /// Bounding rectangle.
+ /// Horizontal corner radius.
+ /// Vertical corner radius.
public void AppendRoundedRectangle(RectF rect, float xCornerRadius, float yCornerRadius)
{
xCornerRadius = Math.Min(xCornerRadius, rect.Width / 2);
@@ -1190,6 +1534,18 @@ public void AppendRoundedRectangle(RectF rect, float xCornerRadius, float yCorne
LineTo(new PointF(minX, minY + yCornerRadius));
}
+ ///
+ /// Appends a rounded rectangle specifying individual corner radii.
+ ///
+ /// Left.
+ /// Top.
+ /// Width.
+ /// Height.
+ /// Top-left radius.
+ /// Top-right radius.
+ /// Bottom-left radius.
+ /// Bottom-right radius.
+ /// Include a duplicate final line before closing.
public void AppendRoundedRectangle(float x, float y, float w, float h, float topLeftCornerRadius, float topRightCornerRadius, float bottomLeftCornerRadius, float bottomRightCornerRadius, bool includeLast = false)
{
topLeftCornerRadius = ClampCornerRadius(topLeftCornerRadius, w, h);
@@ -1235,6 +1591,11 @@ private float ClampCornerRadius(float cornerRadius, float w, float h)
return cornerRadius;
}
+ ///
+ /// Indicates whether the specified sub-path is closed.
+ ///
+ /// Zero-based sub-path index.
+ /// true if closed; otherwise false.
public bool IsSubPathClosed(int subPathIndex)
{
if (subPathIndex >= 0 && subPathIndex < SubPathCount)
@@ -1245,6 +1606,9 @@ public bool IsSubPathClosed(int subPathIndex)
return false;
}
+ ///
+ /// Gets or sets a platform-specific native path object associated with this path. Setting a new value disposes the previous one if disposable.
+ ///
public object PlatformPath
{
get => _platformPath;
@@ -1255,12 +1619,18 @@ public object PlatformPath
}
}
+ ///
+ /// Clears cached bounds and releases any native platform path.
+ ///
public void Invalidate()
{
_cachedBounds = null;
ReleaseNative();
}
+ ///
+ /// Releases native resources associated with the path.
+ ///
public void Dispose()
{
ReleaseNative();
@@ -1274,6 +1644,11 @@ private void ReleaseNative()
_platformPath = null;
}
+ ///
+ /// Offsets every point in the path by the specified amounts.
+ ///
+ /// Delta X.
+ /// Delta Y.
public void Move(float x, float y)
{
for (var i = 0; i < _points.Count; i++)
@@ -1284,12 +1659,19 @@ public void Move(float x, float y)
Invalidate();
}
+ ///
+ /// Offsets a single point by the specified deltas.
+ ///
+ /// Point index.
+ /// Delta X.
+ /// Delta Y.
public void MovePoint(int index, float dx, float dy)
{
_points[index] = _points[index].Offset(dx, dy);
Invalidate();
}
+ ///
public override bool Equals(object obj)
{
if (obj is PathF compareTo)
@@ -1335,6 +1717,7 @@ public override bool Equals(object obj)
return true;
}
+ ///
public override int GetHashCode()
{
unchecked
@@ -1347,6 +1730,12 @@ public override int GetHashCode()
}
}
+ ///
+ /// Determines whether this path and another have equivalent geometry within a tolerance.
+ ///
+ /// The other path.
+ /// Maximum allowed difference per component.
+ /// true if equivalent; otherwise false.
public bool Equals(object obj, float epsilon)
{
if (obj is PathF compareTo)
@@ -1392,6 +1781,9 @@ public bool Equals(object obj, float epsilon)
return true;
}
+ ///
+ /// Gets the axis-aligned bounding box of the path (cached until modified).
+ ///
public RectF Bounds
{
get
@@ -1415,6 +1807,11 @@ public RectF Bounds
}
}
+ ///
+ /// Computes bounds by flattening curves with the given flatness, updating the cache.
+ ///
+ /// Maximum allowed deviation when flattening (smaller = more points).
+ /// The bounding rectangle.
public RectF GetBoundsByFlattening(float flatness = 0.001f)
{
if (_cachedBounds != null)
@@ -1453,6 +1850,12 @@ public RectF GetBoundsByFlattening(float flatness = 0.001f)
return (RectF)_cachedBounds;
}
+ ///
+ /// Creates a new path consisting only of line segments approximating all curves and arcs.
+ ///
+ /// Maximum allowed deviation per segment (smaller = more segments).
+ /// If true, flattens all sub-paths; otherwise stops after the first closed one.
+ /// A flattened path.
public PathF GetFlattenedPath(float flatness = .001f, bool includeSubPaths = false)
{
var flattenedPath = new PathF();
diff --git a/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj b/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
index 6b3ab49a04ab..338f7f8911b4 100644
--- a/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
+++ b/src/Graphics/tests/DeviceTests/Graphics.DeviceTests.csproj
@@ -29,15 +29,6 @@
-
-
-
-
-
diff --git a/src/TestUtils/src/DeviceTests.Runners/HeadlessRunnerOptions.cs b/src/TestUtils/src/DeviceTests.Runners/HeadlessRunnerOptions.cs
index c18fbb5e2ac0..a5f49516b2d0 100644
--- a/src/TestUtils/src/DeviceTests.Runners/HeadlessRunnerOptions.cs
+++ b/src/TestUtils/src/DeviceTests.Runners/HeadlessRunnerOptions.cs
@@ -3,8 +3,8 @@ namespace Microsoft.Maui.TestUtils.DeviceTests.Runners
{
public class HeadlessRunnerOptions
{
- public string TestResultsFilename { get; set; } = "TestResults.xml";
+ public string TestResultsFilename { get; set; } = "testResults.xml";
public bool RequiresUIContext { get; set; } = true;
}
-}
\ No newline at end of file
+}
diff --git a/src/TestUtils/src/DeviceTests/AssertionExtensions.cs b/src/TestUtils/src/DeviceTests/AssertionExtensions.cs
index 515d998dc557..b0a72c9aa8ef 100644
--- a/src/TestUtils/src/DeviceTests/AssertionExtensions.cs
+++ b/src/TestUtils/src/DeviceTests/AssertionExtensions.cs
@@ -15,35 +15,68 @@ namespace Microsoft.Maui.DeviceTests
{
public static partial class AssertionExtensions
{
- public static async Task WaitForGC(params WeakReference[] references)
+ public static async Task Collect()
{
- Assert.NotEmpty(references);
+ await Task.Yield();
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect(2, GCCollectionMode.Forced, true);
+ GC.WaitForPendingFinalizers();
+ GC.Collect(2, GCCollectionMode.Forced, true);
+ await Task.Yield();
+ }
+
+
+ public static async Task WaitForCollect(this WeakReference reference)
+ {
+ for (int i = 0; i < 40 && reference.IsAlive; i++)
+ {
+ await Collect();
+ }
+
+ return reference.IsAlive;
+ }
- bool referencesCollected()
+ public static async Task WaitForCollect(this WeakReference reference)
+ {
+ for (int i = 0; i < 40 && reference.TryGetTarget(out _); i++)
{
- GC.Collect();
- GC.WaitForPendingFinalizers();
+ await Collect();
+ }
- foreach (var reference in references)
+ return reference.TryGetTarget(out _);
+ }
+
+ public static async Task WaitForCollect(params WeakReference[] references)
+ {
+ bool allCollected = true;
+ foreach (var reference in references)
+ {
+ Assert.NotNull(reference);
+ var taskCollect = reference.WaitForCollect();
+ try
+ {
+ await AssertEventuallyAsync(async () => await taskCollect);
+ }
+ catch (XunitException)
{
- Assert.NotNull(reference);
- if (reference.IsAlive)
+ var isAlive = await taskCollect;
+ if (isAlive)
{
- return false;
+ allCollected = false;
}
}
-
- return true;
}
+ return allCollected; // Only true if all references are collected
+ }
- try
- {
- await AssertEventually(referencesCollected, timeout: 10000);
- }
- catch (XunitException ex)
- {
- throw new XunitException(ListLivingReferences(references), ex);
- }
+ public static async Task WaitForGC(params WeakReference[] references)
+ {
+ Assert.NotEmpty(references);
+
+ var collectResult = await WaitForCollect(references);
+
+ Assert.True(collectResult, $"Expected all references to be collected, but some are still alive. {ListLivingReferences(references)}");
}
static string ListLivingReferences(WeakReference[] references)
@@ -267,6 +300,27 @@ public static async Task AssertEventually(this Func assertion, int timeout
throw new XunitException(message);
}
}
+
+ public static async Task AssertEventuallyAsync(this Func> assertion, int timeout = 1000, int interval = 100, string message = "Assertion timed out")
+ {
+ do
+ {
+ if (await assertion())
+ {
+ return;
+ }
+
+ await Task.Delay(interval);
+ timeout -= interval;
+
+ }
+ while (timeout >= 0);
+
+ if (!await assertion())
+ {
+ throw new XunitException(message);
+ }
+ }
// Add these methods to AssertionExtensions class
///
/// Checks if internet connection is available by making an HTTP request