Added ability to set Auto or Right flyout placement

This commit is contained in:
Daniel Clark
2021-02-25 17:39:19 +00:00
parent 36f2953fd4
commit d25744a9f4
16 changed files with 142 additions and 82 deletions
@@ -2,7 +2,8 @@
x:Class="NotificationFlyoutSample.SampleFlyout"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:TheXamlGuy.NotificationFlyout.Uwp.UI.Controls">
xmlns:controls="using:TheXamlGuy.NotificationFlyout.Uwp.UI.Controls"
Placement="Right">
<Grid Width="800" Height="800">
<Button
HorizontalAlignment="Center"
@@ -24,7 +24,7 @@ namespace TheXamlGuy.NotificationFlyout.Common.Helpers
GetAppBarPosition(ref appBarData);
state.Rect = appBarData.rect.ToRect();
state.Position = (TaskbarPosition)appBarData.uEdge;
state.Placement = (TaskbarPlacement)appBarData.uEdge;
return state;
}
@@ -1,7 +1,7 @@
namespace TheXamlGuy.NotificationFlyout.Common.Helpers
{
public enum TaskbarPosition
public enum TaskbarPlacement
{
Left = 0,
Top = 1,
@@ -4,7 +4,7 @@ namespace TheXamlGuy.NotificationFlyout.Common.Helpers
{
public struct TaskbarState
{
public TaskbarPosition Position;
public TaskbarPlacement Placement;
public Rect Rect;
public Screen Screen;
}
@@ -43,18 +43,24 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
public static DependencyProperty PlacementProperty =
DependencyProperty.Register(nameof(Placement),
typeof(NotificationFlyoutContextMenu), typeof(NotificationFlyout),
new PropertyMetadata(NotificationFlyoutPlacement.Auto));
new PropertyMetadata(NotificationFlyoutPlacement.Auto, OnPlacementPropertyChanged));
private static INotificationFlyoutApplication _applicationInstance;
public event EventHandler<object> Closed;
public event TypedEventHandler<NotificationFlyout, NotificationFlyoutClosingEventArgs> Closing;
public event EventHandler<object> Opened;
public event EventHandler<object> Opening;
internal event EventHandler ContextMenuChanged;
internal event EventHandler IconSourceChanged;
internal event EventHandler PlacementChanged;
public UIElement Content
{
get => (UIElement)GetValue(ContentProperty);
@@ -90,6 +96,7 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
get => (NotificationFlyoutPlacement)GetValue(PlacementProperty);
set => SetValue(PlacementProperty, value);
}
public ElementTheme RequestedTheme
{
get => (ElementTheme)GetValue(RequestedThemeProperty);
@@ -107,7 +114,7 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
internal void InvokeOpenedEvent(object obj) => Opened?.Invoke(this, obj);
internal void InvokeOpeningEvent(object obj) => Opening?.Invoke(this, obj);
private static void OnContextMenuPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var sender = dependencyObject as NotificationFlyout;
@@ -120,8 +127,16 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
sender?.OnIconPropertyChanged();
}
private static void OnPlacementPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var sender = dependencyObject as NotificationFlyout;
sender?.OnPlacementPropertyChanged();
}
private void OnContextMenuPropertyChanged() => ContextMenuChanged?.Invoke(this, EventArgs.Empty);
private void OnIconPropertyChanged() => IconSourceChanged?.Invoke(this, EventArgs.Empty);
private void OnPlacementPropertyChanged() => PlacementChanged?.Invoke(this, EventArgs.Empty);
}
}
@@ -3,9 +3,6 @@
public enum NotificationFlyoutPlacement
{
Auto,
Left,
Top,
Right,
Bottom
Right
}
}
@@ -21,8 +21,6 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
}
}
internal void UpdatePlacementVisualState(string placement) => VisualStateManager.GoToState(this, placement, true);
internal void UpdateThemeVisualState(bool isColorPrevalence) => VisualStateManager.GoToState(this, isColorPrevalence ? "ColorPrevalenceTheme" : "DefaultTheme", true);
}
}
@@ -94,6 +94,12 @@
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="FullRight">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="180" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ThemeStates">
<VisualState x:Name="DefaultTheme" />
@@ -1,29 +1,28 @@
using System.Numerics;
using Windows.UI.Xaml;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
{
internal class NotificationFlyoutHost : Control
internal class NotificationFlyoutPresenterHost : Control
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(nameof(Content),
typeof(UIElement), typeof(NotificationFlyoutHost),
typeof(UIElement), typeof(NotificationFlyoutPresenterHost),
new PropertyMetadata(null));
public static readonly DependencyProperty FlyoutPresenterStyleProperty =
DependencyProperty.Register(nameof(FlyoutPresenterStyle),
typeof(Style), typeof(NotificationFlyoutHost),
typeof(Style), typeof(NotificationFlyoutPresenterHost),
new PropertyMetadata(null));
private Flyout _flyout;
private NotificationFlyout _notificationFlyout;
private NotificationFlyoutPresenter _notificationFlyoutPresenter;
private Grid _root;
public NotificationFlyoutHost() => DefaultStyleKey = typeof(NotificationFlyoutHost);
public NotificationFlyoutPresenterHost() => DefaultStyleKey = typeof(NotificationFlyoutPresenterHost);
public UIElement Content
{
@@ -44,10 +43,12 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
flyout.Hide();
}
public void SetFlyoutPlacement(string placement)
public void UpdatePlacement(NotificationFlyoutPresenterPlacement placement)
{
if (_notificationFlyoutPresenter == null) return;
_notificationFlyoutPresenter.UpdatePlacementVisualState(placement);
var state = placement.ToString();
VisualStateManager.GoToState(this, state, true);
VisualStateManager.GoToState(_notificationFlyoutPresenter, state, true);
}
public void SetOwningFlyout(NotificationFlyout flyout)
@@ -2,10 +2,10 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:TheXamlGuy.NotificationFlyout.Uwp.UI.Controls">
<Style TargetType="controls:NotificationFlyoutHost">
<Style TargetType="controls:NotificationFlyoutPresenterHost">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:NotificationFlyoutHost">
<ControlTemplate TargetType="controls:NotificationFlyoutPresenterHost">
<Grid x:Name="Root">
<Grid.Resources>
<Style x:Key="DefaultFlyoutPresenterStyle" TargetType="FlyoutPresenter">
@@ -67,29 +67,26 @@
<VisualState x:Name="Bottom">
<VisualState.Setters>
<Setter Target="Flyout.FlyoutPresenterStyle" Value="{StaticResource BottomFlyoutPresenterStyle}" />
<!--<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="0" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="80" />-->
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Top">
<VisualState.Setters>
<Setter Target="Flyout.FlyoutPresenterStyle" Value="{StaticResource TopFlyoutPresenterStyle}" />
<!--<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="0" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="-80" />-->
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Left">
<VisualState.Setters>
<Setter Target="Flyout.FlyoutPresenterStyle" Value="{StaticResource LeftFlyoutPresenterStyle}" />
<!--<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="-80" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />-->
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Right">
<VisualState.Setters>
<Setter Target="Flyout.FlyoutPresenterStyle" Value="{StaticResource RightFlyoutPresenterStyle}" />
<!--<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="80" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />-->
</VisualState.Setters>
</VisualState>
<VisualState x:Name="FullRight">
<VisualState.Setters>
<Setter Target="Flyout.FlyoutPresenterStyle" Value="{StaticResource BottomFlyoutPresenterStyle}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
@@ -0,0 +1,11 @@
namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
{
internal enum NotificationFlyoutPresenterPlacement
{
Left,
Top,
Right,
Bottom,
FullRight
}
}
@@ -14,11 +14,11 @@
</PropertyGroup>
<ItemGroup>
<Page Include="NotificationFlyoutHost\NotificationFlyoutContextMenuFlyoutHost.xaml">
<Page Include="NotificationFlyoutPresenter\NotificationFlyoutContextMenuFlyoutHost.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="NotificationFlyoutHost\NotificationFlyoutHost.xaml">
<Page Include="NotificationFlyoutPresenter\NotificationFlyoutPresenterHost.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -1,7 +1,7 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///TheXamlGuy.NotificationFlyout.Uwp.UI.Controls/NotificationFlyoutHost/NotificationFlyoutHost.xaml" />
<ResourceDictionary Source="ms-appx:///TheXamlGuy.NotificationFlyout.Uwp.UI.Controls/NotificationFlyoutPresenter/NotificationFlyoutPresenter.xaml" />
<ResourceDictionary Source="ms-appx:///TheXamlGuy.NotificationFlyout.Uwp.UI.Controls/NotificationFlyoutHost/NotificationFlyoutContextMenuFlyoutHost.xaml" />
<ResourceDictionary Source="ms-appx:///TheXamlGuy.NotificationFlyout.Uwp.UI.Controls/NotificationFlyoutPresenter/NotificationFlyoutPresenterHost.xaml" />
<ResourceDictionary Source="ms-appx:///TheXamlGuy.NotificationFlyout.Uwp.UI.Controls/NotificationFlyoutPresenter/NotificationFlyoutContextMenuFlyoutHost.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
@@ -9,7 +9,7 @@ using System.Windows.Media.Imaging;
namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
{
internal class NotificationFlyoutXamlHost : TransparentXamlHost<NotificationFlyoutHost>
internal class NotificationFlyoutXamlHost : TransparentXamlHost<NotificationFlyoutPresenterHost>
{
private const string ShellTrayHandleName = "Shell_TrayWnd";
@@ -34,11 +34,13 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
{
_flyout.IconSourceChanged -= OnIconSourceChanged;
_flyout.ContextMenuChanged -= OnContextMenuChanged;
_flyout.PlacementChanged -= OnPlacementChanged;
}
_flyout = flyout;
_flyout.IconSourceChanged += OnIconSourceChanged;
_flyout.ContextMenuChanged += OnContextMenuChanged;
_flyout.PlacementChanged += OnPlacementChanged;
var content = GetHostContent();
if (content != null)
@@ -47,7 +49,7 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
}
UpdateIcons();
PrepareContextMenu();
UpdateContextMenu();
}
internal void ShowFlyout()
@@ -56,14 +58,21 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
if (content != null)
{
var taskbarState = _taskbarHelper.GetCurrentState();
var flyoutPlacement = taskbarState.Position switch
var flyoutPlacement = FlyoutPlacementMode.Top;
switch (_flyout.Placement)
{
TaskbarPosition.Left => FlyoutPlacementMode.Right,
TaskbarPosition.Top => FlyoutPlacementMode.Bottom,
TaskbarPosition.Right => FlyoutPlacementMode.Left,
TaskbarPosition.Bottom => FlyoutPlacementMode.Top,
_ => throw new ArgumentOutOfRangeException(),
};
case NotificationFlyoutPlacement.Auto:
flyoutPlacement = taskbarState.Placement switch
{
TaskbarPlacement.Left => FlyoutPlacementMode.Right,
TaskbarPlacement.Top => FlyoutPlacementMode.Bottom,
TaskbarPlacement.Right => FlyoutPlacementMode.Left,
TaskbarPlacement.Bottom => FlyoutPlacementMode.Top,
_ => throw new ArgumentOutOfRangeException(),
};
break;
}
Activate();
content.ShowFlyout(flyoutPlacement);
@@ -82,13 +91,13 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
{
PrepareNotificationIcon();
PrepareTaskbar();
UpdateWindow();
UpdatePlacement();
UpdateTheme();
}
protected override void OnDeactivated(EventArgs args) => HideFlyout();
private void OnContextMenuChanged(object sender, EventArgs args) => PrepareContextMenu();
private void OnContextMenuChanged(object sender, EventArgs args) => UpdateContextMenu();
private void OnIconInvoked(object sender, NotificationIconInvokedEventArgs args)
{
@@ -105,7 +114,9 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
private void OnIconSourceChanged(object sender, EventArgs args) => UpdateIcons();
private void OnTaskbarChanged(object sender, EventArgs args) => UpdateWindow();
private void OnPlacementChanged(object sender, EventArgs args) => UpdatePlacement();
private void OnTaskbarChanged(object sender, EventArgs args) => UpdatePlacement();
private void OnThemeChanged(object sender, SystemPersonalisationChangedEventArgs args)
{
@@ -113,7 +124,7 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
UpdateIcons();
}
private void PrepareContextMenu()
private void UpdateContextMenu()
{
if (_contextMenuXamlHost != null)
{
@@ -189,7 +200,7 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
}
}
private void UpdateWindow()
private void UpdatePlacement()
{
if (!IsLoaded) return;
@@ -197,52 +208,75 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
if (flyoutHost == null) return;
var taskbarState = _taskbarHelper.GetCurrentState();
Left = taskbarState.Screen.WorkingArea.Left;
Top = taskbarState.Screen.WorkingArea.Top;
var windowWidth = WindowSize * this.DpiX();
var windowHeight = WindowSize * this.DpiY();
var width = WindowSize * this.DpiX();
var height = WindowSize * this.DpiY();
double top, left, height, width;
double top = 0, left = 0;
var taskbarRect = taskbarState.Rect;
switch (taskbarState.Position)
var taskBarPlacement = taskbarState.Placement;
var presenterPlacement = NotificationFlyoutPresenterPlacement.Bottom;
switch (_flyout.Placement)
{
case TaskbarPosition.Left:
top = taskbarRect.Bottom - windowHeight;
left = taskbarRect.Right;
height = windowHeight;
width = windowWidth;
break;
case NotificationFlyoutPlacement.Auto:
switch (taskBarPlacement)
{
case TaskbarPlacement.Left:
presenterPlacement = NotificationFlyoutPresenterPlacement.Left;
top = taskbarRect.Bottom - height;
left = taskbarRect.Right;
break;
case TaskbarPosition.Top:
top = taskbarRect.Bottom;
left = FlowDirection == FlowDirection.RightToLeft ? taskbarRect.Left : taskbarRect.Right - windowWidth;
height = windowHeight;
width = windowWidth;
break;
case TaskbarPlacement.Top:
presenterPlacement = NotificationFlyoutPresenterPlacement.Top;
top = taskbarRect.Bottom;
left = FlowDirection == FlowDirection.RightToLeft ? taskbarRect.Left : taskbarRect.Right - width;
break;
case TaskbarPosition.Right:
top = taskbarRect.Bottom - windowHeight;
left = taskbarRect.Left - windowWidth;
height = windowHeight;
width = windowWidth;
break;
case TaskbarPlacement.Right:
presenterPlacement = NotificationFlyoutPresenterPlacement.Right;
top = taskbarRect.Bottom - height;
left = taskbarRect.Left - width;
break;
case TaskbarPosition.Bottom:
top = taskbarRect.Top - windowHeight;
left = FlowDirection == FlowDirection.RightToLeft ? taskbarRect.Left : taskbarRect.Right - windowWidth;
height = windowHeight;
width = windowWidth;
break;
case TaskbarPlacement.Bottom:
presenterPlacement = NotificationFlyoutPresenterPlacement.Bottom;
top = taskbarRect.Top - height;
left = FlowDirection == FlowDirection.RightToLeft ? taskbarRect.Left : taskbarRect.Right - width;
break;
default:
throw new ArgumentOutOfRangeException();
default:
throw new ArgumentOutOfRangeException();
}
break;
case NotificationFlyoutPlacement.Right:
presenterPlacement = NotificationFlyoutPresenterPlacement.FullRight;
switch (taskBarPlacement)
{
case TaskbarPlacement.Left:
case TaskbarPlacement.Top:
case TaskbarPlacement.Right:
left = taskbarState.Screen.Bounds.Width - width;
top = taskbarState.Screen.Bounds.Height - height;
break;
case TaskbarPlacement.Bottom:
top = taskbarRect.Top - height;
left = FlowDirection == FlowDirection.RightToLeft ? taskbarRect.Left : taskbarRect.Right - width;
break;
default:
throw new ArgumentOutOfRangeException();
}
break;
}
this.SetWindowPosition(top, left, height, width);
flyoutHost.SetFlyoutPlacement(taskbarState.Position.ToString());
flyoutHost.UpdatePlacement(presenterPlacement);
}
}
}