Skip to content

Commit 97fe914

Browse files
Settings sync with clock flyout
1 parent eb4eeee commit 97fe914

File tree

66 files changed

+422
-129
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+422
-129
lines changed

FluentFlyouts.Flyouts/FluentFlyouts.Flyouts.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
<RootNamespace>FluentFlyouts.Flyouts</RootNamespace>
66
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
77
<UseWinUI>true</UseWinUI>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
89
</PropertyGroup>
910
<ItemGroup>
1011
<PackageReference Include="FluentIcons.WinUI" Version="1.1.271" />
1112
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" />
1213
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.241114003" />
14+
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
1315
<PackageReference Include="WinUIEx" Version="2.5.0" />
1416
</ItemGroup>
1517

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace FluentFlyouts.Core.Interfaces
8+
{
9+
public interface IFlyoutContent : IDisposable
10+
{
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace FluentFlyouts.Flyouts.Interfaces
8+
{
9+
public interface IFlyoutWindow : IDisposable
10+
{
11+
}
12+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq;
5+
using System.Runtime.InteropServices;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using static FluentFlyouts.Flyouts.Helpers.Win32;
9+
10+
namespace FluentFlyouts.Flyouts
11+
{
12+
public partial class OldTrayIcon : IDisposable
13+
{
14+
private IntPtr windowHandle;
15+
private IntPtr notifyIconHandle;
16+
private NotifyIconData notifyIconData;
17+
private WndProcDelegate wndProcDelegate;
18+
19+
public event EventHandler LeftClicked;
20+
public event EventHandler RightClicked;
21+
22+
private uint Id; // Used for callback to recieve clicked messages
23+
24+
public OldTrayIcon(uint Id, string Icon, string ToolTip)
25+
{
26+
this.Id = Id;
27+
wndProcDelegate = new WndProcDelegate(WndProc);
28+
windowHandle = CreateWindow(Icon);
29+
SetWndProc();
30+
notifyIconHandle = LoadIcon(Icon);
31+
notifyIconData = new NotifyIconData
32+
{
33+
cbSize = (uint)Marshal.SizeOf<NotifyIconData>(),
34+
hWnd = windowHandle,
35+
uID = Id,
36+
uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP,
37+
uCallbackMessage = WM_APP + Id,
38+
hIcon = notifyIconHandle,
39+
szTip = ToolTip.PadRight(128, '\0')
40+
};
41+
42+
// Add the icon
43+
Shell_NotifyIcon(NIM_ADD, ref notifyIconData);
44+
}
45+
46+
public void UpdateIcon(string Icon)
47+
{
48+
notifyIconHandle = LoadIcon(Icon);
49+
notifyIconData.hIcon = notifyIconHandle;
50+
51+
Shell_NotifyIcon(NIM_MODIFY, ref notifyIconData);
52+
}
53+
54+
public void UpdateTooltip(string ToolTip)
55+
{
56+
notifyIconData.szTip = ToolTip.PadRight(128, '\0');
57+
58+
Shell_NotifyIcon(NIM_MODIFY, ref notifyIconData);
59+
}
60+
61+
private void SetWndProc()
62+
{
63+
GCHandle.Alloc(wndProcDelegate);
64+
SetWindowLong(windowHandle, 0, Marshal.GetFunctionPointerForDelegate(wndProcDelegate));
65+
}
66+
67+
public void Dispose()
68+
{
69+
Shell_NotifyIcon(NIM_DELETE, ref notifyIconData);
70+
DestroyIcon(notifyIconHandle);
71+
DestroyWindow(windowHandle);
72+
}
73+
}
74+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Reflection;
8+
using System.Runtime.InteropServices;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using static FluentFlyouts.Flyouts.Helpers.Win32;
12+
13+
namespace FluentFlyouts.Flyouts
14+
{
15+
public partial class OldTrayIcon
16+
{
17+
private IntPtr CreateWindow(string Icon)
18+
{
19+
var wndClass = new WndClassEx
20+
{
21+
cbSize = (uint)Marshal.SizeOf<WndClassEx>(),
22+
style = 0,
23+
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(wndProcDelegate),
24+
hInstance = GetModuleHandle(null),
25+
hIcon = LoadIcon(Icon),
26+
hCursor = LoadCursor(),
27+
lpszClassName = "SystemOldTrayIconWindowClass" + Id.ToString(),
28+
};
29+
RegisterClassEx(ref wndClass);
30+
return CreateWindowEx(0, "SystemOldTrayIconWindowClass" + Id.ToString(), Id.ToString(), 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, wndClass.hInstance, IntPtr.Zero);
31+
}
32+
33+
private IntPtr LoadIcon(string Icon)
34+
{
35+
string iconPath = Path.Combine(AppContext.BaseDirectory, Icon);
36+
return LoadImage(IntPtr.Zero, iconPath, 1, 0, 0, 0x00000010 | 0x00000020);
37+
}
38+
39+
private IntPtr LoadCursor() => Helpers.Win32.LoadCursor(IntPtr.Zero, "#32512");
40+
41+
private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam)
42+
{
43+
if (msg == 0x8000 + Id)
44+
{
45+
if (lParam.ToInt32() == WM_LBUTTONDOWN)
46+
{
47+
LeftClicked?.Invoke(this, EventArgs.Empty);
48+
}
49+
else if (lParam.ToInt32() == WM_RBUTTONDOWN)
50+
{
51+
RightClicked?.Invoke(this, EventArgs.Empty);
52+
}
53+
}
54+
55+
return DefWindowProc(hWnd, msg, wParam, lParam);
56+
}
57+
}
58+
}

FluentFlyouts.Flyouts/TrayFlyoutWindow.xaml.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using FluentFlyouts.Core.Interfaces;
12
using FluentFlyouts.Flyouts.Helpers;
3+
using FluentFlyouts.Flyouts.Interfaces;
24
using Microsoft.UI;
35
using Microsoft.UI.Xaml;
46
using Microsoft.UI.Xaml.Controls;
@@ -25,22 +27,24 @@ namespace FluentFlyouts.Flyouts
2527
/// <summary>
2628
/// An empty window that can be used on its own or navigated to within a Frame.
2729
/// </summary>
28-
public sealed partial class TrayFlyoutWindow : WindowEx
30+
public sealed partial class TrayFlyoutWindow : WindowEx, IFlyoutWindow
2931
{
3032
private TrayIcon TrayIcon;
33+
private IFlyoutContent FlyoutContent;
3134

3235
public MenuFlyout? ContextFlyout;
33-
public TrayFlyoutWindow(TrayIcon trayIcon, UIElement flyoutContent, MenuFlyout contextFlyout = null)
36+
public TrayFlyoutWindow(TrayIcon TrayIcon, IFlyoutContent FlyoutContent, MenuFlyout contextFlyout = null)
3437
{
3538
this.InitializeComponent();
3639
this.SetExtendedWindowStyle(ExtendedWindowStyle.Transparent);
3740
this.SetWindowOpacity(0);
3841
this.ExtendsContentIntoTitleBar = true;
3942

40-
this.TrayIcon = trayIcon;
43+
this.FlyoutContent = FlyoutContent;
44+
this.TrayIcon = TrayIcon;
4145
TrayIcon.LeftClicked += (sender, e) => ShowFlyout();
4246

43-
this.Flyout.Content = flyoutContent;
47+
this.Flyout.Content = FlyoutContent as UIElement;
4448

4549
if (contextFlyout is not null)
4650
{
@@ -81,5 +85,12 @@ public void ShowMenuFlyout()
8185
}
8286

8387
private void Flyout_Closed(object sender, object e) => this.Hide();
88+
89+
public void Dispose()
90+
{
91+
FlyoutContent.Dispose();
92+
TrayIcon.Dispose();
93+
this.Close();
94+
}
8495
}
8596
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Runtime.InteropServices;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using TerraFX.Interop.Windows;
8+
using static TerraFX.Interop.Windows.WM;
9+
using static TerraFX.Interop.Windows.Windows;
10+
11+
namespace FluentFlyouts.Flyouts
12+
{
13+
public partial class TrayIcon
14+
{
15+
private static Dictionary<uint, TrayIcon> Icons = new();
16+
private static HashSet<uint> IconId = new();
17+
18+
private static void AddIcon(uint Id, TrayIcon Icon)
19+
{
20+
IconId.Add(0x8000 + Id);
21+
Icons.Add(0x8000 + Id, Icon);
22+
}
23+
24+
private static void RemoveIcon(uint Id)
25+
{
26+
IconId.Remove(0x8000 + Id);
27+
Icons.Remove(0x8000 + Id);
28+
}
29+
30+
[UnmanagedCallersOnly]
31+
public static unsafe LRESULT WndProc(HWND hWnd, uint message, WPARAM wParam, LPARAM lParam)
32+
{
33+
if (IconId.Contains(message))
34+
{
35+
if (lParam == WM_LBUTTONDOWN)
36+
{
37+
TrayIcon Icon;
38+
Icons.TryGetValue(message, out Icon);
39+
Icon?.LeftClicked?.Invoke(null, EventArgs.Empty);
40+
}
41+
else if (lParam == WM_RBUTTONDOWN)
42+
{
43+
TrayIcon Icon;
44+
Icons.TryGetValue(message, out Icon);
45+
Icon?.RightClicked?.Invoke(null, EventArgs.Empty);
46+
}
47+
}
48+
49+
return DefWindowProc(hWnd, message, wParam, lParam);
50+
}
51+
}
52+
}

FluentFlyouts.Flyouts/TrayIcon.cs

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,79 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Diagnostics.CodeAnalysis;
43
using System.Linq;
5-
using System.Runtime.InteropServices;
64
using System.Text;
75
using System.Threading.Tasks;
8-
using static FluentFlyouts.Flyouts.Helpers.Win32;
6+
using TerraFX.Interop.Windows;
7+
using static TerraFX.Interop.Windows.WM;
8+
using static TerraFX.Interop.Windows.Windows;
99

1010
namespace FluentFlyouts.Flyouts
1111
{
12-
public partial class TrayIcon : IDisposable
12+
/*
13+
* TO-DO: Comment how the code works
14+
*/
15+
public unsafe partial class TrayIcon : IDisposable
1316
{
14-
private IntPtr windowHandle;
15-
private IntPtr notifyIconHandle;
16-
private NotifyIconData notifyIconData;
17-
private WndProcDelegate wndProcDelegate;
17+
private HWND windowHandle;
18+
private HICON notifyIconHandle;
19+
private NOTIFYICONDATAW notifyIconData;
1820

1921
public event EventHandler LeftClicked;
2022
public event EventHandler RightClicked;
2123

22-
private uint Id; // Used for callback to recieve clicked messages
24+
public uint Id; // Used for callback to get clicked messages
2325

2426
public TrayIcon(uint Id, string Icon, string ToolTip)
2527
{
26-
this.Id = Id;
27-
wndProcDelegate = new WndProcDelegate(WndProc);
28-
windowHandle = CreateWindow(Icon);
29-
SetWndProc();
30-
notifyIconHandle = LoadIcon(Icon);
31-
notifyIconData = new NotifyIconData
28+
unsafe
3229
{
33-
cbSize = (uint)Marshal.SizeOf<NotifyIconData>(),
34-
hWnd = windowHandle,
35-
uID = Id,
36-
uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP,
37-
uCallbackMessage = WM_APP + Id,
38-
hIcon = notifyIconHandle,
39-
szTip = ToolTip.PadRight(128, '\0')
40-
};
30+
this.Id = Id;
31+
windowHandle = CreateWindow(Icon);
32+
notifyIconHandle = LoadIcon(Icon);
33+
notifyIconData = new NOTIFYICONDATAW
34+
{
35+
cbSize = (uint)sizeof(NOTIFYICONDATAW),
36+
hWnd = windowHandle,
37+
uID = Id,
38+
uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP,
39+
uCallbackMessage = WM_APP + Id,
40+
hIcon = notifyIconHandle,
41+
szTip = GetSzTip(ToolTip)
42+
};
43+
44+
// Add the icon
45+
fixed (NOTIFYICONDATAW* pNotifyIconData = &notifyIconData)
46+
Shell_NotifyIcon(NIM_ADD, pNotifyIconData);
4147

42-
// Add the icon
43-
Shell_NotifyIcon(NIM_ADD, ref notifyIconData);
48+
TrayIcon.AddIcon(Id, this);
49+
}
4450
}
4551

4652
public void UpdateIcon(string Icon)
4753
{
4854
notifyIconHandle = LoadIcon(Icon);
4955
notifyIconData.hIcon = notifyIconHandle;
50-
51-
Shell_NotifyIcon(NIM_MODIFY, ref notifyIconData);
56+
fixed (NOTIFYICONDATAW* pNotifyIconData = &notifyIconData)
57+
Shell_NotifyIcon(NIM_MODIFY, pNotifyIconData);
5258
}
5359

5460
public void UpdateTooltip(string ToolTip)
5561
{
56-
notifyIconData.szTip = ToolTip.PadRight(128, '\0');
57-
58-
Shell_NotifyIcon(NIM_MODIFY, ref notifyIconData);
59-
}
60-
61-
private void SetWndProc()
62-
{
63-
GCHandle.Alloc(wndProcDelegate);
64-
SetWindowLong(windowHandle, 0, Marshal.GetFunctionPointerForDelegate(wndProcDelegate));
62+
notifyIconData.szTip = GetSzTip(ToolTip);
63+
fixed (NOTIFYICONDATAW* pNotifyIconData = &notifyIconData)
64+
Shell_NotifyIcon(NIM_MODIFY, pNotifyIconData);
6565
}
6666

67-
public void Dispose()
67+
~TrayIcon() => Dispose();
68+
public unsafe void Dispose()
6869
{
69-
Shell_NotifyIcon(NIM_DELETE, ref notifyIconData);
70-
DestroyIcon(notifyIconHandle);
71-
DestroyWindow(windowHandle);
70+
fixed (NOTIFYICONDATAW* pNotifyIconData = &notifyIconData)
71+
{
72+
TrayIcon.RemoveIcon(Id);
73+
Shell_NotifyIcon(NIM_DELETE, pNotifyIconData);
74+
DestroyIcon(notifyIconHandle);
75+
DestroyWindow(windowHandle);
76+
}
7277
}
7378
}
7479
}

0 commit comments

Comments
 (0)