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

v0.16.12
This commit is contained in:
ww-rm
2025-11-09 23:23:22 +08:00
committed by GitHub
62 changed files with 609 additions and 332 deletions

View File

@@ -1,5 +1,13 @@
# CHANGELOG # CHANGELOG
## v0.16.12
- 修复 label 控件文字显示问题
- 增强报错日志输出
- 增加实时帧率显示
- 首选项增加预览画面和投影最大帧率设置,移除用户状态和工作区帧率记忆
- 优化某些性能
## v0.16.11 ## v0.16.11
- 增加 shift 切换缩放倍数 - 增加 shift 切换缩放倍数

View File

@@ -7,6 +7,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Threading; using System.Windows.Threading;
using Win32Natives;
namespace SFMLRenderer namespace SFMLRenderer
{ {
@@ -19,6 +20,14 @@ namespace SFMLRenderer
SetActive(false); SetActive(false);
_timer.Tick += (s, e) => DispatchEvents(); _timer.Tick += (s, e) => DispatchEvents();
_timer.Start(); _timer.Start();
SetVisible(false);
var handle = SystemHandle;
var exStyle = User32.GetWindowLong(handle, User32.GWL_EXSTYLE) | User32.WS_EX_LAYERED;
User32.SetWindowLong(handle, User32.GWL_EXSTYLE, exStyle);
User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA);
RendererCreated?.Invoke(this, EventArgs.Empty); RendererCreated?.Invoke(this, EventArgs.Empty);
} }

View File

@@ -20,4 +20,8 @@
<PackageReference Include="SFML.Net" Version="2.6.1" /> <PackageReference Include="SFML.Net" Version="2.6.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Win32Natives\Win32Natives.csproj" />
</ItemGroup>
</Project> </Project>

View File

@@ -92,7 +92,7 @@ namespace Spine.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message); _logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message);
} }
} }

View File

@@ -144,7 +144,7 @@ namespace Spine.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message); _logger.Error("Failed to export {0} {1}, {2}", _format, output, ex.Message);
} }
} }

View File

@@ -47,7 +47,7 @@ namespace Spine.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to save frame {0}, {1}", savePath, ex.Message); _logger.Error("Failed to save frame {0}, {1}", savePath, ex.Message);
} }
finally finally

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V21
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'"); throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
} }
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V21
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V21
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V21
catch (Exception ex) catch (Exception ex)
{ {
_atlas.Dispose(); _atlas.Dispose();
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}"); throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
} }

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V34
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'"); throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
} }
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V34
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V34
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V34
catch (Exception ex) catch (Exception ex)
{ {
_atlas.Dispose(); _atlas.Dispose();
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}"); throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
} }

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V35
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'"); throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
} }
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V35
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V35
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V35
catch (Exception ex) catch (Exception ex)
{ {
_atlas.Dispose(); _atlas.Dispose();
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}"); throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
} }

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V36
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'"); throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
} }
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V36
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V36
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V36
catch (Exception ex) catch (Exception ex)
{ {
_atlas.Dispose(); _atlas.Dispose();
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}"); throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
} }

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V37
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'"); throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
} }
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V37
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V37
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V37
catch (Exception ex) catch (Exception ex)
{ {
_atlas.Dispose(); _atlas.Dispose();
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}"); throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
} }

View File

@@ -37,7 +37,7 @@ namespace Spine.Implementations.V38
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'"); throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
} }
@@ -51,7 +51,7 @@ namespace Spine.Implementations.V38
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -63,7 +63,7 @@ namespace Spine.Implementations.V38
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -71,7 +71,7 @@ namespace Spine.Implementations.V38
catch (Exception ex) catch (Exception ex)
{ {
_atlas.Dispose(); _atlas.Dispose();
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}"); throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
} }

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V40
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'"); throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
} }
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V40
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V40
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V40
catch (Exception ex) catch (Exception ex)
{ {
_atlas.Dispose(); _atlas.Dispose();
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}"); throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
} }

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V41
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'"); throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
} }
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V41
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V41
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V41
catch (Exception ex) catch (Exception ex)
{ {
_atlas.Dispose(); _atlas.Dispose();
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}"); throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
} }

View File

@@ -36,7 +36,7 @@ namespace Spine.Implementations.V42
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'"); throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
} }
@@ -50,7 +50,7 @@ namespace Spine.Implementations.V42
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -62,7 +62,7 @@ namespace Spine.Implementations.V42
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath); _skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
} }
} }
@@ -70,7 +70,7 @@ namespace Spine.Implementations.V42
catch (Exception ex) catch (Exception ex)
{ {
_atlas.Dispose(); _atlas.Dispose();
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}"); throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
} }

View File

@@ -269,7 +269,7 @@ namespace Spine
if (hit && LogHitSlots) if (hit && LogHitSlots)
{ {
_logger.Debug("Hit ({0}): [{1}]", self.Name, hitSlotName); _logger.Info("Hit ({0}): [{1}]", self.Name, hitSlotName);
} }
return hit; return hit;
} }

View File

@@ -82,7 +82,7 @@ namespace Spine
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Warn("Failed to detect version for skel {0}, try all available versions", skelPath); _logger.Warn("Failed to detect version for skel {0}, try all available versions", skelPath);
} }
} }
@@ -118,7 +118,7 @@ namespace Spine
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
throw new InvalidDataException($"Failed to load spine with version '{version}'"); throw new InvalidDataException($"Failed to load spine with version '{version}'");
} }
} }

View File

@@ -43,6 +43,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpineRuntime35", "SpineRunt
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpineRuntime34", "SpineRuntimes\SpineRuntime34\SpineRuntime34.csproj", "{348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpineRuntime34", "SpineRuntimes\SpineRuntime34\SpineRuntime34.csproj", "{348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Win32Natives", "Win32Natives\Win32Natives.csproj", "{48864874-7307-950E-A667-62BB66357C62}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
@@ -105,6 +107,10 @@ Global
{348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}.Debug|x64.Build.0 = Debug|x64 {348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}.Debug|x64.Build.0 = Debug|x64
{348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}.Release|x64.ActiveCfg = Release|x64 {348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}.Release|x64.ActiveCfg = Release|x64
{348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}.Release|x64.Build.0 = Release|x64 {348605F7-3FF4-1DE0-4B91-7AEFE7BC5C55}.Release|x64.Build.0 = Release|x64
{48864874-7307-950E-A667-62BB66357C62}.Debug|x64.ActiveCfg = Debug|x64
{48864874-7307-950E-A667-62BB66357C62}.Debug|x64.Build.0 = Debug|x64
{48864874-7307-950E-A667-62BB66357C62}.Release|x64.ActiveCfg = Release|x64
{48864874-7307-950E-A667-62BB66357C62}.Release|x64.Build.0 = Release|x64
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -1,7 +1,8 @@
using Microsoft.Win32; using Microsoft.Win32;
using NLog; using NLog;
using SpineViewer.Natives; using Win32Natives;
using SpineViewer.Resources; using SpineViewer.Resources;
using SpineViewer.Services;
using SpineViewer.ViewModels.MainWindow; using SpineViewer.ViewModels.MainWindow;
using SpineViewer.Views; using SpineViewer.Views;
using System.Collections.Frozen; using System.Collections.Frozen;
@@ -14,6 +15,7 @@ using System.IO.Pipes;
using System.Reflection; using System.Reflection;
using System.Windows; using System.Windows;
using System.Windows.Interop; using System.Windows.Interop;
using SpineViewer.Extensions;
namespace SpineViewer namespace SpineViewer
{ {
@@ -33,8 +35,8 @@ namespace SpineViewer
#endif #endif
public const string AutoRunFlag = "--autorun"; public const string AutoRunFlag = "--autorun";
private const string MutexName = "__SpineViewerInstance__"; private const string MutexName = $"__{AppName}_Instance__";
private const string PipeName = "__SpineViewerPipe__"; private const string PipeName = $"_{AppName}_Pipe__";
public static readonly string ProcessPath = Environment.ProcessPath; public static readonly string ProcessPath = Environment.ProcessPath;
public static readonly string ProcessDirectory = Path.GetDirectoryName(Environment.ProcessPath); public static readonly string ProcessDirectory = Path.GetDirectoryName(Environment.ProcessPath);
@@ -58,13 +60,16 @@ namespace SpineViewer
AppDomain.CurrentDomain.UnhandledException += (s, e) => AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{ {
_logger.Debug(e.ExceptionObject.ToString());
_logger.Fatal("Unhandled exception: {0}", e.ExceptionObject); _logger.Fatal("Unhandled exception: {0}", e.ExceptionObject);
MessagePopupService.Error(e.ExceptionObject.ToString());
}; };
TaskScheduler.UnobservedTaskException += (s, e) => TaskScheduler.UnobservedTaskException += (s, e) =>
{ {
_logger.Trace(e.Exception.ToString()); _logger.Debug(e.Exception.ToString());
_logger.Error("Unobserved task exception: {0}", e.Exception.Message); _logger.Fatal("Unobserved task exception: {0}", e.Exception.Message);
e.SetObserved(); e.SetObserved();
MessagePopupService.Error(e.Exception.ToString());
}; };
// 单例模式加 IPC 通信 // 单例模式加 IPC 通信
@@ -130,7 +135,7 @@ namespace SpineViewer
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to pass command line args to existed instance, {0}", ex.Message); _logger.Error("Failed to pass command line args to existed instance, {0}", ex.Message);
} }
} }
@@ -191,7 +196,7 @@ namespace SpineViewer
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to process arguments, {0}", ex.Message); _logger.Error("Failed to process arguments, {0}", ex.Message);
} }
} }
@@ -212,9 +217,10 @@ namespace SpineViewer
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{ {
_logger.Trace(e.Exception.ToString()); _logger.Debug(e.Exception.ToString());
_logger.Error("Dispatcher unhandled exception: {0}", e.Exception.Message); _logger.Fatal("Dispatcher unhandled exception: {0}", e.Exception.Message);
e.Handled = true; e.Handled = true;
MessagePopupService.Error(e.Exception.ToString());
} }
public bool AutoRun public bool AutoRun
@@ -231,7 +237,7 @@ namespace SpineViewer
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to query autorun registry key, {0}", ex.Message); _logger.Error("Failed to query autorun registry key, {0}", ex.Message);
return false; return false;
} }
@@ -259,7 +265,7 @@ namespace SpineViewer
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to set autorun registry key, {0}", ex.Message); _logger.Error("Failed to set autorun registry key, {0}", ex.Message);
} }
} }
@@ -343,7 +349,7 @@ namespace SpineViewer
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to switch language to {0}, {1}", value, ex.Message); _logger.Error("Failed to switch language to {0}, {1}", value, ex.Message);
} }
} }
@@ -360,14 +366,13 @@ namespace SpineViewer
{ {
Resources.MergedDictionaries.Add(new() { Source = new(uri, UriKind.Relative) }); Resources.MergedDictionaries.Add(new() { Source = new(uri, UriKind.Relative) });
Resources.MergedDictionaries.Add(new() { Source = new("Resources/Theme.xaml", UriKind.Relative) }); Resources.MergedDictionaries.Add(new() { Source = new("Resources/Theme.xaml", UriKind.Relative) });
var hwnd = new WindowInteropHelper(Current.MainWindow).Handle; Current.MainWindow.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); Current.MainWindow.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
_skin = value; _skin = value;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to switch skin to {0}, {1}", value, ex.Message); _logger.Error("Failed to switch skin to {0}, {1}", value, ex.Message);
} }
} }

