@@ -1,5 +1,12 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.15.19
|
||||
|
||||
- 模型重载后选中最后一个重载模型
|
||||
- 修复 3.4 版本可能的奇数顶点数组导致的越界崩溃问题
|
||||
- 移除参数自动记录中的背景图片路径
|
||||
- 增加测试性桌面投影功能
|
||||
|
||||
## v0.15.18
|
||||
|
||||
- 完善窗口日志颜色标记
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
171
SFMLRenderer/SFMLRenderWindow.cs
Normal file
171
SFMLRenderer/SFMLRenderWindow.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.4</Version>
|
||||
<Version>0.15.19</Version>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.18</Version>
|
||||
<Version>0.15.19</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -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();
|
||||
|
||||
/// <summary>
|
||||
/// 默认的全局纹理加载器
|
||||
/// </summary>
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -73,6 +73,9 @@ namespace SpineViewer.Models
|
||||
|
||||
#region 程序选项
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _wallpaperView;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _renderSelectedOnly;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
<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_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_ShowAxis">Show Axis</s:String>
|
||||
<s:String x:Key="Str_BackgroundColor">Background Color</s:String>
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
<s:String x:Key="Str_MaxFps">最大FPS</s:String>
|
||||
<s:String x:Key="Str_MaxFpsTooltip">プレビュー画面の最大フレームレート。0 に設定すると制限なし。</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_ShowAxis">座標軸を表示</s:String>
|
||||
<s:String x:Key="Str_BackgroundColor">背景色</s:String>
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
<s:String x:Key="Str_MaxFps">最大帧率</s:String>
|
||||
<s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</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_ShowAxis">显示坐标轴</s:String>
|
||||
<s:String x:Key="Str_BackgroundColor">背景颜色</s:String>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.18</Version>
|
||||
<Version>0.15.19</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<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
|
||||
{
|
||||
get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly;
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
private readonly MainWindowViewModel _vmMain;
|
||||
private readonly ObservableCollectionWithLock<SpineObjectModel> _models;
|
||||
private readonly ISFMLRenderer _renderer;
|
||||
private readonly ISFMLRenderer _wallpaperRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// 被选中对象的背景颜色
|
||||
@@ -90,6 +91,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
_vmMain = vmMain;
|
||||
_models = _vmMain.SpineObjects;
|
||||
_renderer = _vmMain.SFMLRenderer;
|
||||
_wallpaperRenderer = _vmMain.WallpaperRenderer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -334,27 +334,31 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
|
||||
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 => { 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 => { return args is not null && args.OfType<SkinViewModel>().Any(); }
|
||||
);
|
||||
private RelayCommand<IList?> _cmd_DisableSkins;
|
||||
|
||||
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 => { 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 => { return args is not null && args.OfType<SlotViewModel>().Any(); }
|
||||
);
|
||||
private RelayCommand<IList?> _cmd_DisableSlots;
|
||||
|
||||
public ObservableCollection<AnimationTrackViewModel> AnimationTracks => _animationTracks;
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<MenuItem Header="{DynamicResource Str_Diagnostics}" Command="{Binding Cmd_ShowDiagnosticsDialog}"/>
|
||||
<Separator/>
|
||||
<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 Header="{DynamicResource Str_Experiment}"/>-->
|
||||
</Menu>
|
||||
@@ -940,7 +940,7 @@
|
||||
MouseDoubleClick="_notifyIcon_MouseDoubleClick">
|
||||
<hc:NotifyIcon.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/>
|
||||
<MenuItem Header="{DynamicResource Str_Exit}" Command="{Binding Cmd_Exit}"/>
|
||||
</ContextMenu>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 给管道通信提供的打开文件外部调用方法
|
||||
/// </summary>
|
||||
public void OpenFiles(IEnumerable<string> filePaths)
|
||||
{
|
||||
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
|
||||
_vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
||||
_vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
||||
_vm.PreferenceViewModel.PropertyChanged += PreferenceViewModel_PropertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <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 事件处理
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -144,16 +144,20 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/>
|
||||
<ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/>
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_WallpaperView}"/>
|
||||
<ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding WallpaperView}"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_AssociateFileSuffix}"/>
|
||||
<ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding AssociateFileSuffix}"/>
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/>
|
||||
<ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1"
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_AssociateFileSuffix}"/>
|
||||
<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}"
|
||||
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user