implemented FullRight placement mode

This commit is contained in:
Daniel Clark
2021-02-27 20:19:58 +00:00
parent 47540457b8
commit efc2f75d85
8 changed files with 226 additions and 74 deletions
@@ -2,17 +2,26 @@
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"
Placement="Auto">
xmlns:controls="using:TheXamlGuy.NotificationFlyout.Uwp.UI.Controls">
<controls:NotificationFlyout.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Click="OnCloseMenuFlyoutItemClick" Text="Close" />
</MenuFlyout>
</controls:NotificationFlyout.ContextFlyout>
<Grid>
<Button
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="Hello World!" />
</Grid>
<StackPanel
Width="400"
Height="500"
Margin="24"
VerticalAlignment="Top">
<ToggleSwitch
Header="Is light dimissed enabled"
IsOn="True"
OffContent="False"
OnContent="True"
Toggled="ToggleSwitch_Toggled" />
<ComboBox Header="Placement" SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem Content="Auto" />
<ComboBoxItem Content="FullRight" />
</ComboBox>
</StackPanel>
</controls:NotificationFlyout>
@@ -1,4 +1,6 @@
using Windows.UI.Xaml;
using TheXamlGuy.NotificationFlyout.Uwp.UI.Controls;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace NotificationFlyoutSample
{
@@ -14,5 +16,32 @@ namespace NotificationFlyoutSample
var app = GetApplication();
app.Exit();
}
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
var toggleSwitch = sender as ToggleSwitch;
if (toggleSwitch.IsOn)
{
this.IsLightDismissEnabled = true;
}
else
{
this.IsLightDismissEnabled = false;
}
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var comboBox = sender as ComboBox;
if (comboBox.SelectedIndex == 0)
{
this.Placement = NotificationFlyoutPlacement.Auto;
}
else
{
this.Placement = NotificationFlyoutPlacement.FullRight;
}
}
}
}
@@ -6,6 +6,10 @@ namespace TheXamlGuy.NotificationFlyout.Shared.UI
{
void Exit();
void OpenFlyout();
void CloseFlyout();
void OpenAsWindow<TUIElement>() where TUIElement : UIElement;
}
}
@@ -32,18 +32,28 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
typeof(NotificationFlyoutPlacement), typeof(NotificationFlyout),
new PropertyMetadata(NotificationFlyoutPlacement.Auto));
public static readonly DependencyProperty TemplateSettingsProperty =
DependencyProperty.Register(nameof(TemplateSettings),
typeof(NotificationFlyoutTemplateSettings), typeof(NotificationFlyout),
new PropertyMetadata(null));
private const double OffsetValue = 1;
private static INotificationFlyoutApplication _applicationInstance;
private Border _backgroundElement;
private UIElement _child;
private Border _container;
private Popup _popup;
public NotificationFlyout() => DefaultStyleKey = typeof(NotificationFlyout);
public NotificationFlyout()
{
DefaultStyleKey = typeof(NotificationFlyout);
TemplateSettings = new NotificationFlyoutTemplateSettings();
}
public event EventHandler<object> Closed;
@@ -77,9 +87,15 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
set => SetValue(PlacementProperty, value);
}
public NotificationFlyoutTemplateSettings TemplateSettings
{
get => (NotificationFlyoutTemplateSettings)GetValue(TemplateSettingsProperty);
set => SetValue(TemplateSettingsProperty, value);
}
public static INotificationFlyoutApplication GetApplication() => _applicationInstance;
public void Hide()
public void Close()
{
if (_popup == null)
{
@@ -89,7 +105,7 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
_popup.IsOpen = false;
}
public void Show()
public void Open()
{
if (_popup == null)
{
@@ -105,17 +121,19 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
internal static void SetApplication(INotificationFlyoutApplication application) => _applicationInstance = application;
internal void SetPlacement(double horizontalOffset, double verticalOffset, NotificationFlyoutTaskbarPlacement flyoutTaskbarPlacement)
internal void SetPlacement(double horizontalOffset, double verticalOffset, double workingAreaHeight, double workingAreaWidth, NotificationFlyoutTaskbarPlacement flyoutTaskbarPlacement)
{
if (_popup == null)
{
PreparePopup();
}
_backgroundElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
VisualStateManager.GoToState(this, "DefaultPlacement", true);
var width = _backgroundElement.DesiredSize.Width;
var height = _backgroundElement.DesiredSize.Height;
_child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var width = _child.DesiredSize.Width;
var height = Placement == NotificationFlyoutPlacement.Auto ? _child.DesiredSize.Height : workingAreaHeight;
var desiredHorizontalOffset = horizontalOffset;
var desiredVerticalOffset = verticalOffset;
@@ -124,8 +142,7 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
switch (Placement)
{
case NotificationFlyoutPlacement.Auto:
visualState = flyoutTaskbarPlacement.ToString();
visualState = $"{flyoutTaskbarPlacement}";
switch (flyoutTaskbarPlacement)
{
case NotificationFlyoutTaskbarPlacement.Left:
@@ -148,16 +165,39 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
break;
case NotificationFlyoutPlacement.FullRight:
visualState = flyoutTaskbarPlacement.ToString();
desiredHorizontalOffset -= width;
desiredVerticalOffset -= height;
visualState = $"{Placement}";
switch (flyoutTaskbarPlacement)
{
case NotificationFlyoutTaskbarPlacement.Left:
desiredHorizontalOffset += workingAreaWidth - width;
desiredVerticalOffset = 0;
break;
case NotificationFlyoutTaskbarPlacement.Top:
desiredHorizontalOffset = workingAreaWidth - width;
break;
case NotificationFlyoutTaskbarPlacement.Right:
desiredHorizontalOffset -= width;
desiredVerticalOffset = 0;
break;
case NotificationFlyoutTaskbarPlacement.Bottom:
desiredHorizontalOffset = workingAreaWidth - width;
desiredVerticalOffset = 0;
break;
}
break;
}
TemplateSettings.SetValue(NotificationFlyoutTemplateSettings.HeightProperty, height);
TemplateSettings.SetValue(NotificationFlyoutTemplateSettings.WidthProperty, width);
TemplateSettings.SetValue(NotificationFlyoutTemplateSettings.NegativeHeightProperty, -height);
TemplateSettings.SetValue(NotificationFlyoutTemplateSettings.NegativeWidthProperty, -width);
_popup.SetValue(Popup.HorizontalOffsetProperty, desiredHorizontalOffset);
_popup.SetValue(Popup.VerticalOffsetProperty, desiredVerticalOffset);
VisualStateManager.GoToState(this, visualState, true);
VisualStateManager.GoToState(this, $"{visualState}Placement", true);
}
internal void ShowContextMenuAt(double x, double y)
@@ -167,14 +207,18 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
ContextFlyout.XamlRoot = XamlRoot;
ContextFlyout.ShowAt(_container);
ContextFlyout.ShowAt(_container, new FlyoutShowOptions { Position = new Point(x, y), ShowMode = FlyoutShowMode.Standard });
ContextFlyout.ShowAt(_container, new FlyoutShowOptions
{
Position = new Point(x, y),
ShowMode = FlyoutShowMode.Standard
});
}
internal void TryHide()
internal void Close(bool shouldRespectIsLightDismissEnbabled)
{
if (IsLightDismissEnabled)
if (!shouldRespectIsLightDismissEnbabled || IsLightDismissEnabled)
{
Hide();
Close();
}
}
@@ -183,7 +227,6 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
protected override void OnApplyTemplate()
{
_container = GetTemplateChild("Container") as Border;
if (_container != null)
{
if (_child != null)
@@ -221,9 +264,10 @@ namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
private void OnIconPropertyChanged() => IconSourceChanged?.Invoke(this, EventArgs.Empty);
private void OnPointerPressed(object sender, PointerRoutedEventArgs args) => InteractedWith?.Invoke(this, EventArgs.Empty);
private void OnPopupClosed(object sender, object args) => Opened?.Invoke(this, args);
private void OnPopupOpened(object sender, object args) => Closed?.Invoke(this, args);
private void OnPopupClosed(object sender, object args) => Closed?.Invoke(this, args);
private void OnPopupOpened(object sender, object args) => Opened?.Invoke(this, args);
private void PreparePopup()
{
@@ -71,34 +71,36 @@
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PlacementStates">
<VisualState x:Name="Bottom">
<VisualState x:Name="DefaultPlacement" />
<VisualState x:Name="BottomPlacement">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="0" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="80" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.Height}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Top">
<VisualState x:Name="TopPlacement">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="0" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="-80" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeHeight}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Left">
<VisualState x:Name="LeftPlacement">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="-80" />
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeWidth}" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Right">
<VisualState x:Name="RightPlacement">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="80" />
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.Width}" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="FullRight">
<VisualState x:Name="FullRightPlacement">
<VisualState.Setters>
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="180" />
<Setter Target="EntranceThemeTransition.FromHorizontalOffset" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.Width}" />
<Setter Target="EntranceThemeTransition.FromVerticalOffset" Value="0" />
<Setter Target="BackgroundElement.Height" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.Height, Mode=TwoWay}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
@@ -1,4 +1,6 @@
namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
using Windows.UI.Xaml.Controls.Primitives;
namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
{
public enum NotificationFlyoutPlacement
{
@@ -0,0 +1,50 @@
using Windows.UI.Xaml;
namespace TheXamlGuy.NotificationFlyout.Uwp.UI.Controls
{
public class NotificationFlyoutTemplateSettings : DependencyObject
{
public static readonly DependencyProperty HeightProperty =
DependencyProperty.Register(nameof(Height),
typeof(double), typeof(NotificationFlyoutTemplateSettings),
new PropertyMetadata(0d));
public static readonly DependencyProperty NegativeHeightProperty =
DependencyProperty.Register(nameof(NegativeHeight),
typeof(double), typeof(NotificationFlyoutTemplateSettings),
new PropertyMetadata(0d));
public static readonly DependencyProperty NegativeWidthProperty =
DependencyProperty.Register(nameof(NegativeWidth),
typeof(double), typeof(NotificationFlyoutTemplateSettings),
new PropertyMetadata(0d));
public static readonly DependencyProperty WidthProperty =
DependencyProperty.Register(nameof(Width),
typeof(double), typeof(NotificationFlyoutTemplateSettings),
new PropertyMetadata(0d));
public double Height
{
get => (double)GetValue(HeightProperty);
set => SetValue(HeightProperty, value);
}
public double NegativeHeight
{
get => (double)GetValue(NegativeHeightProperty);
set => SetValue(NegativeHeightProperty, value);
}
public double NegativeWidth
{
get => (double)GetValue(NegativeWidthProperty);
set => SetValue(NegativeWidthProperty, value);
}
public double Width
{
get => (double)GetValue(WidthProperty);
set => SetValue(WidthProperty, value);
}
}
}
@@ -51,12 +51,29 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
public void Exit() => _notificationFlyoutXamlHost.Close();
public void CloseFlyout(bool shouldRespectIsLightDismissEnbabled)
{
if (Flyout == null) return;
Flyout.Close(shouldRespectIsLightDismissEnbabled);
}
public void CloseFlyout() => CloseFlyout(false);
public void OpenAsWindow<TUIElement>() where TUIElement : Windows.UI.Xaml.UIElement
{
var window = new XamlHost<TUIElement>();
window.Show();
}
public void OpenFlyout()
{
if (Flyout == null) return;
_notificationFlyoutXamlHost.Activate();
Flyout.Open();
UpdateFlyoutPlacement();
}
private static void OnFlyoutPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var sender = dependencyObject as NotificationFlyoutApplication;
@@ -69,25 +86,23 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
{
if (args.PointerButton == PointerButton.Left)
{
ShowFlyout();
OpenFlyout();
}
if (args.PointerButton == PointerButton.Right)
{
var dpiX = _notificationFlyoutXamlHost.DpiX();
var dpiY = _notificationFlyoutXamlHost.DpiY();
PInvoke.GetPhysicalCursorPos(out POINT point);
Flyout.ShowContextMenuAt(point.x / dpiX, point.y / dpiY);
ShowContextMenu();
}
}
private void OnIconSourceChanged(object sender, EventArgs args) => UpdateIcons();
private void OnFlyoutIconSourceChanged(object sender, EventArgs args) => UpdateIcons();
private void OnInteractedWith(object sender, EventArgs args) => _notificationFlyoutXamlHost.Activate();
private void OnFlyoutInteractedWith(object sender, EventArgs args) => _notificationFlyoutXamlHost.Activate();
private void OnNotificationFlyoutXamlHostClosed(object sender, EventArgs args) => _notificationIconHelper?.Dispose();
private void OnNotificationFlyoutXamlHostDeactivated(object sender, EventArgs args) => CloseFlyout(true);
private void OnTaskbarChanged(object sender, EventArgs args) => UpdateFlyoutPlacement();
private void OnThemeChanged(object sender, SystemPersonalisationChangedEventArgs args)
@@ -99,8 +114,8 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
private void PrepareFlyout()
{
if (Flyout == null) return;
Flyout.IconSourceChanged += OnIconSourceChanged;
Flyout.InteractedWith += OnInteractedWith;
Flyout.IconSourceChanged += OnFlyoutIconSourceChanged;
Flyout.InteractedWith += OnFlyoutInteractedWith;
var content = _notificationFlyoutXamlHost.GetHostContent();
if (content != null)
@@ -123,68 +138,65 @@ namespace TheXamlGuy.NotificationFlyout.Wpf.UI.Controls
_notificationFlyoutXamlHost.Show();
}
private void OnNotificationFlyoutXamlHostDeactivated(object sender, EventArgs args)
private void ShowContextMenu()
{
if (Flyout == null) return;
Flyout.Hide();
}
var dpiX = _notificationFlyoutXamlHost.DpiX();
var dpiY = _notificationFlyoutXamlHost.DpiY();
private void ShowFlyout()
{
if (Flyout == null) return;
_notificationFlyoutXamlHost.Activate();
Flyout.Show();
UpdateFlyoutPlacement();
PInvoke.GetPhysicalCursorPos(out POINT point);
Flyout.ShowContextMenuAt(point.x / dpiX, point.y / dpiY);
}
private void UpdateFlyoutPlacement()
{
if (Flyout == null) return;
var taskbarState = _taskbarHelper.GetCurrentState();
_notificationFlyoutXamlHost.Left = 0;
_notificationFlyoutXamlHost.Top = 0;
double left;
double top;
double horizontalOffset;
double verticalOffset;
var dpiX = _notificationFlyoutXamlHost.DpiX();
var dpiY = _notificationFlyoutXamlHost.DpiY();
NotificationFlyoutTaskbarPlacement flyoutTaskBarPlacement;
var taskbarState = _taskbarHelper.GetCurrentState();
var workingAreaHeight = taskbarState.Screen.WorkingArea.Height / dpiX;
var workingAreaWidth = taskbarState.Screen.WorkingArea.Width / dpiY;
switch (taskbarState.Placement)
{
case TaskbarPlacement.Left:
flyoutTaskBarPlacement = NotificationFlyoutTaskbarPlacement.Left;
top = taskbarState.Rect.Bottom / dpiX;
left = taskbarState.Rect.Right / dpiY;
verticalOffset = taskbarState.Rect.Bottom / dpiX;
horizontalOffset = taskbarState.Rect.Right / dpiY;
break;
case TaskbarPlacement.Top:
flyoutTaskBarPlacement = NotificationFlyoutTaskbarPlacement.Top;
top = taskbarState.Rect.Bottom / dpiX;
left = (_notificationFlyoutXamlHost.FlowDirection == FlowDirection.RightToLeft ? taskbarState.Rect.Left : taskbarState.Rect.Right) / dpiY;
verticalOffset = taskbarState.Rect.Bottom / dpiX;
horizontalOffset = (_notificationFlyoutXamlHost.FlowDirection == FlowDirection.RightToLeft ? taskbarState.Rect.Left : taskbarState.Rect.Right) / dpiY;
break;
case TaskbarPlacement.Right:
flyoutTaskBarPlacement = NotificationFlyoutTaskbarPlacement.Right;
top = taskbarState.Rect.Bottom / dpiX;
left = taskbarState.Rect.Left / dpiY;
verticalOffset = taskbarState.Rect.Bottom / dpiX;
horizontalOffset = taskbarState.Rect.Left / dpiY;
break;
case TaskbarPlacement.Bottom:
flyoutTaskBarPlacement = NotificationFlyoutTaskbarPlacement.Bottom;
top = taskbarState.Rect.Top / dpiX;
left = (_notificationFlyoutXamlHost.FlowDirection == FlowDirection.RightToLeft ? taskbarState.Rect.Left : taskbarState.Rect.Right) / dpiY;
verticalOffset = taskbarState.Rect.Top / dpiX;
horizontalOffset = (_notificationFlyoutXamlHost.FlowDirection == FlowDirection.RightToLeft ? taskbarState.Rect.Left : taskbarState.Rect.Right) / dpiY;
break;
default:
throw new ArgumentOutOfRangeException();
}
Flyout.SetPlacement(left, top, flyoutTaskBarPlacement);
Flyout.SetPlacement(horizontalOffset, verticalOffset, workingAreaHeight, workingAreaWidth, flyoutTaskBarPlacement);
}
private async void UpdateIcons()