View File

@@ -1,6 +1,4 @@
using SFML.Graphics; using SkiaSharp;
using SFML.System;
using SkiaSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -9,29 +7,31 @@ using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Win32Natives;
namespace SpineViewer.Extensions namespace SpineViewer.Extensions
{ {
public static class WpfExtension public static class WpfExtension
{ {
public static FloatRect ToFloatRect(this Rect self) public static SFML.Graphics.FloatRect ToFloatRect(this Rect self)
{ {
return new((float)self.X, (float)self.Y, (float)self.Width, (float)self.Height); return new((float)self.X, (float)self.Y, (float)self.Width, (float)self.Height);
} }
public static Vector2f ToVector2f(this Size self) public static SFML.System.Vector2f ToVector2f(this Size self)
{ {
return new((float)self.Width, (float)self.Height); return new((float)self.Width, (float)self.Height);
} }
public static Vector2u ToVector2u(this Size self) public static SFML.System.Vector2u ToVector2u(this Size self)
{ {
return new((uint)self.Width, (uint)self.Height); return new((uint)self.Width, (uint)self.Height);
} }
public static Vector2i ToVector2i(this Size self) public static SFML.System.Vector2i ToVector2i(this Size self)
{ {
return new((int)self.Width, (int)self.Height); return new((int)self.Width, (int)self.Height);
} }
@@ -60,6 +60,18 @@ namespace SpineViewer.Extensions
return wb; return wb;
} }
public static void SetWindowTextColor(this Window self, Color color)
{
var hwnd = new WindowInteropHelper(self).Handle;
Dwmapi.SetWindowTextColor(hwnd, color.R, color.G, color.B);
}
public static void SetWindowCaptionColor(this Window self, Color color)
{
var hwnd = new WindowInteropHelper(self).Handle;
Dwmapi.SetWindowCaptionColor(hwnd, color.R, color.G, color.B);
}
//public static void SaveToFile(this BitmapSource bitmap, string path) //public static void SaveToFile(this BitmapSource bitmap, string path)
//{ //{
// var ext = Path.GetExtension(path)?.ToLowerInvariant(); // var ext = Path.GetExtension(path)?.ToLowerInvariant();

View File

@@ -73,6 +73,22 @@ namespace SpineViewer.Models
#endregion #endregion
#region
[ObservableProperty]
private bool _renderSelectedOnly;
[ObservableProperty]
private HitTestLevel _hitTestLevel;
[ObservableProperty]
private bool _logHitSlots;
[ObservableProperty]
private uint _maxFps = 30;
#endregion
#region #region
public RelayCommand Cmd_SelectAutoRunWorkspaceConfigPath => _cmd_SelectAutoRunWorkspaceConfigPath ??= new(() => public RelayCommand Cmd_SelectAutoRunWorkspaceConfigPath => _cmd_SelectAutoRunWorkspaceConfigPath ??= new(() =>
@@ -89,18 +105,12 @@ namespace SpineViewer.Models
[ObservableProperty] [ObservableProperty]
private AppSkin _appSkin; private AppSkin _appSkin;
[ObservableProperty]
private bool _renderSelectedOnly;
[ObservableProperty]
private HitTestLevel _hitTestLevel;
[ObservableProperty]
private bool _logHitSlots;
[ObservableProperty] [ObservableProperty]
private bool _wallpaperView; private bool _wallpaperView;
[ObservableProperty]
private uint _wallpaperMaxFps = 30;
[ObservableProperty] [ObservableProperty]
private bool _closeToTray; private bool _closeToTray;

View File

@@ -43,7 +43,6 @@ namespace SpineViewer.Models
public uint ResolutionX { get; set; } = 1500; public uint ResolutionX { get; set; } = 1500;
public uint ResolutionY { get; set; } = 1000; public uint ResolutionY { get; set; } = 1000;
public uint MaxFps { get; set; } = 30;
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);

View File

@@ -34,8 +34,6 @@ namespace SpineViewer.Models
public bool FlipY { get; set; } = true; public bool FlipY { get; set; } = true;
public uint MaxFps { get; set; } = 30;
public float Speed { get; set; } = 1f; public float Speed { get; set; } = 1f;
public bool ShowAxis { get; set; } = true; public bool ShowAxis { get; set; } = true;

View File

@@ -123,6 +123,8 @@
<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_WallpaperView">Wallpaper View</s:String>
<s:String x:Key="Str_WallpaperMaxFps">Max FPS of Wallpaper View</s:String>
<s:String x:Key="Str_WallpaperMaxFpsTooltip">Maximum frame rate of the wallpaper view. Set to 0 for no limit.</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_HitTestLevel">Hit Test Accuracy Level</s:String> <s:String x:Key="Str_HitTestLevel">Hit Test Accuracy Level</s:String>
<s:String x:Key="Str_LogHitSlots">Output Hit Test Slot Names</s:String> <s:String x:Key="Str_LogHitSlots">Output Hit Test Slot Names</s:String>
@@ -140,6 +142,9 @@
<s:String x:Key="Str_ForwardFastTooltip">Forward 10 Frames</s:String> <s:String x:Key="Str_ForwardFastTooltip">Forward 10 Frames</s:String>
<s:String x:Key="Str_FullScreenTooltip">Window/Fullscreen; F11</s:String> <s:String x:Key="Str_FullScreenTooltip">Window/Fullscreen; F11</s:String>
<!-- 日志框下方附加信息 -->
<s:String x:Key="Str_RealTimeFps">Real-time FPS: {0:F1}/{1:F1}</s:String>
<!-- 弹窗文本 --> <!-- 弹窗文本 -->
<s:String x:Key="Str_OK">OK</s:String> <s:String x:Key="Str_OK">OK</s:String>
<s:String x:Key="Str_Cancel">Cancel</s:String> <s:String x:Key="Str_Cancel">Cancel</s:String>

View File

@@ -123,6 +123,8 @@
<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_WallpaperView">壁紙表示</s:String>
<s:String x:Key="Str_WallpaperMaxFps">壁紙ビューの最大FPS</s:String>
<s:String x:Key="Str_WallpaperMaxFpsTooltip">壁紙ビューの最大フレームレート。0に設定すると制限がなし。</s:String>
<s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String> <s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String>
<s:String x:Key="Str_HitTestLevel">ヒットテスト精度レベル</s:String> <s:String x:Key="Str_HitTestLevel">ヒットテスト精度レベル</s:String>
<s:String x:Key="Str_LogHitSlots">ヒットテスト結果のスロット名を出力</s:String> <s:String x:Key="Str_LogHitSlots">ヒットテスト結果のスロット名を出力</s:String>
@@ -140,6 +142,9 @@
<s:String x:Key="Str_ForwardFastTooltip">10フレーム進める</s:String> <s:String x:Key="Str_ForwardFastTooltip">10フレーム進める</s:String>
<s:String x:Key="Str_FullScreenTooltip">ウィンドウ/フルスクリーン; F11</s:String> <s:String x:Key="Str_FullScreenTooltip">ウィンドウ/フルスクリーン; F11</s:String>
<!-- 日志框下方附加信息 -->
<s:String x:Key="Str_RealTimeFps">リアルタイムFPS{0:F1}/{1:F1}</s:String>
<!-- 弹窗文本 --> <!-- 弹窗文本 -->
<s:String x:Key="Str_OK">OK</s:String> <s:String x:Key="Str_OK">OK</s:String>
<s:String x:Key="Str_Cancel">キャンセル</s:String> <s:String x:Key="Str_Cancel">キャンセル</s:String>

View File

@@ -123,6 +123,8 @@
<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_WallpaperView">桌面投影</s:String>
<s:String x:Key="Str_WallpaperMaxFps">桌面投影最大帧率</s:String>
<s:String x:Key="Str_WallpaperMaxFpsTooltip">桌面投影的最大帧率,设置为 0 时则无帧率限制</s:String>
<s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String> <s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String>
<s:String x:Key="Str_HitTestLevel">命中检测准确度等级</s:String> <s:String x:Key="Str_HitTestLevel">命中检测准确度等级</s:String>
<s:String x:Key="Str_LogHitSlots">输出命中检测结果的插槽名称</s:String> <s:String x:Key="Str_LogHitSlots">输出命中检测结果的插槽名称</s:String>
@@ -140,6 +142,9 @@
<s:String x:Key="Str_ForwardFastTooltip">快进 10 帧</s:String> <s:String x:Key="Str_ForwardFastTooltip">快进 10 帧</s:String>
<s:String x:Key="Str_FullScreenTooltip">窗口/全屏; F11</s:String> <s:String x:Key="Str_FullScreenTooltip">窗口/全屏; F11</s:String>
<!-- 日志框下方附加信息 -->
<s:String x:Key="Str_RealTimeFps">实时帧率:{0:F1}/{1:F1}</s:String>
<!-- 弹窗文本 --> <!-- 弹窗文本 -->
<s:String x:Key="Str_OK">确认</s:String> <s:String x:Key="Str_OK">确认</s:String>
<s:String x:Key="Str_Cancel">取消</s:String> <s:String x:Key="Str_Cancel">取消</s:String>

View File

@@ -11,7 +11,7 @@
<utils:StringFormatMultiValueConverter x:Key="StrFmtCvter"/> <utils:StringFormatMultiValueConverter x:Key="StrFmtCvter"/>
<utils:BackgroundToForegroundConverter x:Key="Bg2FgCvter"/> <utils:BackgroundToForegroundConverter x:Key="Bg2FgCvter"/>
<Style x:Key="MyGridSplitterBaseStyle" TargetType="{x:Type GridSplitter}"> <Style x:Key="MyGridSplitterBaseStyle" TargetType="GridSplitter">
<Setter Property="Background" Value="{DynamicResource SecondaryBorderBrush}"/> <Setter Property="Background" Value="{DynamicResource SecondaryBorderBrush}"/>
<Setter Property="ShowsPreview" Value="False"/> <Setter Property="ShowsPreview" Value="False"/>
<Style.Triggers> <Style.Triggers>
@@ -28,17 +28,17 @@
</Style.Triggers> </Style.Triggers>
</Style> </Style>
<Style x:Key="MyToggleButtonBaseStyle" TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource ToggleButtonSwitch}"> <Style x:Key="MyToggleButtonBaseStyle" TargetType="ToggleButton" BasedOn="{StaticResource ToggleButtonSwitch}">
<Setter Property="hc:VisualElement.HighlightBrush" Value="{DynamicResource DarkSuccessBrush}"/> <Setter Property="hc:VisualElement.HighlightBrush" Value="{DynamicResource DarkSuccessBrush}"/>
</Style> </Style>
<Style x:Key="MyListBoxBaseStyle" TargetType="{x:Type ListBox}" BasedOn="{StaticResource ListBoxBaseStyle}"> <Style x:Key="MyListBoxBaseStyle" TargetType="ListBox" BasedOn="{StaticResource ListBoxBaseStyle}">
<Setter Property="SelectionMode" Value="Extended"/> <Setter Property="SelectionMode" Value="Extended"/>
<!--<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>--> <!--<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>-->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Visible"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Visible"/>
<Setter Property="ItemContainerStyle"> <Setter Property="ItemContainerStyle">
<Setter.Value> <Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource ListBoxItemBaseStyle}"> <Style TargetType="ListBoxItem" BasedOn="{StaticResource ListBoxItemBaseStyle}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/> <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="0"/> <Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/> <Setter Property="Margin" Value="0"/>
@@ -47,26 +47,26 @@
</Setter> </Setter>
</Style> </Style>
<Style x:Key="MyListViewBaseStyle" TargetType="{x:Type ListView}" BasedOn="{StaticResource ListViewBaseStyle}"> <Style x:Key="MyListViewBaseStyle" TargetType="ListView" BasedOn="{StaticResource ListViewBaseStyle}">
<Setter Property="SelectionMode" Value="Extended"/> <Setter Property="SelectionMode" Value="Extended"/>
<!--<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>--> <!--<Setter Property="VirtualizingPanel.IsVirtualizing" Value="False"/>-->
<Setter Property="Background" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource ListViewItemBaseStyle.Small}"/> <Setter Property="ItemContainerStyle" Value="{StaticResource ListViewItemBaseStyle.Small}"/>
</Style> </Style>
<Style x:Key="MyGroupBoxBaseStyle" TargetType="{x:Type GroupBox}" BasedOn="{StaticResource GroupBoxTab}"> <Style x:Key="MyGroupBoxBaseStyle" TargetType="GroupBox" BasedOn="{StaticResource GroupBoxTab}">
<Setter Property="Background" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/>
<Setter Property="hc:TitleElement.Background" Value="Transparent"/> <Setter Property="hc:TitleElement.Background" Value="Transparent"/>
</Style> </Style>
<Style x:Key="MyLogRichTextBoxStyle" TargetType="{x:Type RichTextBox}" BasedOn="{StaticResource RichTextBoxBaseStyle}"> <Style x:Key="MyLogRichTextBoxStyle" TargetType="RichTextBox" BasedOn="{StaticResource RichTextBoxBaseStyle}">
<Setter Property="IsReadOnly" Value="True"/> <Setter Property="IsReadOnly" Value="True"/>
<Setter Property="FontFamily" Value="Consolas"/> <Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Block.LineHeight" Value="3"/> <Setter Property="Block.LineHeight" Value="3"/>
<Setter Property="VerticalScrollBarVisibility" Value="Visible"/> <Setter Property="VerticalScrollBarVisibility" Value="Visible"/>
</Style> </Style>
<Style x:Key="MyVerticalScrollViewerBaseStyle" TargetType="{x:Type ScrollViewer}" BasedOn="{StaticResource ScrollViewerNativeBaseStyle}"> <Style x:Key="MyVerticalScrollViewerBaseStyle" TargetType="ScrollViewer" BasedOn="{StaticResource ScrollViewerNativeBaseStyle}">
<Setter Property="Padding" Value="0"/> <Setter Property="Padding" Value="0"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
<Style.Triggers> <Style.Triggers>
@@ -99,10 +99,24 @@
</Style.Triggers> </Style.Triggers>
</Style> </Style>
<Style TargetType="{x:Type GridSplitter}" BasedOn="{StaticResource MyGridSplitterBaseStyle}"/> <Style x:Key="MyLabelStyle" TargetType="Label" BasedOn="{StaticResource LabelDefault}">
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource MyToggleButtonBaseStyle}"/> <Setter Property="Template">
<Style TargetType="{x:Type ListBox}" BasedOn="{StaticResource MyListBoxBaseStyle}"/> <Setter.Value>
<Style TargetType="{x:Type ListView}" BasedOn="{StaticResource MyListViewBaseStyle}"/> <ControlTemplate TargetType="Label">
<Style TargetType="{x:Type GroupBox}" BasedOn="{StaticResource MyGroupBoxBaseStyle}"/> <Border CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<!-- 直接复制的原本 LabelDefault 的样式, 但是去除了 RecognizesAccessKey 防止不显示第一个下划线 -->
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="False" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="GridSplitter" BasedOn="{StaticResource MyGridSplitterBaseStyle}"/>
<Style TargetType="ToggleButton" BasedOn="{StaticResource MyToggleButtonBaseStyle}"/>
<Style TargetType="ListBox" BasedOn="{StaticResource MyListBoxBaseStyle}"/>
<Style TargetType="ListView" BasedOn="{StaticResource MyListViewBaseStyle}"/>
<Style TargetType="GroupBox" BasedOn="{StaticResource MyGroupBoxBaseStyle}"/>
<Style TargetType="Label" BasedOn="{StaticResource MyLabelStyle}"/>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -8,7 +8,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.16.11</Version> <Version>0.16.12</Version>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
</PropertyGroup> </PropertyGroup>
@@ -41,5 +41,6 @@
<ProjectReference Include="..\NLog.Windows.Wpf\NLog.Windows.Wpf.csproj" /> <ProjectReference Include="..\NLog.Windows.Wpf\NLog.Windows.Wpf.csproj" />
<ProjectReference Include="..\SFMLRenderer\SFMLRenderer.csproj" /> <ProjectReference Include="..\SFMLRenderer\SFMLRenderer.csproj" />
<ProjectReference Include="..\Spine\Spine.csproj" /> <ProjectReference Include="..\Spine\Spine.csproj" />
<ProjectReference Include="..\Win32Natives\Win32Natives.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -65,7 +65,7 @@ namespace SpineViewer.Utils
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to read json file {0}, {1}", path, ex.Message); _logger.Error("Failed to read json file {0}, {1}", path, ex.Message);
} }
} }
@@ -86,7 +86,7 @@ namespace SpineViewer.Utils
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to save json file {0}, {1}", path, ex.Message); _logger.Error("Failed to save json file {0}, {1}", path, ex.Message);
return false; return false;
} }
@@ -101,7 +101,7 @@ namespace SpineViewer.Utils
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to serialize json object {0}", ex.Message); _logger.Error("Failed to serialize json object {0}", ex.Message);
return string.Empty; return string.Empty;
} }

