add SFMLRenderWindow

This commit is contained in:
ww-rm
2025-09-27 18:20:06 +08:00
parent 66223f952b
commit b74f2811a7
8 changed files with 398 additions and 68 deletions

View File

@@ -64,10 +64,10 @@ namespace SFMLRenderer
hs?.Dispose(); hs?.Dispose();
} }
private nint HwndMessageHook(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled) private IntPtr HwndMessageHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{ {
_renderWindow?.DispatchEvents(); _renderWindow?.DispatchEvents();
return nint.Zero; return IntPtr.Zero;
} }
} }
} }

View File

@@ -0,0 +1,171 @@
using SFML.Graphics;
using SFML.System;
using SFML.Window;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace SFMLRenderer
{
public class SFMLRenderWindow : RenderWindow, ISFMLRenderer
{
private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromMilliseconds(10) };
public SFMLRenderWindow(VideoMode mode, string title, Styles style) : base(mode, title, style)
{
SetActive(false);
_timer.Tick += (s, e) => DispatchEvents();
_timer.Start();
RendererCreated?.Invoke(this, EventArgs.Empty);
}
public event EventHandler? RendererCreated;
public event EventHandler? RendererDisposing
{
add => throw new NotImplementedException();
remove => throw new NotImplementedException();
}
public event EventHandler<MouseMoveEventArgs>? CanvasMouseMove
{
add { MouseMoved += value; }
remove { MouseMoved -= value; }
}
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonPressed
{
add { MouseButtonPressed += value; }
remove { MouseButtonPressed -= value; }
}
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonReleased
{
add { MouseButtonReleased += value; }
remove { MouseButtonReleased -= value; }
}
public event EventHandler<MouseWheelScrollEventArgs>? CanvasMouseWheelScrolled
{
add { MouseWheelScrolled += value; }
remove { MouseWheelScrolled -= value; }
}
public Vector2u Resolution
{
get => Size;
set => Size = value;
}
public Vector2f Center
{
get
{
using var view = GetView();
return view.Center;
}
set
{
using var view = GetView();
view.Center = value;
SetView(view);
}
}
public float Zoom
{
get
{
using var view = GetView();
return Math.Abs(Size.X / view.Size.X); // XXX: 仅使用宽度进行缩放计算
}
set
{
value = Math.Abs(value);
if (value <= 0) return;
using var view = GetView();
var signX = Math.Sign(view.Size.X);
var signY = Math.Sign(view.Size.Y);
var resolution = Size;
view.Size = new(resolution.X / value * signX, resolution.Y / value * signY);
SetView(view);
}
}
public float Rotation
{
get
{
using var view = GetView();
return view.Rotation;
}
set
{
using var view = GetView();
view.Rotation = value;
SetView(view);
}
}
public bool FlipX
{
get
{
using var view = GetView();
return view.Size.X < 0;
}
set
{
using var view = GetView();
var size = view.Size;
if (size.X > 0 && value || size.X < 0 && !value)
size.X *= -1;
view.Size = size;
SetView(view);
}
}
public bool FlipY
{
get
{
using var view = GetView();
return view.Size.Y < 0;
}
set
{
using var view = GetView();
var size = view.Size;
if (size.Y > 0 && value || size.Y < 0 && !value)
size.Y *= -1;
view.Size = size;
SetView(view);
}
}
public uint MaxFps
{
get => _maxFps;
set
{
SetFramerateLimit(value);
_maxFps = value;
}
}
private uint _maxFps = 0;
public bool VerticalSync
{
get => _verticalSync;
set
{
SetVerticalSyncEnabled(value);
_verticalSync = value;
}
}
private bool _verticalSync = false;
}
}

View File

