Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ef13239da | ||
|
|
13ef873650 | ||
|
|
78b9834f6c | ||
|
|
672a695b20 | ||
|
|
e9951ed79a | ||
|
|
0c16b2f104 | ||
|
|
7628075420 | ||
|
|
6f896bdaad | ||
|
|
98930db4b6 | ||
|
|
c7493372e9 | ||
|
|
707aa7f570 | ||
|
|
99ff6f9f0a | ||
|
|
be8193e235 | ||
|
|
21b6dbee4c | ||
|
|
f60418fecb | ||
|
|
1180c735c8 | ||
|
|
3d8f6547e0 | ||
|
|
99ec2704fe | ||
|
|
dbd2cef766 | ||
|
|
212ecc2ff3 | ||
|
|
7806f9298d | ||
|
|
3bc57a8990 | ||
|
|
67c9ea9291 | ||
|
|
f404acc834 |
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: 问题报告/Bug report
|
||||
about: 报告可能的程序错误/Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 问题描述/Describe the bug
|
||||
清晰完整的描述问题是什么以及如何发生的。/A clear and concise description of what the bug is.
|
||||
|
||||
## 复现方式(可选)/To Reproduce (Optional)
|
||||
|
||||
## 截图(可选)/Screenshots (Optional)**
|
||||
如果有必要,提供报错时的有关截图。/If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## 附件(可选)/Attachments (Optional)
|
||||
请将会**出现问题的文件**以及**日志文件**打包成一个 ZIP 后作为附件贴在 issue 内。/Please compress the problematic files and the log files into a single ZIP archive and attach it to this issue.
|
||||
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,5 +1,24 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.15.14
|
||||
|
||||
- 将预览画面的首选项移动至上一次状态参数中
|
||||
- 增加预览画面像素的自动保存和恢复
|
||||
- 增加日志启动时的版本号输出
|
||||
|
||||
## v0.15.13
|
||||
|
||||
- 增加程序布局自动存储和还原
|
||||
- 增加部分预览画面首选项
|
||||
|
||||
## v0.15.12
|
||||
|
||||
- 增加单个模型和单个轨道的时间因子
|
||||
- 增加单个轨道的 Alpha 混合参数
|
||||
- 调整轨道清除命令至右键菜单
|
||||
- 设置默认标签页为模型
|
||||
- 完善导入时的报错信息
|
||||
|
||||
## v0.15.11
|
||||
|
||||
- 修复自定义导出中参数构造错误
|
||||
|
||||
@@ -21,6 +21,8 @@ A simple and user-friendly Spine file viewer and exporter with multi-language su
|
||||
* Skin and custom slot attachment settings.
|
||||
* Custom slot visibility settings.
|
||||
* Debug rendering support.
|
||||
* View/model/track time scale adjustment.
|
||||
* Track alpha blending parameter settings.
|
||||
* Fullscreen preview mode.
|
||||
* Export to single frame/image sequence/animated GIF/video formats.
|
||||
* Automatic resolution batch export.
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
- 支持皮肤/自定义插槽附件设置
|
||||
- 支持自定义插槽可见性
|
||||
- 支持调试渲染
|
||||
- 支持画面/模型/轨道时间倍速设置
|
||||
- 支持设置轨道 Alpha 混合参数
|
||||
- 支持全屏预览
|
||||
- 支持单帧/动图/视频文件导出
|
||||
- 支持自动分辨率批量导出
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.11</Version>
|
||||
<Version>0.15.14</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -66,10 +66,10 @@ namespace Spine
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
throw new KeyNotFoundException($"Unrecognized skel suffix '{skelPath}'");
|
||||
throw new KeyNotFoundException($"Unrecognized skel file suffix");
|
||||
}
|
||||
}
|
||||
else if (!File.Exists(atlasPath)) throw new FileNotFoundException($"{nameof(atlasPath)} not found", skelPath);
|
||||
else if (!File.Exists(atlasPath)) throw new FileNotFoundException($"{nameof(atlasPath)} not found", atlasPath);
|
||||
AtlasPath = Path.GetFullPath(atlasPath);
|
||||
|
||||
// 自动检测版本, 可能会抛出异常
|
||||
@@ -105,14 +105,21 @@ namespace Spine
|
||||
|
||||
// 依然加载不成功就只能报错
|
||||
if (_data is null || Version is null)
|
||||
throw new InvalidDataException($"Failed to load spine by existed versions: '{skelPath}', '{atlasPath}'");
|
||||
throw new InvalidDataException($"Failed to load spine by existed versions");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 根据版本实例化对象
|
||||
Version = version;
|
||||
try
|
||||
{
|
||||
_data = SpineObjectData.New(Version, skelPath, atlasPath, textureLoader);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new InvalidDataException($"Failed to load spine with version '{version}'");
|
||||
}
|
||||
}
|
||||
|
||||
// 创建状态实例
|
||||
_skeleton = _data.CreateSkeleton();
|
||||
@@ -167,6 +174,7 @@ namespace Spine
|
||||
// 拷贝渲染设置
|
||||
UsePma = other.UsePma;
|
||||
Physics = other.Physics;
|
||||
_animationState.TimeScale = other._animationState.TimeScale;
|
||||
|
||||
// 拷贝皮肤加载情况
|
||||
_skinLoadStatus = other._skinLoadStatus.ToDictionary();
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace SpineViewer
|
||||
{
|
||||
InitializeLogConfiguration();
|
||||
_logger = LogManager.GetCurrentClassLogger();
|
||||
_logger.Info("Application Started");
|
||||
_logger.Info("Application Started, v{0}", Version);
|
||||
|
||||
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
|
||||
{
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace SpineViewer.Extensions
|
||||
foreach (var tr in self.AnimationState.IterTracks().Where(t => t is not null))
|
||||
{
|
||||
var t = spineObject.AnimationState.SetAnimation(tr!.TrackIndex, tr.Animation, tr.Loop);
|
||||
t.TimeScale = tr.TimeScale;
|
||||
t.Alpha = tr.Alpha;
|
||||
if (keepTrackTime)
|
||||
t.TrackTime = tr.TrackTime;
|
||||
}
|
||||
@@ -38,7 +40,8 @@ namespace SpineViewer.Extensions
|
||||
foreach (var e in self.AnimationState.IterTracks())
|
||||
{
|
||||
if (e is not null)
|
||||
self.AnimationState.SetAnimation(e.TrackIndex, e.Animation, e.Loop);
|
||||
e.TrackTime = 0; // 直接重置时间能保留原本的 TrackEntry
|
||||
//self.AnimationState.SetAnimation(e.TrackIndex, e.Animation, e.Loop);
|
||||
}
|
||||
self.Update(0);
|
||||
}
|
||||
@@ -65,7 +68,7 @@ namespace SpineViewer.Extensions
|
||||
/// <summary>
|
||||
/// 按给定的帧率获取所有轨道第一个条目动画全时长包围盒大小, 是一个耗时操作, 如果可能的话最好缓存结果
|
||||
/// </summary>
|
||||
public static Rect GetAnimationBounds(this SpineObject self, float fps = 10)
|
||||
public static Rect GetAnimationBounds(this SpineObject self, float fps = 30)
|
||||
{
|
||||
using var copy = self.Copy();
|
||||
var bounds = copy.GetCurrentBounds();
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ namespace SpineViewer.Models
|
||||
|
||||
public string Physics { get; set; } = ISkeleton.Physics.Update.ToString();
|
||||
|
||||
public float TimeScale { get; set; } = 1f;
|
||||
|
||||
public float Scale { get; set; } = 1f;
|
||||
|
||||
public bool FlipX { get; set; }
|
||||
@@ -33,7 +35,7 @@ namespace SpineViewer.Models
|
||||
|
||||
public List<string> DisabledSlots { get; set; } = [];
|
||||
|
||||
public List<string?> Animations { get; set; } = [];
|
||||
public List<TrackConfigModel?> Animations { get; set; } = [];
|
||||
|
||||
public bool DebugTexture { get; set; } = true;
|
||||
|
||||
@@ -54,5 +56,15 @@ namespace SpineViewer.Models
|
||||
public bool DebugPoints { get; set; }
|
||||
|
||||
public bool DebugClippings { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class TrackConfigModel
|
||||
{
|
||||
public string AnimationName { get; set; } = "";
|
||||
|
||||
public float TimeScale { get; set; } = 1f;
|
||||
|
||||
public float Alpha { get; set; } = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace SpineViewer.Models
|
||||
|
||||
public event EventHandler<SlotAttachmentChangedEventArgs>? SlotAttachmentChanged;
|
||||
|
||||
public event EventHandler<AnimationChangedEventArgs>? AnimationChanged;
|
||||
public event EventHandler<TrackPropertyChangedEventArgs>? TrackPropertyChanged;
|
||||
|
||||
public SpineVersion Version => _spineObject.Version;
|
||||
|
||||
@@ -129,6 +129,12 @@ namespace SpineViewer.Models
|
||||
set { lock (_lock) SetProperty(_spineObject.Physics, value, v => _spineObject.Physics = v); }
|
||||
}
|
||||
|
||||
public float TimeScale
|
||||
{
|
||||
get { lock (_lock) return _spineObject.AnimationState.TimeScale; }
|
||||
set { lock (_lock) SetProperty(_spineObject.AnimationState.TimeScale, Math.Clamp(value, 0.01f, 100f), v => _spineObject.AnimationState.TimeScale = v); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缩放倍数, 绝对值大小, 两个方向大小不一致时返回 -1, 设置时不会影响正负号
|
||||
/// </summary>
|
||||
@@ -248,15 +254,59 @@ namespace SpineViewer.Models
|
||||
public void SetAnimation(int index, string name)
|
||||
{
|
||||
bool changed = false;
|
||||
float lastTimeScale = 1f;
|
||||
float lastAlpha = 1f;
|
||||
lock (_lock)
|
||||
{
|
||||
if (_spineObject.Data.AnimationsByName.ContainsKey(name))
|
||||
{
|
||||
_spineObject.AnimationState.SetAnimation(index, name, true);
|
||||
// 需要记录之前的轨道属性值并还原
|
||||
if (_spineObject.AnimationState.GetCurrent(index) is ITrackEntry entry)
|
||||
{
|
||||
lastTimeScale = entry.TimeScale;
|
||||
lastAlpha = entry.Alpha;
|
||||
}
|
||||
entry = _spineObject.AnimationState.SetAnimation(index, name, true);
|
||||
entry.TimeScale = lastTimeScale;
|
||||
entry.Alpha = lastAlpha;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) AnimationChanged?.Invoke(this, new(index, name));
|
||||
if (changed) TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.AnimationName)));
|
||||
}
|
||||
|
||||
public float GetTrackTimeScale(int index)
|
||||
{
|
||||
lock (_lock) return _spineObject.AnimationState.GetCurrent(index)?.TimeScale ?? 1;
|
||||
}
|
||||
|
||||
public void SetTrackTimeScale(int index, float scale)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_spineObject.AnimationState.GetCurrent(index) is ITrackEntry entry)
|
||||
{
|
||||
entry.TimeScale = Math.Clamp(scale, 0.01f, 100f);
|
||||
TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.TimeScale)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetTrackAlpha(int index)
|
||||
{
|
||||
lock (_lock) return _spineObject.AnimationState.GetCurrent(index)?.Alpha ?? 1;
|
||||
}
|
||||
|
||||
public void SetTrackAlpha(int index, float alpha)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_spineObject.AnimationState.GetCurrent(index) is ITrackEntry entry)
|
||||
{
|
||||
entry.Alpha = Math.Clamp(alpha, 0f, 1f);
|
||||
TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.Alpha)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int[] GetTrackIndices()
|
||||
@@ -277,7 +327,7 @@ namespace SpineViewer.Models
|
||||
public void ClearTrack(int index)
|
||||
{
|
||||
lock (_lock) _spineObject.AnimationState.ClearTrack(index);
|
||||
AnimationChanged?.Invoke(this, new(index, null));
|
||||
TrackPropertyChanged?.Invoke(this, new(index, nameof(TrackPropertyChangedEventArgs.AnimationName)));
|
||||
}
|
||||
|
||||
public void ResetAnimationsTime()
|
||||
@@ -388,6 +438,7 @@ namespace SpineViewer.Models
|
||||
|
||||
UsePma = _spineObject.UsePma,
|
||||
Physics = _spineObject.Physics.ToString(),
|
||||
TimeScale = _spineObject.AnimationState.TimeScale,
|
||||
|
||||
DebugTexture = _spineObject.DebugTexture,
|
||||
DebugBounds = _spineObject.DebugBounds,
|
||||
@@ -408,7 +459,22 @@ namespace SpineViewer.Models
|
||||
config.DisabledSlots = _spineObject.Skeleton.Slots.Where(it => it.Disabled).Select(it => it.Name).ToList();
|
||||
|
||||
// XXX: 处理空动画
|
||||
config.Animations.AddRange(_spineObject.AnimationState.IterTracks().Select(tr => tr?.Animation.Name));
|
||||
foreach (var tr in _spineObject.AnimationState.IterTracks())
|
||||
{
|
||||
if (tr is not null)
|
||||
{
|
||||
config.Animations.Add(new()
|
||||
{
|
||||
AnimationName = tr.Animation.Name,
|
||||
TimeScale = tr.TimeScale,
|
||||
Alpha = tr.Alpha
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
config.Animations.Add(null);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -427,6 +493,7 @@ namespace SpineViewer.Models
|
||||
SetProperty(_spineObject.Skeleton.Y, value.Y, v => _spineObject.Skeleton.Y = v, nameof(Y));
|
||||
SetProperty(_spineObject.UsePma, value.UsePma, v => _spineObject.UsePma = v, nameof(UsePma));
|
||||
SetProperty(_spineObject.Physics, Enum.Parse<ISkeleton.Physics>(value.Physics ?? "Update", true), v => _spineObject.Physics = v, nameof(Physics));
|
||||
SetProperty(_spineObject.AnimationState.TimeScale, value.TimeScale, v => _spineObject.AnimationState.TimeScale = v, nameof(TimeScale));
|
||||
|
||||
foreach (var name in _spineObject.Data.Skins.Select(v => v.Name).Except(value.LoadedSkins))
|
||||
if (_spineObject.SetSkinStatus(name, false))
|
||||
@@ -446,11 +513,15 @@ namespace SpineViewer.Models
|
||||
// XXX: 处理空动画
|
||||
_spineObject.AnimationState.ClearTracks();
|
||||
int trackIndex = 0;
|
||||
foreach (var name in value.Animations)
|
||||
foreach (var trConfig in value.Animations)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
_spineObject.AnimationState.SetAnimation(trackIndex, name, true);
|
||||
AnimationChanged?.Invoke(this, new(trackIndex, name));
|
||||
if (trConfig is not null && !string.IsNullOrEmpty(trConfig.AnimationName))
|
||||
{
|
||||
var tr = _spineObject.AnimationState.SetAnimation(trackIndex, trConfig.AnimationName, true);
|
||||
tr.TimeScale = trConfig.TimeScale;
|
||||
tr.Alpha = trConfig.Alpha;
|
||||
TrackPropertyChanged?.Invoke(this, new(trackIndex, nameof(TrackPropertyChangedEventArgs.AnimationName)));
|
||||
}
|
||||
trackIndex++;
|
||||
}
|
||||
|
||||
@@ -540,10 +611,23 @@ namespace SpineViewer.Models
|
||||
public string? AttachmentName { get; } = attachmentName;
|
||||
}
|
||||
|
||||
public class AnimationChangedEventArgs(int trackIndex, string? animationName) : EventArgs
|
||||
/// <summary>
|
||||
/// 模型动画轨道属性变化事件参数, 需要检索 <c><see cref="PropertyName"/></c> 来确定发生变化的属性是什么
|
||||
/// </summary>
|
||||
/// <param name="trackIndex">发生属性变化的轨道索引</param>
|
||||
/// <param name="propertyName">使用 <c>nameof</c> 设置发生改变的属性名</param>
|
||||
public class TrackPropertyChangedEventArgs(int trackIndex, string propertyName) : EventArgs
|
||||
{
|
||||
public int TrackIndex { get; } = trackIndex;
|
||||
public string? AnimationName { get; } = animationName;
|
||||
|
||||
/// <summary>
|
||||
/// 发生变化的属性名, 将会使用 <c>nameof</c> 设置为属性名称字符串
|
||||
/// </summary>
|
||||
public string PropertyName { get; } = propertyName;
|
||||
|
||||
public string? AnimationName { get; }
|
||||
public float TimeScale { get; } = 1f;
|
||||
public float Alpha { get; } = 1f;
|
||||
}
|
||||
|
||||
public class SpineObjectLoadOptions
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
<s:String x:Key="Str_IsShown">Show</s:String>
|
||||
<s:String x:Key="Str_UsePma">Premultiply Alpha</s:String>
|
||||
<s:String x:Key="Str_Physics">Physics</s:String>
|
||||
<s:String x:Key="Str_TimeScale">Time Scale</s:String>
|
||||
<s:String x:Key="Str_TimeScaleTootltip">Time scale for a single model, must be positive.</s:String>
|
||||
|
||||
<s:String x:Key="Str_Transform">Transform</s:String>
|
||||
<s:String x:Key="Str_Scale">Scale</s:String>
|
||||
@@ -84,6 +86,11 @@
|
||||
<s:String x:Key="Str_Animation">Animation</s:String>
|
||||
<s:String x:Key="Str_AppendTrack">Add</s:String>
|
||||
<s:String x:Key="Str_InsertTrack">Insert</s:String>
|
||||
<s:String x:Key="Str_ClearTrack">Clear</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScale">Time Scale</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScaleTooltip">Time scale for a single track, must be positive.</s:String>
|
||||
<s:String x:Key="Str_TrackAlpha">Alpha Blending</s:String>
|
||||
<s:String x:Key="Str_TrackAlphaTooltip">Value range: 0–1. Similar to image blending, controls how animations from higher-index tracks mix into lower-index tracks.</s:String>
|
||||
|
||||
<s:String x:Key="Str_Debug">Debug</s:String>
|
||||
<s:String x:Key="Str_DebugTexture">Texture</s:String>
|
||||
@@ -106,6 +113,7 @@
|
||||
<s:String x:Key="Str_Zoom">Zoom</s:String>
|
||||
<s:String x:Key="Str_Rotation">Rotation (Degrees)</s:String>
|
||||
<s:String x:Key="Str_MaxFps">Max FPS</s:String>
|
||||
<s:String x:Key="Str_MaxFpsTooltip">Maximum frame rate of the preview. Set to 0 for no limit.</s:String>
|
||||
<s:String x:Key="Str_PlaySpeed">Playback Speed</s:String>
|
||||
<s:String x:Key="Str_RenderSelectedOnly">Render Selected Only</s:String>
|
||||
<s:String x:Key="Str_ShowAxis">Show Axis</s:String>
|
||||
@@ -221,6 +229,8 @@
|
||||
|
||||
<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_Language">Language</s:String>
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
<s:String x:Key="Str_IsShown">表示</s:String>
|
||||
<s:String x:Key="Str_UsePma">プレマルチプライドアルファ</s:String>
|
||||
<s:String x:Key="Str_Physics">物理</s:String>
|
||||
<s:String x:Key="Str_TimeScale">時間スケール</s:String>
|
||||
<s:String x:Key="Str_TimeScaleTootltip">単一モデルの時間スケール。正の値のみ指定可能です。</s:String>
|
||||
|
||||
<s:String x:Key="Str_Transform">変換</s:String>
|
||||
<s:String x:Key="Str_Scale">スケール</s:String>
|
||||
@@ -84,6 +86,11 @@
|
||||
<s:String x:Key="Str_Animation">アニメーション</s:String>
|
||||
<s:String x:Key="Str_AppendTrack">追加</s:String>
|
||||
<s:String x:Key="Str_InsertTrack">挿入</s:String>
|
||||
<s:String x:Key="Str_ClearTrack">削除</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScale">時間スケール</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScaleTooltip">単一トラックの時間スケール。正の値のみ指定可能です。</s:String>
|
||||
<s:String x:Key="Str_TrackAlpha">アルファ合成</s:String>
|
||||
<s:String x:Key="Str_TrackAlphaTooltip">値の範囲:0~1。画像の合成と同様に、高インデックストラックのアニメーションが低インデックストラックにどの程度混合されるかを制御します。</s:String>
|
||||
|
||||
<s:String x:Key="Str_Debug">デバッグ</s:String>
|
||||
<s:String x:Key="Str_DebugTexture">テクスチャ</s:String>
|
||||
@@ -106,6 +113,7 @@
|
||||
<s:String x:Key="Str_Zoom">ズーム</s:String>
|
||||
<s:String x:Key="Str_Rotation">回転(度)</s:String>
|
||||
<s:String x:Key="Str_MaxFps">最大FPS</s:String>
|
||||
<s:String x:Key="Str_MaxFpsTooltip">プレビュー画面の最大フレームレート。0 に設定すると制限なし。</s:String>
|
||||
<s:String x:Key="Str_PlaySpeed">再生速度</s:String>
|
||||
<s:String x:Key="Str_RenderSelectedOnly">選択のみレンダリング</s:String>
|
||||
<s:String x:Key="Str_ShowAxis">座標軸を表示</s:String>
|
||||
@@ -221,6 +229,8 @@
|
||||
|
||||
<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_Language">言語</s:String>
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
<s:String x:Key="Str_IsShown">显示</s:String>
|
||||
<s:String x:Key="Str_UsePma">预乘Alpha通道</s:String>
|
||||
<s:String x:Key="Str_Physics">物理</s:String>
|
||||
<s:String x:Key="Str_TimeScale">时间因子</s:String>
|
||||
<s:String x:Key="Str_TimeScaleTootltip">单个模型的时间因子,只能取正数</s:String>
|
||||
|
||||
<s:String x:Key="Str_Transform">变换</s:String>
|
||||
<s:String x:Key="Str_Scale">缩放</s:String>
|
||||
@@ -84,6 +86,11 @@
|
||||
<s:String x:Key="Str_Animation">动画</s:String>
|
||||
<s:String x:Key="Str_AppendTrack">添加</s:String>
|
||||
<s:String x:Key="Str_InsertTrack">插入</s:String>
|
||||
<s:String x:Key="Str_ClearTrack">删除</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScale">时间因子</s:String>
|
||||
<s:String x:Key="Str_TrackTimeScaleTooltip">单个轨道的时间因子,只能取正数</s:String>
|
||||
<s:String x:Key="Str_TrackAlpha">Alpha 混合</s:String>
|
||||
<s:String x:Key="Str_TrackAlphaTooltip">取值范围 0-1,与图像混合类似,可以控制高索引轨道在低索引轨道中的动画混合比例</s:String>
|
||||
|
||||
<s:String x:Key="Str_Debug">调试</s:String>
|
||||
<s:String x:Key="Str_DebugTexture">Texture</s:String>
|
||||
@@ -106,6 +113,7 @@
|
||||
<s:String x:Key="Str_Zoom">缩放</s:String>
|
||||
<s:String x:Key="Str_Rotation">旋转(角度)</s:String>
|
||||
<s:String x:Key="Str_MaxFps">最大帧率</s:String>
|
||||
<s:String x:Key="Str_MaxFpsTooltip">预览画面的最大帧率,设置为 0 时则无帧率限制</s:String>
|
||||
<s:String x:Key="Str_PlaySpeed">播放速度</s:String>
|
||||
<s:String x:Key="Str_RenderSelectedOnly">仅渲染选中</s:String>
|
||||
<s:String x:Key="Str_ShowAxis">显示坐标轴</s:String>
|
||||
@@ -221,6 +229,8 @@
|
||||
|
||||
<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_Language">语言</s:String>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.15.11</Version>
|
||||
<Version>0.15.14</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
foreach (var obj in _selectedObjects)
|
||||
{
|
||||
obj.PropertyChanged -= SingleModel_PropertyChanged;
|
||||
obj.AnimationChanged -= SingleModel_AnimationChanged;
|
||||
obj.TrackPropertyChanged -= SingleModel_TrackPropChanged;
|
||||
}
|
||||
_skins.Clear();
|
||||
_slots.Clear();
|
||||
@@ -44,7 +44,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
foreach (var obj in _selectedObjects)
|
||||
{
|
||||
obj.PropertyChanged += SingleModel_PropertyChanged;
|
||||
obj.AnimationChanged += SingleModel_AnimationChanged;
|
||||
obj.TrackPropertyChanged += SingleModel_TrackPropChanged;
|
||||
}
|
||||
|
||||
IEnumerable<string> commonSkinNames = _selectedObjects[0].Skins;
|
||||
@@ -74,6 +74,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
OnPropertyChanged(nameof(IsShown));
|
||||
OnPropertyChanged(nameof(UsePma));
|
||||
OnPropertyChanged(nameof(Physics));
|
||||
OnPropertyChanged(nameof(TimeScale));
|
||||
|
||||
OnPropertyChanged(nameof(Scale));
|
||||
OnPropertyChanged(nameof(FlipX));
|
||||
@@ -217,6 +218,25 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
public float? TimeScale
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_selectedObjects.Length <= 0) return null;
|
||||
var val = _selectedObjects[0].TimeScale;
|
||||
if (_selectedObjects.Skip(1).Any(it => it.TimeScale != val)) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_selectedObjects.Length <= 0) return;
|
||||
if (value is null) return;
|
||||
foreach (var sp in _selectedObjects) sp.TimeScale = (float)value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public float? Scale
|
||||
{
|
||||
get
|
||||
@@ -384,6 +404,27 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
);
|
||||
private RelayCommand<IList?>? _cmd_InsertTrack;
|
||||
|
||||
public RelayCommand<IList?>? Cmd_ClearTrack => _cmd_ClearTrack ??= new(
|
||||
args =>
|
||||
{
|
||||
if (_selectedObjects.Length <= 0) return;
|
||||
if (args is null) return;
|
||||
if (args.Count <= 0) return;
|
||||
|
||||
foreach (var vm in args.OfType<AnimationTrackViewModel>())
|
||||
foreach (var sp in _selectedObjects)
|
||||
sp.ClearTrack(vm.TrackIndex);
|
||||
},
|
||||
args =>
|
||||
{
|
||||
if (_selectedObjects.Length <= 0) return false;
|
||||
if (args is null) return false;
|
||||
if (args.Count <= 0) return false;
|
||||
return true;
|
||||
}
|
||||
);
|
||||
private RelayCommand<IList?>? _cmd_ClearTrack;
|
||||
|
||||
public bool? DebugTexture
|
||||
{
|
||||
get
|
||||
@@ -574,43 +615,52 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 监听单个模型属性发生变化, 则更新聚合属性值
|
||||
/// </summary>
|
||||
private void SingleModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
private static readonly Dictionary<string, string> _singleModelPropertyMap = new()
|
||||
{
|
||||
if (e.PropertyName == nameof(SpineObjectModel.IsShown)) OnPropertyChanged(nameof(IsShown));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.UsePma)) OnPropertyChanged(nameof(UsePma));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.Physics)) OnPropertyChanged(nameof(Physics));
|
||||
{ nameof(SpineObjectModel.IsShown), nameof(IsShown) },
|
||||
{ nameof(SpineObjectModel.UsePma), nameof(UsePma) },
|
||||
{ nameof(SpineObjectModel.Physics), nameof(Physics) },
|
||||
{ nameof(SpineObjectModel.TimeScale), nameof(TimeScale) },
|
||||
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.Scale)) OnPropertyChanged(nameof(Scale));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.FlipX)) OnPropertyChanged(nameof(FlipX));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.FlipY)) OnPropertyChanged(nameof(FlipY));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.X)) OnPropertyChanged(nameof(X));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.Y)) OnPropertyChanged(nameof(Y));
|
||||
{ nameof(SpineObjectModel.Scale), nameof(Scale) },
|
||||
{ nameof(SpineObjectModel.FlipX), nameof(FlipX) },
|
||||
{ nameof(SpineObjectModel.FlipY), nameof(FlipY) },
|
||||
{ nameof(SpineObjectModel.X), nameof(X) },
|
||||
{ nameof(SpineObjectModel.Y), nameof(Y) },
|
||||
|
||||
// Skins 变化在 SkinViewModel 中监听
|
||||
// Slots 变化在 SlotAttachmentViewModel 中监听
|
||||
// AnimationTracks 变化在 AnimationTrackViewModel 中监听
|
||||
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugTexture)) OnPropertyChanged(nameof(DebugTexture));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugBounds)) OnPropertyChanged(nameof(DebugBounds));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugBones)) OnPropertyChanged(nameof(DebugBones));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugRegions)) OnPropertyChanged(nameof(DebugRegions));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugMeshHulls)) OnPropertyChanged(nameof(DebugMeshHulls));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugMeshes)) OnPropertyChanged(nameof(DebugMeshes));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugBoundingBoxes)) OnPropertyChanged(nameof(DebugBoundingBoxes));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugPaths)) OnPropertyChanged(nameof(DebugPaths));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugPoints)) OnPropertyChanged(nameof(DebugPoints));
|
||||
else if (e.PropertyName == nameof(SpineObjectModel.DebugClippings)) OnPropertyChanged(nameof(DebugClippings));
|
||||
{ nameof(SpineObjectModel.DebugTexture), nameof(DebugTexture) },
|
||||
{ nameof(SpineObjectModel.DebugBounds), nameof(DebugBounds) },
|
||||
{ nameof(SpineObjectModel.DebugBones), nameof(DebugBones) },
|
||||
{ nameof(SpineObjectModel.DebugRegions), nameof(DebugRegions) },
|
||||
{ nameof(SpineObjectModel.DebugMeshHulls), nameof(DebugMeshHulls) },
|
||||
{ nameof(SpineObjectModel.DebugMeshes), nameof(DebugMeshes) },
|
||||
{ nameof(SpineObjectModel.DebugBoundingBoxes), nameof(DebugBoundingBoxes) },
|
||||
{ nameof(SpineObjectModel.DebugPaths), nameof(DebugPaths) },
|
||||
{ nameof(SpineObjectModel.DebugPoints), nameof(DebugPoints) },
|
||||
{ nameof(SpineObjectModel.DebugClippings), nameof(DebugClippings) },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 监听单个模型属性发生变化, 则更新聚合属性值
|
||||
/// </summary>
|
||||
private void SingleModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (_singleModelPropertyMap.TryGetValue(e.PropertyName, out var targetProperty))
|
||||
{
|
||||
OnPropertyChanged(targetProperty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 监听单个模型动画轨道发生变化, 则重建聚合后的动画列表
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void SingleModel_AnimationChanged(object? sender, AnimationChangedEventArgs e)
|
||||
private void SingleModel_TrackPropChanged(object? sender, TrackPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.AnimationName))
|
||||
{
|
||||
// XXX: 这里应该有更好的实现, 当 e.AnimationName == null 的时候代表删除轨道需要重新构建列表
|
||||
// 但是目前无法识别是否增加了轨道, 因此总是重建列表
|
||||
@@ -625,7 +675,7 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
foreach (var idx in commonTrackIndices) _animationTracks.Add(new(idx, _selectedObjects));
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class SkinViewModel : ObservableObject
|
||||
@@ -798,21 +848,36 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
// 使用弱引用, 则此 ViewModel 被释放时无需显式退订事件
|
||||
foreach (var sp in _spines)
|
||||
{
|
||||
WeakEventManager<SpineObjectModel, AnimationChangedEventArgs>.AddHandler(
|
||||
WeakEventManager<SpineObjectModel, TrackPropertyChangedEventArgs>.AddHandler(
|
||||
sp,
|
||||
nameof(sp.AnimationChanged),
|
||||
SingleModel_AnimationChanged
|
||||
nameof(sp.TrackPropertyChanged),
|
||||
SingleModel_TrackPropChanged
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public RelayCommand Cmd_ClearTrack => _cmd_ClearTrack ??= new(() => { foreach (var sp in _spines) sp.ClearTrack(_trackIndex); });
|
||||
private RelayCommand? _cmd_ClearTrack;
|
||||
|
||||
public ReadOnlyCollection<string> AnimationNames => _animationNames.AsReadOnly();
|
||||
|
||||
public int TrackIndex => _trackIndex;
|
||||
|
||||
public float? AnimationDuration
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_spines.Length <= 0) return null;
|
||||
var ani = _spines[0].GetAnimation(_trackIndex);
|
||||
if (ani is null) return null;
|
||||
var val = _spines[0].GetAnimationDuration(ani);
|
||||
foreach (var sp in _spines.Skip(1))
|
||||
{
|
||||
var a = sp.GetAnimation(_trackIndex);
|
||||
if (a is null) return null;
|
||||
if (sp.GetAnimationDuration(a) != val) return null;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
public string? AnimationName
|
||||
{
|
||||
get
|
||||
@@ -834,27 +899,54 @@ namespace SpineViewer.ViewModels.MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
public float? AnimationDuration
|
||||
public float? TrackTimeScale
|
||||
{
|
||||
get
|
||||
{
|
||||
// XXX: 空轨道和多选不相同都会返回 null
|
||||
if (_spines.Length <= 0) return null;
|
||||
var ani = _spines[0].GetAnimation(_trackIndex);
|
||||
if (ani is null) return null;
|
||||
var val = _spines[0].GetAnimationDuration(ani);
|
||||
foreach (var sp in _spines.Skip(1))
|
||||
{
|
||||
var a = sp.GetAnimation(_trackIndex);
|
||||
if (a is null) return null;
|
||||
if (sp.GetAnimationDuration(a) != val) return null;
|
||||
}
|
||||
var val = _spines[0].GetTrackTimeScale(_trackIndex);
|
||||
if (_spines.Skip(1).Any(it => it.GetTrackTimeScale(_trackIndex) != val)) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_spines.Length <= 0) return;
|
||||
if (value is null) return;
|
||||
foreach (var sp in _spines) sp.SetTrackTimeScale(_trackIndex, (float)value);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SingleModel_AnimationChanged(object? sender, AnimationChangedEventArgs e)
|
||||
public float? TrackAlpha
|
||||
{
|
||||
if (e.TrackIndex == _trackIndex) OnPropertyChanged(nameof(AnimationName));
|
||||
get
|
||||
{
|
||||
// XXX: 空轨道和多选不相同都会返回 null
|
||||
if (_spines.Length <= 0) return null;
|
||||
var val = _spines[0].GetTrackAlpha(_trackIndex);
|
||||
if (_spines.Skip(1).Any(it => it.GetTrackAlpha(_trackIndex) != val)) return null;
|
||||
return val;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_spines.Length <= 0) return;
|
||||
if (value is null) return;
|
||||
foreach (var sp in _spines) sp.SetTrackAlpha(_trackIndex, (float)value);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SingleModel_TrackPropChanged(object? sender, TrackPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.TrackIndex == _trackIndex)
|
||||
{
|
||||
if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.AnimationName)) OnPropertyChanged(nameof(AnimationName));
|
||||
else if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.TimeScale)) OnPropertyChanged(nameof(TrackTimeScale));
|
||||
else if (e.PropertyName == nameof(TrackPropertyChangedEventArgs.Alpha)) OnPropertyChanged(nameof(TrackAlpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1">
|
||||
<Grid>
|
||||
<Grid x:Name="_rootGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -89,120 +89,9 @@
|
||||
<!-- 功能页 -->
|
||||
<TabControl x:Name="_mainTabControl" TabStripPlacement="Left">
|
||||
|
||||
<!-- 浏览页 -->
|
||||
<TabItem Header="{DynamicResource Str_Explorer}" DataContext="{Binding ExplorerListViewModel}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<hc:TextBox hc:InfoElement.Placeholder="{StaticResource Str_Filter}"
|
||||
Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Grid.Column="1"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_Folder}"
|
||||
Command="{Binding Cmd_ChangeCurrentDirectory}"
|
||||
ToolTip="{DynamicResource Str_ChangeCurrentDirectoryTooltip}"/>
|
||||
<Button Grid.Column="2"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_ArrowRotateRight}"
|
||||
Command="{Binding Cmd_RefreshItems}"
|
||||
ToolTip="{DynamicResource Str_RefreshItemsTooltip}"/>
|
||||
</Grid>
|
||||
|
||||
<StatusBar DockPanel.Dock="Bottom">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_ListViewStatusBar">
|
||||
<Binding Path="Items.Count" ElementName="_spineFilesListBox"/>
|
||||
<Binding Path="SelectedItems.Count" ElementName="_spineFilesListBox"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</StatusBar>
|
||||
|
||||
<ListBox x:Name="_spineFilesListBox"
|
||||
VirtualizingPanel.IsVirtualizing="True"
|
||||
ItemsSource="{Binding ShownItems}"
|
||||
DisplayMemberPath="FileName"
|
||||
MouseLeftButtonDown="SpineFilesListBox_MouseLeftButtonDown">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="SelectionChanged">
|
||||
<i:InvokeCommandAction Command="{Binding Cmd_SelectionChanged}"
|
||||
CommandParameter="{Binding SelectedItems, ElementName=_spineFilesListBox}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{DynamicResource Str_AddSelectedItems}"
|
||||
Command="{Binding Cmd_AddSelectedItems}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="{DynamicResource Str_GeneratePreviewForSelected}"
|
||||
Command="{Binding Cmd_GeneratePreviews}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{StaticResource Str_DeletePreviewsForSelected}"
|
||||
Command="{Binding Cmd_DeletePreviews}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
|
||||
<GridSplitter Grid.Row="1" ResizeDirection="Rows"/>
|
||||
|
||||
<Grid Grid.Row="2" DataContext="{Binding SelectedItem}">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 文件目录 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_FileDirectory}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
Text="{Binding FileDirectory, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
ToolTip="{Binding Text, RelativeSource={RelativeSource Mode=Self}}"/>
|
||||
|
||||
<!-- 文件名 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_FileName}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Text="{Binding FileName, Mode=OneWay}"
|
||||
IsReadOnly="True"/>
|
||||
|
||||
<!-- 预览图 -->
|
||||
<Border Grid.Row="2" Grid.ColumnSpan="2" Background="#a0a0a0">
|
||||
<Image Source="{Binding PreviewImage, Mode=OneWay}" Stretch="Uniform"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- 模型列表页 -->
|
||||
<TabItem Header="{DynamicResource Str_SpineObject}">
|
||||
<Grid>
|
||||
<Grid x:Name="_modelListGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@@ -421,6 +310,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 显示 -->
|
||||
@@ -434,6 +324,10 @@
|
||||
<!-- 物理 -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="{DynamicResource Str_Physics}"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" SelectedValue="{Binding Physics}" ItemsSource="{Binding PhysicsOptions}"/>
|
||||
|
||||
<!-- 时间因子 -->
|
||||
<Label Grid.Row="3" Grid.Column="0" Content="{DynamicResource Str_TimeScale}" ToolTip="{DynamicResource Str_TimeScaleTootltip}"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding TimeScale}" ToolTip="{DynamicResource Str_TimeScaleTootltip}"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
@@ -503,7 +397,7 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="{Binding Name}"/>
|
||||
<Label Grid.Column="0" Content="{Binding Name}" Background="#bfffffff"/>
|
||||
<ToggleButton Grid.Column="1" IsChecked="{Binding Status}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -541,7 +435,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="{Binding SlotName}" HorizontalAlignment="Left"/>
|
||||
<Label Grid.Column="0" Content="{Binding SlotName}" HorizontalAlignment="Left" Background="#bfffffff"/>
|
||||
<ComboBox Grid.Column="1" SelectedValue="{Binding AttachmentName}" ItemsSource="{Binding AttachmentNames}"/>
|
||||
<ToggleButton Grid.Column="2" IsChecked="{Binding Visible}"/>
|
||||
</Grid>
|
||||
@@ -568,6 +462,9 @@
|
||||
<MenuItem Header="{DynamicResource Str_InsertTrack}"
|
||||
Command="{Binding Cmd_InsertTrack}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{DynamicResource Str_ClearTrack}"
|
||||
Command="{Binding Cmd_ClearTrack}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
|
||||
@@ -575,19 +472,38 @@
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col0"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="ColTrackIdx"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="ColAniTime"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto" SharedSizeGroup="Col2"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Label Grid.Column="0" Content="{Binding TrackIndex}" HorizontalContentAlignment="Left"/>
|
||||
<ComboBox Grid.Column="1" SelectedValue="{Binding AnimationName}" ItemsSource="{Binding AnimationNames}"/>
|
||||
<Label Grid.Column="2"
|
||||
Content="{Binding AnimationDuration}"
|
||||
ContentStringFormat="{}{0:F3} s"/>
|
||||
<Button Grid.Column="3"
|
||||
Command="{Binding Cmd_ClearTrack}"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_TrashXmark}"/>
|
||||
|
||||
<Label Grid.Column="0" Content="{Binding TrackIndex}" HorizontalContentAlignment="Left" VerticalAlignment="Top" Background="#bfffffff"/>
|
||||
<Label Grid.Column="1" Content="{Binding AnimationDuration}" VerticalAlignment="Top" ContentStringFormat="{}{0:F3} s"/>
|
||||
|
||||
<Expander Grid.Column="2" HorizontalContentAlignment="Stretch">
|
||||
<Expander.Header>
|
||||
<!-- hc 的模板自带左侧 10 的 padding, 此处用 -10 的 margin 来抵消去除 -->
|
||||
<ComboBox Margin="-10 0 0 0" Grid.Column="2" SelectedValue="{Binding AnimationName}" ItemsSource="{Binding AnimationNames}"/>
|
||||
</Expander.Header>
|
||||
<Grid Margin="1 0 0 0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 时间因子 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_TrackTimeScale}" ToolTip="{DynamicResource Str_TrackTimeScaleTooltip}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding TrackTimeScale, StringFormat='{}{0:F3}'}" ToolTip="{DynamicResource Str_TrackTimeScaleTooltip}"/>
|
||||
|
||||
<!-- Alpha 混合 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_TrackAlpha}" ToolTip="{DynamicResource Str_TrackAlphaTooltip}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TrackAlpha, StringFormat='{}{0:F3}'}" ToolTip="{DynamicResource Str_TrackAlphaTooltip}"/>
|
||||
</Grid>
|
||||
</Expander>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
@@ -660,6 +576,117 @@
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- 浏览页 -->
|
||||
<TabItem Header="{DynamicResource Str_Explorer}" DataContext="{Binding ExplorerListViewModel}">
|
||||
<Grid x:Name="_explorerGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Top">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<hc:TextBox hc:InfoElement.Placeholder="{StaticResource Str_Filter}"
|
||||
Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Grid.Column="1"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_Folder}"
|
||||
Command="{Binding Cmd_ChangeCurrentDirectory}"
|
||||
ToolTip="{DynamicResource Str_ChangeCurrentDirectoryTooltip}"/>
|
||||
<Button Grid.Column="2"
|
||||
hc:IconElement.Geometry="{StaticResource Geo_ArrowRotateRight}"
|
||||
Command="{Binding Cmd_RefreshItems}"
|
||||
ToolTip="{DynamicResource Str_RefreshItemsTooltip}"/>
|
||||
</Grid>
|
||||
|
||||
<StatusBar DockPanel.Dock="Bottom">
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding Converter="{StaticResource StrFmtCvter}" ConverterParameter="Str_ListViewStatusBar">
|
||||
<Binding Path="Items.Count" ElementName="_spineFilesListBox"/>
|
||||
<Binding Path="SelectedItems.Count" ElementName="_spineFilesListBox"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</StatusBar>
|
||||
|
||||
<ListBox x:Name="_spineFilesListBox"
|
||||
VirtualizingPanel.IsVirtualizing="True"
|
||||
ItemsSource="{Binding ShownItems}"
|
||||
DisplayMemberPath="FileName"
|
||||
MouseLeftButtonDown="SpineFilesListBox_MouseLeftButtonDown">
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="SelectionChanged">
|
||||
<i:InvokeCommandAction Command="{Binding Cmd_SelectionChanged}"
|
||||
CommandParameter="{Binding SelectedItems, ElementName=_spineFilesListBox}"/>
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{DynamicResource Str_AddSelectedItems}"
|
||||
Command="{Binding Cmd_AddSelectedItems}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="{DynamicResource Str_GeneratePreviewForSelected}"
|
||||
Command="{Binding Cmd_GeneratePreviews}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
<MenuItem Header="{StaticResource Str_DeletePreviewsForSelected}"
|
||||
Command="{Binding Cmd_DeletePreviews}"
|
||||
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
|
||||
<GridSplitter Grid.Row="1" ResizeDirection="Rows"/>
|
||||
|
||||
<Grid Grid.Row="2" DataContext="{Binding SelectedItem}">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource LabelDefault}">
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TextBoxBaseStyle}">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Right"/>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 文件目录 -->
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="{DynamicResource Str_FileDirectory}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
Text="{Binding FileDirectory, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
ToolTip="{Binding Text, RelativeSource={RelativeSource Mode=Self}}"/>
|
||||
|
||||
<!-- 文件名 -->
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="{DynamicResource Str_FileName}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
Text="{Binding FileName, Mode=OneWay}"
|
||||
IsReadOnly="True"/>
|
||||
|
||||
<!-- 预览图 -->
|
||||
<Border Grid.Row="2" Grid.ColumnSpan="2" Background="#a0a0a0">
|
||||
<Image Source="{Binding PreviewImage, Mode=OneWay}" Stretch="Uniform"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- 画面参数页 -->
|
||||
<TabItem Header="{DynamicResource Str_Canvas}" DataContext="{Binding SFMLRendererViewModel}">
|
||||
<TabItem.Resources>
|
||||
@@ -695,6 +722,7 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 水平分辨率 -->
|
||||
@@ -729,21 +757,23 @@
|
||||
<Label Grid.Row="7" Grid.Column="0" Content="{DynamicResource Str_FlipY}"/>
|
||||
<ToggleButton Grid.Row="7" Grid.Column="1" IsChecked="{Binding FlipY}"/>
|
||||
|
||||
<Separator Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="2" Margin="0 5"/>
|
||||
|
||||
<!-- 最大帧率 -->
|
||||
<Label Grid.Row="8" Grid.Column="0" Content="{DynamicResource Str_MaxFps}"/>
|
||||
<TextBox Grid.Row="8" Grid.Column="1" Text="{Binding MaxFps}"/>
|
||||
<Label Grid.Row="9" Grid.Column="0" Content="{DynamicResource Str_MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
|
||||
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding MaxFps}" ToolTip="{DynamicResource Str_MaxFpsTooltip}"/>
|
||||
|
||||
<!-- 播放速度 -->
|
||||
<Label Grid.Row="9" Grid.Column="0" Content="{DynamicResource Str_PlaySpeed}"/>
|
||||
<TextBox Grid.Row="9" Grid.Column="1" Text="{Binding Speed}"/>
|
||||
<Label Grid.Row="10" Grid.Column="0" Content="{DynamicResource Str_PlaySpeed}"/>
|
||||
<TextBox Grid.Row="10" Grid.Column="1" Text="{Binding Speed}"/>
|
||||
|
||||
<!-- 显示坐标轴 -->
|
||||
<Label Grid.Row="10" Grid.Column="0" Content="{DynamicResource Str_ShowAxis}"/>
|
||||
<ToggleButton Grid.Row="10" Grid.Column="1" IsChecked="{Binding ShowAxis}"/>
|
||||
<Label Grid.Row="11" Grid.Column="0" Content="{DynamicResource Str_ShowAxis}"/>
|
||||
<ToggleButton Grid.Row="11" Grid.Column="1" IsChecked="{Binding ShowAxis}"/>
|
||||
|
||||
<!-- 背景颜色 -->
|
||||
<Label Grid.Row="11" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<TextBox Grid.Row="11" Grid.Column="1" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<Label Grid.Row="12" Grid.Column="0" Content="{DynamicResource Str_BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
<TextBox Grid.Row="12" Grid.Column="1" Text="{Binding BackgroundColor}" ToolTip="#AARRGGBB"/>
|
||||
|
||||
<!-- 背景图案 -->
|
||||
<!-- 背景图案模式 -->
|
||||
@@ -755,14 +785,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;
|
||||
@@ -53,8 +61,7 @@ public partial class MainWindow : Window
|
||||
_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,10 +71,14 @@ public partial class MainWindow : Window
|
||||
|
||||
// 加载首选项
|
||||
_vm.PreferenceViewModel.LoadPreference();
|
||||
|
||||
LoadLastState();
|
||||
}
|
||||
|
||||
private void MainWindow_Closed(object? sender, EventArgs e)
|
||||
{
|
||||
SaveLastState();
|
||||
|
||||
var vm = _vm.SFMLRendererViewModel;
|
||||
vm.StopRender();
|
||||
}
|
||||
@@ -100,6 +111,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)
|
||||
@@ -236,9 +304,7 @@ public partial class MainWindow : Window
|
||||
IntPtr hwnd = new WindowInteropHelper(this).Handle;
|
||||
if (Win32.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);
|
||||
|
||||
Reference in New Issue
Block a user