diff --git a/CHANGELOG.md b/CHANGELOG.md index dfc5daa..4bdaeaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## v0.15.19 + +- 模型重载后选中最后一个重载模型 +- 修复 3.4 版本可能的奇数顶点数组导致的越界崩溃问题 +- 移除参数自动记录中的背景图片路径 +- 增加测试性桌面投影功能 + ## v0.15.18 - 完善窗口日志颜色标记 diff --git a/SFMLRenderer/SFMLHwndHost.cs b/SFMLRenderer/SFMLHwndHost.cs index 975968b..999e681 100644 --- a/SFMLRenderer/SFMLHwndHost.cs +++ b/SFMLRenderer/SFMLHwndHost.cs @@ -64,10 +64,10 @@ namespace SFMLRenderer 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(); - return nint.Zero; + return IntPtr.Zero; } } } diff --git a/SFMLRenderer/SFMLRenderWindow.cs b/SFMLRenderer/SFMLRenderWindow.cs new file mode 100644 index 0000000..ce31211 --- /dev/null +++ b/SFMLRenderer/SFMLRenderWindow.cs @@ -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? CanvasMouseMove + { + add { MouseMoved += value; } + remove { MouseMoved -= value; } + } + + public event EventHandler? CanvasMouseButtonPressed + { + add { MouseButtonPressed += value; } + remove { MouseButtonPressed -= value; } + } + + public event EventHandler? CanvasMouseButtonReleased + { + add { MouseButtonReleased += value; } + remove { MouseButtonReleased -= value; } + } + + public event EventHandler? 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; + } +} diff --git a/SFMLRenderer/SFMLRenderer.csproj b/SFMLRenderer/SFMLRenderer.csproj index dd4b68c..0d5ddd4 100644 --- a/SFMLRenderer/SFMLRenderer.csproj +++ b/SFMLRenderer/SFMLRenderer.csproj @@ -7,7 +7,7 @@ net8.0-windows $(SolutionDir)out false - 0.15.4 + 0.15.19 true diff --git a/Spine/Spine.csproj b/Spine/Spine.csproj index 06d2bd7..756439e 100644 --- a/Spine/Spine.csproj +++ b/Spine/Spine.csproj @@ -7,7 +7,7 @@ net8.0-windows $(SolutionDir)out false - 0.15.18 + 0.15.19 diff --git a/Spine/SpineWrappers/TextureLoader.cs b/Spine/SpineWrappers/TextureLoader.cs index 0e25705..0d6b0dc 100644 --- a/Spine/SpineWrappers/TextureLoader.cs +++ b/Spine/SpineWrappers/TextureLoader.cs @@ -1,4 +1,5 @@ -using SFML.Graphics; +using NLog; +using SFML.Graphics; using SkiaSharp; using System; using System.Collections.Generic; @@ -22,6 +23,8 @@ namespace Spine.SpineWrappers SpineRuntime41.TextureLoader, SpineRuntime42.TextureLoader { + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + /// /// 默认的全局纹理加载器 /// @@ -44,9 +47,18 @@ namespace Spine.SpineWrappers private Texture ReadTexture(string path) { + if (!File.Exists(path)) + { + _logger.Error($"Texture file not found, {path}"); + throw new FileNotFoundException("Texture file not found", path); + } + using var codec = SKCodec.Create(path, out var result); if (codec is null || result != SKCodecResult.Success) + { + _logger.Error($"Failed to create codec '{path}', {result}"); throw new InvalidOperationException($"Failed to create codec '{path}', {result}"); + } var width = codec.Info.Width; var height = codec.Info.Height; @@ -57,7 +69,10 @@ namespace Spine.SpineWrappers result = codec.GetPixels(info, out var pixels); if (result != SKCodecResult.Success) + { + _logger.Error($"Failed to decode image '{path}', {result}"); throw new InvalidOperationException($"Failed to decode image '{path}', {result}"); + } Texture tex = new((uint)width, (uint)height); tex.Update(pixels); diff --git a/SpineRuntimes/SpineRuntime21/Skeleton.cs b/SpineRuntimes/SpineRuntime21/Skeleton.cs index 11d8763..4f47a24 100644 --- a/SpineRuntimes/SpineRuntime21/Skeleton.cs +++ b/SpineRuntimes/SpineRuntime21/Skeleton.cs @@ -338,7 +338,7 @@ namespace SpineRuntime21 { if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) + for (int ii = 0; ii + 1 < verticesLength; ii += 2) { float vx = vertices[ii], vy = vertices[ii + 1]; minX = Math.Min(minX, vx); diff --git a/SpineRuntimes/SpineRuntime34/Skeleton.cs b/SpineRuntimes/SpineRuntime34/Skeleton.cs index 6cae9a4..64b891d 100644 --- a/SpineRuntimes/SpineRuntime34/Skeleton.cs +++ b/SpineRuntimes/SpineRuntime34/Skeleton.cs @@ -489,7 +489,7 @@ namespace SpineRuntime34 { if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) + for (int ii = 0; ii + 1 < verticesLength; ii += 2) { float vx = vertices[ii], vy = vertices[ii + 1]; minX = Math.Min(minX, vx); diff --git a/SpineRuntimes/SpineRuntime35/Skeleton.cs b/SpineRuntimes/SpineRuntime35/Skeleton.cs index 284c315..d12e9b3 100644 --- a/SpineRuntimes/SpineRuntime35/Skeleton.cs +++ b/SpineRuntimes/SpineRuntime35/Skeleton.cs @@ -521,7 +521,7 @@ namespace SpineRuntime35 { } if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { + for (int ii = 0; ii + 1 < verticesLength; ii += 2) { float vx = vertices[ii], vy = vertices[ii + 1]; minX = Math.Min(minX, vx); minY = Math.Min(minY, vy); diff --git a/SpineRuntimes/SpineRuntime36/Skeleton.cs b/SpineRuntimes/SpineRuntime36/Skeleton.cs index 637f6c1..30a105c 100644 --- a/SpineRuntimes/SpineRuntime36/Skeleton.cs +++ b/SpineRuntimes/SpineRuntime36/Skeleton.cs @@ -528,7 +528,7 @@ namespace SpineRuntime36 { } if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { + for (int ii = 0; ii + 1 < verticesLength; ii += 2) { float vx = vertices[ii], vy = vertices[ii + 1]; minX = Math.Min(minX, vx); minY = Math.Min(minY, vy); diff --git a/SpineRuntimes/SpineRuntime37/Skeleton.cs b/SpineRuntimes/SpineRuntime37/Skeleton.cs index db7a403..b13a7dc 100644 --- a/SpineRuntimes/SpineRuntime37/Skeleton.cs +++ b/SpineRuntimes/SpineRuntime37/Skeleton.cs @@ -580,7 +580,7 @@ namespace SpineRuntime37 { } if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { + for (int ii = 0; ii + 1 < verticesLength; ii += 2) { float vx = vertices[ii], vy = vertices[ii + 1]; minX = Math.Min(minX, vx); minY = Math.Min(minY, vy); diff --git a/SpineRuntimes/SpineRuntime38/Skeleton.cs b/SpineRuntimes/SpineRuntime38/Skeleton.cs index 4b47340..46205fa 100644 --- a/SpineRuntimes/SpineRuntime38/Skeleton.cs +++ b/SpineRuntimes/SpineRuntime38/Skeleton.cs @@ -617,7 +617,7 @@ namespace SpineRuntime38 { } if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { + for (int ii = 0; ii + 1 < verticesLength; ii += 2) { float vx = vertices[ii], vy = vertices[ii + 1]; minX = Math.Min(minX, vx); minY = Math.Min(minY, vy); diff --git a/SpineRuntimes/SpineRuntime40/Skeleton.cs b/SpineRuntimes/SpineRuntime40/Skeleton.cs index 6036b8c..085b74d 100644 --- a/SpineRuntimes/SpineRuntime40/Skeleton.cs +++ b/SpineRuntimes/SpineRuntime40/Skeleton.cs @@ -595,7 +595,7 @@ namespace SpineRuntime40 { } if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { + for (int ii = 0; ii + 1 < verticesLength; ii += 2) { float vx = vertices[ii], vy = vertices[ii + 1]; minX = Math.Min(minX, vx); minY = Math.Min(minY, vy); diff --git a/SpineRuntimes/SpineRuntime41/Skeleton.cs b/SpineRuntimes/SpineRuntime41/Skeleton.cs index 93f0f7e..4d11257 100644 --- a/SpineRuntimes/SpineRuntime41/Skeleton.cs +++ b/SpineRuntimes/SpineRuntime41/Skeleton.cs @@ -641,7 +641,7 @@ namespace SpineRuntime41 { } if (vertices != null) { - for (int ii = 0; ii < verticesLength; ii += 2) { + for (int ii = 0; ii + 1 < verticesLength; ii += 2) { float vx = vertices[ii], vy = vertices[ii + 1]; minX = Math.Min(minX, vx); minY = Math.Min(minY, vy); diff --git a/SpineRuntimes/SpineRuntime42/Skeleton.cs b/SpineRuntimes/SpineRuntime42/Skeleton.cs index cfd1312..c1f6ff8 100644 --- a/SpineRuntimes/SpineRuntime42/Skeleton.cs +++ b/SpineRuntimes/SpineRuntime42/Skeleton.cs @@ -742,7 +742,7 @@ namespace SpineRuntime42 { verticesLength = clipper.ClippedVertices.Count; } - for (int ii = 0; ii < verticesLength; ii += 2) { + for (int ii = 0; ii + 1 < verticesLength; ii += 2) { float vx = vertices[ii], vy = vertices[ii + 1]; minX = Math.Min(minX, vx); minY = Math.Min(minY, vy); diff --git a/SpineViewer/Models/LastStateModel.cs b/SpineViewer/Models/LastStateModel.cs index 639132a..80bac2f 100644 --- a/SpineViewer/Models/LastStateModel.cs +++ b/SpineViewer/Models/LastStateModel.cs @@ -33,7 +33,6 @@ namespace SpineViewer.Models public float Speed { get; set; } = 1f; public bool ShowAxis { get; set; } = true; public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105); - public string BackgroundImagePath { get; set; } public Stretch BackgroundImageMode { get; set; } = Stretch.Uniform; #endregion diff --git a/SpineViewer/Models/PreferenceModel.cs b/SpineViewer/Models/PreferenceModel.cs index da94a82..635ec10 100644 --- a/SpineViewer/Models/PreferenceModel.cs +++ b/SpineViewer/Models/PreferenceModel.cs @@ -73,6 +73,9 @@ namespace SpineViewer.Models #region 程序选项 + [ObservableProperty] + private bool _wallpaperView; + [ObservableProperty] private bool _renderSelectedOnly; diff --git a/SpineViewer/Natives/Gdi32.cs b/SpineViewer/Natives/Gdi32.cs index 6e97ae2..5845cf9 100644 --- a/SpineViewer/Natives/Gdi32.cs +++ b/SpineViewer/Natives/Gdi32.cs @@ -15,15 +15,15 @@ namespace SpineViewer.Natives public static class Gdi32 { [DllImport("gdi32.dll", SetLastError = true)] - public static extern nint CreateCompatibleDC(nint hdc); + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); [DllImport("gdi32.dll", SetLastError = true)] - public static extern bool DeleteDC(nint hdc); + public static extern bool DeleteDC(IntPtr hdc); [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)] - public static extern bool DeleteObject(nint hObject); + public static extern bool DeleteObject(IntPtr hObject); } } diff --git a/SpineViewer/Natives/User32.cs b/SpineViewer/Natives/User32.cs index 06d9714..4702bd6 100644 --- a/SpineViewer/Natives/User32.cs +++ b/SpineViewer/Natives/User32.cs @@ -15,22 +15,61 @@ namespace SpineViewer.Natives public static class User32 { public const int GWL_STYLE = -16; - public const int WS_SIZEBOX = 0x40000; - public const int WS_BORDER = 0x800000; - public const int WS_VISIBLE = 0x10000000; - public const int WS_CHILD = 0x40000000; + public const int WS_OVERLAPPED = 0x00000000; 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 WS_EX_TOPMOST = 0x8; - public const int WS_EX_TRANSPARENT = 0x20; - public const int WS_EX_TOOLWINDOW = 0x80; - public const int WS_EX_WINDOWEDGE = 0x100; - public const int WS_EX_CLIENTEDGE = 0x200; - public const int WS_EX_APPWINDOW = 0x40000; - public const int WS_EX_LAYERED = 0x80000; + public const int WS_EX_DLGMODALFRAME = 0x00000001; + public const int WS_EX_NOPARENTNOTIFY = 0x00000004; + public const int WS_EX_TOPMOST = 0x00000008; + public const int WS_EX_ACCEPTFILES = 0x00000010; + public const int WS_EX_TRANSPARENT = 0x00000020; + public const int WS_EX_MDICHILD = 0x00000040; + 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_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_ALPHA = 0x2; @@ -42,12 +81,23 @@ namespace SpineViewer.Natives public const int ULW_ALPHA = 0x00000002; 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_NOMOVE = 0x0002; - public const uint SWP_NOZORDER = 0x0004; + public const uint SWP_ASYNCWINDOWPOS = 0x4000; + public const uint SWP_DEFERERASE = 0x2000; + 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_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; // 一个未公开的神秘消息 @@ -72,6 +122,8 @@ namespace SpineViewer.Natives public const int SW_RESTORE = 9; public const int SW_SHOWDEFAULT = 10; + public const uint MONITOR_DEFAULTTONULL = 0; + public const uint MONITOR_DEFAULTTOPRIMARY = 1; public const uint MONITOR_DEFAULTTONEAREST = 2; [StructLayout(LayoutKind.Sequential)] @@ -124,29 +176,25 @@ namespace SpineViewer.Natives public string szDevice; } - [DllImport("user32.dll", SetLastError = true)] - public static extern nint GetDC(nint hWnd); + public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); [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)] - 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)] - 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)] - 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)] - 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)] - 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); - - [DllImport("user32.dll", SetLastError = true)] - public static extern bool SetWindowPos(nint hWnd, nint hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + 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 uint GetDoubleClickTime(); @@ -155,28 +203,34 @@ namespace SpineViewer.Natives private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); [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)] - 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)] - 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)] - public static extern nint SetParent(nint hWndChild, nint hWndNewParent); + public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll", SetLastError = true)] - public static extern nint GetParent(nint hWnd); + public static extern IntPtr GetParent(IntPtr hWnd); [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)] - public static extern nint GetWindow(nint hWnd, uint uCmd); + public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); [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")] public static extern bool IsIconic(IntPtr hWnd); @@ -190,6 +244,12 @@ namespace SpineViewer.Natives [DllImport("user32.dll")] 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() { LASTINPUTINFO lastInputInfo = new(); @@ -205,14 +265,59 @@ namespace SpineViewer.Natives return TimeSpan.FromMilliseconds(idleTimeMillis); } - public static nint GetWorkerW() + public static IntPtr GetWorkerW() { + // NOTE: Codes borrowed from @rocksdanister/lively + var progman = FindWindow("Progman", null); - if (progman == nint.Zero) - return nint.Zero; - nint hWnd = FindWindowEx(progman, 0, "WorkerW", null); - Debug.WriteLine($"HWND(Progman.WorkerW): {hWnd:x8}"); - return hWnd; + if (progman == IntPtr.Zero) + return IntPtr.Zero; + + // Send 0x052C to Progman. This message directs Progman to spawn a + // 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) @@ -231,5 +336,22 @@ namespace SpineViewer.Natives width = height = 0; 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() }; + 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; + } } } diff --git a/SpineViewer/Resources/Strings/en.xaml b/SpineViewer/Resources/Strings/en.xaml index 7cdda02..9d7a0d5 100644 --- a/SpineViewer/Resources/Strings/en.xaml +++ b/SpineViewer/Resources/Strings/en.xaml @@ -118,6 +118,7 @@ Max FPS Maximum frame rate of the preview. Set to 0 for no limit. Playback Speed + Wallpaper View Render Selected Only Show Axis Background Color diff --git a/SpineViewer/Resources/Strings/ja.xaml b/SpineViewer/Resources/Strings/ja.xaml index 5f4ef84..e1fb869 100644 --- a/SpineViewer/Resources/Strings/ja.xaml +++ b/SpineViewer/Resources/Strings/ja.xaml @@ -118,6 +118,7 @@ 最大FPS プレビュー画面の最大フレームレート。0 に設定すると制限なし。 再生速度 + 壁紙表示 選択のみレンダリング 座標軸を表示 背景色 diff --git a/SpineViewer/Resources/Strings/zh.xaml b/SpineViewer/Resources/Strings/zh.xaml index 793fd7e..05f485d 100644 --- a/SpineViewer/Resources/Strings/zh.xaml +++ b/SpineViewer/Resources/Strings/zh.xaml @@ -118,6 +118,7 @@ 最大帧率 预览画面的最大帧率,设置为 0 时则无帧率限制 播放速度 + 桌面投影 仅渲染选中 显示坐标轴 背景颜色 diff --git a/SpineViewer/SpineViewer.csproj b/SpineViewer/SpineViewer.csproj index 6e87e0f..7d3c7ca 100644 --- a/SpineViewer/SpineViewer.csproj +++ b/SpineViewer/SpineViewer.csproj @@ -7,7 +7,7 @@ net8.0-windows $(SolutionDir)out false - 0.15.18 + 0.15.19 WinExe true diff --git a/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs b/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs index e844d31..044e33a 100644 --- a/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/MainWindowViewModel.cs @@ -17,9 +17,10 @@ namespace SpineViewer.ViewModels.MainWindow { private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); - public MainWindowViewModel(ISFMLRenderer sfmlRenderer) + public MainWindowViewModel(ISFMLRenderer sfmlRenderer, ISFMLRenderer wallpaperRenderer) { _sfmlRenderer = sfmlRenderer; + _wallpaperRenderer = wallpaperRenderer; _explorerListViewModel = new(this); _spineObjectListViewModel = new(this); _sfmlRendererViewModel = new(this); @@ -34,6 +35,9 @@ namespace SpineViewer.ViewModels.MainWindow public ISFMLRenderer SFMLRenderer => _sfmlRenderer; private readonly ISFMLRenderer _sfmlRenderer; + public ISFMLRenderer WallpaperRenderer => _wallpaperRenderer; + private readonly ISFMLRenderer _wallpaperRenderer; + public TaskbarItemProgressState ProgressState { get => _progressState; set => SetProperty(ref _progressState, value); } private TaskbarItemProgressState _progressState = TaskbarItemProgressState.None; @@ -73,7 +77,15 @@ namespace SpineViewer.ViewModels.MainWindow public SFMLRendererViewModel SFMLRendererViewModel => _sfmlRendererViewModel; private readonly SFMLRendererViewModel _sfmlRendererViewModel; - public RelayCommand Cmd_Exit => new(App.Current.Shutdown); + public RelayCommand Cmd_SwitchWallpaperView => _cmd_SwitchWallpaperView ??= new(() => + { + _preferenceViewModel.WallpaperView = !_preferenceViewModel.WallpaperView; + _preferenceViewModel.SavePreference(); + }); + private RelayCommand _cmd_SwitchWallpaperView; + + public RelayCommand Cmd_Exit => _cmd_Exit ??= new(App.Current.Shutdown); + private RelayCommand? _cmd_Exit; /// /// 打开工作区 @@ -134,18 +146,5 @@ namespace SpineViewer.ViewModels.MainWindow } } - /// - /// 调试命令 - /// - public RelayCommand Cmd_Debug => _cmd_Debug ??= new(Debug_Execute); - private RelayCommand? _cmd_Debug; - - private void Debug_Execute() - { -#if DEBUG - - MessagePopupService.Quest("测试一下"); -#endif - } } } \ No newline at end of file diff --git a/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs b/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs index a5e3adc..bcdefb7 100644 --- a/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs @@ -111,6 +111,7 @@ namespace SpineViewer.ViewModels.MainWindow DebugPoints = DebugPoints, DebugClippings = DebugClippings, + WallpaperView = WallpaperView, RenderSelectedOnly = RenderSelectedOnly, AssociateFileSuffix = AssociateFileSuffix, AppLanguage = AppLanguage, @@ -136,6 +137,7 @@ namespace SpineViewer.ViewModels.MainWindow DebugPoints = value.DebugPoints; DebugClippings = value.DebugClippings; + WallpaperView = value.WallpaperView; RenderSelectedOnly = value.RenderSelectedOnly; AssociateFileSuffix = value.AssociateFileSuffix; AppLanguage = value.AppLanguage; @@ -244,6 +246,19 @@ namespace SpineViewer.ViewModels.MainWindow public static ImmutableArray AppLanguageOptions { get; } = Enum.GetValues().ToImmutableArray(); + public bool AutoRun + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public bool WallpaperView + { + get => _wallpaperView; + set => SetProperty(ref _wallpaperView, value); + } + private bool _wallpaperView; // UI 变化通过 PropertyChanged 事件交由 View 层处理 + public bool RenderSelectedOnly { get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly; diff --git a/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs b/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs index ad7c4dd..cdfb2bb 100644 --- a/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs @@ -35,6 +35,7 @@ namespace SpineViewer.ViewModels.MainWindow private readonly MainWindowViewModel _vmMain; private readonly ObservableCollectionWithLock _models; private readonly ISFMLRenderer _renderer; + private readonly ISFMLRenderer _wallpaperRenderer; /// /// 被选中对象的背景颜色 @@ -90,6 +91,7 @@ namespace SpineViewer.ViewModels.MainWindow _vmMain = vmMain; _models = _vmMain.SpineObjects; _renderer = _vmMain.SFMLRenderer; + _wallpaperRenderer = _vmMain.WallpaperRenderer; } /// @@ -443,6 +445,7 @@ namespace SpineViewer.ViewModels.MainWindow { try { + _wallpaperRenderer.SetActive(true); _renderer.SetActive(true); float delta; @@ -461,7 +464,11 @@ namespace SpineViewer.ViewModels.MainWindow _forwardDelta = 0; } + using var v = _renderer.GetView(); + _wallpaperRenderer.SetView(v); + _renderer.Clear(_backgroundColor); + _wallpaperRenderer.Clear(_backgroundColor); // 渲染背景 lock (_bgLock) @@ -492,6 +499,7 @@ namespace SpineViewer.ViewModels.MainWindow bg.Position = view.Center; bg.Rotation = view.Rotation; _renderer.Draw(bg); + _wallpaperRenderer.Draw(bg); } } @@ -531,10 +539,12 @@ namespace SpineViewer.ViewModels.MainWindow sp.EnableDebug = true; _renderer.Draw(sp); sp.EnableDebug = false; + _wallpaperRenderer.Draw(sp); } } _renderer.Display(); + _wallpaperRenderer.Display(); } } catch (Exception ex) @@ -546,6 +556,7 @@ namespace SpineViewer.ViewModels.MainWindow finally { _renderer.SetActive(false); + _wallpaperRenderer.SetActive(false); } } diff --git a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs index 4ed4a70..a1b93a9 100644 --- a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs @@ -213,6 +213,8 @@ namespace SpineViewer.ViewModels.MainWindow spNew.ObjectConfig = sp.ObjectConfig; _spineObjectModels[idx] = spNew; sp.Dispose(); + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, spNew)); } catch (Exception ex) { @@ -268,6 +270,11 @@ namespace SpineViewer.ViewModels.MainWindow _spineObjectModels[idx] = spNew; sp.Dispose(); success++; + Application.Current.Dispatcher.BeginInvoke(() => + { + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, spNew)); + }); } catch (Exception ex) { diff --git a/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs b/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs index 469ba99..3462a2b 100644 --- a/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs @@ -334,27 +334,31 @@ namespace SpineViewer.ViewModels.MainWindow public ObservableCollection Skins => _skins; - public RelayCommand Cmd_EnableSkins { get; } = new( + public RelayCommand Cmd_EnableSkins => _cmd_EnableSkins ??= new ( args => { if (args is null) return; foreach (var s in args.OfType()) s.Status = true; }, args => { return args is not null && args.OfType().Any(); } ); + private RelayCommand _cmd_EnableSkins; - public RelayCommand Cmd_DisableSkins { get; } = new( + public RelayCommand Cmd_DisableSkins => _cmd_DisableSkins ??= new ( args => { if (args is null) return; foreach (var s in args.OfType()) s.Status = false; }, args => { return args is not null && args.OfType().Any(); } ); + private RelayCommand _cmd_DisableSkins; public ObservableCollection Slots => _slots; - public RelayCommand Cmd_EnableSlots { get; } = new( + public RelayCommand Cmd_EnableSlots => _cmd_EnableSlots ??= new ( args => { if (args is null) return; foreach (var s in args.OfType()) s.Visible = true; }, args => { return args is not null && args.OfType().Any(); } ); + private RelayCommand _cmd_EnableSlots; - public RelayCommand Cmd_DisableSlots { get; } = new( + public RelayCommand Cmd_DisableSlots => _cmd_DisableSlots ??= new ( args => { if (args is null) return; foreach (var s in args.OfType()) s.Visible = false; }, args => { return args is not null && args.OfType().Any(); } ); + private RelayCommand _cmd_DisableSlots; public ObservableCollection AnimationTracks => _animationTracks; diff --git a/SpineViewer/Views/MainWindow.xaml b/SpineViewer/Views/MainWindow.xaml index 965473a..ffde3f7 100644 --- a/SpineViewer/Views/MainWindow.xaml +++ b/SpineViewer/Views/MainWindow.xaml @@ -69,7 +69,7 @@ - + @@ -940,7 +940,7 @@ MouseDoubleClick="_notifyIcon_MouseDoubleClick"> - + diff --git a/SpineViewer/Views/MainWindow.xaml.cs b/SpineViewer/Views/MainWindow.xaml.cs index 44c5dd1..65e2b60 100644 --- a/SpineViewer/Views/MainWindow.xaml.cs +++ b/SpineViewer/Views/MainWindow.xaml.cs @@ -1,7 +1,7 @@ -using Microsoft.Win32; -using NLog; +using NLog; using NLog.Layouts; using NLog.Targets; +using SFMLRenderer; using Spine; using SpineViewer.Models; using SpineViewer.Natives; @@ -12,6 +12,7 @@ using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Reflection.Metadata; using System.Text; using System.Windows; using System.Windows.Controls; @@ -35,72 +36,40 @@ public partial class MainWindow : Window public static readonly string LastStateFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "laststate.json"); private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + private ListViewItem? _listViewDragSourceItem = null; private Point _listViewDragSourcePoint; + private readonly SFMLRenderWindow _wallpaperRenderWindow; private readonly MainWindowViewModel _vm; public MainWindow() { InitializeComponent(); InitializeLogConfiguration(); - DataContext = _vm = new(_renderPanel); - _notifyIcon.Text = _vm.Title; // XXX: hc 的 NotifyIcon 的 Text 似乎没法双向绑定 - _vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; - _vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; + // Initialize Wallpaper RenderWindow + _wallpaperRenderWindow = new(new(1, 1), "SpineViewerWallpaper", SFML.Window.Styles.None); + _wallpaperRenderWindow.SetVisible(false); + var handle = _wallpaperRenderWindow.SystemHandle; + 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.SetWindowLong(handle, User32.GWL_STYLE, style); + User32.SetWindowLong(handle, User32.GWL_EXSTYLE, exStyle); + User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA); + + DataContext = _vm = new(_renderPanel, _wallpaperRenderWindow); + + // XXX: hc 的 NotifyIcon 的 Text 似乎没法双向绑定 + _notifyIcon.Text = _vm.Title; + Loaded += MainWindow_Loaded; ContentRendered += MainWindow_ContentRendered; Closed += MainWindow_Closed; - } - private void MainWindow_Loaded(object sender, RoutedEventArgs e) - { - var vm = _vm.SFMLRendererViewModel; - _renderPanel.CanvasMouseWheelScrolled += vm.CanvasMouseWheelScrolled; - _renderPanel.CanvasMouseButtonPressed += (s, e) => { vm.CanvasMouseButtonPressed(s, e); _spinesListView.Focus(); }; // 用户点击画布后强制转移焦点至列表 - _renderPanel.CanvasMouseMove += vm.CanvasMouseMove; - _renderPanel.CanvasMouseButtonReleased += vm.CanvasMouseButtonReleased; - - // 设置默认参数并启动渲染 - vm.SetResolution(1500, 1000); - vm.Zoom = 0.75f; - vm.CenterX = 0; - vm.CenterY = 0; - vm.FlipY = true; - vm.MaxFps = 30; - vm.StartRender(); - - // 加载首选项 - _vm.PreferenceViewModel.LoadPreference(); - - LoadLastState(); - } - - private void MainWindow_ContentRendered(object? sender, EventArgs e) - { - string[] args = Environment.GetCommandLineArgs(); - if (args.Length > 1) - { - string[] filePaths = args.Skip(1).ToArray(); - _vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths); - } - } - - private void MainWindow_Closed(object? sender, EventArgs e) - { - SaveLastState(); - - var vm = _vm.SFMLRendererViewModel; - vm.StopRender(); - } - - /// - /// 给管道通信提供的打开文件外部调用方法 - /// - public void OpenFiles(IEnumerable filePaths) - { - _vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths); + _vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; + _vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; + _vm.PreferenceViewModel.PropertyChanged += PreferenceViewModel_PropertyChanged; } /// @@ -157,7 +126,6 @@ public partial class MainWindow : Window _vm.SFMLRendererViewModel.Speed = m.Speed; _vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis; _vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor; - _vm.SFMLRendererViewModel.BackgroundImagePath = m.BackgroundImagePath; _vm.SFMLRendererViewModel.BackgroundImageMode = m.BackgroundImageMode; } } @@ -183,13 +151,101 @@ public partial class MainWindow : Window Speed = _vm.SFMLRendererViewModel.Speed, ShowAxis = _vm.SFMLRendererViewModel.ShowAxis, BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor, - BackgroundImagePath = _vm.SFMLRendererViewModel.BackgroundImagePath, BackgroundImageMode = _vm.SFMLRendererViewModel.BackgroundImageMode, }; JsonHelper.Serialize(m, LastStateFilePath); } + /// + /// 给管道通信提供的打开文件外部调用方法 + /// + public void OpenFiles(IEnumerable filePaths) + { + _vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths); + } + + #region MainWindow 事件处理 + + private void MainWindow_Loaded(object sender, RoutedEventArgs e) + { + var vm = _vm.SFMLRendererViewModel; + _renderPanel.CanvasMouseWheelScrolled += vm.CanvasMouseWheelScrolled; + _renderPanel.CanvasMouseButtonPressed += (s, e) => { vm.CanvasMouseButtonPressed(s, e); _spinesListView.Focus(); }; // 用户点击画布后强制转移焦点至列表 + _renderPanel.CanvasMouseMove += vm.CanvasMouseMove; + _renderPanel.CanvasMouseButtonReleased += vm.CanvasMouseButtonReleased; + + // 设置默认参数并启动渲染 + vm.SetResolution(1500, 1000); + vm.Zoom = 0.75f; + vm.CenterX = 0; + vm.CenterY = 0; + vm.FlipY = true; + vm.MaxFps = 30; + vm.StartRender(); + + // 加载首选项 + _vm.PreferenceViewModel.LoadPreference(); + + LoadLastState(); + } + + private void MainWindow_ContentRendered(object? sender, EventArgs e) + { + string[] args = Environment.GetCommandLineArgs(); + if (args.Length > 1) + { + string[] filePaths = args.Skip(1).ToArray(); + _vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths); + } + } + + private void MainWindow_Closed(object? sender, EventArgs e) + { + SaveLastState(); + + var vm = _vm.SFMLRendererViewModel; + vm.StopRender(); + } + + #endregion + + #region PreferenceViewModel 事件处理 + + private void PreferenceViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(PreferenceViewModel.WallpaperView)) + { + if (_vm.PreferenceViewModel.WallpaperView) + { + var workerw = User32.GetWorkerW(); + if (workerw == IntPtr.Zero) + { + _logger.Error("Failed to enable wallpaper view, WorkerW not found"); + return; + } + var wnd = _wallpaperRenderWindow; + var handle = wnd.SystemHandle; + + User32.GetPrimaryScreenResolution(out var sw, out var sh); + + User32.SetParent(handle, workerw); + User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA); + + wnd.Position = new(0, 0); + wnd.Size = new(sw + 1, sh); + wnd.Size = new(sw, sh); + wnd.SetVisible(true); + } + else + { + _wallpaperRenderWindow.SetVisible(false); + } + } + } + + #endregion + #region _spinesListView 事件处理 private void SpinesListView_RequestSelectionChanging(object? sender, NotifyCollectionChangedEventArgs e) @@ -333,7 +389,7 @@ public partial class MainWindow : Window private void _notifyIcon_Click(object sender, RoutedEventArgs e) { - + } private void _notifyIcon_MouseDoubleClick(object sender, RoutedEventArgs e) @@ -600,4 +656,11 @@ public partial class MainWindow : Window } #endregion + + private void DebugMenuItem_Click(object sender, RoutedEventArgs e) + { +#if DEBUG + +#endif + } } \ No newline at end of file diff --git a/SpineViewer/Views/PreferenceDialog.xaml b/SpineViewer/Views/PreferenceDialog.xaml index 37723e4..644cf8a 100644 --- a/SpineViewer/Views/PreferenceDialog.xaml +++ b/SpineViewer/Views/PreferenceDialog.xaml @@ -144,16 +144,20 @@ + -