增加工作区功能
This commit is contained in:
@@ -80,41 +80,5 @@ namespace SpineViewer.Models
|
||||
private AppLanguage _appLanguage;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 序列化与反序列
|
||||
|
||||
/// <summary>
|
||||
/// 保存 Json 文件的格式参数
|
||||
/// </summary>
|
||||
private static readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 从文件反序列对象, 可能抛出异常
|
||||
/// </summary>
|
||||
public static PreferenceModel Deserialize(string path)
|
||||
{
|
||||
if (!File.Exists(path)) throw new FileNotFoundException("Preference file not found", path);
|
||||
var json = File.ReadAllText(path, Encoding.UTF8);
|
||||
var model = JsonSerializer.Deserialize<PreferenceModel>(json, _jsonOptions);
|
||||
return model ?? throw new JsonException($"null data in file '{path}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存至文件, 可能抛出异常
|
||||
/// </summary>
|
||||
public void Serialize(string path)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
var json = JsonSerializer.Serialize(this, _jsonOptions);
|
||||
File.WriteAllText(path, json, Encoding.UTF8);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace SpineViewer.Models
|
||||
{
|
||||
public class SpineObjectConfigModel
|
||||
{
|
||||
public bool IsShown { get; set; } = true;
|
||||
|
||||
public bool UsePma { get; set; }
|
||||
|
||||
public string Physics { get; set; } = ISkeleton.Physics.Update.ToString();
|
||||
@@ -54,41 +52,5 @@ namespace SpineViewer.Models
|
||||
public bool DebugPoints { get; set; }
|
||||
|
||||
public bool DebugClippings { get; set; }
|
||||
|
||||
#region 序列化与反序列
|
||||
|
||||
/// <summary>
|
||||
/// 保存 Json 文件的格式参数
|
||||
/// </summary>
|
||||
private static readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 从文件反序列对象, 可能抛出异常
|
||||
/// </summary>
|
||||
public static SpineObjectConfigModel Deserialize(string path)
|
||||
{
|
||||
if (!File.Exists(path)) throw new FileNotFoundException("Config file not found", path);
|
||||
var json = File.ReadAllText(path, Encoding.UTF8);
|
||||
var model = JsonSerializer.Deserialize<SpineObjectConfigModel>(json, _jsonOptions);
|
||||
return model ?? throw new JsonException($"null data in file '{path}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存至文件, 可能抛出异常
|
||||
/// </summary>
|
||||
public void Serialize(string path)
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
var json = JsonSerializer.Serialize(this, _jsonOptions);
|
||||
File.WriteAllText(path, json, Encoding.UTF8);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ namespace SpineViewer.Models
|
||||
DebugPoints = _loadOptions.DebugPoints,
|
||||
DebugClippings = _loadOptions.DebugClippings
|
||||
};
|
||||
|
||||
_skins = _spineObject.Data.Skins.Select(v => v.Name).ToImmutableArray();
|
||||
_slotAttachments = _spineObject.Data.SlotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.Keys);
|
||||
_animations = _spineObject.Data.Animations.Select(v => v.Name).ToImmutableArray();
|
||||
@@ -71,6 +70,19 @@ namespace SpineViewer.Models
|
||||
_spineObject.AnimationState.SetAnimation(0, _spineObject.Data.Animations[0], true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从工作区配置进行构造
|
||||
/// </summary>
|
||||
public SpineObjectModel(SpineObjectWorkspaceConfigModel cfg)
|
||||
{
|
||||
_spineObject = new(cfg.SkelPath, cfg.AtlasPath);
|
||||
_skins = _spineObject.Data.Skins.Select(v => v.Name).ToImmutableArray();
|
||||
_slotAttachments = _spineObject.Data.SlotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.Keys);
|
||||
_animations = _spineObject.Data.Animations.Select(v => v.Name).ToImmutableArray();
|
||||
ObjectConfig = cfg.ObjectConfig;
|
||||
_isShown = cfg.IsShown;
|
||||
}
|
||||
|
||||
public event EventHandler<SkinStatusChangedEventArgs>? SkinStatusChanged;
|
||||
|
||||
public event EventHandler<SlotAttachmentChangedEventArgs>? SlotAttachmentChanged;
|
||||
@@ -345,99 +357,107 @@ namespace SpineViewer.Models
|
||||
lock (_lock) return _spineObject.GetCurrentBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出参数对象
|
||||
/// </summary>
|
||||
public SpineObjectConfigModel Dump()
|
||||
public SpineObjectConfigModel ObjectConfig
|
||||
{
|
||||
lock (_lock)
|
||||
get
|
||||
{
|
||||
SpineObjectConfigModel config = new()
|
||||
lock (_lock)
|
||||
{
|
||||
Scale = Math.Abs(_spineObject.Skeleton.ScaleX),
|
||||
FlipX = _spineObject.Skeleton.ScaleX < 0,
|
||||
FlipY = _spineObject.Skeleton.ScaleY < 0,
|
||||
X = _spineObject.Skeleton.X,
|
||||
Y = _spineObject.Skeleton.Y,
|
||||
SpineObjectConfigModel config = new()
|
||||
{
|
||||
Scale = Math.Abs(_spineObject.Skeleton.ScaleX),
|
||||
FlipX = _spineObject.Skeleton.ScaleX < 0,
|
||||
FlipY = _spineObject.Skeleton.ScaleY < 0,
|
||||
X = _spineObject.Skeleton.X,
|
||||
Y = _spineObject.Skeleton.Y,
|
||||
|
||||
IsShown = _isShown,
|
||||
UsePma = _spineObject.UsePma,
|
||||
Physics = _spineObject.Physics.ToString(),
|
||||
UsePma = _spineObject.UsePma,
|
||||
Physics = _spineObject.Physics.ToString(),
|
||||
|
||||
DebugTexture = _spineObject.DebugTexture,
|
||||
DebugBounds = _spineObject.DebugBounds,
|
||||
DebugBones = _spineObject.DebugBones,
|
||||
DebugRegions = _spineObject.DebugRegions,
|
||||
DebugMeshHulls = _spineObject.DebugMeshHulls,
|
||||
DebugMeshes = _spineObject.DebugMeshes,
|
||||
DebugBoundingBoxes = _spineObject.DebugBoundingBoxes,
|
||||
DebugPaths = _spineObject.DebugPaths,
|
||||
DebugPoints = _spineObject.DebugPoints,
|
||||
DebugClippings = _spineObject.DebugClippings
|
||||
};
|
||||
DebugTexture = _spineObject.DebugTexture,
|
||||
DebugBounds = _spineObject.DebugBounds,
|
||||
DebugBones = _spineObject.DebugBones,
|
||||
DebugRegions = _spineObject.DebugRegions,
|
||||
DebugMeshHulls = _spineObject.DebugMeshHulls,
|
||||
DebugMeshes = _spineObject.DebugMeshes,
|
||||
DebugBoundingBoxes = _spineObject.DebugBoundingBoxes,
|
||||
DebugPaths = _spineObject.DebugPaths,
|
||||
DebugPoints = _spineObject.DebugPoints,
|
||||
DebugClippings = _spineObject.DebugClippings
|
||||
};
|
||||
|
||||
config.LoadedSkins.AddRange(_spineObject.Data.Skins.Select(it => it.Name).Where(_spineObject.GetSkinStatus));
|
||||
config.LoadedSkins.AddRange(_spineObject.Data.Skins.Select(it => it.Name).Where(_spineObject.GetSkinStatus));
|
||||
|
||||
foreach (var slot in _spineObject.Skeleton.Slots) config.SlotAttachment[slot.Name] = slot.Attachment?.Name;
|
||||
foreach (var slot in _spineObject.Skeleton.Slots) config.SlotAttachment[slot.Name] = slot.Attachment?.Name;
|
||||
|
||||
// XXX: 处理空动画
|
||||
config.Animations.AddRange(_spineObject.AnimationState.IterTracks().Select(tr => tr?.Animation.Name));
|
||||
// XXX: 处理空动画
|
||||
config.Animations.AddRange(_spineObject.AnimationState.IterTracks().Select(tr => tr?.Animation.Name));
|
||||
|
||||
return config;
|
||||
return config;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_spineObject.Skeleton.ScaleX = value.Scale;
|
||||
_spineObject.Skeleton.ScaleY = value.Scale;
|
||||
OnPropertyChanged(nameof(Scale));
|
||||
SetProperty(_spineObject.Skeleton.ScaleX < 0, value.FlipX, v => _spineObject.Skeleton.ScaleX *= -1, nameof(FlipX));
|
||||
SetProperty(_spineObject.Skeleton.ScaleY < 0, value.FlipY, v => _spineObject.Skeleton.ScaleY *= -1, nameof(FlipY));
|
||||
SetProperty(_spineObject.Skeleton.X, value.X, v => _spineObject.Skeleton.X = v, nameof(X));
|
||||
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));
|
||||
|
||||
foreach (var name in _spineObject.Data.Skins.Select(v => v.Name).Except(value.LoadedSkins))
|
||||
if (_spineObject.SetSkinStatus(name, false))
|
||||
SkinStatusChanged?.Invoke(this, new(name, false));
|
||||
foreach (var name in value.LoadedSkins)
|
||||
if (_spineObject.SetSkinStatus(name, true))
|
||||
SkinStatusChanged?.Invoke(this, new(name, true));
|
||||
|
||||
foreach (var (slotName, attachmentName) in value.SlotAttachment)
|
||||
if (_spineObject.SetAttachment(slotName, attachmentName))
|
||||
SlotAttachmentChanged?.Invoke(this, new(slotName, attachmentName));
|
||||
|
||||
// XXX: 处理空动画
|
||||
_spineObject.AnimationState.ClearTracks();
|
||||
int trackIndex = 0;
|
||||
foreach (var name in value.Animations)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
_spineObject.AnimationState.SetAnimation(trackIndex, name, true);
|
||||
AnimationChanged?.Invoke(this, new(trackIndex, name));
|
||||
trackIndex++;
|
||||
}
|
||||
|
||||
SetProperty(_spineObject.DebugTexture, value.DebugTexture, v => _spineObject.DebugTexture = v, nameof(DebugTexture));
|
||||
SetProperty(_spineObject.DebugBounds, value.DebugBounds, v => _spineObject.DebugBounds = v, nameof(DebugBounds));
|
||||
SetProperty(_spineObject.DebugBones, value.DebugBones, v => _spineObject.DebugBones = v, nameof(DebugBones));
|
||||
SetProperty(_spineObject.DebugRegions, value.DebugRegions, v => _spineObject.DebugRegions = v, nameof(DebugRegions));
|
||||
SetProperty(_spineObject.DebugMeshHulls, value.DebugMeshHulls, v => _spineObject.DebugMeshHulls = v, nameof(DebugMeshHulls));
|
||||
SetProperty(_spineObject.DebugMeshes, value.DebugMeshes, v => _spineObject.DebugMeshes = v, nameof(DebugMeshes));
|
||||
SetProperty(_spineObject.DebugBoundingBoxes, value.DebugBoundingBoxes, v => _spineObject.DebugBoundingBoxes = v, nameof(DebugBoundingBoxes));
|
||||
SetProperty(_spineObject.DebugPaths, value.DebugPaths, v => _spineObject.DebugPaths = v, nameof(DebugPaths));
|
||||
SetProperty(_spineObject.DebugPoints, value.DebugPoints, v => _spineObject.DebugPoints = v, nameof(DebugPoints));
|
||||
SetProperty(_spineObject.DebugClippings, value.DebugClippings, v => _spineObject.DebugClippings = v, nameof(DebugClippings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从参数对象加载参数值
|
||||
/// </summary>
|
||||
public void Load(SpineObjectConfigModel config)
|
||||
public SpineObjectWorkspaceConfigModel WorkspaceConfig
|
||||
{
|
||||
lock (_lock)
|
||||
get
|
||||
{
|
||||
_spineObject.Skeleton.ScaleX = config.Scale;
|
||||
_spineObject.Skeleton.ScaleY = config.Scale;
|
||||
OnPropertyChanged(nameof(Scale));
|
||||
SetProperty(_spineObject.Skeleton.ScaleX < 0, config.FlipX, v => _spineObject.Skeleton.ScaleX *= -1, nameof(FlipX));
|
||||
SetProperty(_spineObject.Skeleton.ScaleY < 0, config.FlipY, v => _spineObject.Skeleton.ScaleY *= -1, nameof(FlipY));
|
||||
SetProperty(_spineObject.Skeleton.X, config.X, v => _spineObject.Skeleton.X = v, nameof(X));
|
||||
SetProperty(_spineObject.Skeleton.Y, config.Y, v => _spineObject.Skeleton.Y = v, nameof(Y));
|
||||
|
||||
IsShown = config.IsShown;
|
||||
SetProperty(_spineObject.UsePma, config.UsePma, v => _spineObject.UsePma = v, nameof(UsePma));
|
||||
SetProperty(_spineObject.Physics, Enum.Parse<ISkeleton.Physics>(config.Physics ?? "Update", true), v => _spineObject.Physics = v, nameof(Physics));
|
||||
|
||||
foreach (var name in _spineObject.Data.Skins.Select(v => v.Name).Except(config.LoadedSkins))
|
||||
if (_spineObject.SetSkinStatus(name, false))
|
||||
SkinStatusChanged?.Invoke(this, new(name, false));
|
||||
foreach (var name in config.LoadedSkins)
|
||||
if (_spineObject.SetSkinStatus(name, true))
|
||||
SkinStatusChanged?.Invoke(this, new(name, true));
|
||||
|
||||
foreach (var (slotName, attachmentName) in config.SlotAttachment)
|
||||
if (_spineObject.SetAttachment(slotName, attachmentName))
|
||||
SlotAttachmentChanged?.Invoke(this, new(slotName, attachmentName));
|
||||
|
||||
// XXX: 处理空动画
|
||||
_spineObject.AnimationState.ClearTracks();
|
||||
int trackIndex = 0;
|
||||
foreach (var name in config.Animations)
|
||||
return new()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
_spineObject.AnimationState.SetAnimation(trackIndex, name, true);
|
||||
AnimationChanged?.Invoke(this, new(trackIndex, name));
|
||||
trackIndex++;
|
||||
}
|
||||
|
||||
SetProperty(_spineObject.DebugTexture, config.DebugTexture, v => _spineObject.DebugTexture = v, nameof(DebugTexture));
|
||||
SetProperty(_spineObject.DebugBounds, config.DebugBounds, v => _spineObject.DebugBounds = v, nameof(DebugBounds));
|
||||
SetProperty(_spineObject.DebugBones, config.DebugBones, v => _spineObject.DebugBones = v, nameof(DebugBones));
|
||||
SetProperty(_spineObject.DebugRegions, config.DebugRegions, v => _spineObject.DebugRegions = v, nameof(DebugRegions));
|
||||
SetProperty(_spineObject.DebugMeshHulls, config.DebugMeshHulls, v => _spineObject.DebugMeshHulls = v, nameof(DebugMeshHulls));
|
||||
SetProperty(_spineObject.DebugMeshes, config.DebugMeshes, v => _spineObject.DebugMeshes = v, nameof(DebugMeshes));
|
||||
SetProperty(_spineObject.DebugBoundingBoxes, config.DebugBoundingBoxes, v => _spineObject.DebugBoundingBoxes = v, nameof(DebugBoundingBoxes));
|
||||
SetProperty(_spineObject.DebugPaths, config.DebugPaths, v => _spineObject.DebugPaths = v, nameof(DebugPaths));
|
||||
SetProperty(_spineObject.DebugPoints, config.DebugPoints, v => _spineObject.DebugPoints = v, nameof(DebugPoints));
|
||||
SetProperty(_spineObject.DebugClippings, config.DebugClippings, v => _spineObject.DebugClippings = v, nameof(DebugClippings));
|
||||
SkelPath = SkelPath,
|
||||
AtlasPath = AtlasPath,
|
||||
IsShown = IsShown,
|
||||
ObjectConfig = ObjectConfig
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
57
SpineViewer/Models/WorkspaceModel.cs
Normal file
57
SpineViewer/Models/WorkspaceModel.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace SpineViewer.Models
|
||||
{
|
||||
public class WorkspaceModel
|
||||
{
|
||||
public RendererWorkspaceConfigModel RendererConfig { get; set; } = new();
|
||||
public List<SpineObjectWorkspaceConfigModel> LoadedSpineObjects { get; set; } = [];
|
||||
}
|
||||
|
||||
public class RendererWorkspaceConfigModel
|
||||
{
|
||||
public uint ResolutionX { get; set; } = 100;
|
||||
|
||||
public uint ResolutionY { get; set; } = 100;
|
||||
|
||||
public float CenterX { get; set; }
|
||||
|
||||
public float CenterY { get; set; }
|
||||
|
||||
public float Zoom { get; set; } = 1f;
|
||||
|
||||
public float Rotation { get; set; }
|
||||
|
||||
public bool FlipX { get; set; }
|
||||
|
||||
public bool FlipY { get; set; } = true;
|
||||
|
||||
public uint MaxFps { get; set; } = 30;
|
||||
|
||||
public bool ShowAxis { get; set; } = true;
|
||||
|
||||
public Color BackgroundColor { get; set; }
|
||||
|
||||
// TODO: 背景图片
|
||||
//public string? BackgroundImagePath { get; set; }
|
||||
|
||||
//public ? BackgroundImageDisplayMode { get; set; }
|
||||
}
|
||||
|
||||
public class SpineObjectWorkspaceConfigModel
|
||||
{
|
||||
public string SkelPath { get; set; } = "";
|
||||
public string AtlasPath { get; set; } = "";
|
||||
public bool IsShown { get; set; } = true;
|
||||
public SpineObjectConfigModel ObjectConfig { get; set; } = new();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user