-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[NET10] Add VSM to Page template styles for orientation-based SafeAreaEdges handling #31924
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
159 changes: 159 additions & 0 deletions
159
src/Controls/tests/Core.UnitTests/SafeAreaOrientationTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| using Microsoft.Maui.Devices; | ||
| using Microsoft.Maui.Graphics; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Maui.Controls.Core.UnitTests; | ||
|
|
||
| public class SafeAreaOrientationTests : BaseTestFixture | ||
| { | ||
| [Fact] | ||
| public void SafeAreaEdgesCanBeSetViaVisualStateManager() | ||
| { | ||
| DeviceDisplay.SetCurrent(new MockDeviceDisplay( | ||
| new(100, 200, 2, DisplayOrientation.Portrait, DisplayRotation.Rotation0))); | ||
|
|
||
| var page = new ContentPage(); | ||
|
|
||
| VisualStateManager.SetVisualStateGroups(page, new VisualStateGroupList | ||
| { | ||
| new VisualStateGroup | ||
| { | ||
| Name = "OrientationStates", | ||
| States = | ||
| { | ||
| new VisualState | ||
| { | ||
| Name = "Portrait", | ||
| StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Portrait } }, | ||
| Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = SafeAreaEdges.All } } | ||
| }, | ||
| new VisualState | ||
| { | ||
| Name = "Landscape", | ||
| StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Landscape } }, | ||
| Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = new SafeAreaEdges(SafeAreaRegions.All, SafeAreaRegions.None) } } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| var window = new Window(page); | ||
|
|
||
| // Initially in portrait, should have All edges | ||
| Assert.Equal(SafeAreaEdges.All, page.SafeAreaEdges); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SafeAreaEdgesChangesWhenOrientationChanges() | ||
| { | ||
| var mockDeviceDisplay = new MockDeviceDisplay( | ||
| new(100, 200, 2, DisplayOrientation.Portrait, DisplayRotation.Rotation0)); | ||
| DeviceDisplay.SetCurrent(mockDeviceDisplay); | ||
|
|
||
| var page = new ContentPage(); | ||
|
|
||
| VisualStateManager.SetVisualStateGroups(page, new VisualStateGroupList | ||
| { | ||
| new VisualStateGroup | ||
| { | ||
| Name = "OrientationStates", | ||
| States = | ||
| { | ||
| new VisualState | ||
| { | ||
| Name = "Portrait", | ||
| StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Portrait } }, | ||
| Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = SafeAreaEdges.All } } | ||
| }, | ||
| new VisualState | ||
| { | ||
| Name = "Landscape", | ||
| StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Landscape } }, | ||
| Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = new SafeAreaEdges(SafeAreaRegions.All, SafeAreaRegions.None) } } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| var window = new Window(page); | ||
|
|
||
| // Initially in portrait | ||
| Assert.Equal(SafeAreaEdges.All, page.SafeAreaEdges); | ||
|
|
||
| // Change to landscape | ||
| mockDeviceDisplay.SetMainDisplayOrientation(DisplayOrientation.Landscape); | ||
|
|
||
| // Should now use different safe area edges (horizontal All, vertical None) | ||
| var expected = new SafeAreaEdges(SafeAreaRegions.All, SafeAreaRegions.None); | ||
| Assert.Equal(expected, page.SafeAreaEdges); | ||
|
|
||
| window.Page = null; // Cleanup | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SafeAreaEdgesCanUseContainerRegion() | ||
| { | ||
| DeviceDisplay.SetCurrent(new MockDeviceDisplay( | ||
| new(100, 200, 2, DisplayOrientation.Portrait, DisplayRotation.Rotation0))); | ||
|
|
||
| var page = new ContentPage(); | ||
|
|
||
| VisualStateManager.SetVisualStateGroups(page, new VisualStateGroupList | ||
| { | ||
| new VisualStateGroup | ||
| { | ||
| Name = "OrientationStates", | ||
| States = | ||
| { | ||
| new VisualState | ||
| { | ||
| Name = "Portrait", | ||
| StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Portrait } }, | ||
| Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = SafeAreaEdges.Container } } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| var window = new Window(page); | ||
|
|
||
| // Should use Container region | ||
| Assert.Equal(SafeAreaEdges.Container, page.SafeAreaEdges); | ||
|
|
||
| window.Page = null; // Cleanup | ||
| } | ||
|
|
||
| [Fact] | ||
| public void SafeAreaEdgesWorksWithDifferentPageTypes() | ||
| { | ||
| DeviceDisplay.SetCurrent(new MockDeviceDisplay( | ||
| new(100, 200, 2, DisplayOrientation.Portrait, DisplayRotation.Rotation0))); | ||
|
|
||
| var contentPage = new ContentPage(); | ||
| var tabbedPage = new TabbedPage(); | ||
|
|
||
| VisualStateManager.SetVisualStateGroups(contentPage, new VisualStateGroupList | ||
| { | ||
| new VisualStateGroup | ||
| { | ||
| Name = "OrientationStates", | ||
| States = | ||
| { | ||
| new VisualState | ||
| { | ||
| Name = "Portrait", | ||
| StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Portrait } }, | ||
| Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = SafeAreaEdges.All } } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| var window = new Window(contentPage); | ||
|
|
||
| // ContentPage should respect VSM | ||
| Assert.Equal(SafeAreaEdges.All, contentPage.SafeAreaEdges); | ||
|
|
||
| window.Page = null; // Cleanup | ||
| } | ||
| } | ||
123 changes: 123 additions & 0 deletions
123
src/Controls/tests/TestCases.HostApp/Issues/PageSafeAreaOrientationVSM.xaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| <?xml version="1.0" encoding="utf-8" ?> | ||
| <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
| x:Class="Maui.Controls.Sample.Issues.PageSafeAreaOrientationVSM" | ||
| Title="Page SafeArea VSM Test"> | ||
| <!-- Apply VSM to the Page itself to change SafeAreaEdges based on orientation --> | ||
| <VisualStateManager.VisualStateGroups> | ||
| <VisualStateGroupList> | ||
| <VisualStateGroup x:Name="OrientationStates"> | ||
| <VisualState x:Name="Portrait"> | ||
| <VisualState.StateTriggers> | ||
| <OrientationStateTrigger Orientation="Portrait" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="SafeAreaEdges" Value="Container" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState x:Name="Landscape"> | ||
| <VisualState.StateTriggers> | ||
| <OrientationStateTrigger Orientation="Landscape" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="SafeAreaEdges" Value="Container" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| </VisualStateGroup> | ||
| </VisualStateGroupList> | ||
| </VisualStateManager.VisualStateGroups> | ||
|
|
||
| <ScrollView BackgroundColor="LightGray"> | ||
| <VerticalStackLayout Spacing="20" Padding="20"> | ||
|
|
||
| <Label Text="Page SafeArea with Orientation VSM Test" | ||
| FontSize="24" | ||
| FontAttributes="Bold" | ||
| HorizontalOptions="Center"/> | ||
|
|
||
| <Label Text="This test validates that Page SafeAreaEdges responds to orientation changes via VisualStateManager." | ||
| FontSize="14" | ||
| HorizontalOptions="Center"/> | ||
|
|
||
| <Border BackgroundColor="LightBlue" | ||
| Stroke="DarkBlue" | ||
| StrokeThickness="2" | ||
| Padding="15"> | ||
| <VerticalStackLayout Spacing="10"> | ||
| <Label Text="Current State" | ||
| FontSize="18" | ||
| FontAttributes="Bold"/> | ||
|
|
||
| <Label x:Name="OrientationLabel" | ||
| Text="Orientation: Detecting..." | ||
| FontSize="16" | ||
| AutomationId="OrientationLabel"/> | ||
|
|
||
| <Label x:Name="SafeAreaEdgesLabel" | ||
| Text="SafeAreaEdges: Detecting..." | ||
| FontSize="16" | ||
| AutomationId="SafeAreaEdgesLabel"/> | ||
|
|
||
| <Label x:Name="PageDimensionsLabel" | ||
| Text="Page Dimensions: Loading..." | ||
| FontSize="16" | ||
| AutomationId="PageDimensionsLabel"/> | ||
| </VerticalStackLayout> | ||
| </Border> | ||
|
|
||
| <Border BackgroundColor="LightYellow" | ||
| Stroke="Orange" | ||
| StrokeThickness="2" | ||
| Padding="15"> | ||
| <VerticalStackLayout Spacing="10"> | ||
| <Label Text="Test Instructions" | ||
| FontSize="18" | ||
| FontAttributes="Bold"/> | ||
|
|
||
| <Label Text="• Rotate device from Portrait to Landscape" | ||
| FontSize="14"/> | ||
| <Label Text="• Observe SafeAreaEdges updates automatically" | ||
| FontSize="14"/> | ||
| <Label Text="• The page safe area should be Container in both orientations" | ||
| FontSize="14"/> | ||
| </VerticalStackLayout> | ||
| </Border> | ||
|
|
||
| <!-- Visual markers to show safe area effect --> | ||
| <Border BackgroundColor="White" | ||
| Stroke="Red" | ||
| StrokeThickness="3" | ||
| Padding="10"> | ||
| <VerticalStackLayout Spacing="5"> | ||
| <Label Text="Visual Markers" | ||
| FontSize="18" | ||
| FontAttributes="Bold"/> | ||
|
|
||
| <BoxView Color="Red" | ||
| HeightRequest="50" | ||
| WidthRequest="100" | ||
| HorizontalOptions="Start" | ||
| AutomationId="TopLeftMarker"/> | ||
|
|
||
| <BoxView Color="Green" | ||
| HeightRequest="50" | ||
| WidthRequest="100" | ||
| HorizontalOptions="End" | ||
| AutomationId="TopRightMarker"/> | ||
|
|
||
| <BoxView Color="Blue" | ||
| HeightRequest="50" | ||
| WidthRequest="100" | ||
| HorizontalOptions="Center" | ||
| AutomationId="CenterMarker"/> | ||
| </VerticalStackLayout> | ||
| </Border> | ||
|
|
||
| <Label Text="End of Content" | ||
| FontSize="18" | ||
| FontAttributes="Bold" | ||
| HorizontalOptions="Center"/> | ||
|
|
||
| </VerticalStackLayout> | ||
| </ScrollView> | ||
| </ContentPage> |
48 changes: 48 additions & 0 deletions
48
src/Controls/tests/TestCases.HostApp/Issues/PageSafeAreaOrientationVSM.xaml.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| namespace Maui.Controls.Sample.Issues; | ||
|
|
||
| [Issue(IssueTracker.None, 0, "Page SafeAreaEdges with OrientationStateTrigger VSM Test", PlatformAffected.All)] | ||
| public partial class PageSafeAreaOrientationVSM : ContentPage | ||
| { | ||
| public PageSafeAreaOrientationVSM() | ||
| { | ||
| InitializeComponent(); | ||
|
|
||
| // Monitor size changes for orientation detection | ||
| this.SizeChanged += OnSizeChanged; | ||
|
|
||
| // Update info when page appears | ||
| this.Appearing += OnPageAppearing; | ||
| } | ||
|
|
||
| private void OnPageAppearing(object sender, EventArgs e) | ||
| { | ||
| // Delay to allow layout to complete | ||
| Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(500), UpdateInfo); | ||
| } | ||
|
|
||
| private void OnSizeChanged(object sender, EventArgs e) | ||
| { | ||
| // Update info when size changes (indicating orientation change) | ||
| Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(100), UpdateInfo); | ||
| } | ||
|
|
||
| private void UpdateInfo() | ||
| { | ||
| try | ||
| { | ||
| // Determine orientation based on actual page dimensions | ||
| var isLandscape = this.Width > this.Height; | ||
| var orientationText = isLandscape ? "Landscape" : "Portrait"; | ||
|
|
||
| OrientationLabel.Text = $"Orientation: {orientationText}"; | ||
| SafeAreaEdgesLabel.Text = $"SafeAreaEdges: {this.SafeAreaEdges}"; | ||
| PageDimensionsLabel.Text = $"Page Dimensions: {this.Width:F1} x {this.Height:F1}"; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| OrientationLabel.Text = $"Orientation: Error - {ex.Message}"; | ||
| SafeAreaEdgesLabel.Text = "SafeAreaEdges: Error"; | ||
| PageDimensionsLabel.Text = "Page Dimensions: Error"; | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a test for
DisplayOrientation.Unknownor rapid changes to ensure VSM falls back gracefully without exceptions. This covers edge cases like startup or sensor errors, verifying default SafeAreaEdges.