View File

@@ -112,7 +112,7 @@ namespace SpineViewer.ViewModels.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message); _logger.Error("Failed to export {0}, {1}", output, ex.Message);
} }
_vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None; _vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
@@ -169,7 +169,7 @@ namespace SpineViewer.ViewModels.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message); _logger.Error("Failed to export {0}, {1}", output, ex.Message);
} }
} }

View File

@@ -149,7 +149,7 @@ namespace SpineViewer.ViewModels.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message); _logger.Error("Failed to export {0}, {1}", output, ex.Message);
} }
_vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None; _vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
@@ -206,7 +206,7 @@ namespace SpineViewer.ViewModels.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message); _logger.Error("Failed to export {0}, {1}", output, ex.Message);
} }
} }

View File

@@ -85,7 +85,7 @@ namespace SpineViewer.ViewModels.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message); _logger.Error("Failed to export {0}, {1}", output, ex.Message);
} }
} }
@@ -121,7 +121,7 @@ namespace SpineViewer.ViewModels.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message); _logger.Error("Failed to export {0}, {1}", output, ex.Message);
} }
done++; done++;

View File

@@ -76,7 +76,7 @@ namespace SpineViewer.ViewModels.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message); _logger.Error("Failed to export {0}, {1}", output, ex.Message);
} }
_vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None; _vmMain.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
@@ -133,7 +133,7 @@ namespace SpineViewer.ViewModels.Exporters
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to export {0}, {1}", output, ex.Message); _logger.Error("Failed to export {0}, {1}", output, ex.Message);
} }
} }

