Adjust icon sizes to account for requested DPI (#14564)
Refresh icons when window DPI changes Provide ICON_SMALL icons Fixed Win32Icon.Size being incorrect when the exact size couldn't be found in the icon
This commit is contained in:
@@ -47,7 +47,7 @@
|
||||
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
|
||||
"src\\tools\\DevGenerators\\DevGenerators.csproj",
|
||||
"src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj",
|
||||
"src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj",
|
||||
"src\\Windows\\Avalonia.Win32.Interoperability\\Avalonia.Win32.Interoperability.csproj",
|
||||
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
|
||||
"tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
|
||||
"tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
|
||||
|
||||
@@ -1,27 +1,88 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Win32.Interop;
|
||||
|
||||
namespace Avalonia.Win32
|
||||
{
|
||||
class IconImpl : IWindowIconImpl
|
||||
internal class IconImpl : IWindowIconImpl, IDisposable
|
||||
{
|
||||
private readonly Win32Icon _icon;
|
||||
private readonly byte[] _iconData;
|
||||
private readonly Win32Icon _smallIcon;
|
||||
private readonly Win32Icon _bigIcon;
|
||||
|
||||
public IconImpl(Win32Icon icon, byte[] iconData)
|
||||
private static readonly int s_taskbarIconSize = Win32Platform.WindowsVersion < PlatformConstants.Windows10 ? 32 : 24;
|
||||
|
||||
public IconImpl(Stream smallIcon, Stream bigIcon)
|
||||
{
|
||||
_icon = icon;
|
||||
_iconData = iconData;
|
||||
_smallIcon = CreateIconImpl(smallIcon);
|
||||
_bigIcon = CreateIconImpl(bigIcon);
|
||||
}
|
||||
|
||||
public IntPtr HIcon => _icon.Handle;
|
||||
|
||||
public void Save(Stream outputStream)
|
||||
public IconImpl(Stream icon)
|
||||
{
|
||||
outputStream.Write(_iconData, 0, _iconData.Length);
|
||||
_smallIcon = _bigIcon = CreateIconImpl(icon);
|
||||
}
|
||||
|
||||
private static Win32Icon CreateIconImpl(Stream stream)
|
||||
{
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Position = 0;
|
||||
}
|
||||
|
||||
if (stream is MemoryStream memoryStream)
|
||||
{
|
||||
var iconData = memoryStream.ToArray();
|
||||
|
||||
return new Win32Icon(iconData);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
var iconData = ms.ToArray();
|
||||
|
||||
return new Win32Icon(iconData);
|
||||
}
|
||||
}
|
||||
|
||||
// GetSystemMetrics returns values scaled for the primary monitor, as of the time at which the process started.
|
||||
// This is no good for a per-monitor DPI aware application. GetSystemMetricsForDpi would solve the problem,
|
||||
// but is only available in Windows 10 version 1607 and later. So instead, we just hard-code the 96dpi icon sizes.
|
||||
|
||||
public Win32Icon LoadSmallIcon(double scaleFactor) => new(_smallIcon, GetScaledSize(16, scaleFactor));
|
||||
|
||||
public Win32Icon LoadBigIcon(double scaleFactor)
|
||||
{
|
||||
var targetSize = GetScaledSize(s_taskbarIconSize, scaleFactor);
|
||||
var icon = new Win32Icon(_bigIcon, targetSize);
|
||||
|
||||
// The exact size of a taskbar icon in Windows 10 and later is 24px @ 96dpi. But if an ICO file doesn't have
|
||||
// that size, 16px can be selected instead. If this happens, fall back to a 32 pixel icon. Windows will downscale it.
|
||||
if (s_taskbarIconSize == 24 && icon.Size.Width < targetSize.Width)
|
||||
{
|
||||
icon.Dispose();
|
||||
icon = new(_bigIcon, GetScaledSize(32, scaleFactor));
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private static PixelSize GetScaledSize(int baseSize, double factor)
|
||||
{
|
||||
var scaled = (int)Math.Ceiling(baseSize * factor);
|
||||
return new(scaled, scaled);
|
||||
}
|
||||
|
||||
public void Save(Stream outputStream) => _bigIcon.CopyTo(outputStream);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_smallIcon.Dispose();
|
||||
_bigIcon.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using static Avalonia.Win32.Interop.UnmanagedMethods;
|
||||
|
||||
namespace Avalonia.Win32.Interop
|
||||
@@ -7,8 +8,30 @@ namespace Avalonia.Win32.Interop
|
||||
internal class TaskBarList
|
||||
{
|
||||
private static IntPtr s_taskBarList;
|
||||
private static bool s_initialized;
|
||||
private static object s_lock = new();
|
||||
|
||||
private static HrInit? s_hrInitDelegate;
|
||||
private static MarkFullscreenWindow? s_markFullscreenWindowDelegate;
|
||||
private static SetOverlayIcon? s_setOverlayIconDelegate;
|
||||
|
||||
private static unsafe IntPtr Init()
|
||||
{
|
||||
int result = CoCreateInstance(in ShellIds.TaskBarList, IntPtr.Zero, 1, in ShellIds.ITaskBarList2, out IntPtr instance);
|
||||
|
||||
var ptr = (ITaskBarList3VTable**)instance.ToPointer();
|
||||
|
||||
s_hrInitDelegate ??= Marshal.GetDelegateForFunctionPointer<HrInit>((*ptr)->HrInit);
|
||||
|
||||
if (s_hrInitDelegate(instance) != HRESULT.S_OK)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static IntPtr LazyInit() => LazyInitializer.EnsureInitialized(ref s_taskBarList, ref s_initialized, ref s_lock, Init);
|
||||
|
||||
/// <summary>
|
||||
/// Ported from https://github.com/chromium/chromium/blob/master/ui/views/win/fullscreen_handler.cc
|
||||
@@ -17,26 +40,11 @@ namespace Avalonia.Win32.Interop
|
||||
/// <param name="fullscreen">Fullscreen state.</param>
|
||||
public static unsafe void MarkFullscreen(IntPtr hwnd, bool fullscreen)
|
||||
{
|
||||
if (s_taskBarList == IntPtr.Zero)
|
||||
{
|
||||
int result = CoCreateInstance(in ShellIds.TaskBarList, IntPtr.Zero, 1, in ShellIds.ITaskBarList2, out s_taskBarList);
|
||||
|
||||
if (s_taskBarList != IntPtr.Zero)
|
||||
{
|
||||
var ptr = (ITaskBarList2VTable**)s_taskBarList.ToPointer();
|
||||
|
||||
s_hrInitDelegate ??= Marshal.GetDelegateForFunctionPointer<HrInit>((*ptr)->HrInit);
|
||||
|
||||
if (s_hrInitDelegate(s_taskBarList) != HRESULT.S_OK)
|
||||
{
|
||||
s_taskBarList = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
LazyInit();
|
||||
|
||||
if (s_taskBarList != IntPtr.Zero)
|
||||
{
|
||||
var ptr = (ITaskBarList2VTable**)s_taskBarList.ToPointer();
|
||||
var ptr = (ITaskBarList3VTable**)s_taskBarList.ToPointer();
|
||||
|
||||
s_markFullscreenWindowDelegate ??=
|
||||
Marshal.GetDelegateForFunctionPointer<MarkFullscreenWindow>((*ptr)->MarkFullscreenWindow);
|
||||
@@ -44,5 +52,20 @@ namespace Avalonia.Win32.Interop
|
||||
s_markFullscreenWindowDelegate(s_taskBarList, hwnd, fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe void SetOverlayIcon(IntPtr hwnd, IntPtr hIcon, string? description)
|
||||
{
|
||||
LazyInit();
|
||||
|
||||
if (s_taskBarList != IntPtr.Zero)
|
||||
{
|
||||
var ptr = (ITaskBarList3VTable**)s_taskBarList.ToPointer();
|
||||
|
||||
s_setOverlayIconDelegate ??=
|
||||
Marshal.GetDelegateForFunctionPointer<SetOverlayIcon>((*ptr)->SetOverlayIcon);
|
||||
|
||||
s_setOverlayIconDelegate(s_taskBarList, hwnd, hIcon, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1454,6 +1454,9 @@ namespace Avalonia.Win32.Interop
|
||||
[DllImport("shell32", CharSet = CharSet.Auto)]
|
||||
public static extern int Shell_NotifyIcon(NIM dwMessage, NOTIFYICONDATA lpData);
|
||||
|
||||
[DllImport("shell32", CharSet = CharSet.Auto)]
|
||||
public static extern nint SHAppBarMessage(AppBarMessage dwMessage, ref APPBARDATA lpData);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetClassLongPtrW", ExactSpelling = true)]
|
||||
private static extern IntPtr SetClassLong64(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong);
|
||||
|
||||
@@ -1604,6 +1607,10 @@ namespace Avalonia.Win32.Interop
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")]
|
||||
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
|
||||
public static extern bool SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint
|
||||
dwWidth, uint dwHeight, int XSrc, int YSrc, uint uStartScan, uint cScanLines,
|
||||
@@ -1716,14 +1723,14 @@ namespace Avalonia.Win32.Interop
|
||||
|
||||
[DllImport("dwmapi.dll", SetLastError = false)]
|
||||
public static extern int DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind);
|
||||
|
||||
|
||||
[Flags]
|
||||
public enum LayeredWindowFlags
|
||||
{
|
||||
LWA_ALPHA = 0x00000002,
|
||||
LWA_COLORKEY = 0x00000001,
|
||||
}
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, LayeredWindowFlags dwFlags);
|
||||
|
||||
@@ -1820,7 +1827,7 @@ namespace Avalonia.Win32.Interop
|
||||
private static extern int IntMsgWaitForMultipleObjectsEx(int nCount, IntPtr[]? pHandles, int dwMilliseconds,
|
||||
QueueStatusFlags dwWakeMask, MsgWaitForMultipleObjectsFlags dwFlags);
|
||||
|
||||
internal static int MsgWaitForMultipleObjectsEx(int nCount, IntPtr[]? pHandles, int dwMilliseconds,
|
||||
internal static int MsgWaitForMultipleObjectsEx(int nCount, IntPtr[]? pHandles, int dwMilliseconds,
|
||||
QueueStatusFlags dwWakeMask, MsgWaitForMultipleObjectsFlags dwFlags)
|
||||
{
|
||||
int result = IntMsgWaitForMultipleObjectsEx(nCount, pHandles, dwMilliseconds, dwWakeMask, dwFlags);
|
||||
@@ -2388,7 +2395,9 @@ namespace Avalonia.Win32.Interop
|
||||
public enum Icons
|
||||
{
|
||||
ICON_SMALL = 0,
|
||||
ICON_BIG = 1
|
||||
ICON_BIG = 1,
|
||||
/// <summary>The small icon, but with the system theme variant rather than the window's own theme. Requested by other processes, e.g. the taskbar and Task Manager.</summary>
|
||||
ICON_SMALL2 = 2,
|
||||
}
|
||||
|
||||
public static class ShellIds
|
||||
@@ -2411,9 +2420,10 @@ namespace Avalonia.Win32.Interop
|
||||
}
|
||||
|
||||
public delegate void MarkFullscreenWindow(IntPtr This, IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fullscreen);
|
||||
public delegate void SetOverlayIcon(IntPtr This, IntPtr hWnd, IntPtr hIcon, [MarshalAs(UnmanagedType.LPWStr)] string? pszDescription);
|
||||
public delegate HRESULT HrInit(IntPtr This);
|
||||
|
||||
public struct ITaskBarList2VTable
|
||||
public struct ITaskBarList3VTable
|
||||
{
|
||||
public IntPtr IUnknown1;
|
||||
public IntPtr IUnknown2;
|
||||
@@ -2424,6 +2434,36 @@ namespace Avalonia.Win32.Interop
|
||||
public IntPtr ActivateTab;
|
||||
public IntPtr SetActiveAlt;
|
||||
public IntPtr MarkFullscreenWindow;
|
||||
public IntPtr SetProgressValue;
|
||||
public IntPtr SetProgressState;
|
||||
public IntPtr RegisterTab;
|
||||
public IntPtr UnregisterTab;
|
||||
public IntPtr SetTabOrder;
|
||||
public IntPtr SetTabActive;
|
||||
public IntPtr ThumbBarAddButtons;
|
||||
public IntPtr ThumbBarUpdateButtons;
|
||||
public IntPtr ThumbBarSetImageList;
|
||||
public IntPtr SetOverlayIcon;
|
||||
public IntPtr SetThumbnailTooltip;
|
||||
public IntPtr SetThumbnailClip;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct APPBARDATA
|
||||
{
|
||||
private static readonly int s_size = Marshal.SizeOf(typeof(APPBARDATA));
|
||||
|
||||
public int cbSize;
|
||||
public nint hWnd;
|
||||
public uint uCallbackMessage;
|
||||
public uint uEdge;
|
||||
public RECT rc;
|
||||
public int lParam;
|
||||
|
||||
public APPBARDATA()
|
||||
{
|
||||
cbSize = s_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2517,6 +2557,12 @@ namespace Avalonia.Win32.Interop
|
||||
SETVERSION = 0x00000004
|
||||
}
|
||||
|
||||
internal enum AppBarMessage : uint
|
||||
{
|
||||
ABM_GETSTATE = 0x00000004,
|
||||
ABM_GETTASKBARPOS = 0x00000005,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum NIF : uint
|
||||
{
|
||||
|
||||
@@ -25,9 +25,11 @@ internal class Win32Icon : IDisposable
|
||||
Handle = CreateIcon(bmp, hotSpot);
|
||||
}
|
||||
|
||||
public Win32Icon(byte[] iconData)
|
||||
public Win32Icon(byte[] iconData, PixelSize size = default)
|
||||
{
|
||||
Handle = LoadIconFromData(iconData);
|
||||
_bytes = iconData;
|
||||
|
||||
(Handle, Size) = LoadIconFromData(iconData, ReplaceZeroesWithSystemMetrics(size));
|
||||
if (Handle == IntPtr.Zero)
|
||||
{
|
||||
using var bmp = new Bitmap(new MemoryStream(iconData));
|
||||
@@ -35,7 +37,22 @@ internal class Win32Icon : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public Win32Icon(Win32Icon original, PixelSize size = default)
|
||||
{
|
||||
_bytes = original._bytes ?? throw new ArgumentException("Original icon was created from a bitmap and cannot be copied.", nameof(original));
|
||||
|
||||
(Handle, Size) = LoadIconFromData(_bytes, ReplaceZeroesWithSystemMetrics(size));
|
||||
if (Handle == IntPtr.Zero)
|
||||
{
|
||||
using var bmp = new Bitmap(new MemoryStream(_bytes));
|
||||
Handle = CreateIcon(bmp);
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr Handle { get; private set; }
|
||||
public PixelSize Size { get; }
|
||||
|
||||
private readonly byte[]? _bytes;
|
||||
|
||||
IntPtr CreateIcon(Bitmap bitmap, PixelPoint hotSpot = default)
|
||||
{
|
||||
@@ -160,19 +177,15 @@ internal class Win32Icon : IDisposable
|
||||
|
||||
private static int s_bitDepth;
|
||||
|
||||
static unsafe IntPtr LoadIconFromData(byte[] iconData, int width = 0, int height = 0)
|
||||
private static PixelSize ReplaceZeroesWithSystemMetrics(PixelSize pixelSize) => new(
|
||||
pixelSize.Width == 0 ? UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXICON) : pixelSize.Width,
|
||||
pixelSize.Height == 0 ? UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYICON) : pixelSize.Height
|
||||
);
|
||||
|
||||
private static unsafe (IntPtr, PixelSize) LoadIconFromData(byte[] iconData, PixelSize size)
|
||||
{
|
||||
if (iconData.Length < sizeof(ICONDIR))
|
||||
return IntPtr.Zero;
|
||||
|
||||
// Get the correct width and height.
|
||||
if (width == 0)
|
||||
width = UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXICON);
|
||||
|
||||
if (height == 0)
|
||||
height = UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYICON);
|
||||
|
||||
|
||||
return default;
|
||||
|
||||
if (s_bitDepth == 0)
|
||||
{
|
||||
@@ -196,14 +209,14 @@ internal class Win32Icon : IDisposable
|
||||
|
||||
if (dir->idReserved != 0 || dir->idType != 1 || dir->idCount == 0)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
return default;
|
||||
}
|
||||
|
||||
byte bestWidth = 0;
|
||||
byte bestHeight = 0;
|
||||
|
||||
if (sizeof(ICONDIRENTRY) * (dir->idCount - 1) + sizeof(ICONDIR) > iconData.Length)
|
||||
return IntPtr.Zero;
|
||||
return default;
|
||||
|
||||
var entries = new ReadOnlySpan<ICONDIRENTRY>(&dir->idEntries, dir->idCount);
|
||||
var _bestBytesInRes = 0u;
|
||||
@@ -247,8 +260,8 @@ internal class Win32Icon : IDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
int bestDelta = Math.Abs(bestWidth - width) + Math.Abs(bestHeight - height);
|
||||
int thisDelta = Math.Abs(entry.bWidth - width) + Math.Abs(entry.bHeight - height);
|
||||
int bestDelta = Math.Abs(bestWidth - size.Width) + Math.Abs(bestHeight - size.Height);
|
||||
int thisDelta = Math.Abs(entry.bWidth - size.Width) + Math.Abs(entry.bHeight - size.Height);
|
||||
|
||||
if ((thisDelta < bestDelta) ||
|
||||
(thisDelta == bestDelta && (iconBitDepth <= s_bitDepth && iconBitDepth > _bestBitDepth ||
|
||||
@@ -268,14 +281,9 @@ internal class Win32Icon : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
if (_bestImageOffset > int.MaxValue)
|
||||
if (_bestImageOffset > int.MaxValue || _bestBytesInRes > int.MaxValue)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_bestBytesInRes > int.MaxValue)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
return default;
|
||||
}
|
||||
|
||||
uint endOffset;
|
||||
@@ -285,14 +293,16 @@ internal class Win32Icon : IDisposable
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
return default;
|
||||
}
|
||||
|
||||
if (endOffset > iconData.Length)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
return default;
|
||||
}
|
||||
|
||||
var bestSize = new PixelSize(bestWidth, bestHeight);
|
||||
|
||||
// Copy the bytes into an aligned buffer if needed.
|
||||
if ((_bestImageOffset % IntPtr.Size) != 0)
|
||||
{
|
||||
@@ -304,8 +314,8 @@ internal class Win32Icon : IDisposable
|
||||
{
|
||||
fixed (byte* pbAlignedBuffer = alignedBuffer)
|
||||
{
|
||||
return UnmanagedMethods.CreateIconFromResourceEx(pbAlignedBuffer, _bestBytesInRes, 1,
|
||||
0x00030000, 0, 0, 0);
|
||||
return (UnmanagedMethods.CreateIconFromResourceEx(pbAlignedBuffer, _bestBytesInRes, 1,
|
||||
0x00030000, 0, 0, 0), bestSize);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -318,17 +328,22 @@ internal class Win32Icon : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
return UnmanagedMethods.CreateIconFromResourceEx(checked(b + _bestImageOffset), _bestBytesInRes,
|
||||
1, 0x00030000, 0, 0, 0);
|
||||
return (UnmanagedMethods.CreateIconFromResourceEx(checked(b + _bestImageOffset), _bestBytesInRes,
|
||||
1, 0x00030000, 0, 0, 0), bestSize);
|
||||
}
|
||||
catch (OverflowException)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(Stream stream)
|
||||
{
|
||||
stream.Write(_bytes ?? throw new InvalidOperationException("Icon was created from a bitmap, not Win32 icon data"), 0, _bytes.Length);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UnmanagedMethods.DestroyIcon(Handle);
|
||||
|
||||
@@ -129,6 +129,13 @@ namespace Avalonia.Win32
|
||||
|
||||
private PopupImpl(IWindowBaseImpl parent, bool dummy)
|
||||
{
|
||||
_windowProperties = new WindowProperties
|
||||
{
|
||||
ShowInTaskbar = false,
|
||||
IsResizable = false,
|
||||
Decorations = SystemDecorations.None,
|
||||
};
|
||||
|
||||
_parent = parent;
|
||||
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Platform;
|
||||
@@ -8,7 +9,6 @@ using Avalonia.LogicalTree;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Metadata;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Win32.Interop;
|
||||
using static Avalonia.Win32.Interop.UnmanagedMethods;
|
||||
|
||||
@@ -19,8 +19,12 @@ namespace Avalonia.Win32
|
||||
private static readonly Win32Icon s_emptyIcon;
|
||||
private readonly int _uniqueId;
|
||||
private static int s_nextUniqueId;
|
||||
private static nint s_taskBarMonitor;
|
||||
|
||||
private bool _iconAdded;
|
||||
private IconImpl? _icon;
|
||||
private IconImpl? _iconImpl;
|
||||
private bool _iconStale;
|
||||
private Win32Icon? _icon;
|
||||
private string? _tooltipText;
|
||||
private readonly Win32NativeToManagedMenuExporter _exporter;
|
||||
private static readonly Dictionary<int, TrayIconImpl> s_trayIcons = new();
|
||||
@@ -36,6 +40,8 @@ namespace Avalonia.Win32
|
||||
|
||||
public TrayIconImpl()
|
||||
{
|
||||
FindTaskBarMonitor();
|
||||
|
||||
_exporter = new Win32NativeToManagedMenuExporter();
|
||||
|
||||
_uniqueId = ++s_nextUniqueId;
|
||||
@@ -49,28 +55,47 @@ namespace Avalonia.Win32
|
||||
|
||||
internal static void ProcWnd(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
if (msg == (int)CustomWindowsMessage.WM_TRAYMOUSE && s_trayIcons.TryGetValue(wParam.ToInt32(), out var value))
|
||||
switch (msg)
|
||||
{
|
||||
value.WndProc(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
if (msg == WM_TASKBARCREATED)
|
||||
{
|
||||
foreach (var tray in s_trayIcons.Values)
|
||||
{
|
||||
if (tray._iconAdded)
|
||||
case (uint)CustomWindowsMessage.WM_TRAYMOUSE:
|
||||
if (s_trayIcons.TryGetValue(wParam.ToInt32(), out var value))
|
||||
{
|
||||
tray.UpdateIcon(true);
|
||||
tray.UpdateIcon();
|
||||
value.WndProc(hWnd, msg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case (uint)WindowsMessage.WM_DISPLAYCHANGE:
|
||||
FindTaskBarMonitor();
|
||||
foreach (var tray in s_trayIcons.Values)
|
||||
{
|
||||
if (tray._iconAdded)
|
||||
{
|
||||
tray._iconStale = true;
|
||||
tray.UpdateIcon();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (msg == WM_TASKBARCREATED)
|
||||
{
|
||||
FindTaskBarMonitor();
|
||||
foreach (var tray in s_trayIcons.Values)
|
||||
{
|
||||
if (tray._iconAdded)
|
||||
{
|
||||
tray.UpdateIcon(true);
|
||||
tray.UpdateIcon();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetIcon(IWindowIconImpl? icon)
|
||||
{
|
||||
_icon = icon as IconImpl;
|
||||
_iconImpl = (IconImpl?)icon;
|
||||
_iconStale = true;
|
||||
UpdateIcon();
|
||||
}
|
||||
|
||||
@@ -87,8 +112,30 @@ namespace Avalonia.Win32
|
||||
UpdateIcon(!_iconAdded);
|
||||
}
|
||||
|
||||
private static void FindTaskBarMonitor()
|
||||
{
|
||||
var taskBarData = new APPBARDATA();
|
||||
if (SHAppBarMessage(AppBarMessage.ABM_GETTASKBARPOS, ref taskBarData) != 0)
|
||||
{
|
||||
s_taskBarMonitor = MonitorFromPoint(new() { X = taskBarData.rc.left, Y = taskBarData.rc.top }, MONITOR.MONITOR_DEFAULTTOPRIMARY);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIcon(bool remove = false)
|
||||
{
|
||||
Win32Icon? newIcon = null;
|
||||
if (_iconStale && _iconImpl is not null)
|
||||
{
|
||||
var scaling = 1.0;
|
||||
if ((HRESULT)GetDpiForMonitor(s_taskBarMonitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out var dpiY) == HRESULT.S_OK)
|
||||
{
|
||||
Debug.Assert(dpiX == dpiY);
|
||||
scaling = dpiX / 96.0;
|
||||
}
|
||||
|
||||
newIcon = _iconImpl.LoadSmallIcon(scaling);
|
||||
}
|
||||
|
||||
var iconData = new NOTIFYICONDATA
|
||||
{
|
||||
hWnd = Win32Platform.Instance.Handle,
|
||||
@@ -99,7 +146,7 @@ namespace Avalonia.Win32
|
||||
{
|
||||
iconData.uFlags = NIF.TIP | NIF.MESSAGE | NIF.ICON;
|
||||
iconData.uCallbackMessage = (int)CustomWindowsMessage.WM_TRAYMOUSE;
|
||||
iconData.hIcon = _icon?.HIcon ?? s_emptyIcon.Handle;
|
||||
iconData.hIcon = (_iconStale ? newIcon : _icon)?.Handle ?? s_emptyIcon.Handle;
|
||||
iconData.szTip = _tooltipText ?? "";
|
||||
|
||||
if (!_iconAdded)
|
||||
@@ -118,6 +165,13 @@ namespace Avalonia.Win32
|
||||
Shell_NotifyIcon(NIM.DELETE, iconData);
|
||||
_iconAdded = false;
|
||||
}
|
||||
|
||||
if (_iconStale)
|
||||
{
|
||||
_icon?.Dispose();
|
||||
_icon = newIcon;
|
||||
_iconStale = false;
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
|
||||
|
||||
@@ -225,13 +225,13 @@ namespace Avalonia.Win32
|
||||
{
|
||||
using (var stream = File.OpenRead(fileName))
|
||||
{
|
||||
return CreateIconImpl(stream);
|
||||
return new IconImpl(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public IWindowIconImpl LoadIcon(Stream stream)
|
||||
{
|
||||
return CreateIconImpl(stream);
|
||||
return new IconImpl(stream);
|
||||
}
|
||||
|
||||
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
|
||||
@@ -239,38 +239,7 @@ namespace Avalonia.Win32
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
bitmap.Save(memoryStream);
|
||||
|
||||
var iconData = memoryStream.ToArray();
|
||||
|
||||
return new IconImpl(new Win32Icon(iconData), iconData);
|
||||
}
|
||||
}
|
||||
|
||||
private static IconImpl CreateIconImpl(Stream stream)
|
||||
{
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Position = 0;
|
||||
}
|
||||
|
||||
if (stream is MemoryStream memoryStream)
|
||||
{
|
||||
var iconData = memoryStream.ToArray();
|
||||
|
||||
return new IconImpl(new Win32Icon(iconData), iconData);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(ms);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
var iconData = ms.ToArray();
|
||||
|
||||
return new IconImpl(new Win32Icon(iconData), iconData);
|
||||
}
|
||||
return new IconImpl(memoryStream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,9 +129,10 @@ namespace Avalonia.Win32
|
||||
|
||||
case WindowsMessage.WM_DPICHANGED:
|
||||
{
|
||||
var dpi = ToInt32(wParam) & 0xffff;
|
||||
_dpi = (uint)wParam >> 16;
|
||||
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam);
|
||||
_scaling = dpi / 96.0;
|
||||
_scaling = _dpi / 96.0;
|
||||
RefreshIcon();
|
||||
ScalingChanged?.Invoke(_scaling);
|
||||
|
||||
using (SetResizeReason(WindowResizeReason.DpiChange))
|
||||
@@ -149,6 +150,22 @@ namespace Avalonia.Win32
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
case WindowsMessage.WM_GETICON:
|
||||
if (_iconImpl == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var requestIcon = (Icons)wParam;
|
||||
var requestDpi = (uint) lParam;
|
||||
|
||||
if (requestDpi == 0)
|
||||
{
|
||||
requestDpi = _dpi;
|
||||
}
|
||||
|
||||
return LoadIcon(requestIcon, requestDpi)?.Handle ?? default;
|
||||
|
||||
case WindowsMessage.WM_KEYDOWN:
|
||||
case WindowsMessage.WM_SYSKEYDOWN:
|
||||
{
|
||||
|
||||
@@ -5,26 +5,24 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Avalonia.Collections.Pooled;
|
||||
using Avalonia.Controls.Platform;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Platform;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.Input.Raw;
|
||||
using Avalonia.Input.TextInput;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Metadata;
|
||||
using Avalonia.OpenGL.Egl;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Rendering.Composition;
|
||||
using Avalonia.Rendering;
|
||||
using Avalonia.Win32.DirectX;
|
||||
using Avalonia.Win32.Input;
|
||||
using Avalonia.Win32.Interop;
|
||||
using Avalonia.Win32.OpenGl.Angle;
|
||||
using Avalonia.Win32.OpenGl;
|
||||
using Avalonia.Win32.WinRT.Composition;
|
||||
using Avalonia.Win32.OpenGl.Angle;
|
||||
using Avalonia.Win32.WinRT;
|
||||
using Avalonia.Win32.WinRT.Composition;
|
||||
using static Avalonia.Win32.Interop.UnmanagedMethods;
|
||||
using Avalonia.Input.Platform;
|
||||
using System.Diagnostics;
|
||||
using Avalonia.Platform.Storage.FileIO;
|
||||
using Avalonia.Threading;
|
||||
@@ -84,10 +82,13 @@ namespace Avalonia.Win32
|
||||
private string? _className;
|
||||
private IntPtr _hwnd;
|
||||
private IInputRoot? _owner;
|
||||
private WindowProperties _windowProperties;
|
||||
protected WindowProperties _windowProperties;
|
||||
private IconImpl? _iconImpl;
|
||||
private readonly Dictionary<(Icons type, uint dpi), Win32Icon> _iconCache = new();
|
||||
private bool _trackingMouse;//ToDo - there is something missed. Needs investigation @Steven Kirk
|
||||
private bool _topmost;
|
||||
private double _scaling = 1;
|
||||
private uint _dpi = 96;
|
||||
private WindowState _showWindowState;
|
||||
private WindowState _lastWindowState;
|
||||
private OleDropTarget? _dropTarget;
|
||||
@@ -144,7 +145,7 @@ namespace Avalonia.Win32
|
||||
|
||||
CreateWindow();
|
||||
_framebuffer = new FramebufferManager(_hwnd);
|
||||
|
||||
|
||||
if (this is not PopupImpl)
|
||||
{
|
||||
UpdateInputMethod(GetKeyboardLayout(0));
|
||||
@@ -247,7 +248,9 @@ namespace Avalonia.Win32
|
||||
}
|
||||
}
|
||||
|
||||
public Size? FrameSize
|
||||
Size? ITopLevelImpl.FrameSize => FrameSize;
|
||||
|
||||
public Size FrameSize
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -335,7 +338,7 @@ namespace Avalonia.Win32
|
||||
{
|
||||
return _nativeControlHost;
|
||||
}
|
||||
|
||||
|
||||
if (featureType == typeof(IStorageProvider))
|
||||
{
|
||||
return _storageProvider;
|
||||
@@ -602,6 +605,8 @@ namespace Avalonia.Win32
|
||||
DestroyWindow(_hwnd);
|
||||
_hwnd = IntPtr.Zero;
|
||||
}
|
||||
|
||||
ClearIconCache();
|
||||
}
|
||||
|
||||
public void Invalidate(Rect rect)
|
||||
@@ -727,11 +732,53 @@ namespace Avalonia.Win32
|
||||
|
||||
public void SetIcon(IWindowIconImpl? icon)
|
||||
{
|
||||
var impl = icon as IconImpl;
|
||||
_iconImpl = (IconImpl?)icon;
|
||||
ClearIconCache();
|
||||
RefreshIcon();
|
||||
}
|
||||
|
||||
var hIcon = impl?.HIcon ?? IntPtr.Zero;
|
||||
PostMessage(_hwnd, (int)WindowsMessage.WM_SETICON,
|
||||
new IntPtr((int)Icons.ICON_BIG), hIcon);
|
||||
private void ClearIconCache()
|
||||
{
|
||||
foreach (var icon in _iconCache.Values)
|
||||
{
|
||||
icon.Dispose();
|
||||
}
|
||||
_iconCache.Clear();
|
||||
}
|
||||
|
||||
private Win32Icon? LoadIcon(Icons type, uint dpi)
|
||||
{
|
||||
if (_iconImpl == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type == Icons.ICON_SMALL2)
|
||||
{
|
||||
type = Icons.ICON_SMALL;
|
||||
}
|
||||
|
||||
var iconKey = (type, dpi);
|
||||
if (!_iconCache.TryGetValue(iconKey, out var icon))
|
||||
{
|
||||
var scale = dpi / 96.0;
|
||||
_iconCache[iconKey] = icon = type switch
|
||||
{
|
||||
Icons.ICON_SMALL => _iconImpl.LoadSmallIcon(scale),
|
||||
Icons.ICON_BIG => _iconImpl.LoadBigIcon(scale),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private void RefreshIcon()
|
||||
{
|
||||
SendMessage(_hwnd, (int)WindowsMessage.WM_SETICON, (nint)Icons.ICON_SMALL, LoadIcon(Icons.ICON_SMALL, _dpi)?.Handle ?? default);
|
||||
SendMessage(_hwnd, (int)WindowsMessage.WM_SETICON, (nint)Icons.ICON_BIG, LoadIcon(Icons.ICON_BIG, _dpi)?.Handle ?? default);
|
||||
|
||||
TaskBarList.SetOverlayIcon(_hwnd, default, null); // This will prompt the taskbar to redraw the icon
|
||||
}
|
||||
|
||||
public void ShowTaskbarIcon(bool value)
|
||||
@@ -786,15 +833,15 @@ namespace Avalonia.Win32
|
||||
DwmSetWindowAttribute(
|
||||
_hwnd,
|
||||
(int)DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||
&pvUseBackdropBrush,
|
||||
sizeof(int));
|
||||
&pvUseBackdropBrush,
|
||||
sizeof(int));
|
||||
if (TransparencyLevel == WindowTransparencyLevel.Mica)
|
||||
{
|
||||
SetTransparencyMica();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected virtual IntPtr CreateWindowOverride(ushort atom)
|
||||
{
|
||||
return CreateWindowEx(
|
||||
@@ -863,10 +910,10 @@ namespace Avalonia.Win32
|
||||
if (GetDpiForMonitor(
|
||||
monitor,
|
||||
MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI,
|
||||
out var dpix,
|
||||
out _dpi,
|
||||
out _) == 0)
|
||||
{
|
||||
_scaling = dpix / 96.0;
|
||||
_scaling = _dpi / 96.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1511,7 +1558,7 @@ namespace Avalonia.Win32
|
||||
public RECT WindowRect { get; set; }
|
||||
};
|
||||
|
||||
private struct WindowProperties
|
||||
protected struct WindowProperties
|
||||
{
|
||||
public bool ShowInTaskbar;
|
||||
public bool IsResizable;
|
||||
|
||||
Reference in New Issue
Block a user