2
0
mirror of https://github.com/9ParsonsB/Pulsar.git synced 2025-07-01 08:23:42 -04:00

feat: cosmetic overhaul of native notification popup (#13)

* feat: cosmetic overhaul of native notification popup

* fix: use correct screen default

* feat: move notification test from debug build type to button press

* fix: not debug anymore

* fix: rearrange native notification settings controls

* fix: account for display scaling when positioning notification

* fix: guard against a screen that no longer exists

* fix: safer way to get screens, in case control tree changes
This commit is contained in:
Xjph
2021-08-29 20:27:20 -02:30
committed by GitHub
parent 4cebc3a344
commit 7d2cc417ba
10 changed files with 401 additions and 35 deletions

View File

@ -11,6 +11,8 @@ using Avalonia.VisualTree;
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Controls.ApplicationLifetimes;
namespace Observatory.UI.Views
{
@ -150,7 +152,35 @@ namespace Observatory.UI.Views
#region Notification settings
TextBlock nativeNotifyLabel = new() { Text = "Basic Notification" };
Expander notificationExpander = new()
{
Header = "Basic Notifications",
DataContext = Properties.Core.Default,
Margin = new Thickness(0, 20),
Background = this.Background,
BorderThickness = new Thickness(0)
};
Grid notificationGrid = new();
notificationGrid.ColumnDefinitions = new()
{
new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) },
new ColumnDefinition() { Width = new GridLength(3, GridUnitType.Star) }
};
notificationGrid.RowDefinitions = new()
{
new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) },
new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) },
new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) },
new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) },
new RowDefinition() { Height = new GridLength(0, GridUnitType.Auto) }
};
TextBlock nativeNotifyLabel = new() { Text = "Enabled" };
CheckBox nativeNotifyCheckbox = new() { IsChecked = Properties.Core.Default.NativeNotify, Content = nativeNotifyLabel };
nativeNotifyCheckbox.Checked += (object sender, RoutedEventArgs e) =>
@ -164,10 +194,167 @@ namespace Observatory.UI.Views
Properties.Core.Default.NativeNotify = false;
Properties.Core.Default.Save();
};
Button notifyTestButton = new()
{
Content = "Test",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right
};
corePanel.AddControl(nativeNotifyCheckbox, rowTracker.NextIndex(), 0, 2);
notifyTestButton.Click += (object sender, RoutedEventArgs e) =>
{
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
{
var notifyWindow = new UI.Views.NotificationView() { DataContext = new UI.ViewModels.NotificationViewModel("Test Notification", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras suscipit hendrerit libero ac scelerisque.") };
notifyWindow.Show();
});
};
#endregion
TextBlock notifyFontLabel = new()
{
Text = "Font: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
ComboBox notifyFontDropDown = new()
{
MinWidth = 200
};
notifyFontDropDown.Items = new System.Drawing.Text.InstalledFontCollection().Families.Select(font => font.Name);
if (Properties.Core.Default.NativeNotifyFont.Length > 0)
{
notifyFontDropDown.SelectedItem = Properties.Core.Default.NativeNotifyFont;
}
notifyFontDropDown.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
var comboBox = (ComboBox)sender;
Properties.Core.Default.NativeNotifyFont = comboBox.SelectedItem.ToString();
Properties.Core.Default.Save();
};
TextBlock monitorLabel = new()
{
Text = "Display: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
ComboBox monitorDropDown = new()
{
MinWidth = 200
};
List<string> displays = new();
displays.Add("Primary");
var application = (IClassicDesktopStyleApplicationLifetime)Application.Current.ApplicationLifetime;
var screens = application.MainWindow.Screens.All;
if (screens.Count > 1)
for (int i = 0; i < screens.Count; i++)
{
displays.Add((i + 1).ToString());
}
monitorDropDown.Items = displays;
if (Properties.Core.Default.NativeNotifyScreen == -1)
{
monitorDropDown.SelectedItem = "Primary";
}
else
{
monitorDropDown.SelectedItem = (Properties.Core.Default.NativeNotifyScreen).ToString();
}
monitorDropDown.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
var comboBox = (ComboBox)sender;
string selectedItem = comboBox.SelectedItem.ToString();
int selectedScreen = selectedItem == "Primary" ? -1 : Int32.Parse(selectedItem);
Properties.Core.Default.NativeNotifyScreen = selectedScreen;
Properties.Core.Default.Save();
};
TextBlock cornerLabel = new()
{
Text = "Corner: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
ComboBox cornerDropDown = new()
{
MinWidth = 200
};
List<string> corners = new()
{
"Bottom-Right",
"Bottom-Left",
"Top-Right",
"Top-Left"
};
cornerDropDown.Items = corners;
cornerDropDown.SelectedItem = corners[Properties.Core.Default.NativeNotifyCorner];
cornerDropDown.SelectionChanged += (object sender, SelectionChangedEventArgs e) =>
{
var comboBox = (ComboBox)sender;
Properties.Core.Default.NativeNotifyCorner = comboBox.SelectedIndex;
Properties.Core.Default.Save();
};
TextBlock colourLabel = new()
{
Text = "Colour: ",
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
BrushConverter brushConverter = new();
Egorozh.ColorPicker.Dialog.ColorPickerButton colourPickerButton = new()
{
Width = 25,
Height = 25,
Color = Color.FromUInt32(Properties.Core.Default.NativeNotifyColour),
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Left
};
colourPickerButton.PropertyChanged += (object sender, AvaloniaPropertyChangedEventArgs e) =>
{
if (e.Property.Name == "Color")
{
Properties.Core.Default.NativeNotifyColour = ((Color)e.NewValue).ToUint32();
Properties.Core.Default.Save();
}
};
notificationGrid.AddControl(monitorLabel, 0, 0);
notificationGrid.AddControl(monitorDropDown, 0, 1, 2);
notificationGrid.AddControl(cornerLabel, 1, 0);
notificationGrid.AddControl(cornerDropDown, 1, 1, 2);
notificationGrid.AddControl(notifyFontLabel, 2, 0);
notificationGrid.AddControl(notifyFontDropDown, 2, 1, 2);
notificationGrid.AddControl(colourLabel, 3, 0);
notificationGrid.AddControl(colourPickerButton, 3, 1);
notificationGrid.AddControl(notifyTestButton, 3, 1);
notificationGrid.AddControl(nativeNotifyCheckbox, 4, 0, 2);
notificationExpander.Content = notificationGrid;
corePanel.AddControl(notificationExpander, rowTracker.NextIndex(), 0, 2);
#endregion
#region System Context Priming setting

View File

@ -5,25 +5,40 @@
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="150"
x:Class="Observatory.UI.Views.NotificationView"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
Title="Notification"
Width="400" Height="150"
Topmost="True">
<StackPanel DataContext="{Binding Notification}">
<TextBlock
Padding="10"
FontWeight="Bold"
FontSize="30"
FontFamily="Ebrima"
Text="{Binding Title}">
Title
</TextBlock>
<TextBlock
Padding="20,0"
FontWeight="Normal"
FontSize="20"
FontFamily="Ebrima"
Text="{Binding Detail}">
Detail
</TextBlock>
</StackPanel>
MinWidth="400" MinHeight="150"
MaxWidth="400" MaxHeight="150"
Topmost="True"
SizeToContent="Height"
TransparencyLevelHint="AcrylicBlur"
Background="Transparent"
Focusable="False">
<Panel DataContext="{Binding Notification}">
<Border BorderBrush="{Binding Colour}" BorderThickness="4">
<StackPanel Width="400">
<TextBlock
Name="Title"
Padding="10"
FontWeight="Normal"
FontSize="30"
Foreground="{Binding Colour}"
Text="{Binding Title}">
Title
</TextBlock>
<TextBlock
Name="Detail"
Padding="20,0"
FontWeight="Normal"
FontSize="20"
TextWrapping="Wrap"
Foreground="{Binding Colour}"
Text="{Binding Detail}">
Detail
</TextBlock>
</StackPanel>
</Border>
</Panel>
</Window>

View File

@ -1,6 +1,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using System;
using System.Runtime.InteropServices;
namespace Observatory.UI.Views
{
@ -10,11 +13,53 @@ namespace Observatory.UI.Views
{
InitializeComponent();
SystemDecorations = SystemDecorations.None;
var screenBounds = Screens.Primary.Bounds;
Position = screenBounds.BottomRight - new PixelPoint((int)Width, (int)Height);
MakeClickThrough(); //Platform specific, currently windows only.
int screen = Properties.Core.Default.NativeNotifyScreen;
int corner = Properties.Core.Default.NativeNotifyCorner;
string font = Properties.Core.Default.NativeNotifyFont;
if (font.Length > 0)
{
var titleText = this.Find<TextBlock>("Title");
var detailText = this.Find<TextBlock>("Detail");
var fontFamily = new Avalonia.Media.FontFamily(font);
titleText.FontFamily = fontFamily;
detailText.FontFamily = fontFamily;
}
PixelRect screenBounds;
if (screen == -1 || screen > Screens.All.Count)
screenBounds = Screens.Primary.Bounds;
else
screenBounds = Screens.All[screen - 1].Bounds;
double scale = LayoutHelper.GetLayoutScale(this);
double scaleWidth = Width * scale;
double scaleHeight = Height * scale;
switch (corner)
{
default: case 0:
Position = screenBounds.BottomRight - new PixelPoint((int)scaleWidth + 50, (int)scaleHeight + 50);
break;
case 1:
Position = screenBounds.BottomLeft - new PixelPoint(-50, (int)scaleHeight + 50);
break;
case 2:
Position = screenBounds.TopRight - new PixelPoint((int)scaleWidth + 50, -50);
break;
case 3:
Position = screenBounds.TopLeft + new PixelPoint(50, 50);
break;
}
var timer = new System.Timers.Timer();
timer.Elapsed += CloseNotification;
timer.Interval = 5000;
timer.Interval = 8000;
timer.Start();
#if DEBUG
this.AttachDevTools();
@ -33,5 +78,31 @@ namespace Observatory.UI.Views
{
AvaloniaXamlLoader.Load(this);
}
private void MakeClickThrough()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var style = GetWindowLong(this.PlatformImpl.Handle.Handle, GWL_EXSTYLE);
//PlatformImpl not part of formal Avalonia API and may not be available in future versions.
SetWindowLong(this.PlatformImpl.Handle.Handle, GWL_EXSTYLE, style | WS_EX_LAYERED | WS_EX_TRANSPARENT);
SetLayeredWindowAttributes(this.PlatformImpl.Handle.Handle, 0, 255, LWA_ALPHA);
}
}
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
[DllImport("user32.dll")]
static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
[DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
internal const int GWL_EXSTYLE = -20;
internal const int WS_EX_LAYERED = 0x80000;
internal const int LWA_ALPHA = 0x2;
internal const int WS_EX_TRANSPARENT = 0x00000020;
}
}