Merge pull request #106 from ww-rm/dev/wpf

v0.15.19
This commit is contained in:
ww-rm
2025-09-27 23:48:05 +08:00
committed by GitHub
31 changed files with 568 additions and 145 deletions

View File

@@ -1,5 +1,12 @@
# CHANGELOG # CHANGELOG
## v0.15.19
- 模型重载后选中最后一个重载模型
- 修复 3.4 版本可能的奇数顶点数组导致的越界崩溃问题
- 移除参数自动记录中的背景图片路径
- 增加测试性桌面投影功能
## v0.15.18 ## v0.15.18
- 完善窗口日志颜色标记 - 完善窗口日志颜色标记

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

@@ -7,7 +7,7 @@
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath> <BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.4</Version> <Version>0.15.19</Version>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
</PropertyGroup> </PropertyGroup>

View File

@@ -7,7 +7,7 @@
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath> <BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.18</Version> <Version>0.15.19</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -1,4 +1,5 @@
using SFML.Graphics; using NLog;
using SFML.Graphics;
using SkiaSharp; using SkiaSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -22,6 +23,8 @@ namespace Spine.SpineWrappers
SpineRuntime41.TextureLoader, SpineRuntime41.TextureLoader,
SpineRuntime42.TextureLoader SpineRuntime42.TextureLoader
{ {
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
/// <summary> /// <summary>
/// 默认的全局纹理加载器 /// 默认的全局纹理加载器
/// </summary> /// </summary>
@@ -44,9 +47,18 @@ namespace Spine.SpineWrappers
private Texture ReadTexture(string path) 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); using var codec = SKCodec.Create(path, out var result);
if (codec is null || result != SKCodecResult.Success) if (codec is null || result != SKCodecResult.Success)
{
_logger.Error($"Failed to create codec '{path}', {result}");
throw new InvalidOperationException($"Failed to create codec '{path}', {result}"); throw new InvalidOperationException($"Failed to create codec '{path}', {result}");
}
var width = codec.Info.Width; var width = codec.Info.Width;
var height = codec.Info.Height; var height = codec.Info.Height;
@@ -57,7 +69,10 @@ namespace Spine.SpineWrappers
result = codec.GetPixels(info, out var pixels); result = codec.GetPixels(info, out var pixels);
if (result != SKCodecResult.Success) if (result != SKCodecResult.Success)
{
_logger.Error($"Failed to decode image '{path}', {result}");
throw new InvalidOperationException($"Failed to decode image '{path}', {result}"); throw new InvalidOperationException($"Failed to decode image '{path}', {result}");
}
Texture tex = new((uint)width, (uint)height); Texture tex = new((uint)width, (uint)height);
tex.Update(pixels); tex.Update(pixels);

View File

@@ -338,7 +338,7 @@ namespace SpineRuntime21 {
if (vertices != null) 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]; float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx); minX = Math.Min(minX, vx);

View File

@@ -489,7 +489,7 @@ namespace SpineRuntime34 {
if (vertices != null) 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]; float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx); minX = Math.Min(minX, vx);

View File

@@ -521,7 +521,7 @@ namespace SpineRuntime35 {
} }
if (vertices != null) { 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]; float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx); minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy); minY = Math.Min(minY, vy);

View File

@@ -528,7 +528,7 @@ namespace SpineRuntime36 {
} }
if (vertices != null) { 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]; float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx); minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy); minY = Math.Min(minY, vy);

View File

@@ -580,7 +580,7 @@ namespace SpineRuntime37 {
} }
if (vertices != null) { 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]; float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx); minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy); minY = Math.Min(minY, vy);

View File

@@ -617,7 +617,7 @@ namespace SpineRuntime38 {
} }
if (vertices != null) { 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]; float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx); minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy); minY = Math.Min(minY, vy);

View File

@@ -595,7 +595,7 @@ namespace SpineRuntime40 {
} }
if (vertices != null) { 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]; float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx); minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy); minY = Math.Min(minY, vy);

View File

@@ -641,7 +641,7 @@ namespace SpineRuntime41 {
} }
if (vertices != null) { 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]; float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx); minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy); minY = Math.Min(minY, vy);

View File

