Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ protected override IEnumerable<SectionModel> CreateItems() => new[]
new SectionModel(typeof(StylesPage), "Styles",
"Define the visual elements appearance."),

new SectionModel(typeof(StateTriggersPage), "State Triggers",
"Use state triggers to automatically change visual states based on device properties like orientation, rotation, size, and platform."),

new SectionModel(typeof(TriggersPage), "Triggers",
"Triggers allow you to express actions declaratively in XAML that change the appearance of controls based on events or property changes. "),

Expand Down
145 changes: 145 additions & 0 deletions src/Controls/src/Core/DisplayRotationStateTrigger.cs
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;
SetActive(currentRotation == Rotation);
}
}
5 changes: 3 additions & 2 deletions src/Controls/src/Core/OrientationStateTrigger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public DisplayOrientation Orientation

/// <summary>Bindable property for <see cref="Orientation"/>.</summary>
public static readonly BindableProperty OrientationProperty =
BindableProperty.Create(nameof(Orientation), typeof(DisplayOrientation), typeof(OrientationStateTrigger), null,
BindableProperty.Create(nameof(Orientation), typeof(DisplayOrientation), typeof(OrientationStateTrigger), DisplayOrientation.Unknown,
propertyChanged: OnOrientationChanged);

static void OnOrientationChanged(BindableObject bindable, object oldvalue, object newvalue)
Expand Down Expand Up @@ -56,8 +56,9 @@ void UpdateState()
var currentOrientation = DeviceDisplay.MainDisplayInfo.Orientation;
if (Orientation.IsLandscape())
SetActive(currentOrientation.IsLandscape());
else
else if (Orientation.IsPortrait())
SetActive(currentOrientation.IsPortrait());
else SetActive(false);
}
}
}
Loading
Loading