@@ -15,15 +15,15 @@ namespace SpineViewer.Natives
public static class Gdi32 public static class Gdi32
{ {
[DllImport("gdi32.dll", SetLastError = true)] [DllImport("gdi32.dll", SetLastError = true)]
public static extern nint CreateCompatibleDC(nint hdc); public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)] [DllImport("gdi32.dll", SetLastError = true)]
public static extern bool DeleteDC(nint hdc); public static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", SetLastError = true)] [DllImport("gdi32.dll", SetLastError = true)]
public static extern nint SelectObject(nint hdc, nint hgdiobj); public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll", SetLastError = true)] [DllImport("gdi32.dll", SetLastError = true)]
public static extern bool DeleteObject(nint hObject); public static extern bool DeleteObject(IntPtr hObject);
} }
} }

View File

@@ -15,22 +15,61 @@ namespace SpineViewer.Natives
public static class User32 public static class User32
{ {
public const int GWL_STYLE = -16; public const int GWL_STYLE = -16;
public const int WS_SIZEBOX = 0x40000; public const int WS_OVERLAPPED = 0x00000000;
public const int WS_BORDER = 0x800000;
public const int WS_VISIBLE = 0x10000000;
public const int WS_CHILD = 0x40000000;
public const int WS_POPUP = unchecked((int)0x80000000); public const int WS_POPUP = unchecked((int)0x80000000);
public const int WS_CHILD = 0x40000000;
public const int WS_MINIMIZE = 0x20000000;
public const int WS_VISIBLE = 0x10000000;
public const int WS_DISABLED = 0x08000000;
public const int WS_CLIPSIBLINGS = 0x04000000;
public const int WS_CLIPCHILDREN = 0x02000000;
public const int WS_MAXIMIZE = 0x01000000;
public const int WS_BORDER = 0x00800000;
public const int WS_DLGFRAME = 0x00400000;
public const int WS_VSCROLL = 0x00200000;
public const int WS_HSCROLL = 0x00100000;
public const int WS_SYSMENU = 0x00080000;
public const int WS_THICKFRAME = 0x00040000;
public const int WS_GROUP = 0x00020000;
public const int WS_TABSTOP = 0x00010000;
public const int WS_MINIMIZEBOX = 0x00020000;
public const int WS_MAXIMIZEBOX = 0x00010000;
public const int WS_CHILDWINDOW = WS_CHILD;
public const int WS_CAPTION = WS_BORDER | WS_DLGFRAME;
public const int WS_TILED = WS_OVERLAPPED;
public const int WS_ICONIC = WS_MINIMIZE;
public const int WS_SIZEBOX = WS_THICKFRAME;
public const int WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW;
public const int WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
public const int WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU;
public const int GWL_EXSTYLE = -20; public const int GWL_EXSTYLE = -20;
public const int WS_EX_TOPMOST = 0x8; public const int WS_EX_DLGMODALFRAME = 0x00000001;
public const int WS_EX_TRANSPARENT = 0x20; public const int WS_EX_NOPARENTNOTIFY = 0x00000004;
public const int WS_EX_TOOLWINDOW = 0x80; public const int WS_EX_TOPMOST = 0x00000008;
public const int WS_EX_WINDOWEDGE = 0x100; public const int WS_EX_ACCEPTFILES = 0x00000010;
public const int WS_EX_CLIENTEDGE = 0x200; public const int WS_EX_TRANSPARENT = 0x00000020;
public const int WS_EX_APPWINDOW = 0x40000; public const int WS_EX_MDICHILD = 0x00000040;
public const int WS_EX_LAYERED = 0x80000; public const int WS_EX_TOOLWINDOW = 0x00000080;
public const int WS_EX_WINDOWEDGE = 0x00000100;
public const int WS_EX_CLIENTEDGE = 0x00000200;
public const int WS_EX_CONTEXTHELP = 0x00000400;
public const int WS_EX_RIGHT = 0x00001000;
public const int WS_EX_LEFT = 0x00000000;
public const int WS_EX_RTLREADING = 0x00002000;
public const int WS_EX_LTRREADING = 0x00000000;
public const int WS_EX_LEFTSCROLLBAR = 0x00004000;
public const int WS_EX_RIGHTSCROLLBAR = 0x00000000;
public const int WS_EX_CONTROLPARENT = 0x00010000;
public const int WS_EX_STATICEDGE = 0x00020000;
public const int WS_EX_APPWINDOW = 0x00040000;
public const int WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE; public const int WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE;
public const int WS_EX_NOACTIVATE = 0x8000000; public const int WS_EX_PALETTEWINDOW = WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
public const int WS_EX_LAYERED = 0x00080000;
public const int WS_EX_NOINHERITLAYOUT = 0x00100000;
public const int WS_EX_LAYOUTRTL = 0x00400000;
public const int WS_EX_COMPOSITED = 0x02000000;
public const int WS_EX_NOACTIVATE = 0x08000000;
public const uint LWA_COLORKEY = 0x1; public const uint LWA_COLORKEY = 0x1;
public const uint LWA_ALPHA = 0x2; public const uint LWA_ALPHA = 0x2;
@@ -42,12 +81,23 @@ namespace SpineViewer.Natives
public const int ULW_ALPHA = 0x00000002; public const int ULW_ALPHA = 0x00000002;
public const int ULW_OPAQUE = 0x00000004; public const int ULW_OPAQUE = 0x00000004;
public const nint HWND_TOPMOST = -1; public const IntPtr HWND_TOPMOST = -1;
public const uint SWP_NOSIZE = 0x0001; public const uint SWP_ASYNCWINDOWPOS = 0x4000;
public const uint SWP_NOMOVE = 0x0002; public const uint SWP_DEFERERASE = 0x2000;
public const uint SWP_NOZORDER = 0x0004; public const uint SWP_NOSENDCHANGING = 0x0400;
public const uint SWP_NOOWNERZORDER = 0x0200;
public const uint SWP_NOREPOSITION = 0x0200;
public const uint SWP_NOCOPYBITS = 0x0100;
public const uint SWP_HIDEWINDOW = 0x0080;
public const uint SWP_SHOWWINDOW = 0x0040;
public const uint SWP_DRAWFRAME = 0x0020;
public const uint SWP_FRAMECHANGED = 0x0020; public const uint SWP_FRAMECHANGED = 0x0020;
public const uint SWP_NOACTIVATE = 0x0010;
public const uint SWP_NOREDRAW = 0x0008;
public const uint SWP_NOZORDER = 0x0004;
public const uint SWP_NOMOVE = 0x0002;
public const uint SWP_NOSIZE = 0x0001;
public const int WM_SPAWN_WORKER = 0x052C; // 一个未公开的神秘消息 public const int WM_SPAWN_WORKER = 0x052C; // 一个未公开的神秘消息
@@ -72,6 +122,8 @@ namespace SpineViewer.Natives
public const int SW_RESTORE = 9; public const int SW_RESTORE = 9;
public const int SW_SHOWDEFAULT = 10; public const int SW_SHOWDEFAULT = 10;
public const uint MONITOR_DEFAULTTONULL = 0;
public const uint MONITOR_DEFAULTTOPRIMARY = 1;
public const uint MONITOR_DEFAULTTONEAREST = 2; public const uint MONITOR_DEFAULTTONEAREST = 2;
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@@ -124,29 +176,25 @@ namespace SpineViewer.Natives
public string szDevice; public string szDevice;
} }
[DllImport("user32.dll", SetLastError = true)] public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
public static extern nint GetDC(nint hWnd);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern int ReleaseDC(nint hWnd, nint hDC); public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern int SetWindowLong(nint hWnd, int nIndex, int dwNewLong); public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern int GetWindowLong(nint hWnd, int nIndex); public static extern bool GetLayeredWindowAttributes(IntPtr hWnd, ref uint crKey, ref byte bAlpha, ref uint dwFlags);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern bool GetLayeredWindowAttributes(nint hWnd, ref uint crKey, ref byte bAlpha, ref uint dwFlags); public static extern bool SetLayeredWindowAttributes(IntPtr hWnd, uint pcrKey, byte pbAlpha, uint pdwFlags);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern bool SetLayeredWindowAttributes(nint hWnd, uint pcrKey, byte pbAlpha, uint pdwFlags); public static extern bool UpdateLayeredWindow(IntPtr hWnd, IntPtr hdcDst, IntPtr pptDst, ref SIZE psize, IntPtr hdcSrc, ref POINT pptSrc, int crKey, ref BLENDFUNCTION pblend, int dwFlags);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern bool UpdateLayeredWindow(nint hWnd, nint hdcDst, nint pptDst, ref SIZE psize, nint hdcSrc, ref POINT pptSrc, int crKey, ref BLENDFUNCTION pblend, int dwFlags); public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(nint hWnd, nint hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern uint GetDoubleClickTime(); public static extern uint GetDoubleClickTime();
@@ -155,28 +203,34 @@ namespace SpineViewer.Natives
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern nint FindWindow(string lpClassName, string lpWindowName); public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern nint SendMessageTimeout(nint hWnd, uint Msg, nint wParam, nint lParam, uint fuFlags, uint uTimeout, out nint lpdwResult); public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern nint FindWindowEx(nint parentHandle, nint childAfter, string className, string windowTitle); public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern nint SetParent(nint hWndChild, nint hWndNewParent); public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern nint GetParent(nint hWnd); public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern nint GetAncestor(nint hWnd, uint gaFlags); public static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern nint GetWindow(nint hWnd, uint uCmd); public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern bool ShowWindow(nint hWnd, int nCmdShow); public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")] [DllImport("user32.dll")]
public static extern bool IsIconic(IntPtr hWnd); public static extern bool IsIconic(IntPtr hWnd);
@@ -190,6 +244,12 @@ namespace SpineViewer.Natives
[DllImport("user32.dll")] [DllImport("user32.dll")]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi); private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
[DllImport("user32.dll")]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
public static TimeSpan GetLastInputElapsedTime() public static TimeSpan GetLastInputElapsedTime()
{ {
LASTINPUTINFO lastInputInfo = new(); LASTINPUTINFO lastInputInfo = new();
@@ -205,14 +265,59 @@ namespace SpineViewer.Natives
return TimeSpan.FromMilliseconds(idleTimeMillis); return TimeSpan.FromMilliseconds(idleTimeMillis);
} }
public static nint GetWorkerW() public static IntPtr GetWorkerW()
{ {
// NOTE: Codes borrowed from @rocksdanister/lively
var progman = FindWindow("Progman", null); var progman = FindWindow("Progman", null);
if (progman == nint.Zero) if (progman == IntPtr.Zero)
return nint.Zero; return IntPtr.Zero;
nint hWnd = FindWindowEx(progman, 0, "WorkerW", null);
Debug.WriteLine($"HWND(Progman.WorkerW): {hWnd:x8}"); // Send 0x052C to Progman. This message directs Progman to spawn a
return hWnd; // WorkerW behind the desktop icons. If it is already there, nothing
// happens.
SendMessageTimeout(progman, WM_SPAWN_WORKER, 0, 0, SMTO_NORMAL, 1000, out _);
// Spy++ output
// .....
// 0x00010190 "" WorkerW
// ...
// 0x000100EE "" SHELLDLL_DefView
// 0x000100F0 "FolderView" SysListView32
// 0x00100B8A "" WorkerW <-- This is the WorkerW instance we are after!
// 0x000100EC "Program Manager" Progman
var workerw = IntPtr.Zero;
// We enumerate all Windows, until we find one, that has the SHELLDLL_DefView
// as a child.
// If we found that window, we take its next sibling and assign it to workerw.
EnumWindows(new EnumWindowsProc((tophandle, topparamhandle) =>
{
IntPtr p = FindWindowEx(tophandle, IntPtr.Zero, "SHELLDLL_DefView", null);
if (p != IntPtr.Zero)
{
// Gets the WorkerW Window after the current one.
workerw = FindWindowEx(IntPtr.Zero, tophandle, "WorkerW", null);
}
return true;
}), IntPtr.Zero);
// Some Windows 11 builds have a different Progman window layout.
// If the above code failed to find WorkerW, we should try this.
// Spy++ output
// 0x000100EC "Program Manager" Progman
// 0x000100EE "" SHELLDLL_DefView
// 0x000100F0 "FolderView" SysListView32
// 0x00100B8A "" WorkerW <-- This is the WorkerW instance we are after!
if (workerw == IntPtr.Zero)
{
workerw = FindWindowEx(progman, IntPtr.Zero, "WorkerW", null);
}
Debug.WriteLine($"HWND(WorkerW): {workerw:x8}");
return workerw;
} }
public static bool GetScreenResolution(IntPtr hwnd, out uint width, out uint height) public static bool GetScreenResolution(IntPtr hwnd, out uint width, out uint height)
@@ -231,5 +336,22 @@ namespace SpineViewer.Natives
width = height = 0; width = height = 0;
return false; return false;
} }
public static bool GetPrimaryScreenResolution(out uint width, out uint height)
{
IntPtr hMon = MonitorFromWindow(IntPtr.Zero, MONITOR_DEFAULTTOPRIMARY);
var mi = new MONITORINFOEX { cbSize = (uint)Marshal.SizeOf<MONITORINFOEX>() };
if (GetMonitorInfo(hMon, ref mi))
{
int widthPx = mi.rcMonitor.Right - mi.rcMonitor.Left;
int heightPx = mi.rcMonitor.Bottom - mi.rcMonitor.Top;
width = (uint)widthPx;
height = (uint)heightPx;
return true;
}
width = height = 0;
return false;
}
} }
} }

