-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add DisplayRotationStateTrigger for granular device rotation control #31297
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
Merged
PureWeen
merged 12 commits into
net10.0
from
copilot/fix-1c0f6ffb-931c-49ad-bb8f-bf0f37f3071a
Sep 11, 2025
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
3fb16d1
Initial plan
Copilot 1e72a32
Implement DisplayRotationStateTrigger with comprehensive tests and mo…
Copilot 7c1b32f
Complete DisplayRotationStateTrigger implementation with 19 passing t…
Copilot c638e0b
Move DisplayRotationStateTrigger documentation to inline XML comments
Copilot ed72ae4
For David!
mattleibow fd0a5dd
Update src/Controls/tests/Core.UnitTests/DisplayRotationStateTriggerT…
mattleibow e69f9ad
Fix tests
mattleibow e819839
Update src/Controls/src/Core/DisplayRotationStateTrigger.cs
mattleibow 91b80ef
Update DisplayRotationStateTrigger.cs
mattleibow 37c3dc3
Remove #nullable disable and fix nullability in DisplayRotationStateT…
Copilot 2a8af04
revert regen
mattleibow f026c25
Add a sample
mattleibow 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
187 changes: 187 additions & 0 deletions
187
src/Controls/samples/Controls.Sample/Pages/UserInterface/StateTriggersPage.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,187 @@ | ||
| <views:BasePage | ||
| xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
| x:Class="Maui.Controls.Sample.Pages.StateTriggersPage" | ||
| xmlns:views="clr-namespace:Maui.Controls.Sample.Pages.Base" | ||
| Title="State Triggers"> | ||
| <views:BasePage.Content> | ||
| <ScrollView> | ||
| <VerticalStackLayout Spacing="20" Padding="12"> | ||
|
|
||
| <!-- Adaptive Trigger Example --> | ||
| <Border Padding="15" Background="LightBlue"> | ||
| <VisualStateManager.VisualStateGroups> | ||
| <VisualStateGroupList> | ||
| <VisualStateGroup Name="WindowSizeStates"> | ||
| <VisualState Name="SmallWindow"> | ||
| <VisualState.StateTriggers> | ||
| <AdaptiveTrigger MinWindowWidth="0" MinWindowHeight="0" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="Pink" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState Name="MediumWindow"> | ||
| <VisualState.StateTriggers> | ||
| <AdaptiveTrigger MinWindowWidth="600" MinWindowHeight="400" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="LightGreen" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState Name="LargeWindow"> | ||
| <VisualState.StateTriggers> | ||
| <AdaptiveTrigger MinWindowWidth="1000" MinWindowHeight="600" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="Gold" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| </VisualStateGroup> | ||
| </VisualStateGroupList> | ||
| </VisualStateManager.VisualStateGroups> | ||
| <VerticalStackLayout> | ||
| <Label Text="Adaptive Trigger Example" FontSize="18" FontAttributes="Bold" /> | ||
| <Label Text="• Small window (0x0+): Pink background" /> | ||
| <Label Text="• Medium window (600x400+): Light green background" /> | ||
| <Label Text="• Large window (1000x600+): Gold background" /> | ||
| <Label Text="Resize the window to see the background color change!" /> | ||
| </VerticalStackLayout> | ||
| </Border> | ||
|
|
||
| <!-- Device State Trigger Example --> | ||
| <Border Padding="15" Background="LightCoral"> | ||
| <VisualStateManager.VisualStateGroups> | ||
| <VisualStateGroupList> | ||
| <VisualStateGroup Name="DeviceStates"> | ||
| <VisualState Name="AndroidState"> | ||
| <VisualState.StateTriggers> | ||
| <DeviceStateTrigger Device="Android" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="LimeGreen" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState Name="iOSState"> | ||
| <VisualState.StateTriggers> | ||
| <DeviceStateTrigger Device="iOS" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="DeepSkyBlue" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState Name="MacCatalystState"> | ||
| <VisualState.StateTriggers> | ||
| <DeviceStateTrigger Device="MacCatalyst" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="MediumPurple" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState Name="WindowsState"> | ||
| <VisualState.StateTriggers> | ||
| <DeviceStateTrigger Device="WinUI" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="Orange" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| </VisualStateGroup> | ||
| </VisualStateGroupList> | ||
| </VisualStateManager.VisualStateGroups> | ||
| <VerticalStackLayout> | ||
| <Label Text="Device State Trigger Example" FontSize="18" FontAttributes="Bold" /> | ||
| <Label Text="• Android: Lime green background" /> | ||
| <Label Text="• iOS: Deep sky blue background" /> | ||
| <Label Text="• MacCatalyst: Medium purple background" /> | ||
| <Label Text="• Windows: Orange background" /> | ||
| <Label Text="The background color indicates your current platform!" /> | ||
| </VerticalStackLayout> | ||
| </Border> | ||
|
|
||
| <!-- Display Rotation Trigger Example --> | ||
| <Border Padding="15" Background="Lavender"> | ||
| <VisualStateManager.VisualStateGroups> | ||
| <VisualStateGroupList> | ||
| <VisualStateGroup Name="RotationStates"> | ||
| <VisualState Name="Rotation0State"> | ||
| <VisualState.StateTriggers> | ||
| <DisplayRotationStateTrigger Rotation="Rotation0" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="Red" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState Name="Rotation90State"> | ||
| <VisualState.StateTriggers> | ||
| <DisplayRotationStateTrigger Rotation="Rotation90" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="Green" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState Name="Rotation180State"> | ||
| <VisualState.StateTriggers> | ||
| <DisplayRotationStateTrigger Rotation="Rotation180" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="Blue" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState Name="Rotation270State"> | ||
| <VisualState.StateTriggers> | ||
| <DisplayRotationStateTrigger Rotation="Rotation270" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="Yellow" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| </VisualStateGroup> | ||
| </VisualStateGroupList> | ||
| </VisualStateManager.VisualStateGroups> | ||
| <VerticalStackLayout> | ||
| <Label Text="Display Rotation State Trigger Example" FontSize="18" FontAttributes="Bold" /> | ||
| <Label Text="• 0° (Portrait up): Red background" /> | ||
| <Label Text="• 90° (Landscape left): Green background" /> | ||
| <Label Text="• 180° (Portrait upside down): Blue background" /> | ||
| <Label Text="• 270° (Landscape right): Yellow background" /> | ||
| <Label Text="Rotate your device to see the background color change!" /> | ||
| </VerticalStackLayout> | ||
| </Border> | ||
|
|
||
| <!-- Orientation Trigger Example --> | ||
| <Border Padding="15" Background="Wheat"> | ||
| <VisualStateManager.VisualStateGroups> | ||
| <VisualStateGroupList> | ||
| <VisualStateGroup Name="OrientationStates"> | ||
| <VisualState Name="PortraitState"> | ||
| <VisualState.StateTriggers> | ||
| <OrientationStateTrigger Orientation="Portrait" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="CornflowerBlue" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| <VisualState Name="LandscapeState"> | ||
| <VisualState.StateTriggers> | ||
| <OrientationStateTrigger Orientation="Landscape" /> | ||
| </VisualState.StateTriggers> | ||
| <VisualState.Setters> | ||
| <Setter Property="Background" Value="Coral" /> | ||
| </VisualState.Setters> | ||
| </VisualState> | ||
| </VisualStateGroup> | ||
| </VisualStateGroupList> | ||
| </VisualStateManager.VisualStateGroups> | ||
| <VerticalStackLayout> | ||
| <Label Text="Orientation State Trigger Example" FontSize="18" FontAttributes="Bold" /> | ||
| <Label Text="• Portrait: Cornflower blue background" /> | ||
| <Label Text="• Landscape: Coral background" /> | ||
| <Label Text="Change device orientation to see the background color change!" /> | ||
| </VerticalStackLayout> | ||
| </Border> | ||
|
|
||
| </VerticalStackLayout> | ||
| </ScrollView> | ||
| </views:BasePage.Content> | ||
| </views:BasePage> |
10 changes: 10 additions & 0 deletions
10
src/Controls/samples/Controls.Sample/Pages/UserInterface/StateTriggersPage.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,10 @@ | ||
| namespace Maui.Controls.Sample.Pages | ||
| { | ||
| public partial class StateTriggersPage | ||
| { | ||
| public StateTriggersPage() | ||
| { | ||
| InitializeComponent(); | ||
| } | ||
| } | ||
| } |
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
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,145 @@ | ||
| using Microsoft.Maui.Controls.Internals; | ||
| using Microsoft.Maui.Devices; | ||
|
|
||
| namespace Microsoft.Maui.Controls; | ||
|
|
||
| /// <summary> | ||
| /// Trigger that activates when the device display rotation matches the specified <see cref="Rotation"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// The <see cref="DisplayRotationStateTrigger"/> enables developers to create visual states that are triggered based on the device's display rotation. | ||
| /// Unlike <see cref="OrientationStateTrigger"/> which only differentiates between portrait and landscape orientations, | ||
| /// this trigger provides granular control over specific rotation angles (0°, 90°, 180°, 270°). | ||
| /// </para> | ||
| /// <para> | ||
| /// This trigger is particularly useful for applications that need to respond to specific device orientations, | ||
| /// such as games that have different layouts for each rotation state or apps that need to handle upside-down orientations differently. | ||
| /// </para> | ||
| /// </remarks> | ||
| /// <example> | ||
| /// <para> | ||
| /// The following example shows how to use <see cref="DisplayRotationStateTrigger"/> to change the background color based on device rotation: | ||
| /// </para> | ||
| /// <code lang="XAML"><![CDATA[ | ||
| /// <ContentPage.Resources> | ||
| /// <Style TargetType="ContentPage"> | ||
| /// <Setter Property="VisualStateManager.VisualStateGroups"> | ||
| /// <VisualStateGroupList> | ||
| /// <VisualStateGroup> | ||
| /// <VisualState Name="Rotation0"> | ||
| /// <VisualState.StateTriggers> | ||
| /// <controls:DisplayRotationStateTrigger Rotation="Rotation0" /> | ||
| /// </VisualState.StateTriggers> | ||
| /// <VisualState.Setters> | ||
| /// <Setter Property="BackgroundColor" Value="Red" /> | ||
| /// </VisualState.Setters> | ||
| /// </VisualState> | ||
| /// <VisualState Name="Rotation90"> | ||
| /// <VisualState.StateTriggers> | ||
| /// <controls:DisplayRotationStateTrigger Rotation="Rotation90" /> | ||
| /// </VisualState.StateTriggers> | ||
| /// <VisualState.Setters> | ||
| /// <Setter Property="BackgroundColor" Value="Green" /> | ||
| /// </VisualState.Setters> | ||
| /// </VisualState> | ||
| /// <VisualState Name="Rotation180"> | ||
| /// <VisualState.StateTriggers> | ||
| /// <controls:DisplayRotationStateTrigger Rotation="Rotation180" /> | ||
| /// </VisualState.StateTriggers> | ||
| /// <VisualState.Setters> | ||
| /// <Setter Property="BackgroundColor" Value="Blue" /> | ||
| /// </VisualState.Setters> | ||
| /// </VisualState> | ||
| /// <VisualState Name="Rotation270"> | ||
| /// <VisualState.StateTriggers> | ||
| /// <controls:DisplayRotationStateTrigger Rotation="Rotation270" /> | ||
| /// </VisualState.StateTriggers> | ||
| /// <VisualState.Setters> | ||
| /// <Setter Property="BackgroundColor" Value="Yellow" /> | ||
| /// </VisualState.Setters> | ||
| /// </VisualState> | ||
| /// </VisualStateGroup> | ||
| /// </VisualStateGroupList> | ||
| /// </Setter> | ||
| /// </Style> | ||
| /// </ContentPage.Resources> | ||
| /// ]]></code> | ||
| /// </example> | ||
| public sealed class DisplayRotationStateTrigger : StateTriggerBase | ||
| { | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="DisplayRotationStateTrigger"/> class. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The trigger will immediately evaluate its state based on the current device display rotation. | ||
| /// </remarks> | ||
| public DisplayRotationStateTrigger() | ||
| { | ||
| UpdateState(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the display rotation that will activate this trigger. | ||
| /// </summary> | ||
| /// <value> | ||
| /// The <see cref="DisplayRotation"/> value that will activate this trigger when the device display matches it. | ||
| /// </value> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// The trigger will be active when the device's current display rotation matches this property value. | ||
| /// Possible values are: | ||
| /// </para> | ||
| /// <list type="bullet"> | ||
| /// <item><description><see cref="DisplayRotation.Rotation0"/> - Device is in its natural position (0 degrees)</description></item> | ||
| /// <item><description><see cref="DisplayRotation.Rotation90"/> - Device is rotated 90 degrees</description></item> | ||
| /// <item><description><see cref="DisplayRotation.Rotation180"/> - Device is rotated 180 degrees (upside down)</description></item> | ||
| /// <item><description><see cref="DisplayRotation.Rotation270"/> - Device is rotated 270 degrees</description></item> | ||
| /// <item><description><see cref="DisplayRotation.Unknown"/> - Device rotation is unknown</description></item> | ||
| /// </list> | ||
| /// </remarks> | ||
| public DisplayRotation Rotation | ||
| { | ||
| get => (DisplayRotation)GetValue(RotationProperty); | ||
| set => SetValue(RotationProperty, value); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Identifies the <see cref="Rotation"/> bindable property. | ||
| /// </summary> | ||
| public static readonly BindableProperty RotationProperty = | ||
| BindableProperty.Create(nameof(Rotation), typeof(DisplayRotation), typeof(DisplayRotationStateTrigger), DisplayRotation.Unknown, | ||
| propertyChanged: OnRotationChanged); | ||
|
|
||
| static void OnRotationChanged(BindableObject bindable, object oldvalue, object newvalue) | ||
| { | ||
| ((DisplayRotationStateTrigger)bindable).UpdateState(); | ||
| } | ||
|
|
||
| protected override void OnAttached() | ||
| { | ||
| base.OnAttached(); | ||
|
|
||
| if (!DesignMode.IsDesignModeEnabled) | ||
| { | ||
| UpdateState(); | ||
| DeviceDisplay.MainDisplayInfoChanged += OnInfoPropertyChanged; | ||
| } | ||
| } | ||
|
|
||
| protected override void OnDetached() | ||
| { | ||
| base.OnDetached(); | ||
|
|
||
| DeviceDisplay.MainDisplayInfoChanged -= OnInfoPropertyChanged; | ||
| } | ||
|
|
||
| void OnInfoPropertyChanged(object? sender, DisplayInfoChangedEventArgs e) => | ||
| UpdateState(); | ||
|
|
||
| void UpdateState() | ||
| { | ||
| var currentRotation = DeviceDisplay.MainDisplayInfo.Rotation; | ||
mattleibow marked this conversation as resolved.
Show resolved
Hide resolved
mattleibow marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| SetActive(currentRotation == Rotation); | ||
| } | ||
| } | ||
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
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.
Uh oh!
There was an error while loading. Please reload this page.