View File

@@ -169,7 +169,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to generate preview: {0}, {1}", m.PreviewFilePath, ex.Message); _logger.Error("Failed to generate preview: {0}, {1}", m.PreviewFilePath, ex.Message);
} }
_logger.LogCurrentProcessMemoryUsage(); _logger.LogCurrentProcessMemoryUsage();
@@ -221,7 +221,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to generate preview: {0}, {1}", m.PreviewFilePath, ex.Message); _logger.Error("Failed to generate preview: {0}, {1}", m.PreviewFilePath, ex.Message);
error++; error++;
} }
@@ -261,7 +261,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to delete preview: {0}, {1}", m.PreviewFilePath, ex.Message); _logger.Error("Failed to delete preview: {0}, {1}", m.PreviewFilePath, ex.Message);
} }
} }
@@ -302,7 +302,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to delete preview: {0}, {1}", m.PreviewFilePath, ex.Message); _logger.Error("Failed to delete preview: {0}, {1}", m.PreviewFilePath, ex.Message);
error++; error++;
} }
@@ -340,7 +340,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to enumerate files in dir: {0}, {1}", _currentDirectory, ex.Message); _logger.Error("Failed to enumerate files in dir: {0}, {1}", _currentDirectory, ex.Message);
} }
} }
@@ -408,7 +408,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Warn("Failed to load preview image for {0}, {1}", FullPath, ex.Message); _logger.Warn("Failed to load preview image for {0}, {1}", FullPath, ex.Message);
return null; return null;
} }

View File