View File

@@ -17,9 +17,10 @@ namespace SpineViewer.ViewModels.MainWindow
{ {
private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
public MainWindowViewModel(ISFMLRenderer sfmlRenderer) public MainWindowViewModel(ISFMLRenderer sfmlRenderer, ISFMLRenderer wallpaperRenderer)
{ {
_sfmlRenderer = sfmlRenderer; _sfmlRenderer = sfmlRenderer;
_wallpaperRenderer = wallpaperRenderer;
_explorerListViewModel = new(this); _explorerListViewModel = new(this);
_spineObjectListViewModel = new(this); _spineObjectListViewModel = new(this);
_sfmlRendererViewModel = new(this); _sfmlRendererViewModel = new(this);
@@ -34,6 +35,9 @@ namespace SpineViewer.ViewModels.MainWindow
public ISFMLRenderer SFMLRenderer => _sfmlRenderer; public ISFMLRenderer SFMLRenderer => _sfmlRenderer;
private readonly ISFMLRenderer _sfmlRenderer; private readonly ISFMLRenderer _sfmlRenderer;
public ISFMLRenderer WallpaperRenderer => _wallpaperRenderer;
private readonly ISFMLRenderer _wallpaperRenderer;
public TaskbarItemProgressState ProgressState { get => _progressState; set => SetProperty(ref _progressState, value); } public TaskbarItemProgressState ProgressState { get => _progressState; set => SetProperty(ref _progressState, value); }
private TaskbarItemProgressState _progressState = TaskbarItemProgressState.None; private TaskbarItemProgressState _progressState = TaskbarItemProgressState.None;
@@ -133,19 +137,5 @@ namespace SpineViewer.ViewModels.MainWindow
_spineObjectListViewModel.LoadedSpineObjects = value.LoadedSpineObjects; _spineObjectListViewModel.LoadedSpineObjects = value.LoadedSpineObjects;
} }
} }
/// <summary>
/// 调试命令
/// </summary>
public RelayCommand Cmd_Debug => _cmd_Debug ??= new(Debug_Execute);
private RelayCommand? _cmd_Debug;
private void Debug_Execute()
{
#if DEBUG
MessagePopupService.Quest("测试一下");
#endif
}
} }
} }

