Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c3b076b58 | ||
|
|
01e12f4524 | ||
|
|
a814d3d99a | ||
|
|
6a4508dceb | ||
|
|
b7d7274a5a | ||
|
|
71359a4328 | ||
|
|
3a3691bcca | ||
|
|
3d649e36cc | ||
|
|
a24db3c447 | ||
|
|
699a055707 | ||
|
|
1f8ed1c31c | ||
|
|
e2fc27663c | ||
|
|
b3cd0b9349 | ||
|
|
1c545b8c37 | ||
|
|
d660dd1c4a | ||
|
|
9c0acf7302 | ||
|
|
415df555c7 | ||
|
|
5ef13239da | ||
|
|
13ef873650 | ||
|
|
78b9834f6c | ||
|
|
672a695b20 | ||
|
|
e9951ed79a | ||
|
|
0c16b2f104 | ||
|
|
7628075420 | ||
|
|
6f896bdaad | ||
|
|
98930db4b6 | ||
|
|
c7493372e9 |
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,5 +1,30 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.15.16
|
||||
|
||||
- 修改模型添加顺序, 每次向顶层添加
|
||||
- 添加模型后自动选中最近添加的模型S
|
||||
- 点击预览画面或者选中项发生变化时转移焦点至列表
|
||||
- 增加移除全部菜单项
|
||||
- 增加单例模式和命令行文件参数
|
||||
- 增加文件关联设置
|
||||
|
||||
## v0.15.15
|
||||
|
||||
- 增加报错信息
|
||||
- 导入后自动选中最后一项
|
||||
|
||||
## v0.15.14
|
||||
|
||||
- 将预览画面的首选项移动至上一次状态参数中
|
||||
- 增加预览画面像素的自动保存和恢复
|
||||
- 增加日志启动时的版本号输出
|
||||
|
||||
## v0.15.13
|
||||
|
||||
- 增加程序布局自动存储和还原
|
||||
- 增加部分预览画面首选项
|
||||
|
||||
## v0.15.12
|
||||
|
||||
- 增加单个模型和单个轨道的时间因子
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V21
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V34
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V34
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V34
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V34
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V35
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V35
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V35
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V35
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V36
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -30,8 +30,15 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V37
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -31,8 +31,15 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
@@ -42,8 +49,9 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +61,9 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +71,8 @@ namespace Spine.Implementations.SpineWrappers.V38
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V40
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V41
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -30,10 +30,16 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
: base(skelPath, atlasPath, textureLoader)
|
||||
{
|
||||
// 加载 atlas
|
||||
try { _atlas = new Atlas(atlasPath, textureLoader); }
|
||||
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
|
||||
try
|
||||
{
|
||||
_atlas = new Atlas(atlasPath, textureLoader);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
|
||||
}
|
||||
|
||||
// 加载 skel
|
||||
try
|
||||
{
|
||||
if (Utf8Validator.IsUtf8(skelPath))
|
||||
@@ -42,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
{
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -53,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
{
|
||||
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V42
|
||||
catch (Exception ex)
|
||||
{
|
||||
_atlas.Dispose();
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
|
||||
}
|
||||
|
||||
// 加载动画数据
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.12</Version>
|
||||
<Version>0.15.16</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -115,8 +115,9 @@ namespace Spine
|
||||
{
|
||||
_data = SpineObjectData.New(Version, skelPath, atlasPath, textureLoader);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
throw new InvalidDataException($"Failed to load spine with version '{version}'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using Spine.SpineWrappers.Attachments;
|
||||
|
||||
namespace Spine.SpineWrappers
|
||||
@@ -17,6 +18,8 @@ namespace Spine.SpineWrappers
|
||||
ISpineObjectData,
|
||||
IDisposable
|
||||
{
|
||||
protected static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// 构建版本对象
|
||||
/// </summary>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using NLog;
|
||||
using SpineViewer.Natives;
|
||||
using SpineViewer.Views;
|
||||
using System.Collections.Frozen;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
|
||||
@@ -15,15 +18,24 @@ namespace SpineViewer
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
public static string Version => Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
public const string ProgId = "SpineViewer.skel";
|
||||
|
||||
public static readonly string ProcessPath = Environment.ProcessPath;
|
||||
public static readonly string ProcessDirectory = Path.GetDirectoryName(Environment.ProcessPath);
|
||||
public static readonly string ProcessName = Process.GetCurrentProcess().ProcessName;
|
||||
public static readonly string Version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
|
||||
private const string MutexName = "SpineViewerInstance";
|
||||
private const string PipeName = "SpineViewerPipe";
|
||||
|
||||
private static readonly Logger _logger;
|
||||
private static readonly Mutex _instanceMutex;
|
||||
|
||||
static App()
|
||||
{
|
||||
InitializeLogConfiguration();
|
||||
_logger = LogManager.GetCurrentClassLogger();
|
||||
_logger.Info("Application Started");
|
||||
_logger.Info("Application Started, v{0}", Version);
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
|
||||
{
|
||||
@@ -35,6 +47,17 @@ namespace SpineViewer
|
||||
_logger.Error("Unobserved task exception: {0}", e.Exception.Message);
|
||||
e.SetObserved();
|
||||
};
|
||||
|
||||
// 单例模式加 IPC 通信
|
||||
_instanceMutex = new Mutex(true, MutexName, out var createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
ShowExistedInstance();
|
||||
SendCommandLineArgs();
|
||||
Environment.Exit(0); // 不再启动新实例
|
||||
return;
|
||||
}
|
||||
StartPipeServer();
|
||||
}
|
||||
|
||||
private static void InitializeLogConfiguration()
|
||||
@@ -50,7 +73,9 @@ namespace SpineViewer
|
||||
ArchiveNumbering = NLog.Targets.ArchiveNumberingMode.Rolling,
|
||||
ArchiveAboveSize = 1048576,
|
||||
MaxArchiveFiles = 5,
|
||||
Layout = "${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${level:uppercase=true} - ${callsite-filename:includeSourcePath=false}:${callsite-linenumber} - ${message}"
|
||||
Layout = "${date:format=yyyy-MM-dd HH\\:mm\\:ss} - ${level:uppercase=true} - ${processid} - ${callsite-filename:includeSourcePath=false}:${callsite-linenumber} - ${message}",
|
||||
ConcurrentWrites = true,
|
||||
KeepFileOpen = false,
|
||||
};
|
||||
|
||||
config.AddTarget(fileTarget);
|
||||
@@ -58,20 +83,113 @@ namespace SpineViewer
|
||||
LogManager.Configuration = config;
|
||||
}
|
||||
|
||||
private static void ShowExistedInstance()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 2. 遍历同名进程
|
||||
var processes = Process.GetProcessesByName(ProcessName);
|
||||
foreach (var p in processes)
|
||||
{
|
||||
// 跳过当前进程
|
||||
if (p.Id == Process.GetCurrentProcess().Id)
|
||||
continue;
|
||||
|
||||
IntPtr hWnd = p.MainWindowHandle;
|
||||
if (hWnd != IntPtr.Zero)
|
||||
{
|
||||
// 3. 显示并置顶窗口
|
||||
if (User32.IsIconic(hWnd))
|
||||
{
|
||||
User32.ShowWindow(hWnd, User32.SW_RESTORE);
|
||||
}
|
||||
User32.SetForegroundWindow(hWnd);
|
||||
break; // 找到一个就可以退出
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略异常,不影响当前进程退出
|
||||
}
|
||||
}
|
||||
|
||||
private static void SendCommandLineArgs()
|
||||
{
|
||||
var args = Environment.GetCommandLineArgs().Skip(1).ToArray();
|
||||
if (args.Length <= 0)
|
||||
return;
|
||||
|
||||
_logger.Info("Send command line args to existed instance, \"{0}\"", string.Join(", ", args));
|
||||
try
|
||||
{
|
||||
// 已有实例在运行,把参数通过命名管道发过去
|
||||
using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out))
|
||||
{
|
||||
client.Connect(10000); // 10 秒超时
|
||||
using (var writer = new StreamWriter(client))
|
||||
{
|
||||
foreach (var v in args)
|
||||
{
|
||||
writer.WriteLine(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to pass command line args to existed instance, {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void StartPipeServer()
|
||||
{
|
||||
var t = new Task(() =>
|
||||
{
|
||||
while (Current is null) Thread.Sleep(10);
|
||||
while (true)
|
||||
{
|
||||
var windowCreated = false;
|
||||
Current.Dispatcher.Invoke(() => windowCreated = Current.MainWindow is MainWindow);
|
||||
if (windowCreated)
|
||||
break;
|
||||
else
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
using (var server = new NamedPipeServerStream(PipeName, PipeDirection.In))
|
||||
{
|
||||
server.WaitForConnection();
|
||||
using (var reader = new StreamReader(server))
|
||||
{
|
||||
var args = new List<string>();
|
||||
string? line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
args.Add(line);
|
||||
|
||||
if (args.Count > 0)
|
||||
{
|
||||
Current.Dispatcher.Invoke(() => ((MainWindow)Current.MainWindow).OpenFiles(args));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, default, TaskCreationOptions.LongRunning);
|
||||
t.Start();
|
||||
}
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
// 正式启动窗口
|
||||
base.OnStartup(e);
|
||||
|
||||
var dict = new ResourceDictionary();
|
||||
|
||||
var uiCulture = CultureInfo.CurrentUICulture.Name.ToLowerInvariant();
|
||||
_logger.Info("Current UI Culture: {0}", uiCulture);
|
||||
|
||||
if (uiCulture.StartsWith("zh")) { } // 默认就是中文, 无需操作
|
||||
else if (uiCulture.StartsWith("ja")) Language = AppLanguage.JA;
|
||||
else Language = AppLanguage.EN;
|
||||
|
||||
Resources.MergedDictionaries.Add(dict);
|
||||
}
|
||||
|
||||
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
||||
|
||||
40
SpineViewer/Models/LastStateModel.cs
Normal file
40
SpineViewer/Models/LastStateModel.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SpineViewer.Models
|
||||
{
|
||||
public class LastStateModel
|
||||
{
|
||||
#region 画面布局状态
|
||||
|
||||
public double WindowLeft { get; set; }
|
||||
public double WindowTop { get; set; }
|
||||
public double WindowWidth { get; set; }
|
||||
public double WindowHeight { get; set; }
|
||||
public WindowState WindowState { get; set; }
|
||||
|
||||
public double RootGridCol0Width { get; set; }
|
||||
public double ModelListRow0Height { get; set; }
|
||||
public double ExplorerGridRow0Height { get; set; }
|
||||
public double RightPanelGridRow0Height { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 预览画面状态
|
||||
|
||||
public uint ResolutionX { get; set; } = 1500;
|
||||
public uint ResolutionY { get; set; } = 1000;
|
||||
public uint MaxFps { get; set; } = 30;
|
||||
public float Speed { get; set; } = 1f;
|
||||
public bool ShowAxis { get; set; } = true;
|
||||
public Color BackgroundColor { get; set; } = Color.FromRgb(105, 105, 105);
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,9 @@ namespace SpineViewer.Models
|
||||
[ObservableProperty]
|
||||
private bool _renderSelectedOnly;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _associateFileSuffix;
|
||||
|
||||
[ObservableProperty]
|
||||
private AppLanguage _appLanguage;
|
||||
|
||||
|
||||
29
SpineViewer/Natives/Gdi32.cs
Normal file
29
SpineViewer/Natives/Gdi32.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace SpineViewer.Natives
|
||||
{
|
||||
/// <summary>
|
||||
/// gdi32.dll 包装类
|
||||
/// </summary>
|
||||
public static class Gdi32
|
||||
{
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
public static extern nint CreateCompatibleDC(nint hdc);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
public static extern bool DeleteDC(nint hdc);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
public static extern nint SelectObject(nint hdc, nint hgdiobj);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
public static extern bool DeleteObject(nint hObject);
|
||||
}
|
||||
}
|
||||
23
SpineViewer/Natives/Shell32.cs
Normal file
23
SpineViewer/Natives/Shell32.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace SpineViewer.Natives
|
||||
{
|
||||
/// <summary>
|
||||
/// shell32.dll 包装类
|
||||
/// </summary>
|
||||
public static class Shell32
|
||||
{
|
||||
public const uint SHCNE_ASSOCCHANGED = 0x08000000;
|
||||
public const uint SHCNF_IDLIST = 0x0000;
|
||||
|
||||
[DllImport("shell32.dll")]
|
||||
public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@ using System.Windows;
|
||||
namespace SpineViewer.Natives
|
||||
{
|
||||
/// <summary>
|
||||
/// Win32 Sdk 包装类
|
||||
/// user32.dll 包装类
|
||||
/// </summary>
|
||||
public static class Win32
|
||||
public static class User32
|
||||
{
|
||||
public const int GWL_STYLE = -16;
|
||||
public const int WS_SIZEBOX = 0x40000;
|
||||
@@ -178,17 +178,11 @@ namespace SpineViewer.Natives
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
public static extern nint CreateCompatibleDC(nint hdc);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool IsIconic(IntPtr hWnd);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
public static extern bool DeleteDC(nint hdc);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
public static extern nint SelectObject(nint hdc, nint hgdiobj);
|
||||
|
||||
[DllImport("gdi32.dll", SetLastError = true)]
|
||||
public static extern bool DeleteObject(nint hObject);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
|
||||
@@ -19,6 +19,8 @@ namespace SpineViewer.Resources
|
||||
public static string Str_GeneratePreviewsTitle => Get<string>("Str_GeneratePreviewsTitle");
|
||||
public static string Str_DeletePreviewsTitle => Get<string>("Str_DeletePreviewsTitle");
|
||||
public static string Str_AddSpineObjectsTitle => Get<string>("Str_AddSpineObjectsTitle");
|
||||
public static string Str_OpenSkelFileTitle => Get<string>("Str_OpenSkelFileTitle");
|
||||
public static string Str_OpenAtlasFileTitle => Get<string>("Str_OpenAtlasFileTitle");
|
||||
public static string Str_ReloadSpineObjectsTitle => Get<string>("Str_ReloadSpineObjectsTitle");
|
||||
public static string Str_CustomFFmpegExporterTitle => Get<string>("Str_CustomFFmpegExporterTitle");
|
||||
|
||||
|
||||
BIN
SpineViewer/Resources/Images/skel.ico
Normal file
BIN
SpineViewer/Resources/Images/skel.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 191 KiB |
BIN
SpineViewer/Resources/Images/skel.png
Normal file
BIN
SpineViewer/Resources/Images/skel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 188 KiB |
BIN
SpineViewer/Resources/Images/spineviewer.png
Normal file
BIN
SpineViewer/Resources/Images/spineviewer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@@ -37,8 +37,11 @@
|
||||
<s:String x:Key="Str_Show">Show</s:String>
|
||||
<s:String x:Key="Str_ListViewStatusBar">{0} items, {1} selected</s:String>
|
||||
<s:String x:Key="Str_AddSpineObject">Add...</s:String>
|
||||
<s:String x:Key="Str_RemoveSpineObject">Remove</s:String>
|
||||
<s:String x:Key="Str_OpenSkelFileTitle">Select Skeleton File (skel)</s:String>
|
||||
<s:String x:Key="Str_OpenAtlasFileTitle">Select Atlas File (atlas)</s:String>
|
||||
<s:String x:Key="Str_AddSpineObjectFromClipboard">Add from Clipboard</s:String>
|
||||
<s:String x:Key="Str_RemoveSpineObject">Remove</s:String>
|
||||
<s:String x:Key="Str_RemoveAllSpineObject">Remove All</s:String>
|
||||
<s:String x:Key="Str_Reload">Reload</s:String>
|
||||
<s:String x:Key="Str_MoveUpSpineObject">Move Up</s:String>
|
||||
<s:String x:Key="Str_MoveDownSpineObject">Move Down</s:String>
|
||||
@@ -229,7 +232,10 @@
|
||||
|
||||
<s:String x:Key="Str_SpineLoadPreference">Model Loading Options</s:String>
|
||||
|
||||
<s:String x:Key="Str_RendererPreference">Preview Options</s:String>
|
||||
|
||||
<s:String x:Key="Str_AppPreference">Application Options</s:String>
|
||||
<s:String x:Key="Str_AssociateFileSuffix">Associate File Extension</s:String>
|
||||
<s:String x:Key="Str_Language">Language</s:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -37,8 +37,11 @@
|
||||
<s:String x:Key="Str_Show">表示</s:String>
|
||||
<s:String x:Key="Str_ListViewStatusBar">全{0}件、選択中{1}件</s:String>
|
||||
<s:String x:Key="Str_AddSpineObject">追加...</s:String>
|
||||
<s:String x:Key="Str_RemoveSpineObject">削除</s:String>
|
||||
<s:String x:Key="Str_OpenSkelFileTitle">スケルトンファイルを選択(skel)</s:String>
|
||||
<s:String x:Key="Str_OpenAtlasFileTitle">アトラスファイルを選択(atlas)</s:String>
|
||||
<s:String x:Key="Str_AddSpineObjectFromClipboard">クリップボードから追加</s:String>
|
||||
<s:String x:Key="Str_RemoveSpineObject">削除</s:String>
|
||||
<s:String x:Key="Str_RemoveAllSpineObject">すべて削除</s:String>
|
||||
<s:String x:Key="Str_Reload">再読み込み</s:String>
|
||||
<s:String x:Key="Str_MoveUpSpineObject">上へ移動</s:String>
|
||||
<s:String x:Key="Str_MoveDownSpineObject">下へ移動</s:String>
|
||||
@@ -229,7 +232,10 @@
|
||||
|
||||
<s:String x:Key="Str_SpineLoadPreference">モデル読み込みオプション</s:String>
|
||||
|
||||
<s:String x:Key="Str_RendererPreference">プレビュー画面オプション</s:String>
|
||||
|
||||
<s:String x:Key="Str_AppPreference">アプリケーションプション</s:String>
|
||||
<s:String x:Key="Str_AssociateFileSuffix">ファイル拡張子を関連付ける</s:String>
|
||||
<s:String x:Key="Str_Language">言語</s:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -37,8 +37,11 @@
|
||||
<s:String x:Key="Str_Show">显示</s:String>
|
||||
<s:String x:Key="Str_ListViewStatusBar">共 {0} 项,已选择 {1} 项</s:String>
|
||||
<s:String x:Key="Str_AddSpineObject">添加...</s:String>
|
||||
<s:String x:Key="Str_RemoveSpineObject">移除</s:String>
|
||||
<s:String x:Key="Str_OpenSkelFileTitle">选择骨骼文件(skel)</s:String>
|
||||
<s:String x:Key="Str_OpenAtlasFileTitle">选择图集文件(atlas)</s:String>
|
||||
<s:String x:Key="Str_AddSpineObjectFromClipboard">从剪贴板添加</s:String>
|
||||
<s:String x:Key="Str_RemoveSpineObject">移除</s:String>
|
||||
<s:String x:Key="Str_RemoveAllSpineObject">移除全部</s:String>
|
||||
<s:String x:Key="Str_Reload">重新加载</s:String>
|
||||
<s:String x:Key="Str_MoveUpSpineObject">上移</s:String>
|
||||
<s:String x:Key="Str_MoveDownSpineObject">下移</s:String>
|
||||
@@ -229,7 +232,10 @@
|
||||
|
||||
<s:String x:Key="Str_SpineLoadPreference">模型加载选项</s:String>
|
||||
|
||||
<s:String x:Key="Str_RendererPreference">预览画面选项</s:String>
|
||||
|
||||
<s:String x:Key="Str_AppPreference">应用程序选项</s:String>
|
||||
<s:String x:Key="Str_AssociateFileSuffix">关联文件后缀</s:String>
|
||||
<s:String x:Key="Str_Language">语言</s:String>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -61,6 +61,18 @@ namespace SpineViewer.Services
|
||||
return dialog.ShowDialog() ?? false;
|
||||
}
|
||||
|
||||
public static bool ShowOpenFileDialog(out string? fileName, string title = null, string filter = "")
|
||||
{
|
||||
var dialog = new OpenFileDialog() { Title = title, Filter = filter };
|
||||
if (dialog.ShowDialog() is true)
|
||||
{
|
||||
fileName = dialog.FileName;
|
||||
return true;
|
||||
}
|
||||
fileName = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户选择的文件夹
|
||||
/// </summary>
|
||||
|
||||
@@ -7,19 +7,22 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.12</Version>
|
||||
<Version>0.15.16</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>$(NoWarn);NETSDK1206</NoWarn>
|
||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||
<ApplicationIcon>Resources\Images\spineviewer.ico</ApplicationIcon>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="appicon.ico" />
|
||||
<Content Include="Resources\Images\skel.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Resources\Images\spineviewer.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.Win32;
|
||||
using NLog;
|
||||
using Spine.SpineWrappers;
|
||||
using SpineViewer.Models;
|
||||
using SpineViewer.Natives;
|
||||
using SpineViewer.Services;
|
||||
using SpineViewer.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -23,6 +26,10 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
/// </summary>
|
||||
public static readonly string PreferenceFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "preference.json");
|
||||
|
||||
private static readonly string SkelFileDescription = "SpineViewer File";
|
||||
private static readonly string SkelIconFilePath = Path.Combine(App.ProcessDirectory, "Resources\\Images\\skel.ico");
|
||||
private static readonly string ShellOpenCommand = $"\"{App.ProcessPath}\" \"%1\"";
|
||||
|
||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly MainWindowViewModel _vmMain;
|
||||
@@ -63,8 +70,19 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
/// </summary>
|
||||
public void LoadPreference()
|
||||
{
|
||||
if (JsonHelper.Deserialize<PreferenceModel>(PreferenceFilePath, out var obj, true))
|
||||
Preference = obj;
|
||||
if (JsonHelper.Deserialize<PreferenceModel>(PreferenceFilePath, out var obj, true))
|
||||
{
|
||||
try
|
||||
{
|
||||
Preference = obj;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to load some prefereneces, {0}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,6 +112,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
DebugClippings = DebugClippings,
|
||||
|
||||
RenderSelectedOnly = RenderSelectedOnly,
|
||||
AssociateFileSuffix = AssociateFileSuffix,
|
||||
AppLanguage = AppLanguage,
|
||||
};
|
||||
}
|
||||
@@ -118,6 +137,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
DebugClippings = value.DebugClippings;
|
||||
|
||||
RenderSelectedOnly = value.RenderSelectedOnly;
|
||||
AssociateFileSuffix = value.AssociateFileSuffix;
|
||||
AppLanguage = value.AppLanguage;
|
||||
}
|
||||
}
|
||||
@@ -230,6 +250,71 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
set => SetProperty(_vmMain.SFMLRendererViewModel.RenderSelectedOnly, value, v => _vmMain.SFMLRendererViewModel.RenderSelectedOnly = v);
|
||||
}
|
||||
|
||||
public bool AssociateFileSuffix
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查 .skel 的 ProgID
|
||||
using (var key = Registry.CurrentUser.OpenSubKey(@"Software\Classes\.skel"))
|
||||
{
|
||||
var progIdValue = key?.GetValue("") as string;
|
||||
if (!string.Equals(progIdValue, App.ProgId, StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查 command 指令是否相同
|
||||
using (var key = Registry.CurrentUser.OpenSubKey($@"Software\Classes\{App.ProgId}\shell\open\command"))
|
||||
{
|
||||
var command = key?.GetValue("") as string;
|
||||
if (string.IsNullOrWhiteSpace(command))
|
||||
return false;
|
||||
return command == ShellOpenCommand;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
SetProperty(AssociateFileSuffix, value, v =>
|
||||
{
|
||||
if (v)
|
||||
{
|
||||
// 文件关联
|
||||
using (var key = Registry.CurrentUser.CreateSubKey(@"Software\Classes\.skel"))
|
||||
{
|
||||
key?.SetValue("", App.ProgId);
|
||||
}
|
||||
|
||||
using (var key = Registry.CurrentUser.CreateSubKey($@"Software\Classes\{App.ProgId}"))
|
||||
{
|
||||
key?.SetValue("", SkelFileDescription);
|
||||
using (var iconKey = key?.CreateSubKey("DefaultIcon"))
|
||||
{
|
||||
iconKey?.SetValue("", $"\"{SkelIconFilePath}\"");
|
||||
}
|
||||
using (var shellKey = key?.CreateSubKey(@"shell\open\command"))
|
||||
{
|
||||
shellKey?.SetValue("", ShellOpenCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 删除关联
|
||||
Registry.CurrentUser.DeleteSubKeyTree(@"Software\Classes\.skel", false);
|
||||
Registry.CurrentUser.DeleteSubKeyTree($@"Software\Classes\{App.ProgId}", false);
|
||||
}
|
||||
|
||||
Shell32.SHChangeNotify(Shell32.SHCNE_ASSOCCHANGED, Shell32.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public AppLanguage AppLanguage
|
||||
{
|
||||
get => ((App)App.Current).Language;
|
||||
|
||||
@@ -86,6 +86,14 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
/// </summary>
|
||||
public event NotifyCollectionChangedEventHandler? RequestSelectionChanging;
|
||||
|
||||
public void SetResolution(uint x, uint y)
|
||||
{
|
||||
var lastRes = _renderer.Resolution;
|
||||
_renderer.Resolution = new(x, y);
|
||||
if (lastRes.X != x) OnPropertyChanged(nameof(ResolutionX));
|
||||
if (lastRes.Y != y) OnPropertyChanged(nameof(ResolutionY));
|
||||
}
|
||||
|
||||
public uint ResolutionX
|
||||
{
|
||||
get => _renderer.Resolution.X;
|
||||
@@ -455,8 +463,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
set
|
||||
{
|
||||
ResolutionX = value.ResolutionX;
|
||||
ResolutionY = value.ResolutionY;
|
||||
SetResolution(value.ResolutionX, value.ResolutionY);
|
||||
CenterX = value.CenterX;
|
||||
CenterY = value.CenterY;
|
||||
Zoom = value.Zoom;
|
||||
|
||||
@@ -11,6 +11,7 @@ using SpineViewer.ViewModels.Exporters;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -45,6 +46,11 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
_customFFmpegExporterViewModel = new(_vmMain);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 请求选中项发生变化
|
||||
/// </summary>
|
||||
public event NotifyCollectionChangedEventHandler? RequestSelectionChanging;
|
||||
|
||||
/// <summary>
|
||||
/// 单帧导出 ViewModel
|
||||
/// </summary>
|
||||
@@ -101,7 +107,12 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
|
||||
private void AddSpineObject_Execute()
|
||||
{
|
||||
MessagePopupService.Info("Not Implemented, please drag files into here or add them from clipboard :)");
|
||||
if (!DialogService.ShowOpenFileDialog(out var skelFileName, AppResource.Str_OpenSkelFileTitle))
|
||||
return;
|
||||
if (!DialogService.ShowOpenFileDialog(out var atlasFileName, AppResource.Str_OpenAtlasFileTitle))
|
||||
return;
|
||||
AddSpineObject(skelFileName, atlasFileName);
|
||||
_logger.LogCurrentProcessMemoryUsage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -138,6 +149,34 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除全部模型
|
||||
/// </summary>
|
||||
public RelayCommand<IList?> Cmd_RemoveAllSpineObject => _cmd_RemoveAllSpineObject ??= new(RemoveAllSpineObject_Execute, RemoveAllSpineObject_CanExecute);
|
||||
private RelayCommand<IList?>? _cmd_RemoveAllSpineObject;
|
||||
|
||||
private void RemoveAllSpineObject_Execute(IList? args)
|
||||
{
|
||||
if (!RemoveAllSpineObject_CanExecute(args)) return;
|
||||
|
||||
if (!MessagePopupService.Quest(string.Format(AppResource.Str_RemoveItemsQuest, args.Count)))
|
||||
return;
|
||||
|
||||
lock (_spineObjectModels.Lock)
|
||||
{
|
||||
foreach (var sp in _spineObjectModels)
|
||||
sp.Dispose();
|
||||
_spineObjectModels.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private bool RemoveAllSpineObject_CanExecute(IList? args)
|
||||
{
|
||||
if (args is null) return false;
|
||||
if (args.Count <= 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从剪贴板文件列表添加模型
|
||||
/// </summary>
|
||||
@@ -457,7 +496,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
var skelPath = paths[i];
|
||||
var skelPath = paths[totalCount - 1 - i]; // 从后往前添加, 每次插入到列表的第一个
|
||||
reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}";
|
||||
|
||||
if (AddSpineObject(skelPath))
|
||||
@@ -480,7 +519,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全地在末尾添加一个模型, 发生错误会输出日志
|
||||
/// 安全地在列表头添加一个模型, 发生错误会输出日志
|
||||
/// </summary>
|
||||
/// <returns>是否添加成功</returns>
|
||||
private bool AddSpineObject(string skelPath, string? atlasPath = null)
|
||||
@@ -488,7 +527,20 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
try
|
||||
{
|
||||
var sp = new SpineObjectModel(skelPath, atlasPath);
|
||||
lock (_spineObjectModels.Lock) _spineObjectModels.Add(sp);
|
||||
lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp);
|
||||
if (Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -499,22 +551,6 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AddSpineObject(SpineObjectWorkspaceConfigModel cfg)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sp = new SpineObjectModel(cfg);
|
||||
lock (_spineObjectModels.Lock) _spineObjectModels.Add(sp);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to load: {0}, {1}", cfg.SkelPath, ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<SpineObjectWorkspaceConfigModel> LoadedSpineObjects
|
||||
{
|
||||
get
|
||||
@@ -577,7 +613,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
var cfg = models[i];
|
||||
var cfg = models[totalCount - 1 - i]; // 从后往前添加, 每次插入到列表的第一个
|
||||
reporter.ProgressText = $"[{i}/{totalCount}] {cfg}";
|
||||
|
||||
if (AddSpineObject(cfg))
|
||||
@@ -605,5 +641,38 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
sp.ResetAnimationsTime();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全地在列表头添加一个模型, 发生错误会输出日志
|
||||
/// </summary>
|
||||
/// <returns>是否添加成功</returns>
|
||||
private bool AddSpineObject(SpineObjectWorkspaceConfigModel cfg)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sp = new SpineObjectModel(cfg);
|
||||
lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp);
|
||||
if (Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||
}
|
||||
else
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset));
|
||||
RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp));
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Trace(ex.ToString());
|
||||
_logger.Error("Failed to load: {0}, {1}", cfg.SkelPath, ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1">
|
||||
<Grid>
|
||||
<Grid x:Name="_rootGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -91,7 +91,7 @@
|
||||
|
||||
<!-- 模型列表页 -->
|
||||
<TabItem Header="{DynamicResource Str_SpineObject}">
|
||||
<Grid>
|
||||
<Grid x:Name="_modelListGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -147,13 +147,16 @@
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{DynamicResource Str_AddSpineObject}"
|
||||
Command="{Binding Cmd_AddSpineObject}"/>
|
||||
<MenuItem Header="{DynamicResource Str_AddSpineObjectFromClipboard}"
|
||||
InputGestureText="Ctrl+V"
|
||||
Command="{Binding Cmd_AddSpineObjectFromClipboard}"/>
|
||||
<MenuItem Header="{DynamicResource Str_RemoveSpineObject}"
|
||||
InputGestureText="Delete"
|
||||
Command="{Binding Cmd_RemoveSpineObject}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_AddSpineObjectFromClipboard}"
|
||||
InputGestureText="Ctrl+V"
|
||||
Command="{Binding Cmd_AddSpineObjectFromClipboard}"/>
|
||||
<MenuItem Header="{DynamicResource Str_RemoveAllSpineObject}"
|
||||
Command="{Binding Cmd_RemoveAllSpineObject}"
|
||||
CommandParameter="{Binding PlacementTarget.Items, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_Reload}"
|
||||
InputGestureText="Ctrl+R"
|
||||
Command="{Binding Cmd_ReloadSpineObject}"
|
||||
@@ -578,7 +581,7 @@
|
||||
|
||||
<!-- 浏览页 -->
|
||||
<TabItem Header="{DynamicResource Str_Explorer}" DataContext="{Binding ExplorerListViewModel}">
|
||||
<Grid>
|
||||
<Grid x:Name="_explorerGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -785,14 +788,14 @@
|
||||
<GridSplitter Grid.Column="1" ResizeDirection="Columns"/>
|
||||
|
||||
<Border Grid.Column="2">
|
||||
<Grid>
|
||||
<Grid x:Name="_rightPanelGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="5*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid >
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
|
||||
@@ -3,12 +3,15 @@ using NLog;
|
||||
using NLog.Layouts;
|
||||
using NLog.Targets;
|
||||
using Spine;
|
||||
using SpineViewer.Models;
|
||||
using SpineViewer.Natives;
|
||||
using SpineViewer.Resources;
|
||||
using SpineViewer.Utils;
|
||||
using SpineViewer.ViewModels.MainWindow;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -26,6 +29,11 @@ namespace SpineViewer.Views;
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 上一次状态文件保存路径
|
||||
/// </summary>
|
||||
public static readonly string LastStateFilePath = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "laststate.json");
|
||||
|
||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
private ListViewItem? _listViewDragSourceItem = null;
|
||||
private Point _listViewDragSourcePoint;
|
||||
@@ -38,9 +46,10 @@ public partial class MainWindow : Window
|
||||
InitializeLogConfiguration();
|
||||
_vm = new (_renderPanel);
|
||||
DataContext = _vm;
|
||||
_vm.SpineObjectListViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
||||
_vm.SFMLRendererViewModel.RequestSelectionChanging += SpinesListView_RequestSelectionChanging;
|
||||
|
||||
Loaded += MainWindow_Loaded;
|
||||
ContentRendered += MainWindow_ContentRendered;
|
||||
Closed += MainWindow_Closed;
|
||||
}
|
||||
|
||||
@@ -48,13 +57,12 @@ public partial class MainWindow : Window
|
||||
{
|
||||
var vm = _vm.SFMLRendererViewModel;
|
||||
_renderPanel.CanvasMouseWheelScrolled += vm.CanvasMouseWheelScrolled;
|
||||
_renderPanel.CanvasMouseButtonPressed += vm.CanvasMouseButtonPressed;
|
||||
_renderPanel.CanvasMouseButtonPressed += (s, e) => { vm.CanvasMouseButtonPressed(s, e); _spinesListView.Focus(); }; // 用户点击画布后强制转移焦点至列表
|
||||
_renderPanel.CanvasMouseMove += vm.CanvasMouseMove;
|
||||
_renderPanel.CanvasMouseButtonReleased += vm.CanvasMouseButtonReleased;
|
||||
|
||||
// 设置默认参数并启动渲染
|
||||
vm.ResolutionX = 1500;
|
||||
vm.ResolutionY = 1000;
|
||||
vm.SetResolution(1500, 1000);
|
||||
vm.Zoom = 0.75f;
|
||||
vm.CenterX = 0;
|
||||
vm.CenterY = 0;
|
||||
@@ -64,14 +72,36 @@ public partial class MainWindow : Window
|
||||
|
||||
// 加载首选项
|
||||
_vm.PreferenceViewModel.LoadPreference();
|
||||
|
||||
LoadLastState();
|
||||
}
|
||||
|
||||
private void MainWindow_ContentRendered(object? sender, EventArgs e)
|
||||
{
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
if (args.Length > 1)
|
||||
{
|
||||
string[] filePaths = args.Skip(1).ToArray();
|
||||
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_Closed(object? sender, EventArgs e)
|
||||
{
|
||||
SaveLastState();
|
||||
|
||||
var vm = _vm.SFMLRendererViewModel;
|
||||
vm.StopRender();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 给管道通信提供的打开文件外部调用方法
|
||||
/// </summary>
|
||||
public void OpenFiles(IEnumerable<string> filePaths)
|
||||
{
|
||||
_vm.SpineObjectListViewModel.AddSpineObjectFromFileList(filePaths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化窗口日志器
|
||||
/// </summary>
|
||||
@@ -100,6 +130,63 @@ public partial class MainWindow : Window
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
|
||||
private void LoadLastState()
|
||||
{
|
||||
if (JsonHelper.Deserialize<LastStateModel>(LastStateFilePath, out var m, true))
|
||||
{
|
||||
Left = m.WindowLeft;
|
||||
Top = m.WindowTop;
|
||||
Width = m.WindowWidth;
|
||||
Height = m.WindowHeight;
|
||||
if (m.WindowState == WindowState.Maximized)
|
||||
{
|
||||
WindowState = WindowState.Maximized;
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
}
|
||||
|
||||
_rootGrid.ColumnDefinitions[0].Width = new(m.RootGridCol0Width);
|
||||
_modelListGrid.RowDefinitions[0].Height = new(m.ModelListRow0Height);
|
||||
_explorerGrid.RowDefinitions[0].Height = new(m.ExplorerGridRow0Height);
|
||||
_rightPanelGrid.RowDefinitions[0].Height = new(m.RightPanelGridRow0Height);
|
||||
|
||||
_vm.SFMLRendererViewModel.SetResolution(m.ResolutionX, m.ResolutionY);
|
||||
_vm.SFMLRendererViewModel.MaxFps = m.MaxFps;
|
||||
_vm.SFMLRendererViewModel.Speed = m.Speed;
|
||||
_vm.SFMLRendererViewModel.ShowAxis = m.ShowAxis;
|
||||
_vm.SFMLRendererViewModel.BackgroundColor = m.BackgroundColor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void SaveLastState()
|
||||
{
|
||||
var m = new LastStateModel()
|
||||
{
|
||||
WindowLeft = Left,
|
||||
WindowTop = Top,
|
||||
WindowWidth = Width,
|
||||
WindowHeight = Height,
|
||||
WindowState = WindowState,
|
||||
|
||||
RootGridCol0Width = _rootGrid.ColumnDefinitions[0].ActualWidth,
|
||||
ModelListRow0Height = _modelListGrid.RowDefinitions[0].ActualHeight,
|
||||
ExplorerGridRow0Height = _explorerGrid.RowDefinitions[0].ActualHeight,
|
||||
RightPanelGridRow0Height = _rightPanelGrid.RowDefinitions[0].ActualHeight,
|
||||
|
||||
ResolutionX = _vm.SFMLRendererViewModel.ResolutionX,
|
||||
ResolutionY = _vm.SFMLRendererViewModel.ResolutionY,
|
||||
MaxFps = _vm.SFMLRendererViewModel.MaxFps,
|
||||
Speed = _vm.SFMLRendererViewModel.Speed,
|
||||
ShowAxis = _vm.SFMLRendererViewModel.ShowAxis,
|
||||
BackgroundColor = _vm.SFMLRendererViewModel.BackgroundColor,
|
||||
};
|
||||
|
||||
JsonHelper.Serialize(m, LastStateFilePath);
|
||||
}
|
||||
|
||||
#region _spinesListView 事件处理
|
||||
|
||||
private void SpinesListView_RequestSelectionChanging(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
@@ -125,6 +212,9 @@ public partial class MainWindow : Window
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果选中项发生变化也强制转移焦点
|
||||
_spinesListView.Focus();
|
||||
}
|
||||
|
||||
private void SpinesListView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
@@ -234,11 +324,9 @@ public partial class MainWindow : Window
|
||||
if (_fullScreenLayout.Visibility == Visibility.Visible) return;
|
||||
|
||||
IntPtr hwnd = new WindowInteropHelper(this).Handle;
|
||||
if (Win32.GetScreenResolution(hwnd, out var resX, out var resY))
|
||||
if (User32.GetScreenResolution(hwnd, out var resX, out var resY))
|
||||
{
|
||||
var vm = _vm.SFMLRendererViewModel;
|
||||
vm.ResolutionX = resX;
|
||||
vm.ResolutionY = resY;
|
||||
_vm.SFMLRendererViewModel.SetResolution(resX, resY);
|
||||
}
|
||||
|
||||
HandyControl.Controls.IconElement.SetGeometry(_fullScreenButton, AppResource.Geo_ArrowsMinimize);
|
||||
|
||||
@@ -143,13 +143,17 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_RenderSelectedOnly}"/>
|
||||
<ToggleButton Grid.Row="0" Grid.Column="1" IsChecked="{Binding RenderSelectedOnly}"/>
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
|
||||
<ComboBox Grid.Row="1" Grid.Column="1"
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_AssociateFileSuffix}"/>
|
||||
<ToggleButton Grid.Row="1" Grid.Column="1" IsChecked="{Binding AssociateFileSuffix}"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_Language}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1"
|
||||
SelectedItem="{Binding AppLanguage}"
|
||||
ItemsSource="{x:Static vm:PreferenceViewModel.AppLanguageOptions}"/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user