@@ -5,7 +5,6 @@ using NLog;
using Spine; using Spine;
using Spine.Implementations; using Spine.Implementations;
using SpineViewer.Models; using SpineViewer.Models;
using SpineViewer.Natives;
using SpineViewer.Services; using SpineViewer.Services;
using SpineViewer.Utils; using SpineViewer.Utils;
using System; using System;
@@ -76,7 +75,7 @@ namespace SpineViewer.ViewModels.MainWindow
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to load some prefereneces, {0}", ex.Message); _logger.Error("Failed to load some prefereneces, {0}", ex.Message);
} }
} }
@@ -108,12 +107,15 @@ namespace SpineViewer.ViewModels.MainWindow
DebugPoints = DebugPoints, DebugPoints = DebugPoints,
DebugClippings = DebugClippings, DebugClippings = DebugClippings,
AppLanguage = AppLanguage,
AppSkin = AppSkin,
RenderSelectedOnly = RenderSelectedOnly, RenderSelectedOnly = RenderSelectedOnly,
HitTestLevel = HitTestLevel, HitTestLevel = HitTestLevel,
LogHitSlots = LogHitSlots, LogHitSlots = LogHitSlots,
MaxFps = MaxFps,
AppLanguage = AppLanguage,
AppSkin = AppSkin,
WallpaperView = WallpaperView, WallpaperView = WallpaperView,
WallpaperMaxFps = WallpaperMaxFps,
CloseToTray = CloseToTray, CloseToTray = CloseToTray,
AutoRun = AutoRun, AutoRun = AutoRun,
AutoRunWorkspaceConfigPath = AutoRunWorkspaceConfigPath, AutoRunWorkspaceConfigPath = AutoRunWorkspaceConfigPath,
@@ -140,12 +142,15 @@ namespace SpineViewer.ViewModels.MainWindow
DebugPoints = value.DebugPoints; DebugPoints = value.DebugPoints;
DebugClippings = value.DebugClippings; DebugClippings = value.DebugClippings;
AppLanguage = value.AppLanguage;
AppSkin = value.AppSkin;
RenderSelectedOnly = value.RenderSelectedOnly; RenderSelectedOnly = value.RenderSelectedOnly;
HitTestLevel = value.HitTestLevel; HitTestLevel = value.HitTestLevel;
LogHitSlots = value.LogHitSlots; LogHitSlots = value.LogHitSlots;
MaxFps = value.MaxFps;
AppLanguage = value.AppLanguage;
AppSkin = value.AppSkin;
WallpaperView = value.WallpaperView; WallpaperView = value.WallpaperView;
WallpaperMaxFps = value.WallpaperMaxFps;
CloseToTray = value.CloseToTray; CloseToTray = value.CloseToTray;
AutoRun = value.AutoRun; AutoRun = value.AutoRun;
AutoRunWorkspaceConfigPath = value.AutoRunWorkspaceConfigPath; AutoRunWorkspaceConfigPath = value.AutoRunWorkspaceConfigPath;
@@ -251,26 +256,10 @@ namespace SpineViewer.ViewModels.MainWindow
#endregion #endregion
#region #region
public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray();
public static ImmutableArray<AppSkin> AppSkinOptions { get; } = Enum.GetValues<AppSkin>().ToImmutableArray();
public static ImmutableArray<HitTestLevel> HitTestLevelOptions { get; } = Enum.GetValues<HitTestLevel>().ToImmutableArray(); public static ImmutableArray<HitTestLevel> HitTestLevelOptions { get; } = Enum.GetValues<HitTestLevel>().ToImmutableArray();
public AppLanguage AppLanguage
{
get => ((App)App.Current).Language;
set => SetProperty(((App)App.Current).Language, value, v => ((App)App.Current).Language = v);
}
public AppSkin AppSkin
{
get => ((App)App.Current).Skin;
set => SetProperty(((App)App.Current).Skin, value, v => ((App)App.Current).Skin = v);
}
public bool RenderSelectedOnly public bool RenderSelectedOnly
{ {
get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly; get => _vmMain.SFMLRendererViewModel.RenderSelectedOnly;
@@ -289,12 +278,44 @@ namespace SpineViewer.ViewModels.MainWindow
set => SetProperty(SpineExtension.LogHitSlots, value, v => SpineExtension.LogHitSlots = v); set => SetProperty(SpineExtension.LogHitSlots, value, v => SpineExtension.LogHitSlots = v);
} }
public uint MaxFps
{
get => _vmMain.SFMLRendererViewModel.MaxFps;
set => SetProperty(_vmMain.SFMLRendererViewModel.MaxFps, value, v => _vmMain.SFMLRendererViewModel.MaxFps = v);
}
#endregion
#region
public static ImmutableArray<AppLanguage> AppLanguageOptions { get; } = Enum.GetValues<AppLanguage>().ToImmutableArray();
public static ImmutableArray<AppSkin> AppSkinOptions { get; } = Enum.GetValues<AppSkin>().ToImmutableArray();
public AppLanguage AppLanguage
{
get => ((App)App.Current).Language;
set => SetProperty(((App)App.Current).Language, value, v => ((App)App.Current).Language = v);
}
public AppSkin AppSkin
{
get => ((App)App.Current).Skin;
set => SetProperty(((App)App.Current).Skin, value, v => ((App)App.Current).Skin = v);
}
public bool WallpaperView public bool WallpaperView
{ {
get => _vmMain.SFMLRendererViewModel.WallpaperView; get => _vmMain.SFMLRendererViewModel.WallpaperView;
set => SetProperty(_vmMain.SFMLRendererViewModel.WallpaperView, value, v => _vmMain.SFMLRendererViewModel.WallpaperView = v); set => SetProperty(_vmMain.SFMLRendererViewModel.WallpaperView, value, v => _vmMain.SFMLRendererViewModel.WallpaperView = v);
} }
public uint WallpaperMaxFps
{
get => _vmMain.SFMLRendererViewModel.WallpaperMaxFps;
set => SetProperty(_vmMain.SFMLRendererViewModel.WallpaperMaxFps, value, v => _vmMain.SFMLRendererViewModel.WallpaperMaxFps = v);
}
public bool CloseToTray public bool CloseToTray
{ {
get => _vmMain.CloseToTray; get => _vmMain.CloseToTray;

View File

@@ -50,7 +50,7 @@ namespace SpineViewer.ViewModels.MainWindow
/// <summary> /// <summary>
/// 坐标轴顶点缓冲区 /// 坐标轴顶点缓冲区
/// </summary> /// </summary>
private readonly SFML.Graphics.VertexArray _axisVertices = new(SFML.Graphics.PrimitiveType.Lines, 2); // XXX: 暂时未使用 Dispose 释放 private readonly SFML.Graphics.VertexArray _axisVertices = new(SFML.Graphics.PrimitiveType.Lines, 4); // XXX: 暂时未使用 Dispose 释放
/// <summary> /// <summary>
/// 帧间隔计时器 /// 帧间隔计时器
@@ -61,6 +61,7 @@ namespace SpineViewer.ViewModels.MainWindow
/// 渲染任务 /// 渲染任务
/// </summary> /// </summary>
private Task? _renderTask = null; private Task? _renderTask = null;
private Task? _wallpaperRenderTask = null;
private CancellationTokenSource? _cancelToken = null; private CancellationTokenSource? _cancelToken = null;
/// <summary> /// <summary>
@@ -87,6 +88,12 @@ namespace SpineViewer.ViewModels.MainWindow
_models = _vmMain.SpineObjects; _models = _vmMain.SpineObjects;
_renderer = _vmMain.SFMLRenderer; _renderer = _vmMain.SFMLRenderer;
_wallpaperRenderer = _vmMain.WallpaperRenderer; _wallpaperRenderer = _vmMain.WallpaperRenderer;
// 画一个很长的坐标轴, 用 1e9 比较合适
_axisVertices[0] = new(new(-1e9f, 0), _axisColor);
_axisVertices[1] = new(new(1e9f, 0), _axisColor);
_axisVertices[2] = new(new(0, -1e9f), _axisColor);
_axisVertices[3] = new(new(0, 1e9f), _axisColor);
} }
/// <summary> /// <summary>
@@ -153,9 +160,27 @@ namespace SpineViewer.ViewModels.MainWindow
public uint MaxFps public uint MaxFps
{ {
get => _renderer.MaxFps; get => _renderer.MaxFps;
set => SetProperty(_renderer.MaxFps, value, v => _renderer.MaxFps = _wallpaperRenderer.MaxFps = value); set => SetProperty(_renderer.MaxFps, value, v => _renderer.MaxFps = value);
} }
public uint WallpaperMaxFps
{
get => _wallpaperRenderer.MaxFps;
set => SetProperty(_wallpaperRenderer.MaxFps, value, v => _wallpaperRenderer.MaxFps = value);
}
public float RealTimeFps => _realTimeFps;
private float _realTimeFps;
private float _accumFpsTime;
private int _accumFpsCount;
public float WallpaperRealTimeFps => _wallpaperRealTimeFps;
private float _wallpaperRealTimeFps;
private int _accumWallpaperFpsCount;
private readonly object _accumWallpaperFpsCountLock = new();
public float Speed public float Speed
{ {
get => _speed; get => _speed;
@@ -188,7 +213,7 @@ namespace SpineViewer.ViewModels.MainWindow
/// </summary> /// </summary>
private SFML.Graphics.Color _axisColor = SFML.Graphics.Color.White; private SFML.Graphics.Color _axisColor = SFML.Graphics.Color.White;
public string BackgroundImagePath public string? BackgroundImagePath
{ {
get => _backgroundImagePath; get => _backgroundImagePath;
set => SetProperty(_backgroundImagePath, value, v => set => SetProperty(_backgroundImagePath, value, v =>
@@ -237,7 +262,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
}); });
} }
private string _backgroundImagePath; private string? _backgroundImagePath;
public Stretch BackgroundImageMode public Stretch BackgroundImageMode
{ {
@@ -447,26 +472,29 @@ namespace SpineViewer.ViewModels.MainWindow
{ {
if (_renderTask is not null) return; if (_renderTask is not null) return;
_cancelToken = new(); _cancelToken = new();
_renderTask = new Task(RenderTask, _cancelToken.Token, TaskCreationOptions.LongRunning); _renderTask = new(RenderTask, _cancelToken.Token, TaskCreationOptions.LongRunning);
_wallpaperRenderTask = new(WallpaperRenderTask, _cancelToken.Token, TaskCreationOptions.LongRunning);
_renderTask.Start(); _renderTask.Start();
_wallpaperRenderTask.Start();
IsUpdating = true; IsUpdating = true;
} }
public void StopRender() public void StopRender()
{ {
IsUpdating = false; IsUpdating = false;
if (_renderTask is null || _cancelToken is null) return; if (_cancelToken is null || _renderTask is null || _wallpaperRenderTask is null) return;
_cancelToken.Cancel(); _cancelToken.Cancel();
_wallpaperRenderTask.Wait();
_renderTask.Wait(); _renderTask.Wait();
_cancelToken = null; _wallpaperRenderTask = null;
_renderTask = null; _renderTask = null;
_cancelToken = null;
} }
private void RenderTask() private void RenderTask()
{ {
try try
{ {
_wallpaperRenderer.SetActive(true);
_renderer.SetActive(true); _renderer.SetActive(true);
float delta; float delta;
@@ -475,111 +503,211 @@ namespace SpineViewer.ViewModels.MainWindow
delta = _clock.ElapsedTime.AsSeconds(); delta = _clock.ElapsedTime.AsSeconds();
_clock.Restart(); _clock.Restart();
// 停止更新的时候只是时间不前进, 但是坐标变换还是要更新, 否则无法移动对象 UpdateLogicFrame(delta);
if (!_isUpdating) delta = 0; UpdateRenderFrame();
}
}
catch (Exception ex)
{
_logger.Debug(ex.ToString());
_logger.Fatal("Render task stopped, {0}", ex.Message);
MessagePopupService.Error(ex.ToString());
}
finally
{
_renderer.SetActive(false);
}
}
// 加上要快进的量 private void UpdateLogicFrame(float delta)
lock (_forwardDeltaLock) {
// 计算实时帧率, 1 秒刷新一次
_accumFpsTime += delta;
if (_accumFpsTime > 1f)
{
_realTimeFps = _accumFpsCount / _accumFpsTime;
_accumFpsCount = 0;
lock (_accumWallpaperFpsCountLock)
{
_wallpaperRealTimeFps = _accumWallpaperFpsCount / _accumFpsTime;
_accumWallpaperFpsCount = 0;
}
_accumFpsTime = 0f;
OnPropertyChanged(nameof(RealTimeFps));
OnPropertyChanged(nameof(WallpaperRealTimeFps));
}
// 停止更新的时候只是时间不前进, 但是坐标变换还是要更新, 否则无法移动对象
if (!_isUpdating) delta = 0;
// 加上要快进的量
lock (_forwardDeltaLock)
{
delta += _forwardDelta;
_forwardDelta = 0;
}
// 更新模型对象时间
lock (_models.Lock)
{
foreach (var sp in _models.Where(sp => sp.IsShown && (!_renderSelectedOnly || sp.IsSelected)).Reverse())
{
if (_cancelToken?.IsCancellationRequested ?? true) break; // 提前中止
sp.Update(0); // 避免物理效果出现问题
sp.Update(delta * _speed);
}
}
// 更新背景图位置和缩放
lock (_bgLock)
{
if (_backgroundImageSprite is not null)
{
using var view = _renderer.GetView();
var bg = _backgroundImageSprite;
var viewSize = view.Size;
var bgSize = bg.Texture.Size;
var scaleX = Math.Abs(viewSize.X / bgSize.X);
var scaleY = Math.Abs(viewSize.Y / bgSize.Y);
var signX = Math.Sign(viewSize.X);
var signY = Math.Sign(viewSize.Y);
if (_backgroundImageMode == Stretch.None)
{ {
delta += _forwardDelta; scaleX = scaleY = 1f / _renderer.Zoom;
_forwardDelta = 0; }
else if (_backgroundImageMode == Stretch.Uniform)
{
scaleX = scaleY = Math.Min(scaleX, scaleY);
}
else if (_backgroundImageMode == Stretch.UniformToFill)
{
scaleX = scaleY = Math.Max(scaleX, scaleY);
}
bg.Scale = new(signX * scaleX, signY * scaleY);
bg.Position = view.Center;
bg.Rotation = view.Rotation;
}
}
}
private void UpdateRenderFrame()
{
if (!_vmMain.IsVisible)
{
// 必须休眠一会, 否则会空转影响整体渲染循环
Thread.Sleep(1);
return;
}
// 清除背景
_renderer.Clear(_backgroundColor);
// 渲染背景
lock (_bgLock)
{
if (_backgroundImageSprite is not null)
{
_renderer.Draw(_backgroundImageSprite);
}
}
// 渲染坐标轴
if (_showAxis)
{
_renderer.Draw(_axisVertices);
}
// 渲染 Spine
lock (_models.Lock)
{
foreach (var sp in _models.Where(sp => sp.IsShown && (!_renderSelectedOnly || sp.IsSelected)).Reverse())
{
if (_cancelToken?.IsCancellationRequested ?? true) break; // 提前中止
// 为选中对象绘制一个半透明背景
if (sp.IsSelected)
{
var rc = sp.GetCurrentBounds().ToFloatRect();
_selectedBackgroundVertices[0] = new(new(rc.Left, rc.Top), _selectedBackgroundColor);
_selectedBackgroundVertices[1] = new(new(rc.Left + rc.Width, rc.Top), _selectedBackgroundColor);
_selectedBackgroundVertices[2] = new(new(rc.Left + rc.Width, rc.Top + rc.Height), _selectedBackgroundColor);
_selectedBackgroundVertices[3] = new(new(rc.Left, rc.Top + rc.Height), _selectedBackgroundColor);
_renderer.Draw(_selectedBackgroundVertices);
} }
// 仅在预览画面临时启用调试模式
sp.EnableDebug = true;
_renderer.Draw(sp);
sp.EnableDebug = false;
}
}
// 显示内容
_renderer.Display();
// 帧数加一
_accumFpsCount++;
}
private void WallpaperRenderTask()
{
try
{
_wallpaperRenderer.SetActive(true);
while (!_cancelToken?.IsCancellationRequested ?? false)
{
if (!_wallpaperView)
{
Thread.Sleep(10);
continue;
}
// 同步视图
using var view = _renderer.GetView(); using var view = _renderer.GetView();
_wallpaperRenderer.SetView(view); _wallpaperRenderer.SetView(view);
if (_vmMain.IsVisible) _renderer.Clear(_backgroundColor); // 清除背景
if (_wallpaperView) _wallpaperRenderer.Clear(_backgroundColor); _wallpaperRenderer.Clear(_backgroundColor);
// 渲染背景 // 渲染背景
lock (_bgLock) lock (_bgLock)
{ {
if (_backgroundImageSprite is not null) if (_backgroundImageSprite is not null)
{ {
var bg = _backgroundImageSprite; _wallpaperRenderer.Draw(_backgroundImageSprite);
var viewSize = view.Size;
var bgSize = bg.Texture.Size;
var scaleX = Math.Abs(viewSize.X / bgSize.X);
var scaleY = Math.Abs(viewSize.Y / bgSize.Y);
var signX = Math.Sign(viewSize.X);
var signY = Math.Sign(viewSize.Y);
if (_backgroundImageMode == Stretch.None)
{
scaleX = scaleY = 1f / _renderer.Zoom;
}
else if (_backgroundImageMode == Stretch.Uniform)
{
scaleX = scaleY = Math.Min(scaleX, scaleY);
}
else if (_backgroundImageMode == Stretch.UniformToFill)
{
scaleX = scaleY = Math.Max(scaleX, scaleY);
}
bg.Scale = new(signX * scaleX, signY * scaleY);
bg.Position = view.Center;
bg.Rotation = view.Rotation;
if (_vmMain.IsVisible) _renderer.Draw(bg);
if (_wallpaperView) _wallpaperRenderer.Draw(bg);
} }
} }
if (_showAxis && _vmMain.IsVisible)
{
// 画一个很长的坐标轴, 用 1e9 比较合适
_axisVertices[0] = new(new(-1e9f, 0), _axisColor);
_axisVertices[1] = new(new(1e9f, 0), _axisColor);
_renderer.Draw(_axisVertices);
_axisVertices[0] = new(new(0, -1e9f), _axisColor);
_axisVertices[1] = new(new(0, 1e9f), _axisColor);
_renderer.Draw(_axisVertices);
}
// 渲染 Spine // 渲染 Spine
lock (_models.Lock) lock (_models.Lock)
{ {
foreach (var sp in _models.Where(sp => sp.IsShown && (!_renderSelectedOnly || sp.IsSelected)).Reverse()) foreach (var sp in _models.Where(sp => sp.IsShown && (!_renderSelectedOnly || sp.IsSelected)).Reverse())
{ {
if (_cancelToken?.IsCancellationRequested ?? true) break; // 提前中止 if (_cancelToken?.IsCancellationRequested ?? true)
break; // 提前中止
sp.Update(0); // 避免物理效果出现问题 _wallpaperRenderer.Draw(sp);
sp.Update(delta * _speed);
if (_vmMain.IsVisible)
{
// 为选中对象绘制一个半透明背景
if (sp.IsSelected)
{
var rc = sp.GetCurrentBounds().ToFloatRect();
_selectedBackgroundVertices[0] = new(new(rc.Left, rc.Top), _selectedBackgroundColor);
_selectedBackgroundVertices[1] = new(new(rc.Left + rc.Width, rc.Top), _selectedBackgroundColor);
_selectedBackgroundVertices[2] = new(new(rc.Left + rc.Width, rc.Top + rc.Height), _selectedBackgroundColor);
_selectedBackgroundVertices[3] = new(new(rc.Left, rc.Top + rc.Height), _selectedBackgroundColor);
_renderer.Draw(_selectedBackgroundVertices);
}
// 仅在预览画面临时启用调试模式
sp.EnableDebug = true;
_renderer.Draw(sp);
sp.EnableDebug = false;
}
if (_wallpaperView) _wallpaperRenderer.Draw(sp);
} }
} }
if (_vmMain.IsVisible) _renderer.Display(); // 显示渲染
if (_wallpaperView) _wallpaperRenderer.Display(); _wallpaperRenderer.Display();
// 帧数加一
lock (_accumWallpaperFpsCountLock) _accumWallpaperFpsCount++;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Fatal("Render task stopped, {0}", ex.Message); _logger.Fatal("Wallpaper render task stopped, {0}", ex.Message);
MessagePopupService.Error(ex.ToString()); MessagePopupService.Error(ex.ToString());
} }
finally finally
{ {
_renderer.SetActive(false);
_wallpaperRenderer.SetActive(false); _wallpaperRenderer.SetActive(false);
} }
} }
@@ -598,7 +726,6 @@ namespace SpineViewer.ViewModels.MainWindow
Rotation = Rotation, Rotation = Rotation,
FlipX = FlipX, FlipX = FlipX,
FlipY = FlipY, FlipY = FlipY,
MaxFps = MaxFps,
Speed = Speed, Speed = Speed,
ShowAxis = ShowAxis, ShowAxis = ShowAxis,
BackgroundColor = BackgroundColor, BackgroundColor = BackgroundColor,
@@ -615,7 +742,6 @@ namespace SpineViewer.ViewModels.MainWindow
Rotation = value.Rotation; Rotation = value.Rotation;
FlipX = value.FlipX; FlipX = value.FlipX;
FlipY = value.FlipY; FlipY = value.FlipY;
MaxFps = value.MaxFps;
Speed = value.Speed; Speed = value.Speed;
ShowAxis = value.ShowAxis; ShowAxis = value.ShowAxis;
BackgroundColor = value.BackgroundColor; BackgroundColor = value.BackgroundColor;

View File

@@ -213,7 +213,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message); _logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message);
} }
return false; return false;
@@ -340,7 +340,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message); _logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message);
} }
} }
@@ -401,7 +401,7 @@ namespace SpineViewer.ViewModels.MainWindow
catch (Exception ex) catch (Exception ex)
{ {
error++; error++;
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message); _logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message);
} }
} }
@@ -718,7 +718,7 @@ namespace SpineViewer.ViewModels.MainWindow
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to load: {0}, {1}", cfg.SkelPath, ex.Message); _logger.Error("Failed to load: {0}, {1}", cfg.SkelPath, ex.Message);
} }
return false; return false;