@@ -742,7 +742,7 @@ namespace SpineRuntime42 {
verticesLength = clipper.ClippedVertices.Count; 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]; float vx = vertices[ii], vy = vertices[ii + 1];
minX = Math.Min(minX, vx); minX = Math.Min(minX, vx);
minY = Math.Min(minY, vy); minY = Math.Min(minY, vy);

View File

@@ -33,7 +33,6 @@ namespace SpineViewer.Models
public float Speed { get; set; } = 1f; public float Speed { get; set; } = 1f;
public bool ShowAxis { get; set; } = true; public bool ShowAxis { get; set; } = true;
public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105); public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105);
public string BackgroundImagePath { get; set; }
public Stretch BackgroundImageMode { get; set; } = Stretch.Uniform; public Stretch BackgroundImageMode { get; set; } = Stretch.Uniform;
#endregion #endregion

View File

@@ -73,6 +73,9 @@ namespace SpineViewer.Models
#region #region
[ObservableProperty]
private bool _wallpaperView;
[ObservableProperty] [ObservableProperty]
private bool _renderSelectedOnly; private bool _renderSelectedOnly;

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

@@ -118,6 +118,7 @@
<s:String x:Key="Str_MaxFps">Max FPS</s:String> <s:String x:Key="Str_MaxFps">Max FPS</s:String>
<s:String x:Key="Str_MaxFpsTooltip">Maximum frame rate of the preview. Set to 0 for no limit.</s:String> <s:String x:Key="Str_MaxFpsTooltip">Maximum frame rate of the preview. Set to 0 for no limit.</s:String>
<s:String x:Key="Str_PlaySpeed">Playback Speed</s:String> <s:String x:Key="Str_PlaySpeed">Playback Speed</s:String>
<s:String x:Key="Str_WallpaperView">Wallpaper View</s:String>
<s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String> <s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String>
<s:String x:Key="Str_ShowAxis">Show Axis</s:String> <s:String x:Key="Str_ShowAxis">Show Axis</s:String>
<s:String x:Key="Str_BackgroundColor">Background Color</s:String> <s:String x:Key="Str_BackgroundColor">Background Color</s:String>

View File

@@ -118,6 +118,7 @@
<s:String x:Key="Str_MaxFps">最大FPS</s:String> <s:String x:Key="Str_MaxFps">最大FPS</s:String>
<s:String x:Key="Str_MaxFpsTooltip">プレビュー画面の最大フレームレート。0 に設定すると制限なし。</s:String> <s:String x:Key="Str_MaxFpsTooltip">プレビュー画面の最大フレームレート。0 に設定すると制限なし。</s:String>
<s:String x:Key="Str_PlaySpeed">再生速度</s:String> <s:String x:Key="Str_PlaySpeed">再生速度</s:String>
<s:String x:Key="Str_WallpaperView">壁紙表示</s:String>
<s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String> <s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String>
<s:String x:Key="Str_ShowAxis">座標軸を表示</s:String> <s:String x:Key="Str_ShowAxis">座標軸を表示</s:String>
<s:String x:Key="Str_BackgroundColor">背景色</s:String> <s:String x:Key="Str_BackgroundColor">背景色</s:String>

View File

@@ -118,6 +118,7 @@
<s:String x:Key="Str_MaxFps">最大帧率</s:String> <s:String x:Key="Str_MaxFps">最大帧率</s:String>
<s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</s:String> <s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</s:String>
<s:String x:Key="Str_PlaySpeed">播放速度</s:String> <s:String x:Key="Str_PlaySpeed">播放速度</s:String>
<s:String x:Key="Str_WallpaperView">桌面投影</s:String>
<s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String> <s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String>
<s:String x:Key="Str_ShowAxis">显示坐标轴</s:String> <s:String x:Key="Str_ShowAxis">显示坐标轴</s:String>
<s:String x:Key="Str_BackgroundColor">背景颜色</s:String> <s:String x:Key="Str_BackgroundColor">背景颜色</s:String>

View File

@@ -7,7 +7,7 @@
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath> <BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.18</Version> <Version>0.15.19</Version>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
</PropertyGroup> </PropertyGroup>

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;
@@ -73,7 +77,15 @@ namespace SpineViewer.ViewModels.MainWindow
public SFMLRendererViewModel SFMLRendererViewModel => _sfmlRendererViewModel; public SFMLRendererViewModel SFMLRendererViewModel => _sfmlRendererViewModel;
private readonly 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;
/// <summary> /// <summary>
/// 打开工作区 /// 打开工作区
@@ -134,18 +146,5 @@ namespace SpineViewer.ViewModels.MainWindow
} }
} }
/// <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