View File

@@ -35,6 +35,7 @@ namespace SpineViewer.ViewModels.MainWindow
private readonly MainWindowViewModel _vmMain; private readonly MainWindowViewModel _vmMain;
private readonly ObservableCollectionWithLock<SpineObjectModel> _models; private readonly ObservableCollectionWithLock<SpineObjectModel> _models;
private readonly ISFMLRenderer _renderer; private readonly ISFMLRenderer _renderer;
private readonly ISFMLRenderer _wallpaperRenderer;
/// <summary> /// <summary>
/// 被选中对象的背景颜色 /// 被选中对象的背景颜色
@@ -90,6 +91,7 @@ namespace SpineViewer.ViewModels.MainWindow
_vmMain = vmMain; _vmMain = vmMain;
_models = _vmMain.SpineObjects; _models = _vmMain.SpineObjects;
_renderer = _vmMain.SFMLRenderer; _renderer = _vmMain.SFMLRenderer;
_wallpaperRenderer = _vmMain.WallpaperRenderer;
} }
/// <summary> /// <summary>
@@ -443,6 +445,7 @@ namespace SpineViewer.ViewModels.MainWindow
{ {
try try
{ {
_wallpaperRenderer.SetActive(true);
_renderer.SetActive(true); _renderer.SetActive(true);
float delta; float delta;
@@ -461,7 +464,11 @@ namespace SpineViewer.ViewModels.MainWindow
_forwardDelta = 0; _forwardDelta = 0;
} }
using var v = _renderer.GetView();
_wallpaperRenderer.SetView(v);
_renderer.Clear(_backgroundColor); _renderer.Clear(_backgroundColor);
_wallpaperRenderer.Clear(_backgroundColor);
// 渲染背景 // 渲染背景
lock (_bgLock) lock (_bgLock)
@@ -492,6 +499,7 @@ namespace SpineViewer.ViewModels.MainWindow
bg.Position = view.Center; bg.Position = view.Center;
bg.Rotation = view.Rotation; bg.Rotation = view.Rotation;
_renderer.Draw(bg); _renderer.Draw(bg);
_wallpaperRenderer.Draw(bg);
} }
} }
@@ -531,10 +539,12 @@ namespace SpineViewer.ViewModels.MainWindow
sp.EnableDebug = true; sp.EnableDebug = true;
_renderer.Draw(sp); _renderer.Draw(sp);
sp.EnableDebug = false; sp.EnableDebug = false;
_wallpaperRenderer.Draw(sp);
} }
} }
_renderer.Display(); _renderer.Display();
_wallpaperRenderer.Display();
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -546,6 +556,7 @@ namespace SpineViewer.ViewModels.MainWindow
finally finally
{ {
_renderer.SetActive(false); _renderer.SetActive(false);
_wallpaperRenderer.SetActive(false);
} }
} }