View File

@@ -35,7 +35,7 @@ namespace SpineViewer.ViewModels
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Error("Failed to finish work: {0}, {1}", _title, ex.Message); _logger.Error("Failed to finish work: {0}, {1}", _title, ex.Message);
WorkFinished?.Invoke(this, false); WorkFinished?.Invoke(this, false);
} }

View File

@@ -19,7 +19,7 @@
<Grid Margin="30"> <Grid Margin="30">
<Grid.Resources> <Grid.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/> <Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style> </Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Natives; using SpineViewer.Extensions;
using SpineViewer.Resources; using SpineViewer.Resources;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -14,6 +14,7 @@ using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views namespace SpineViewer.Views
{ {
@@ -30,9 +31,8 @@ namespace SpineViewer.Views
private void AboutDialog_SourceInitialized(object? sender, EventArgs e) private void AboutDialog_SourceInitialized(object? sender, EventArgs e)
{ {
var hwnd = new WindowInteropHelper(this).Handle; this.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); this.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
} }
} }
} }

View File

@@ -24,7 +24,7 @@
<Border> <Border>
<Border.Resources> <Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/> <Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style> </Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Natives; using SpineViewer.Extensions;
using SpineViewer.Resources; using SpineViewer.Resources;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -14,6 +14,7 @@ using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views namespace SpineViewer.Views
{ {
@@ -30,9 +31,8 @@ namespace SpineViewer.Views
private void DiagnosticsDialog_SourceInitialized(object? sender, EventArgs e) private void DiagnosticsDialog_SourceInitialized(object? sender, EventArgs e)
{ {
var hwnd = new WindowInteropHelper(this).Handle; this.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); this.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
} }
} }
} }

View File

