Files
HandyControl/src/Shared/HandyControl_Shared/Data/GlowWindow/GlowEdge.cs
T
2020-01-18 14:23:47 +08:00

588 lines
24 KiB
C#

using System;
using System.Runtime.InteropServices;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using HandyControl.Controls;
using HandyControl.Tools.Interop;
namespace HandyControl.Data
{
internal class GlowEdge : HwndWrapper
{
private const string GlowEdgeClassName = "HandyControlGlowEdge";
private const int GlowDepth = 9;
private const int CornerGripThickness = 18;
private static ushort _sharedWindowClassAtom;
// ReSharper disable once NotAccessedField.Local
private static InteropValues.WndProc _sharedWndProc;
private readonly GlowBitmap[] _activeGlowBitmaps = new GlowBitmap[16];
private readonly GlowBitmap[] _inactiveGlowBitmaps = new GlowBitmap[16];
private readonly Dock _orientation;
private readonly GlowWindow _targetWindow;
private Color _activeGlowColor = Colors.Transparent;
private int _height;
private Color _inactiveGlowColor = Colors.Transparent;
private FieldInvalidationTypes _invalidatedValues;
private bool _isActive;
private bool _isVisible;
private int _left;
private bool _pendingDelayRender;
private int _top;
private int _width;
internal static long CreatedGlowEdges { get; private set; }
internal static long DisposedGlowEdges { get; private set; }
internal GlowEdge(GlowWindow owner, Dock orientation)
{
_targetWindow = owner ?? throw new ArgumentNullException(nameof(owner));
_orientation = orientation;
CreatedGlowEdges += 1L;
}
private bool IsDeferringChanges => _targetWindow.DeferGlowChangesCount > 0;
private static ushort SharedWindowClassAtom
{
get
{
if (_sharedWindowClassAtom == 0)
{
var wndclass = default(InteropValues.WNDCLASS);
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hbrBackground = IntPtr.Zero;
wndclass.hCursor = IntPtr.Zero;
wndclass.hIcon = IntPtr.Zero;
wndclass.lpfnWndProc = _sharedWndProc = InteropMethods.DefWindowProc;
wndclass.lpszClassName = GlowEdgeClassName;
wndclass.lpszMenuName = null;
wndclass.style = 0u;
_sharedWindowClassAtom = InteropMethods.RegisterClass(ref wndclass);
}
return _sharedWindowClassAtom;
}
}
internal bool IsVisible
{
get => _isVisible;
set => UpdateProperty(ref _isVisible, value, FieldInvalidationTypes.Render | FieldInvalidationTypes.Visibility);
}
internal int Left
{
get => _left;
set => UpdateProperty(ref _left, value, FieldInvalidationTypes.Location);
}
internal int Top
{
get => _top;
set => UpdateProperty(ref _top, value, FieldInvalidationTypes.Location);
}
internal int Width
{
get => _width;
set => UpdateProperty( ref _width, value, FieldInvalidationTypes.Size | FieldInvalidationTypes.Render);
}
internal int Height
{
get => _height;
set => UpdateProperty(ref _height, value, FieldInvalidationTypes.Size | FieldInvalidationTypes.Render);
}
internal bool IsActive
{
get => _isActive;
set => UpdateProperty(ref _isActive, value, FieldInvalidationTypes.Render);
}
internal Color ActiveGlowColor
{
get => _activeGlowColor;
set => UpdateProperty(ref _activeGlowColor, value, FieldInvalidationTypes.ActiveColor | FieldInvalidationTypes.Render);
}
internal Color InactiveGlowColor
{
get => _inactiveGlowColor;
set => UpdateProperty(ref _inactiveGlowColor, value, FieldInvalidationTypes.InactiveColor | FieldInvalidationTypes.Render);
}
private IntPtr TargetWindowHandle => new WindowInteropHelper(_targetWindow).Handle;
protected override bool IsWindowSubclassed => true;
private bool IsPositionValid => (_invalidatedValues & (FieldInvalidationTypes.Location | FieldInvalidationTypes.Size | FieldInvalidationTypes.Visibility)) == FieldInvalidationTypes.None;
private void UpdateProperty<T>(ref T field, T value, FieldInvalidationTypes invalidatedValues) where T : struct
{
if (!field.Equals(value))
{
field = value;
_invalidatedValues |= invalidatedValues;
if (!IsDeferringChanges) CommitChanges();
}
}
protected override ushort CreateWindowClassCore() => SharedWindowClassAtom;
protected override void DestroyWindowClassCore()
{
}
protected override IntPtr CreateWindowCore()
{
return InteropMethods.CreateWindowEx(
524416,
new IntPtr(WindowClassAtom),
string.Empty,
-2046820352,
0,
0,
0,
0,
new WindowInteropHelper(_targetWindow).Owner,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
}
internal void ChangeOwner(IntPtr newOwner) => InteropMethods.SetWindowLongPtr(Handle, InteropValues.GWLP.HWNDPARENT, newOwner);
protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
if (msg <= 70)
{
if (msg == 6) return IntPtr.Zero;
if (msg == 70)
{
var windowpos = (InteropValues.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(InteropValues.WINDOWPOS));
windowpos.flags |= 16u;
Marshal.StructureToPtr(windowpos, lParam, true);
}
}
else
{
if (msg != 126)
{
if (msg == 132) return new IntPtr(WmNcHitTest(lParam));
switch (msg)
{
case 161:
case 163:
case 164:
case 166:
case 167:
case 169:
case 171:
case 173:
{
var targetWindowHandle = TargetWindowHandle;
InteropMethods.SendMessage(targetWindowHandle, 6, new IntPtr(2), IntPtr.Zero);
InteropMethods.SendMessage(targetWindowHandle, msg, wParam, IntPtr.Zero);
return IntPtr.Zero;
}
}
}
else
{
if (IsVisible) RenderLayeredWindow();
}
}
return base.WndProc(hwnd, msg, wParam, lParam);
}
private int WmNcHitTest(IntPtr lParam)
{
var xLParam = InteropMethods.GetXLParam(lParam.ToInt32());
var yLParam = InteropMethods.GetYLParam(lParam.ToInt32());
InteropMethods.GetWindowRect(Handle, out var rect);
switch (_orientation)
{
case Dock.Left:
if (yLParam - CornerGripThickness < rect.Top) return 13;
if (yLParam + CornerGripThickness > rect.Bottom) return 16;
return 10;
case Dock.Top:
if (xLParam - CornerGripThickness < rect.Left) return 13;
if (xLParam + CornerGripThickness > rect.Right) return 14;
return 12;
case Dock.Right:
if (yLParam - CornerGripThickness < rect.Top) return 14;
if (yLParam + CornerGripThickness > rect.Bottom) return 17;
return 11;
default:
if (xLParam - CornerGripThickness < rect.Left) return 16;
if (xLParam + CornerGripThickness > rect.Right) return 17;
return 15;
}
}
internal void CommitChanges()
{
InvalidateCachedBitmaps();
UpdateWindowPosCore();
UpdateLayeredWindowCore();
_invalidatedValues = FieldInvalidationTypes.None;
}
private void InvalidateCachedBitmaps()
{
if (_invalidatedValues.HasFlag(FieldInvalidationTypes.ActiveColor)) ClearCache(_activeGlowBitmaps);
if (_invalidatedValues.HasFlag(FieldInvalidationTypes.InactiveColor)) ClearCache(_inactiveGlowBitmaps);
}
private void UpdateWindowPosCore()
{
if (_invalidatedValues.HasFlag(FieldInvalidationTypes.Location) ||
_invalidatedValues.HasFlag(FieldInvalidationTypes.Size) ||
_invalidatedValues.HasFlag(FieldInvalidationTypes.Visibility))
{
var num = 532;
if (_invalidatedValues.HasFlag(FieldInvalidationTypes.Visibility))
{
if (IsVisible)
num |= 64;
else
num |= 131;
}
if (!_invalidatedValues.HasFlag(FieldInvalidationTypes.Location)) num |= 2;
if (!_invalidatedValues.HasFlag(FieldInvalidationTypes.Size)) num |= 1;
InteropMethods.SetWindowPos(Handle, IntPtr.Zero, Left, Top, Width, Height, num);
}
}
private void UpdateLayeredWindowCore()
{
if (IsVisible && _invalidatedValues.HasFlag(FieldInvalidationTypes.Render))
{
if (IsPositionValid)
{
BeginDelayedRender();
return;
}
CancelDelayedRender();
RenderLayeredWindow();
}
}
private void BeginDelayedRender()
{
if (!_pendingDelayRender)
{
_pendingDelayRender = true;
CompositionTarget.Rendering += CommitDelayedRender;
}
}
private void CancelDelayedRender()
{
if (_pendingDelayRender)
{
_pendingDelayRender = false;
CompositionTarget.Rendering -= CommitDelayedRender;
}
}
private void CommitDelayedRender(object sender, EventArgs e)
{
CancelDelayedRender();
if (IsVisible) RenderLayeredWindow();
}
private void RenderLayeredWindow()
{
using var glowDrawingContext = new GlowDrawingContext(Width, Height);
if (glowDrawingContext.IsInitialized)
{
switch (_orientation)
{
case Dock.Left:
DrawLeft(glowDrawingContext);
break;
case Dock.Top:
DrawTop(glowDrawingContext);
break;
case Dock.Right:
DrawRight(glowDrawingContext);
break;
default:
DrawBottom(glowDrawingContext);
break;
}
var point = new InteropValues.POINT
{
X = Left,
Y = Top
};
var size = new InteropValues.SIZE
{
cx = Width,
cy = Height
};
var point2 = new InteropValues.POINT
{
X = 0,
Y = 0
};
InteropMethods.UpdateLayeredWindow(
Handle,
glowDrawingContext.ScreenDC,
ref point,
ref size,
glowDrawingContext.WindowDC,
ref point2,
0u,
ref glowDrawingContext.Blend,
2u);
}
}
private GlowBitmap GetOrCreateBitmap(GlowDrawingContext drawingContext, GlowBitmapPart bitmapPart)
{
GlowBitmap[] array;
Color color;
if (IsActive)
{
array = _activeGlowBitmaps;
color = ActiveGlowColor;
}
else
{
array = _inactiveGlowBitmaps;
color = InactiveGlowColor;
}
return array[(int) bitmapPart] ?? (array[(int) bitmapPart] = GlowBitmap.Create(drawingContext, bitmapPart, color));
}
private void ClearCache(GlowBitmap[] cache)
{
for (var i = 0; i < cache.Length; i++)
using (cache[i])
{
cache[i] = null;
}
}
protected override void DisposeManagedResources()
{
ClearCache(_activeGlowBitmaps);
ClearCache(_inactiveGlowBitmaps);
}
protected override void DisposeNativeResources()
{
base.DisposeNativeResources();
DisposedGlowEdges += 1L;
}
private void DrawLeft(GlowDrawingContext drawingContext)
{
var orCreateBitmap = GetOrCreateBitmap(drawingContext, GlowBitmapPart.CornerTopLeft);
var orCreateBitmap2 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.LeftTop);
var orCreateBitmap3 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.Left);
var orCreateBitmap4 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.LeftBottom);
var orCreateBitmap5 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.CornerBottomLeft);
var height = orCreateBitmap.Height;
var num = height + orCreateBitmap2.Height;
var num2 = drawingContext.Height - orCreateBitmap5.Height;
var num3 = num2 - orCreateBitmap4.Height;
var num4 = num3 - num;
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, 0, orCreateBitmap.Width, orCreateBitmap.Height,
drawingContext.BackgroundDC, 0, 0, orCreateBitmap.Width, orCreateBitmap.Height,
drawingContext.Blend);
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap2.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, height, orCreateBitmap2.Width,
orCreateBitmap2.Height, drawingContext.BackgroundDC, 0, 0, orCreateBitmap2.Width,
orCreateBitmap2.Height, drawingContext.Blend);
if (num4 > 0)
{
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap3.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, num, orCreateBitmap3.Width, num4,
drawingContext.BackgroundDC, 0, 0, orCreateBitmap3.Width, orCreateBitmap3.Height,
drawingContext.Blend);
}
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap4.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, num3, orCreateBitmap4.Width,
orCreateBitmap4.Height, drawingContext.BackgroundDC, 0, 0, orCreateBitmap4.Width,
orCreateBitmap4.Height, drawingContext.Blend);
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap5.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, num2, orCreateBitmap5.Width,
orCreateBitmap5.Height, drawingContext.BackgroundDC, 0, 0, orCreateBitmap5.Width,
orCreateBitmap5.Height, drawingContext.Blend);
}
private void DrawRight(GlowDrawingContext drawingContext)
{
var orCreateBitmap = GetOrCreateBitmap(drawingContext, GlowBitmapPart.CornerTopRight);
var orCreateBitmap2 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.RightTop);
var orCreateBitmap3 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.Right);
var orCreateBitmap4 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.RightBottom);
var orCreateBitmap5 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.CornerBottomRight);
var height = orCreateBitmap.Height;
var num = height + orCreateBitmap2.Height;
var num2 = drawingContext.Height - orCreateBitmap5.Height;
var num3 = num2 - orCreateBitmap4.Height;
var num4 = num3 - num;
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, 0, orCreateBitmap.Width, orCreateBitmap.Height,
drawingContext.BackgroundDC, 0, 0, orCreateBitmap.Width, orCreateBitmap.Height,
drawingContext.Blend);
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap2.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, height, orCreateBitmap2.Width,
orCreateBitmap2.Height, drawingContext.BackgroundDC, 0, 0, orCreateBitmap2.Width,
orCreateBitmap2.Height, drawingContext.Blend);
if (num4 > 0)
{
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap3.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, num, orCreateBitmap3.Width, num4,
drawingContext.BackgroundDC, 0, 0, orCreateBitmap3.Width, orCreateBitmap3.Height,
drawingContext.Blend);
}
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap4.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, num3, orCreateBitmap4.Width,
orCreateBitmap4.Height, drawingContext.BackgroundDC, 0, 0, orCreateBitmap4.Width,
orCreateBitmap4.Height, drawingContext.Blend);
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap5.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, 0, num2, orCreateBitmap5.Width,
orCreateBitmap5.Height, drawingContext.BackgroundDC, 0, 0, orCreateBitmap5.Width,
orCreateBitmap5.Height, drawingContext.Blend);
}
private void DrawTop(GlowDrawingContext drawingContext)
{
var orCreateBitmap = GetOrCreateBitmap(drawingContext, GlowBitmapPart.TopLeft);
var orCreateBitmap2 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.Top);
var orCreateBitmap3 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.TopRight);
var num2 = GlowDepth + orCreateBitmap.Width;
var num3 = drawingContext.Width - GlowDepth - orCreateBitmap3.Width;
var num4 = num3 - num2;
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, GlowDepth, 0, orCreateBitmap.Width, orCreateBitmap.Height,
drawingContext.BackgroundDC, 0, 0, orCreateBitmap.Width, orCreateBitmap.Height,
drawingContext.Blend);
if (num4 > 0)
{
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap2.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, num2, 0, num4, orCreateBitmap2.Height,
drawingContext.BackgroundDC, 0, 0, orCreateBitmap2.Width, orCreateBitmap2.Height,
drawingContext.Blend);
}
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap3.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, num3, 0, orCreateBitmap3.Width,
orCreateBitmap3.Height, drawingContext.BackgroundDC, 0, 0, orCreateBitmap3.Width,
orCreateBitmap3.Height, drawingContext.Blend);
}
private void DrawBottom(GlowDrawingContext drawingContext)
{
var orCreateBitmap = GetOrCreateBitmap(drawingContext, GlowBitmapPart.BottomLeft);
var orCreateBitmap2 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.Bottom);
var orCreateBitmap3 = GetOrCreateBitmap(drawingContext, GlowBitmapPart.BottomRight);
var num2 = GlowDepth + orCreateBitmap.Width;
var num3 = drawingContext.Width - GlowDepth - orCreateBitmap3.Width;
var num4 = num3 - num2;
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, GlowDepth, 0, orCreateBitmap.Width, orCreateBitmap.Height,
drawingContext.BackgroundDC, 0, 0, orCreateBitmap.Width, orCreateBitmap.Height,
drawingContext.Blend);
if (num4 > 0)
{
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap2.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, num2, 0, num4, orCreateBitmap2.Height,
drawingContext.BackgroundDC, 0, 0, orCreateBitmap2.Width, orCreateBitmap2.Height,
drawingContext.Blend);
}
InteropMethods.SelectObject(drawingContext.BackgroundDC, orCreateBitmap3.Handle);
InteropMethods.AlphaBlend(drawingContext.WindowDC, num3, 0, orCreateBitmap3.Width,
orCreateBitmap3.Height, drawingContext.BackgroundDC, 0, 0, orCreateBitmap3.Width,
orCreateBitmap3.Height, drawingContext.Blend);
}
internal void UpdateWindowPos()
{
var targetWindowHandle = TargetWindowHandle;
InteropMethods.GetWindowRect(targetWindowHandle, out var rect);
InteropMethods.GetWindowPlacement(targetWindowHandle);
if (IsVisible)
switch (_orientation)
{
case Dock.Left:
Left = rect.Left - GlowDepth;
Top = rect.Top - GlowDepth;
Width = GlowDepth;
Height = rect.Height + CornerGripThickness;
return;
case Dock.Top:
Left = rect.Left - GlowDepth;
Top = rect.Top - GlowDepth;
Width = rect.Width + CornerGripThickness;
Height = GlowDepth;
return;
case Dock.Right:
Left = rect.Right;
Top = rect.Top - GlowDepth;
Width = GlowDepth;
Height = rect.Height + CornerGripThickness;
return;
default:
Left = rect.Left - GlowDepth;
Top = rect.Bottom;
Width = rect.Width + CornerGripThickness;
Height = GlowDepth;
break;
}
}
[Flags]
private enum FieldInvalidationTypes
{
None = 0,
Location = 1,
Size = 2,
ActiveColor = 4,
InactiveColor = 8,
Render = 16,
Visibility = 32
}
}
}