View File

@@ -69,7 +69,7 @@
<MenuItem Header="{DynamicResource Str_Diagnostics}" Command="{Binding Cmd_ShowDiagnosticsDialog}"/> <MenuItem Header="{DynamicResource Str_Diagnostics}" Command="{Binding Cmd_ShowDiagnosticsDialog}"/>
<Separator/> <Separator/>
<MenuItem Header="{DynamicResource Str_Abount}" Command="{Binding Cmd_ShowAboutDialog}"/> <MenuItem Header="{DynamicResource Str_Abount}" Command="{Binding Cmd_ShowAboutDialog}"/>
<MenuItem Header="{DynamicResource Str_Debug}" Command="{Binding Cmd_Debug}"/> <MenuItem Header="{DynamicResource Str_Debug}" Click="DebugMenuItem_Click"/>
</MenuItem> </MenuItem>
<!--<MenuItem Header="{DynamicResource Str_Experiment}"/>--> <!--<MenuItem Header="{DynamicResource Str_Experiment}"/>-->
</Menu> </Menu>

View File

@@ -1,7 +1,7 @@
using Microsoft.Win32; using NLog;
using NLog;
using NLog.Layouts; using NLog.Layouts;
using NLog.Targets; using NLog.Targets;
using SFMLRenderer;
using Spine; using Spine;
using SpineViewer.Models; using SpineViewer.Models;
using SpineViewer.Natives; using SpineViewer.Natives;
@@ -35,17 +35,24 @@ public partial class MainWindow : Window
public static readonly string LastStateFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "laststate.json"); public static readonly string LastStateFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "laststate.json");
private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
private ListViewItem? _listViewDragSourceItem = null; private ListViewItem? _listViewDragSourceItem = null;
private Point _listViewDragSourcePoint; private Point _listViewDragSourcePoint;
private readonly SFMLRenderWindow _wallpaperRenderWindow;
private readonly MainWindowViewModel _vm; private readonly MainWindowViewModel _vm;
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
InitializeLogConfiguration(); InitializeLogConfiguration();
DataContext = _vm = new(_renderPanel);
_notifyIcon.Text = _vm.Title; // XXX: hc 的 NotifyIcon 的 Text 似乎没法双向绑定 _wallpaperRenderWindow = new(new(500, 500), "SpineViewerWallpaper", SFML.Window.Styles.None);
_wallpaperRenderWindow.SetVisible(false);
DataContext = _vm = new(_renderPanel, _wallpaperRenderWindow);
// XXX: hc 的 NotifyIcon 的 Text 似乎没法双向绑定
_notifyIcon.Text = _vm.Title;
_vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; _vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
_vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; _vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
@@ -333,7 +340,7 @@ public partial class MainWindow : Window
private void _notifyIcon_Click(object sender, RoutedEventArgs e) private void _notifyIcon_Click(object sender, RoutedEventArgs e)
{ {
} }
private void _notifyIcon_MouseDoubleClick(object sender, RoutedEventArgs e) private void _notifyIcon_MouseDoubleClick(object sender, RoutedEventArgs e)
@@ -600,4 +607,33 @@ public partial class MainWindow : Window
} }
#endregion #endregion
private void DebugMenuItem_Click(object sender, RoutedEventArgs e)
{
#if DEBUG
var www = _wallpaperRenderWindow;
User32.GetPrimaryScreenResolution(out var sw, out var sh);
www.Position = new(0, 0);
www.Size = new(sw, sh);
var handle = www.SystemHandle;
Debug.WriteLine($"Handle: {handle:x8}");
var style = User32.GetWindowLong(handle, User32.GWL_STYLE) | User32.WS_POPUP;
var exStyle = User32.GetWindowLong(handle, User32.GWL_EXSTYLE) | User32.WS_EX_LAYERED | User32.WS_EX_TOOLWINDOW | User32.WS_EX_TOPMOST;
User32.SetWindowLong(handle, User32.GWL_STYLE, style);
User32.SetWindowLong(handle, User32.GWL_EXSTYLE, exStyle);
User32.SetLayeredWindowAttributes(handle, 0, 200, User32.LWA_ALPHA);
var workerw = User32.GetWorkerW();
var res = User32.SetParent(handle, workerw);
Debug.WriteLine($"SetParent: {res:x8}");
User32.SetLayeredWindowAttributes(handle, 0, 255, User32.LWA_ALPHA);
var s = www.Size;
www.Size = new(s.X + 1, s.Y + 1);
www.Size = s;
www.SetVisible(true);
#endif
}
} }