@@ -31,7 +31,7 @@
<Border> <Border>
<Border.Resources> <Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/> <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style> </Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Natives; using Win32Natives;
using SpineViewer.Resources; using SpineViewer.Resources;
using SpineViewer.Services; using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters; using SpineViewer.ViewModels.Exporters;
@@ -16,6 +16,7 @@ using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using SpineViewer.Extensions;
namespace SpineViewer.Views.ExporterDialogs namespace SpineViewer.Views.ExporterDialogs
{ {
@@ -32,9 +33,8 @@ namespace SpineViewer.Views.ExporterDialogs
private void CustomFFmpegExporterDialog_SourceInitialized(object? sender, EventArgs e) private void CustomFFmpegExporterDialog_SourceInitialized(object? sender, EventArgs e)
{ {
var hwnd = new WindowInteropHelper(this).Handle; this.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); this.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
} }
private void ButtonOK_Click(object sender, RoutedEventArgs e) private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -31,7 +31,7 @@
<Border> <Border>
<Border.Resources> <Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/> <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style> </Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Natives; using SpineViewer.Extensions;
using SpineViewer.Resources; using SpineViewer.Resources;
using SpineViewer.Services; using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters; using SpineViewer.ViewModels.Exporters;
@@ -16,6 +16,7 @@ using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views.ExporterDialogs namespace SpineViewer.Views.ExporterDialogs
{ {
@@ -32,9 +33,8 @@ namespace SpineViewer.Views.ExporterDialogs
private void FFmpegVideoExporterDialog_SourceInitialized(object? sender, EventArgs e) private void FFmpegVideoExporterDialog_SourceInitialized(object? sender, EventArgs e)
{ {
var hwnd = new WindowInteropHelper(this).Handle; this.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); this.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
} }
private void ButtonOK_Click(object sender, RoutedEventArgs e) private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -31,7 +31,7 @@
<Border> <Border>
<Border.Resources> <Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/> <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style> </Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Natives; using SpineViewer.Extensions;
using SpineViewer.Resources; using SpineViewer.Resources;
using SpineViewer.Services; using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters; using SpineViewer.ViewModels.Exporters;
@@ -16,6 +16,7 @@ using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views.ExporterDialogs namespace SpineViewer.Views.ExporterDialogs
{ {
@@ -32,9 +33,8 @@ namespace SpineViewer.Views.ExporterDialogs
private void FrameExporterDialog_SourceInitialized(object? sender, EventArgs e) private void FrameExporterDialog_SourceInitialized(object? sender, EventArgs e)
{ {
var hwnd = new WindowInteropHelper(this).Handle; this.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); this.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
} }
private void ButtonOK_Click(object sender, RoutedEventArgs e) private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -31,7 +31,7 @@
<Border> <Border>
<Border.Resources> <Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/> <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style> </Style>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Natives; using SpineViewer.Extensions;
using SpineViewer.Resources; using SpineViewer.Resources;
using SpineViewer.Services; using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters; using SpineViewer.ViewModels.Exporters;
@@ -16,6 +16,7 @@ using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views.ExporterDialogs namespace SpineViewer.Views.ExporterDialogs
{ {
@@ -32,9 +33,8 @@ namespace SpineViewer.Views.ExporterDialogs
private void FrameSequenceExporterDialog_SourceInitialized(object? sender, EventArgs e) private void FrameSequenceExporterDialog_SourceInitialized(object? sender, EventArgs e)
{ {
var hwnd = new WindowInteropHelper(this).Handle; this.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); this.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
} }
private void ButtonOK_Click(object sender, RoutedEventArgs e) private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -239,7 +239,7 @@
TabStripPlacement="Bottom" TabStripPlacement="Bottom"
DataContext="{Binding SpineObjectTabViewModel}"> DataContext="{Binding SpineObjectTabViewModel}">
<TabControl.Resources> <TabControl.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/> <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style> </Style>
@@ -695,7 +695,7 @@
</Border> </Border>
</TabItem.Header> </TabItem.Header>
<TabItem.Resources> <TabItem.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/> <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style> </Style>
@@ -793,16 +793,6 @@
<Separator Margin="0 5"/> <Separator Margin="0 5"/>
<!-- 最大帧率 -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
<TextBox Grid.Column="1" Text="{Binding MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
</Grid>
<!-- 播放速度 --> <!-- 播放速度 -->
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -912,14 +902,16 @@
</Grid> </Grid>
<StatusBar DockPanel.Dock="Bottom"> <StatusBar DockPanel.Dock="Bottom">
<TextBlock Foreground="{DynamicResource PrimaryTextBrush}"> <StatusBarItem>
<TextBlock.Text> <TextBlock Foreground="{DynamicResource PrimaryTextBrush}">
<MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_ListViewStatusBar"> <TextBlock.Text>
<Binding Path="Items.Count" ElementName="_spineFilesListBox"/> <MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_ListViewStatusBar">
<Binding Path="SelectedItems.Count" ElementName="_spineFilesListBox"/> <Binding Path="Items.Count" ElementName="_spineFilesListBox"/>
</MultiBinding> <Binding Path="SelectedItems.Count" ElementName="_spineFilesListBox"/>
</TextBlock.Text> </MultiBinding>
</TextBlock> </TextBlock.Text>
</TextBlock>
</StatusBarItem>
</StatusBar> </StatusBar>
<ListBox x:Name="_spineFilesListBox" <ListBox x:Name="_spineFilesListBox"
@@ -955,7 +947,7 @@
<Grid Grid.Row="2" DataContext="{Binding SelectedItem}"> <Grid Grid.Row="2" DataContext="{Binding SelectedItem}">
<Grid.Resources> <Grid.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/> <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style> </Style>
@@ -1052,7 +1044,23 @@
<!-- 日志框容器 --> <!-- 日志框容器 -->
<Border Grid.Row="2" Name="_loggerBoxContainer"> <Border Grid.Row="2" Name="_loggerBoxContainer">
<RichTextBox x:Name="_loggerRichTextBox" Grid.Row="2" Style="{StaticResource MyLogRichTextBoxStyle}"/> <Border x:Name="_loggerBoxPanel" DataContext="{Binding SFMLRendererViewModel}">
<DockPanel>
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem>
<TextBlock Foreground="{DynamicResource PrimaryTextBrush}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_RealTimeFps">
<Binding Path="RealTimeFps"/>
<Binding Path="WallpaperRealTimeFps"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StatusBarItem>
</StatusBar>
<RichTextBox x:Name="_loggerRichTextBox" Grid.Row="2" Style="{StaticResource MyLogRichTextBoxStyle}"/>
</DockPanel>
</Border>
</Border> </Border>
</Grid> </Grid>
</Border> </Border>

View File

@@ -1,8 +1,8 @@
using NLog; using NLog;
using SFMLRenderer; using SFMLRenderer;
using Spine; using Spine;
using SpineViewer.Extensions;
using SpineViewer.Models; using SpineViewer.Models;
using SpineViewer.Natives;
using SpineViewer.Resources; using SpineViewer.Resources;
using SpineViewer.Services; using SpineViewer.Services;
using SpineViewer.Utils; using SpineViewer.Utils;
@@ -24,6 +24,7 @@ using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Threading; using System.Windows.Threading;
using Win32Natives;
namespace SpineViewer.Views; namespace SpineViewer.Views;
@@ -47,7 +48,7 @@ public partial class MainWindow : Window
private readonly List<IDisposable> _userStateWatchers = []; private readonly List<IDisposable> _userStateWatchers = [];
private DispatcherTimer _saveUserStateTimer; private DispatcherTimer _saveUserStateTimer;
private readonly TimeSpan _saveTimerDelay = TimeSpan.FromSeconds(3); private readonly TimeSpan _saveTimerDelay = TimeSpan.FromSeconds(1);
public bool RootGridCol0Folded public bool RootGridCol0Folded
{ {
@@ -98,13 +99,13 @@ public partial class MainWindow : Window
// Initialize Wallpaper RenderWindow // Initialize Wallpaper RenderWindow
_wallpaperRenderWindow = new(new(1, 1), "SpineViewerWallpaper", SFML.Window.Styles.None); _wallpaperRenderWindow = new(new(1, 1), "SpineViewerWallpaper", SFML.Window.Styles.None);
_wallpaperRenderWindow.SetVisible(false); _wallpaperRenderWindow.MaxFps = 30;
var handle = _wallpaperRenderWindow.SystemHandle; var handle = _wallpaperRenderWindow.SystemHandle;
var style = User32.GetWindowLong(handle, User32.GWL_STYLE) | User32.WS_POPUP; 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; var exStyle = User32.GetWindowLong(handle, User32.GWL_EXSTYLE) | User32.WS_EX_TOOLWINDOW;
User32.SetWindowLong(handle, User32.GWL_STYLE, style); User32.SetWindowLong(handle, User32.GWL_STYLE, style);
User32.SetWindowLong(handle, User32.GWL_EXSTYLE, exStyle); User32.SetWindowLong(handle, User32.GWL_EXSTYLE, exStyle);
User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA);
DataContext = _vm = new(_renderPanel, _wallpaperRenderWindow); DataContext = _vm = new(_renderPanel, _wallpaperRenderWindow);
@@ -153,9 +154,8 @@ public partial class MainWindow : Window
private void MainWindow_SourceInitialized(object? sender, EventArgs e) private void MainWindow_SourceInitialized(object? sender, EventArgs e)
{ {
var hwnd = new WindowInteropHelper(this).Handle; this.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); this.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
} }
private void MainWindow_Loaded(object sender, RoutedEventArgs e) private void MainWindow_Loaded(object sender, RoutedEventArgs e)
@@ -274,7 +274,6 @@ public partial class MainWindow : Window
_vm.ExplorerListViewModel.CurrentDirectory = m.ExploringDirectory; _vm.ExplorerListViewModel.CurrentDirectory = m.ExploringDirectory;
_vm.SFMLRendererViewModel.SetResolution(m.ResolutionX, m.ResolutionY); _vm.SFMLRendererViewModel.SetResolution(m.ResolutionX, m.ResolutionY);
_vm.SFMLRendererViewModel.MaxFps = m.MaxFps;
_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;
@@ -310,7 +309,6 @@ public partial class MainWindow : Window
ResolutionX = _vm.SFMLRendererViewModel.ResolutionX, ResolutionX = _vm.SFMLRendererViewModel.ResolutionX,
ResolutionY = _vm.SFMLRendererViewModel.ResolutionY, ResolutionY = _vm.SFMLRendererViewModel.ResolutionY,
MaxFps = _vm.SFMLRendererViewModel.MaxFps,
Speed = _vm.SFMLRendererViewModel.Speed, Speed = _vm.SFMLRendererViewModel.Speed,
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis, ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor, BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
@@ -400,7 +398,6 @@ public partial class MainWindow : Window
{ {
case nameof(SFMLRendererViewModel.ResolutionX): case nameof(SFMLRendererViewModel.ResolutionX):
case nameof(SFMLRendererViewModel.ResolutionY): case nameof(SFMLRendererViewModel.ResolutionY):
case nameof(SFMLRendererViewModel.MaxFps):
case nameof(SFMLRendererViewModel.Speed): case nameof(SFMLRendererViewModel.Speed):
case nameof(SFMLRendererViewModel.ShowAxis): case nameof(SFMLRendererViewModel.ShowAxis):
case nameof(SFMLRendererViewModel.BackgroundColor): case nameof(SFMLRendererViewModel.BackgroundColor):
@@ -440,6 +437,7 @@ public partial class MainWindow : Window
private void SFMLRendererViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) private void SFMLRendererViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{ {
// XXX: 资源管理器重启后窗口会有问题无法重新显示, 需要重启应用, 否则要重新创建窗口
if (e.PropertyName == nameof(SFMLRendererViewModel.WallpaperView)) if (e.PropertyName == nameof(SFMLRendererViewModel.WallpaperView))
{ {
var wnd = _wallpaperRenderWindow; var wnd = _wallpaperRenderWindow;
@@ -451,17 +449,25 @@ public partial class MainWindow : Window
_logger.Error("Failed to enable wallpaper view, WorkerW not found"); _logger.Error("Failed to enable wallpaper view, WorkerW not found");
return; return;
} }
var handle = wnd.SystemHandle;
User32.GetPrimaryScreenResolution(out var sw, out var sh); User32.GetPrimaryScreenResolution(out var sw, out var sh);
_vm.SFMLRendererViewModel.SetResolution(sw, sh);
User32.SetParent(handle, workerw); var handle = wnd.SystemHandle;
// 每次都进行设置, 确保会成为顶层子窗口
var lastParent = User32.SetParent(handle, workerw);
Debug.WriteLine($"0x{lastParent:x8} = SetParent(0x{handle:x8}, 0x{workerw:x8})");
User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA); User32.SetLayeredWindowAttributes(handle, 0, byte.MaxValue, User32.LWA_ALPHA);
_vm.SFMLRendererViewModel.SetResolution(sw, sh); // XXX: 每次新设置成桌面子窗口之后, 要确保窗口 Size 发生一次变化来触发 SFML 内部的渲染视图更新
var ssize = new SFML.System.Vector2u(sw, sh);
if (lastParent != workerw && ssize == wnd.Size)
{
wnd.Size = new(sw + 1, sh);
}
wnd.Position = new(0, 0); wnd.Position = new(0, 0);
wnd.Size = new(sw + 1, sh); wnd.Size = ssize;
wnd.Size = new(sw, sh);
wnd.SetVisible(true); wnd.SetVisible(true);
} }
else else
@@ -694,7 +700,7 @@ public partial class MainWindow : Window
_renderPanelButtonsPopupContainer.Child = _renderPanelButtonsPanel; _renderPanelButtonsPopupContainer.Child = _renderPanelButtonsPanel;
_loggerBoxContainer.Child = null; _loggerBoxContainer.Child = null;
_loggerBoxPopupContainer.Child = _loggerRichTextBox; _loggerBoxPopupContainer.Child = _loggerBoxPanel;
} }
private void SwitchToNormalLayout() private void SwitchToNormalLayout()
@@ -704,7 +710,7 @@ public partial class MainWindow : Window
HandyControl.Controls.IconElement.SetGeometry(_fullScreenButton, AppResource.Geo_ArrowsMaximize); HandyControl.Controls.IconElement.SetGeometry(_fullScreenButton, AppResource.Geo_ArrowsMaximize);
_loggerBoxPopupContainer.Child = null; _loggerBoxPopupContainer.Child = null;
_loggerBoxContainer.Child = _loggerRichTextBox; _loggerBoxContainer.Child = _loggerBoxPanel;
_renderPanelButtonsPopupContainer.Child = null; _renderPanelButtonsPopupContainer.Child = null;
_renderPanelButtonsContainer.Child = _renderPanelButtonsPanel; _renderPanelButtonsContainer.Child = _renderPanelButtonsPanel;

View File

@@ -31,7 +31,7 @@
<Border> <Border>
<Border.Resources> <Border.Resources>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}"> <Style TargetType="{x:Type Label}" BasedOn="{StaticResource MyLabelStyle}">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Right"/> <Setter Property="HorizontalContentAlignment" Value="Right"/>
</Style> </Style>
@@ -195,30 +195,8 @@
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
<GroupBox Header="{DynamicResource Str_AppPreference}"> <GroupBox Header="{DynamicResource Str_RendererPreference}">
<StackPanel> <StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_Language}"/>
<ComboBox Grid.Column="1"
SelectedItem="{Binding AppLanguage}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_Skin}"/>
<ComboBox Grid.Column="1"
SelectedItem="{Binding AppSkin}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppSkinOptions}"/>
</Grid>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/> <ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
@@ -248,6 +226,41 @@
<ToggleButton Grid.Column="1" IsChecked="{Binding LogHitSlots}" ToolTip="{DynamicResource Str_LogHitSlotsTooltip}"/> <ToggleButton Grid.Column="1" IsChecked="{Binding LogHitSlots}" ToolTip="{DynamicResource Str_LogHitSlotsTooltip}"/>
</Grid> </Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
<TextBox Grid.Column="1" Text="{Binding MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
</Grid>
</StackPanel>
</GroupBox>
<GroupBox Header="{DynamicResource Str_AppPreference}">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_Language}"/>
<ComboBox Grid.Column="1"
SelectedItem="{Binding AppLanguage}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_Skin}"/>
<ComboBox Grid.Column="1"
SelectedItem="{Binding AppSkin}"
ItemsSource="{x:Static vm:PreferenceViewModel.AppSkinOptions}"/>
</Grid>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/> <ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
@@ -257,6 +270,15 @@
<ToggleButton Grid.Column="1" IsChecked="{Binding WallpaperView}"/> <ToggleButton Grid.Column="1" IsChecked="{Binding WallpaperView}"/>
</Grid> </Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{DynamicResource Str_WallpaperMaxFps}" ToolTip="{DynamicResource Str_WallpaperMaxFpsTooltip}"/>
<TextBox Grid.Column="1" Text="{Binding WallpaperMaxFps}" ToolTip="{DynamicResource Str_WallpaperMaxFpsTooltip}"/>
</Grid>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/> <ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol"/>