@@ -111,6 +111,7 @@ namespace SpineViewer.ViewModels.MainWindow
DebugPoints = DebugPoints, DebugPoints = DebugPoints,
DebugClippings = DebugClippings, DebugClippings = DebugClippings,
WallpaperView = WallpaperView,
RenderSelectedOnly = RenderSelectedOnly, RenderSelectedOnly = RenderSelectedOnly,
AssociateFileSuffix = AssociateFileSuffix, AssociateFileSuffix = AssociateFileSuffix,
AppLanguage = AppLanguage, AppLanguage = AppLanguage,
@@ -136,6 +137,7 @@ namespace SpineViewer.ViewModels.MainWindow
DebugPoints = value.DebugPoints; DebugPoints = value.DebugPoints;
DebugClippings = value.DebugClippings; DebugClippings = value.DebugClippings;
WallpaperView = value.WallpaperView;
RenderSelectedOnly = value.RenderSelectedOnly; RenderSelectedOnly = value.RenderSelectedOnly;
AssociateFileSuffix = value.AssociateFileSuffix; AssociateFileSuffix = value.AssociateFileSuffix;
AppLanguage = value.AppLanguage; AppLanguage = value.AppLanguage;
@@ -244,6 +246,19 @@ namespace SpineViewer.ViewModels.MainWindow
public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray(); public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().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 public bool RenderSelectedOnly
{ {
get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly; get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly;

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

@@ -213,6 +213,8 @@ namespace SpineViewer.ViewModels.MainWindow
spNew.ObjectConfig = sp.ObjectConfig; spNew.ObjectConfig = sp.ObjectConfig;
_spineObjectModels[idx] = spNew; _spineObjectModels[idx] = spNew;
sp.Dispose(); sp.Dispose();
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, spNew));
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -268,6 +270,11 @@ namespace SpineViewer.ViewModels.MainWindow
_spineObjectModels[idx] = spNew; _spineObjectModels[idx] = spNew;
sp.Dispose(); sp.Dispose();
success++; success++;
Application.Current.Dispatcher.BeginInvoke(() =>
{
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, spNew));
});
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -334,27 +334,31 @@ namespace SpineViewer.ViewModels.MainWindow
public ObservableCollection<SkinViewModel> Skins => _skins; public ObservableCollection<SkinViewModel> Skins => _skins;
public RelayCommand<IList?> Cmd_EnableSkins { get; } = new( public RelayCommand<IList?> Cmd_EnableSkins => _cmd_EnableSkins ??= new (
args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = true; }, args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = true; },
args => { return args is not null && args.OfType<SkinViewModel>().Any(); } args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
); );
private RelayCommand<IList?> _cmd_EnableSkins;
public RelayCommand<IList?> Cmd_DisableSkins { get; } = new( public RelayCommand<IList?> Cmd_DisableSkins => _cmd_DisableSkins ??= new (
args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = false; }, args => { if (args is null) return; foreach (var s in args.OfType<SkinViewModel>()) s.Status = false; },
args => { return args is not null && args.OfType<SkinViewModel>().Any(); } args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
); );
private RelayCommand<IList?> _cmd_DisableSkins;
public ObservableCollection<SlotViewModel> Slots => _slots; public ObservableCollection<SlotViewModel> Slots => _slots;
public RelayCommand<IList?> Cmd_EnableSlots { get; } = new( public RelayCommand<IList?> Cmd_EnableSlots => _cmd_EnableSlots ??= new (
args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = true; }, args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = true; },
args => { return args is not null && args.OfType<SlotViewModel>().Any(); } args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
); );
private RelayCommand<IList?> _cmd_EnableSlots;
public RelayCommand<IList?> Cmd_DisableSlots { get; } = new( public RelayCommand<IList?> Cmd_DisableSlots => _cmd_DisableSlots ??= new (
args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = false; }, args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = false; },
args => { return args is not null && args.OfType<SlotViewModel>().Any(); } args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
); );
private RelayCommand<IList?> _cmd_DisableSlots;
public ObservableCollection<AnimationTrackViewModel> AnimationTracks => _animationTracks; public ObservableCollection<AnimationTrackViewModel> AnimationTracks => _animationTracks;

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>
@@ -940,7 +940,7 @@
MouseDoubleClick="_notifyIcon_MouseDoubleClick"> MouseDoubleClick="_notifyIcon_MouseDoubleClick">
<hc:NotifyIcon.ContextMenu> <hc:NotifyIcon.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="There may be a funtion :)" IsChecked="True"/> <MenuItem Header="{DynamicResource Str_WallpaperView}" Command="{Binding Cmd_SwitchWallpaperView}" IsChecked="{Binding PreferenceViewModel.WallpaperView}"/>
<Separator/> <Separator/>
<MenuItem Header="{DynamicResource Str_Exit}" Command="{Binding Cmd_Exit}"/> <MenuItem Header="{DynamicResource Str_Exit}" Command="{Binding Cmd_Exit}"/>
</ContextMenu> </ContextMenu>

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;
@@ -12,6 +12,7 @@ using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Reflection.Metadata;
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; 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"); 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 似乎没法双向绑定
_vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; // Initialize Wallpaper RenderWindow
_vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging; _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; Loaded += MainWindow_Loaded;
ContentRendered += MainWindow_ContentRendered; ContentRendered += MainWindow_ContentRendered;
Closed += MainWindow_Closed; Closed += MainWindow_Closed;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e) _vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
{ _vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
var vm = _vm.SFMLRendererViewModel; _vm.PreferenceViewModel.PropertyChanged += PreferenceViewModel_PropertyChanged;
_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();
}
/// <summary>
/// 给管道通信提供的打开文件外部调用方法
/// </summary>
public void OpenFiles(IEnumerable<string> filePaths)
{
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
} }
/// <summary> /// <summary>
@@ -157,7 +126,6 @@ public partial class MainWindow : Window
_vm.SFMLRendererViewModel.Speed = m.Speed; _vm.SFMLRendererViewModel.Speed = m.Speed;
_vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis; _vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis;
_vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor; _vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor;
_vm.SFMLRendererViewModel.BackgroundImagePath = m.BackgroundImagePath;
_vm.SFMLRendererViewModel.BackgroundImageMode = m.BackgroundImageMode; _vm.SFMLRendererViewModel.BackgroundImageMode = m.BackgroundImageMode;
} }
} }
@@ -183,13 +151,101 @@ public partial class MainWindow : Window
Speed = _vm.SFMLRendererViewModel.Speed, Speed = _vm.SFMLRendererViewModel.Speed,
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis, ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor, BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
BackgroundImagePath = _vm.SFMLRendererViewModel.BackgroundImagePath,
BackgroundImageMode = _vm.SFMLRendererViewModel.BackgroundImageMode, BackgroundImageMode = _vm.SFMLRendererViewModel.BackgroundImageMode,
}; };
JsonHelper.Serialize(m, LastStateFilePath); JsonHelper.Serialize(m, LastStateFilePath);
} }
/// <summary>
/// 给管道通信提供的打开文件外部调用方法
/// </summary>
public void OpenFiles(IEnumerable<string> 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 #region _spinesListView
private void SpinesListView_RequestSelectionChanging(object? sender, NotifyCollectionChangedEventArgs e) private void SpinesListView_RequestSelectionChanging(object? sender, NotifyCollectionChangedEventArgs e)
@@ -600,4 +656,11 @@ public partial class MainWindow : Window
} }
#endregion #endregion
private void DebugMenuItem_Click(object sender, RoutedEventArgs e)
{
#if DEBUG
#endif
}
} }

View File

@@ -144,16 +144,20 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/> <Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_WallpaperView}"/>
<ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/> <ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding WallpaperView}"/>
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_AssociateFileSuffix}"/> <Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/>
<ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding AssociateFileSuffix}"/> <ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/>
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_Language}"/> <Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_AssociateFileSuffix}"/>
<ComboBox Grid.Row="2" Grid.Column="1" <ToggleButton Grid.Row="2" Grid.Column="1" IsChecked="{Binding AssociateFileSuffix}"/>
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
<ComboBox Grid.Row="3" Grid.Column="1"
SelectedItem="{Binding AppLanguage}" SelectedItem="{Binding AppLanguage}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/> ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>