View File

@@ -1,4 +1,4 @@
using SpineViewer.Natives; using SpineViewer.Extensions;
using SpineViewer.Resources; using SpineViewer.Resources;
using SpineViewer.Services; using SpineViewer.Services;
using SpineViewer.ViewModels.Exporters; using SpineViewer.ViewModels.Exporters;
@@ -16,6 +16,7 @@ using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views namespace SpineViewer.Views
{ {
@@ -32,9 +33,8 @@ namespace SpineViewer.Views
private void PreferenceDialog_SourceInitialized(object? sender, EventArgs e) private void PreferenceDialog_SourceInitialized(object? sender, EventArgs e)
{ {
var hwnd = new WindowInteropHelper(this).Handle; this.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); this.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
} }
private void ButtonOK_Click(object sender, RoutedEventArgs e) private void ButtonOK_Click(object sender, RoutedEventArgs e)

View File

@@ -1,4 +1,4 @@
using SpineViewer.Natives; using SpineViewer.Extensions;
using SpineViewer.Resources; using SpineViewer.Resources;
using SpineViewer.ViewModels; using SpineViewer.ViewModels;
using System; using System;
@@ -16,6 +16,7 @@ using System.Windows.Interop;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using Win32Natives;
namespace SpineViewer.Views namespace SpineViewer.Views
{ {
@@ -33,9 +34,8 @@ namespace SpineViewer.Views
private void ProgressDialog_SourceInitialized(object? sender, EventArgs e) private void ProgressDialog_SourceInitialized(object? sender, EventArgs e)
{ {
var hwnd = new WindowInteropHelper(this).Handle; this.SetWindowTextColor(AppResource.Color_PrimaryText);
Dwmapi.SetWindowTextColor(hwnd, AppResource.Color_PrimaryText); this.SetWindowCaptionColor(AppResource.Color_Region);
Dwmapi.SetWindowCaptionColor(hwnd, AppResource.Color_Region);
} }
private void ProgressWindow_Loaded(object sender, RoutedEventArgs e) private void ProgressWindow_Loaded(object sender, RoutedEventArgs e)

View File

@@ -42,7 +42,7 @@ namespace SpineViewerCLI
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Trace(ex.ToString()); _logger.Debug(ex.ToString());
_logger.Fatal("Failed to execute, {0}", ex.Message); _logger.Fatal("Failed to execute, {0}", ex.Message);
return -1; return -1;
} }

View File

@@ -8,7 +8,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath> <BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.16.11</Version> <Version>0.16.12</Version>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
</PropertyGroup> </PropertyGroup>

View File

@@ -4,9 +4,8 @@ using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Media;
namespace SpineViewer.Natives namespace Win32Natives
{ {
/// <summary> /// <summary>
/// dwmapi.dll 包装类 /// dwmapi.dll 包装类
@@ -24,15 +23,15 @@ namespace SpineViewer.Natives
[DllImport("dwmapi.dll")] [DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, uint dwAttribute, ref uint pvAttribute, int cbAttribute); private static extern int DwmSetWindowAttribute(IntPtr hwnd, uint dwAttribute, ref uint pvAttribute, int cbAttribute);
public static bool SetWindowCaptionColor(IntPtr hwnd, Color color) public static bool SetWindowCaptionColor(IntPtr hwnd, byte r, byte g, byte b)
{ {
int c = color.R | (color.G << 8) | (color.B << 16); int c = r | (g << 8) | (b << 16);
return 0 == DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, ref c, sizeof(uint)); return 0 == DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, ref c, sizeof(uint));
} }
public static bool SetWindowTextColor(IntPtr hwnd, Color color) public static bool SetWindowTextColor(IntPtr hwnd, byte r, byte g, byte b)
{ {
int c = color.R | (color.G << 8) | (color.B << 16); int c = r | (g << 8) | (b << 16);
return 0 == DwmSetWindowAttribute(hwnd, DWMWA_TEXT_COLOR, ref c, sizeof(uint)); return 0 == DwmSetWindowAttribute(hwnd, DWMWA_TEXT_COLOR, ref c, sizeof(uint));
} }

View File

@@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
namespace SpineViewer.Natives namespace Win32Natives
{ {
/// <summary> /// <summary>
/// gdi32.dll 包装类 /// gdi32.dll 包装类

View File

@@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
namespace SpineViewer.Natives namespace Win32Natives
{ {
/// <summary> /// <summary>
/// shell32.dll 包装类 /// shell32.dll 包装类

View File

@@ -7,7 +7,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
namespace SpineViewer.Natives namespace Win32Natives
{ {
/// <summary> /// <summary>
/// user32.dll 包装类 /// user32.dll 包装类
@@ -316,7 +316,7 @@ namespace SpineViewer.Natives
workerw = FindWindowEx(progman, IntPtr.Zero, "WorkerW", null); workerw = FindWindowEx(progman, IntPtr.Zero, "WorkerW", null);
} }
Debug.WriteLine($"HWND(WorkerW): {workerw:x8}"); Debug.WriteLine($"HWND(WorkerW): 0x{workerw:x8}");
return workerw; return workerw;
} }

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.16.0</Version>
</PropertyGroup>
</Project>