Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3442ace981 | ||
|
|
547cebf5a9 | ||
|
|
7a24d22bc6 | ||
|
|
8f5728afe4 | ||
|
|
41b5ac2c61 | ||
|
|
694ca3bf25 | ||
|
|
674d314b55 | ||
|
|
08a35cc5d1 | ||
|
|
176e5db4d9 | ||
|
|
2535a9ebf9 | ||
|
|
8ff99ee925 | ||
|
|
abc8218487 | ||
|
|
e4765750c3 | ||
|
|
02cddf556b | ||
|
|
e1e6d3c72d | ||
|
|
b401a16002 | ||
|
|
523b0ce295 | ||
|
|
abb06726f0 | ||
|
|
d9190e9418 | ||
|
|
9fe3761eca | ||
|
|
51824afba6 | ||
|
|
160a49ad5f | ||
|
|
9d4907d77e | ||
|
|
53d30e0503 | ||
|
|
9609a2fd5d | ||
|
|
66cf0efcb9 | ||
|
|
0129b9df31 | ||
|
|
a7a5521be1 | ||
|
|
f7f7211ca2 | ||
|
|
8c921a6ed5 | ||
|
|
f14ab870f7 | ||
|
|
26e81ffdb6 | ||
|
|
598a88203e | ||
|
|
914d02e754 | ||
|
|
5cf30f391b |
4
.github/workflows/dotnet-desktop.yml
vendored
4
.github/workflows/dotnet-desktop.yml
vendored
@@ -47,8 +47,8 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
release_name: Release ${{ github.ref_name }}
|
||||
tag_name: ${{ env.VERSION }}
|
||||
release_name: Release ${{ env.VERSION }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,13 +1,38 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.10.6
|
||||
|
||||
- 增加文件夹检测
|
||||
- 增加从剪贴板添加(可复制本地文件/文件夹直接打开)
|
||||
- 修复预览图导致的批量添加可能卡死
|
||||
|
||||
## v0.10.5
|
||||
|
||||
- 修复一些问题
|
||||
|
||||
## v0.10.4
|
||||
|
||||
- 修复一些问题
|
||||
|
||||
## v0.10.3
|
||||
|
||||
- 增加自动版本检测
|
||||
- 增加文件拖放打开
|
||||
|
||||
## v0.10.2
|
||||
|
||||
- 增加列表右键菜单快捷键
|
||||
- 增加预览缩略图复制
|
||||
- 增加列表视图切换
|
||||
|
||||
## v0.10.1
|
||||
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><EFBFBD>ͼ
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- 增加列表预览图
|
||||
- 增加列表预览图导出
|
||||
|
||||
## v0.10.0
|
||||
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD>Χ<EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD>
|
||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˹<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>ʽת<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܣ<EFBFBD>Ŀǰ<EFBFBD><EFBFBD>֧<EFBFBD>ֲ<EFBFBD><EFBFBD>ְ汾<EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- <EFBFBD>Ż<EFBFBD><EFBFBD>˲<EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
- 增加了画面和列表的选择联动,并删除了预览画面显示包围盒选项
|
||||
- 增加了骨骼文件格式转换功能,目前仅支持部分版本的不完整功能
|
||||
- 优化了部分使用体验
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
A simple and user-friendly Spine file viewer and exporter.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
一个简单好用的 Spine 文件查看&导出程序.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
| `4.2.x` | :white_check_mark: | |
|
||||
| `4.3.x` | | |
|
||||
|
||||
- 支持文件拖放/复制到剪贴板打开
|
||||
- 支持自动检测版本
|
||||
- 支持列表缩略图预览
|
||||
- 支持多骨骼文件动画预览
|
||||
- 支持每个骨骼独立参数设置
|
||||
- 支持动画PNG帧序列导出
|
||||
@@ -46,6 +49,8 @@
|
||||
|
||||
**文件**菜单可以选择**打开**或者**批量打开**进行骨骼文件导入.
|
||||
|
||||
或者直接把要打开的骨骼文件拖进列表, 这种方式只支持 `.json` 和 `.skel` 后缀的文件, 其他的会被忽略.
|
||||
|
||||
### 骨骼调整
|
||||
|
||||
在**模型列表**中选择一项或多项, 将会在**模型参数**面板显示可供调节的参数.
|
||||
|
||||
@@ -129,8 +129,8 @@ namespace SpineRuntime38 {
|
||||
if (skeletonData.hash.Length == 0) skeletonData.hash = null;
|
||||
skeletonData.version = input.ReadString();
|
||||
if (skeletonData.version.Length == 0) skeletonData.version = null;
|
||||
if ("3.8.75" == skeletonData.version)
|
||||
throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
||||
//if ("3.8.75" == skeletonData.version)
|
||||
// throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
||||
skeletonData.x = input.ReadFloat();
|
||||
skeletonData.y = input.ReadFloat();
|
||||
skeletonData.width = input.ReadFloat();
|
||||
|
||||
@@ -100,8 +100,8 @@ namespace SpineRuntime38 {
|
||||
var skeletonMap = (Dictionary<string, Object>)root["skeleton"];
|
||||
skeletonData.hash = (string)skeletonMap["hash"];
|
||||
skeletonData.version = (string)skeletonMap["spine"];
|
||||
if ("3.8.75" == skeletonData.version)
|
||||
throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
||||
//if ("3.8.75" == skeletonData.version)
|
||||
// throw new Exception("Unsupported skeleton data, please export with a newer version of Spine.");
|
||||
skeletonData.x = GetFloat(skeletonMap, "x", 0);
|
||||
skeletonData.y = GetFloat(skeletonMap, "y", 0);
|
||||
skeletonData.width = GetFloat(skeletonMap, "width", 0);
|
||||
|
||||
158
SpineViewer/Controls/SpineListView.Designer.cs
generated
158
SpineViewer/Controls/SpineListView.Designer.cs
generated
@@ -36,23 +36,30 @@
|
||||
toolStripMenuItem_Insert = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Remove = new ToolStripMenuItem();
|
||||
toolStripSeparator1 = new ToolStripSeparator();
|
||||
toolStripMenuItem_MoveUp = new ToolStripMenuItem();
|
||||
toolStripMenuItem_MoveDown = new ToolStripMenuItem();
|
||||
toolStripSeparator2 = new ToolStripSeparator();
|
||||
toolStripMenuItem_BatchAdd = new ToolStripMenuItem();
|
||||
toolStripMenuItem_RemoveAll = new ToolStripMenuItem();
|
||||
toolStripSeparator2 = new ToolStripSeparator();
|
||||
toolStripMenuItem_MoveUp = new ToolStripMenuItem();
|
||||
toolStripMenuItem_MoveDown = new ToolStripMenuItem();
|
||||
toolStripMenuItem_MoveTop = new ToolStripMenuItem();
|
||||
toolStripMenuItem_MoveBottom = new ToolStripMenuItem();
|
||||
toolStripSeparator3 = new ToolStripSeparator();
|
||||
toolStripMenuItem_SelectAll = new ToolStripMenuItem();
|
||||
toolStripMenuItem_CopyPreview = new ToolStripMenuItem();
|
||||
toolStripSeparator4 = new ToolStripSeparator();
|
||||
toolStripMenuItem_ChangeView = new ToolStripMenuItem();
|
||||
toolStripMenuItem_LargeIconView = new ToolStripMenuItem();
|
||||
toolStripMenuItem_SmallIconView = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ListView = new ToolStripMenuItem();
|
||||
toolStripMenuItem_DetailsView = new ToolStripMenuItem();
|
||||
imageList_LargeIcon = new ImageList(components);
|
||||
imageList_SmallIcon = new ImageList(components);
|
||||
toolStripMenuItem_AddFromClipboard = new ToolStripMenuItem();
|
||||
contextMenuStrip.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// listView
|
||||
//
|
||||
listView.Alignment = ListViewAlignment.Left;
|
||||
listView.AllowDrop = true;
|
||||
listView.Columns.AddRange(new ColumnHeader[] { columnHeader_Name });
|
||||
listView.ContextMenuStrip = contextMenuStrip;
|
||||
@@ -71,8 +78,8 @@
|
||||
listView.ItemDrag += listView_ItemDrag;
|
||||
listView.SelectedIndexChanged += listView_SelectedIndexChanged;
|
||||
listView.DragDrop += listView_DragDrop;
|
||||
listView.DragEnter += listView_DragEnter;
|
||||
listView.DragOver += listView_DragOver;
|
||||
listView.KeyDown += listView_KeyDown;
|
||||
//
|
||||
// columnHeader_Name
|
||||
//
|
||||
@@ -82,104 +89,145 @@
|
||||
// contextMenuStrip
|
||||
//
|
||||
contextMenuStrip.ImageScalingSize = new Size(24, 24);
|
||||
contextMenuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_Add, toolStripMenuItem_Insert, toolStripMenuItem_Remove, toolStripSeparator1, toolStripMenuItem_MoveUp, toolStripMenuItem_MoveDown, toolStripSeparator2, toolStripMenuItem_BatchAdd, toolStripMenuItem_RemoveAll, toolStripSeparator3, toolStripMenuItem_ChangeView });
|
||||
contextMenuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_Add, toolStripMenuItem_Insert, toolStripMenuItem_Remove, toolStripSeparator1, toolStripMenuItem_BatchAdd, toolStripMenuItem_RemoveAll, toolStripSeparator2, toolStripMenuItem_MoveUp, toolStripMenuItem_MoveDown, toolStripMenuItem_MoveTop, toolStripMenuItem_MoveBottom, toolStripSeparator3, toolStripMenuItem_CopyPreview, toolStripMenuItem_AddFromClipboard, toolStripMenuItem_SelectAll, toolStripSeparator4, toolStripMenuItem_ChangeView });
|
||||
contextMenuStrip.Name = "contextMenuStrip";
|
||||
contextMenuStrip.Size = new Size(188, 262);
|
||||
contextMenuStrip.Size = new Size(329, 451);
|
||||
contextMenuStrip.Closed += contextMenuStrip_Closed;
|
||||
contextMenuStrip.Opening += contextMenuStrip_Opening;
|
||||
//
|
||||
// toolStripMenuItem_Add
|
||||
//
|
||||
toolStripMenuItem_Add.Name = "toolStripMenuItem_Add";
|
||||
toolStripMenuItem_Add.Size = new Size(187, 30);
|
||||
toolStripMenuItem_Add.Text = "添加(&A)...";
|
||||
toolStripMenuItem_Add.Size = new Size(328, 30);
|
||||
toolStripMenuItem_Add.Text = "添加...";
|
||||
toolStripMenuItem_Add.Click += toolStripMenuItem_Add_Click;
|
||||
//
|
||||
// toolStripMenuItem_Insert
|
||||
//
|
||||
toolStripMenuItem_Insert.Enabled = false;
|
||||
toolStripMenuItem_Insert.Name = "toolStripMenuItem_Insert";
|
||||
toolStripMenuItem_Insert.Size = new Size(187, 30);
|
||||
toolStripMenuItem_Insert.Text = "插入(&I)...";
|
||||
toolStripMenuItem_Insert.Size = new Size(328, 30);
|
||||
toolStripMenuItem_Insert.Text = "插入...";
|
||||
toolStripMenuItem_Insert.Click += toolStripMenuItem_Insert_Click;
|
||||
//
|
||||
// toolStripMenuItem_Remove
|
||||
//
|
||||
toolStripMenuItem_Remove.Enabled = false;
|
||||
toolStripMenuItem_Remove.Name = "toolStripMenuItem_Remove";
|
||||
toolStripMenuItem_Remove.Size = new Size(187, 30);
|
||||
toolStripMenuItem_Remove.Text = "移除(&R)";
|
||||
toolStripMenuItem_Remove.ShortcutKeys = Keys.Delete;
|
||||
toolStripMenuItem_Remove.Size = new Size(328, 30);
|
||||
toolStripMenuItem_Remove.Text = "移除";
|
||||
toolStripMenuItem_Remove.Click += toolStripMenuItem_Remove_Click;
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
toolStripSeparator1.Name = "toolStripSeparator1";
|
||||
toolStripSeparator1.Size = new Size(184, 6);
|
||||
toolStripSeparator1.Size = new Size(325, 6);
|
||||
//
|
||||
// toolStripMenuItem_BatchAdd
|
||||
//
|
||||
toolStripMenuItem_BatchAdd.Name = "toolStripMenuItem_BatchAdd";
|
||||
toolStripMenuItem_BatchAdd.Size = new Size(328, 30);
|
||||
toolStripMenuItem_BatchAdd.Text = "批量添加...";
|
||||
toolStripMenuItem_BatchAdd.Click += toolStripMenuItem_BatchAdd_Click;
|
||||
//
|
||||
// toolStripMenuItem_RemoveAll
|
||||
//
|
||||
toolStripMenuItem_RemoveAll.Name = "toolStripMenuItem_RemoveAll";
|
||||
toolStripMenuItem_RemoveAll.Size = new Size(328, 30);
|
||||
toolStripMenuItem_RemoveAll.Text = "移除全部";
|
||||
toolStripMenuItem_RemoveAll.Click += toolStripMenuItem_RemoveAll_Click;
|
||||
//
|
||||
// toolStripSeparator2
|
||||
//
|
||||
toolStripSeparator2.Name = "toolStripSeparator2";
|
||||
toolStripSeparator2.Size = new Size(325, 6);
|
||||
//
|
||||
// toolStripMenuItem_MoveUp
|
||||
//
|
||||
toolStripMenuItem_MoveUp.Name = "toolStripMenuItem_MoveUp";
|
||||
toolStripMenuItem_MoveUp.Size = new Size(187, 30);
|
||||
toolStripMenuItem_MoveUp.Text = "上移(&U)";
|
||||
toolStripMenuItem_MoveUp.ShortcutKeys = Keys.Alt | Keys.W;
|
||||
toolStripMenuItem_MoveUp.Size = new Size(328, 30);
|
||||
toolStripMenuItem_MoveUp.Text = "上移";
|
||||
toolStripMenuItem_MoveUp.Click += toolStripMenuItem_MoveUp_Click;
|
||||
//
|
||||
// toolStripMenuItem_MoveDown
|
||||
//
|
||||
toolStripMenuItem_MoveDown.Name = "toolStripMenuItem_MoveDown";
|
||||
toolStripMenuItem_MoveDown.Size = new Size(187, 30);
|
||||
toolStripMenuItem_MoveDown.Text = "下移(&D)";
|
||||
toolStripMenuItem_MoveDown.ShortcutKeys = Keys.Alt | Keys.S;
|
||||
toolStripMenuItem_MoveDown.Size = new Size(328, 30);
|
||||
toolStripMenuItem_MoveDown.Text = "下移";
|
||||
toolStripMenuItem_MoveDown.Click += toolStripMenuItem_MoveDown_Click;
|
||||
//
|
||||
// toolStripSeparator2
|
||||
// toolStripMenuItem_MoveTop
|
||||
//
|
||||
toolStripSeparator2.Name = "toolStripSeparator2";
|
||||
toolStripSeparator2.Size = new Size(184, 6);
|
||||
toolStripMenuItem_MoveTop.Name = "toolStripMenuItem_MoveTop";
|
||||
toolStripMenuItem_MoveTop.ShortcutKeys = Keys.Alt | Keys.Shift | Keys.W;
|
||||
toolStripMenuItem_MoveTop.Size = new Size(328, 30);
|
||||
toolStripMenuItem_MoveTop.Text = "置顶";
|
||||
toolStripMenuItem_MoveTop.Click += toolStripMenuItem_MoveTop_Click;
|
||||
//
|
||||
// toolStripMenuItem_BatchAdd
|
||||
// toolStripMenuItem_MoveBottom
|
||||
//
|
||||
toolStripMenuItem_BatchAdd.Name = "toolStripMenuItem_BatchAdd";
|
||||
toolStripMenuItem_BatchAdd.Size = new Size(187, 30);
|
||||
toolStripMenuItem_BatchAdd.Text = "批量添加(&B)...";
|
||||
toolStripMenuItem_BatchAdd.Click += toolStripMenuItem_BatchAdd_Click;
|
||||
//
|
||||
// toolStripMenuItem_RemoveAll
|
||||
//
|
||||
toolStripMenuItem_RemoveAll.Enabled = false;
|
||||
toolStripMenuItem_RemoveAll.Name = "toolStripMenuItem_RemoveAll";
|
||||
toolStripMenuItem_RemoveAll.Size = new Size(187, 30);
|
||||
toolStripMenuItem_RemoveAll.Text = "移除全部(&X)";
|
||||
toolStripMenuItem_RemoveAll.Click += toolStripMenuItem_RemoveAll_Click;
|
||||
toolStripMenuItem_MoveBottom.Name = "toolStripMenuItem_MoveBottom";
|
||||
toolStripMenuItem_MoveBottom.ShortcutKeys = Keys.Alt | Keys.Shift | Keys.S;
|
||||
toolStripMenuItem_MoveBottom.Size = new Size(328, 30);
|
||||
toolStripMenuItem_MoveBottom.Text = "置底";
|
||||
toolStripMenuItem_MoveBottom.Click += toolStripMenuItem_MoveBottom_Click;
|
||||
//
|
||||
// toolStripSeparator3
|
||||
//
|
||||
toolStripSeparator3.Name = "toolStripSeparator3";
|
||||
toolStripSeparator3.Size = new Size(184, 6);
|
||||
toolStripSeparator3.Size = new Size(325, 6);
|
||||
//
|
||||
// toolStripMenuItem_SelectAll
|
||||
//
|
||||
toolStripMenuItem_SelectAll.Name = "toolStripMenuItem_SelectAll";
|
||||
toolStripMenuItem_SelectAll.ShortcutKeys = Keys.Control | Keys.A;
|
||||
toolStripMenuItem_SelectAll.Size = new Size(328, 30);
|
||||
toolStripMenuItem_SelectAll.Text = "全选";
|
||||
toolStripMenuItem_SelectAll.Click += toolStripMenuItem_SelectAll_Click;
|
||||
//
|
||||
// toolStripMenuItem_CopyPreview
|
||||
//
|
||||
toolStripMenuItem_CopyPreview.Name = "toolStripMenuItem_CopyPreview";
|
||||
toolStripMenuItem_CopyPreview.ShortcutKeys = Keys.Control | Keys.C;
|
||||
toolStripMenuItem_CopyPreview.Size = new Size(328, 30);
|
||||
toolStripMenuItem_CopyPreview.Text = "复制预览图 (256x256)";
|
||||
toolStripMenuItem_CopyPreview.Click += toolStripMenuItem_CopyPreview_Click;
|
||||
//
|
||||
// toolStripSeparator4
|
||||
//
|
||||
toolStripSeparator4.Name = "toolStripSeparator4";
|
||||
toolStripSeparator4.Size = new Size(325, 6);
|
||||
//
|
||||
// toolStripMenuItem_ChangeView
|
||||
//
|
||||
toolStripMenuItem_ChangeView.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_LargeIconView, toolStripMenuItem_SmallIconView, toolStripMenuItem_DetailsView });
|
||||
toolStripMenuItem_ChangeView.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_LargeIconView, toolStripMenuItem_ListView, toolStripMenuItem_DetailsView });
|
||||
toolStripMenuItem_ChangeView.Name = "toolStripMenuItem_ChangeView";
|
||||
toolStripMenuItem_ChangeView.Size = new Size(187, 30);
|
||||
toolStripMenuItem_ChangeView.Size = new Size(328, 30);
|
||||
toolStripMenuItem_ChangeView.Text = "切换视图";
|
||||
//
|
||||
// toolStripMenuItem_LargeIconView
|
||||
//
|
||||
toolStripMenuItem_LargeIconView.Name = "toolStripMenuItem_LargeIconView";
|
||||
toolStripMenuItem_LargeIconView.Size = new Size(164, 34);
|
||||
toolStripMenuItem_LargeIconView.ShortcutKeys = Keys.Alt | Keys.D1;
|
||||
toolStripMenuItem_LargeIconView.Size = new Size(241, 34);
|
||||
toolStripMenuItem_LargeIconView.Text = "大图标";
|
||||
toolStripMenuItem_LargeIconView.Click += toolStripMenuItem_LargeIconView_Click;
|
||||
//
|
||||
// toolStripMenuItem_SmallIconView
|
||||
// toolStripMenuItem_ListView
|
||||
//
|
||||
toolStripMenuItem_SmallIconView.Name = "toolStripMenuItem_SmallIconView";
|
||||
toolStripMenuItem_SmallIconView.Size = new Size(164, 34);
|
||||
toolStripMenuItem_SmallIconView.Text = "小图标";
|
||||
toolStripMenuItem_SmallIconView.Click += toolStripMenuItem_SmallIconView_Click;
|
||||
toolStripMenuItem_ListView.Name = "toolStripMenuItem_ListView";
|
||||
toolStripMenuItem_ListView.ShortcutKeys = Keys.Alt | Keys.D2;
|
||||
toolStripMenuItem_ListView.Size = new Size(241, 34);
|
||||
toolStripMenuItem_ListView.Text = "列表";
|
||||
toolStripMenuItem_ListView.Click += toolStripMenuItem_ListView_Click;
|
||||
//
|
||||
// toolStripMenuItem_DetailsView
|
||||
//
|
||||
toolStripMenuItem_DetailsView.Name = "toolStripMenuItem_DetailsView";
|
||||
toolStripMenuItem_DetailsView.Size = new Size(164, 34);
|
||||
toolStripMenuItem_DetailsView.Text = "列表";
|
||||
toolStripMenuItem_DetailsView.ShortcutKeys = Keys.Alt | Keys.D3;
|
||||
toolStripMenuItem_DetailsView.Size = new Size(241, 34);
|
||||
toolStripMenuItem_DetailsView.Text = "详细信息";
|
||||
toolStripMenuItem_DetailsView.Click += toolStripMenuItem_DetailsView_Click;
|
||||
//
|
||||
// imageList_LargeIcon
|
||||
@@ -194,6 +242,14 @@
|
||||
imageList_SmallIcon.ImageSize = new Size(48, 48);
|
||||
imageList_SmallIcon.TransparentColor = Color.Transparent;
|
||||
//
|
||||
// toolStripMenuItem_AddFromClipboard
|
||||
//
|
||||
toolStripMenuItem_AddFromClipboard.Name = "toolStripMenuItem_AddFromClipboard";
|
||||
toolStripMenuItem_AddFromClipboard.ShortcutKeys = Keys.Control | Keys.V;
|
||||
toolStripMenuItem_AddFromClipboard.Size = new Size(328, 30);
|
||||
toolStripMenuItem_AddFromClipboard.Text = "从剪贴板添加";
|
||||
toolStripMenuItem_AddFromClipboard.Click += toolStripMenuItem_AddFromClipboard_Click;
|
||||
//
|
||||
// SpineListView
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
@@ -223,7 +279,13 @@
|
||||
private ToolStripSeparator toolStripSeparator3;
|
||||
private ToolStripMenuItem toolStripMenuItem_ChangeView;
|
||||
private ToolStripMenuItem toolStripMenuItem_LargeIconView;
|
||||
private ToolStripMenuItem toolStripMenuItem_SmallIconView;
|
||||
private ToolStripMenuItem toolStripMenuItem_ListView;
|
||||
private ToolStripMenuItem toolStripMenuItem_DetailsView;
|
||||
private ToolStripMenuItem toolStripMenuItem_MoveTop;
|
||||
private ToolStripMenuItem toolStripMenuItem_MoveBottom;
|
||||
private ToolStripMenuItem toolStripMenuItem_CopyPreview;
|
||||
private ToolStripMenuItem toolStripMenuItem_SelectAll;
|
||||
private ToolStripSeparator toolStripSeparator4;
|
||||
private ToolStripMenuItem toolStripMenuItem_AddFromClipboard;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Collections.ObjectModel;
|
||||
using SpineViewer.Spine;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace SpineViewer.Controls
|
||||
{
|
||||
@@ -59,28 +60,7 @@ namespace SpineViewer.Controls
|
||||
|
||||
var progressDialog = new Dialogs.ProgressDialog();
|
||||
progressDialog.DoWork += BatchAdd_Work;
|
||||
progressDialog.RunWorkerAsync(openDialog);
|
||||
progressDialog.ShowDialog();
|
||||
}
|
||||
|
||||
public void ExportPreviews()
|
||||
{
|
||||
lock (Spines)
|
||||
{
|
||||
if (spines.Count <= 0)
|
||||
{
|
||||
MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var saveDialog = new Dialogs.ExportPreviewDialog();
|
||||
if (saveDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var progressDialog = new Dialogs.ProgressDialog();
|
||||
progressDialog.DoWork += ExportPreview_Work;
|
||||
progressDialog.RunWorkerAsync(saveDialog);
|
||||
progressDialog.RunWorkerAsync(openDialog.Result);
|
||||
progressDialog.ShowDialog();
|
||||
}
|
||||
|
||||
@@ -102,19 +82,18 @@ namespace SpineViewer.Controls
|
||||
spines[i].IsSelected = listView.SelectedIndices.Contains(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void listView_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Control && e.KeyCode == Keys.A)
|
||||
// XXX: 图标显示的时候没法自动刷新顺序, 只能切换视图刷新, 不知道什么原理
|
||||
if (listView.View == View.LargeIcon)
|
||||
{
|
||||
listView.BeginUpdate();
|
||||
foreach (ListViewItem item in listView.Items)
|
||||
{
|
||||
item.Selected = true;
|
||||
}
|
||||
listView.View = View.List;
|
||||
listView.View = View.LargeIcon;
|
||||
listView.EndUpdate();
|
||||
}
|
||||
|
||||
if (listView.SelectedItems.Count > 0)
|
||||
listView.SelectedItems[0].EnsureVisible();
|
||||
}
|
||||
|
||||
private void listView_ItemDrag(object sender, ItemDragEventArgs e)
|
||||
@@ -122,81 +101,109 @@ namespace SpineViewer.Controls
|
||||
DoDragDrop(e.Item, DragDropEffects.Move);
|
||||
}
|
||||
|
||||
private void listView_DragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
if (e.Data.GetDataPresent(DataFormats.Serializable))
|
||||
e.Effect = DragDropEffects.Move;
|
||||
else if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
e.Effect = DragDropEffects.Copy;
|
||||
else
|
||||
e.Effect = DragDropEffects.None;
|
||||
}
|
||||
|
||||
private void listView_DragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
// 检查拖放目标是否有效
|
||||
e.Effect = DragDropEffects.Move;
|
||||
|
||||
// 获取鼠标位置并确定目标索引
|
||||
var point = listView.PointToClient(new(e.X, e.Y));
|
||||
var targetItem = listView.GetItemAt(point.X, point.Y);
|
||||
|
||||
// 高亮目标项
|
||||
if (targetItem != null)
|
||||
if (e.Data.GetDataPresent(DataFormats.Serializable))
|
||||
{
|
||||
foreach (ListViewItem item in listView.Items)
|
||||
// 获取鼠标位置并确定目标索引
|
||||
var point = listView.PointToClient(new(e.X, e.Y));
|
||||
var targetItem = listView.GetItemAt(point.X, point.Y);
|
||||
|
||||
// 高亮目标项
|
||||
if (targetItem != null)
|
||||
{
|
||||
item.BackColor = listView.BackColor;
|
||||
foreach (ListViewItem item in listView.Items)
|
||||
{
|
||||
item.BackColor = listView.BackColor;
|
||||
}
|
||||
targetItem.BackColor = Color.LightGray;
|
||||
}
|
||||
targetItem.BackColor = Color.LightGray;
|
||||
}
|
||||
}
|
||||
|
||||
private void listView_DragDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
// 获取拖放源项和目标项
|
||||
var draggedItem = (ListViewItem)e.Data.GetData(typeof(ListViewItem));
|
||||
int draggedIndex = draggedItem.Index;
|
||||
var point = listView.PointToClient(new Point(e.X, e.Y));
|
||||
var targetItem = listView.GetItemAt(point.X, point.Y);
|
||||
int targetIndex = targetItem is null ? listView.Items.Count : targetItem.Index;
|
||||
if (e.Data.GetDataPresent(DataFormats.Serializable))
|
||||
{
|
||||
// 获取拖放源项和目标项
|
||||
var draggedItem = (ListViewItem)e.Data.GetData(typeof(ListViewItem));
|
||||
int draggedIndex = draggedItem.Index;
|
||||
var point = listView.PointToClient(new Point(e.X, e.Y));
|
||||
var targetItem = listView.GetItemAt(point.X, point.Y);
|
||||
int targetIndex = targetItem is null ? listView.Items.Count : targetItem.Index;
|
||||
|
||||
if (targetIndex <= draggedIndex)
|
||||
{
|
||||
lock (Spines)
|
||||
if (targetIndex <= draggedIndex)
|
||||
{
|
||||
var draggedSpine = spines[draggedIndex];
|
||||
spines.RemoveAt(draggedIndex);
|
||||
spines.Insert(targetIndex, draggedSpine);
|
||||
lock (Spines)
|
||||
{
|
||||
var draggedSpine = spines[draggedIndex];
|
||||
spines.RemoveAt(draggedIndex);
|
||||
spines.Insert(targetIndex, draggedSpine);
|
||||
}
|
||||
listView.Items.RemoveAt(draggedIndex);
|
||||
listView.Items.Insert(targetIndex, draggedItem);
|
||||
}
|
||||
listView.Items.RemoveAt(draggedIndex);
|
||||
listView.Items.Insert(targetIndex, draggedItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (Spines)
|
||||
else
|
||||
{
|
||||
var draggedSpine = spines[draggedIndex];
|
||||
spines.RemoveAt(draggedIndex);
|
||||
spines.Insert(targetIndex - 1, draggedSpine);
|
||||
lock (Spines)
|
||||
{
|
||||
var draggedSpine = spines[draggedIndex];
|
||||
spines.RemoveAt(draggedIndex);
|
||||
spines.Insert(targetIndex - 1, draggedSpine);
|
||||
}
|
||||
listView.Items.RemoveAt(draggedIndex);
|
||||
listView.Items.Insert(targetIndex - 1, draggedItem);
|
||||
}
|
||||
listView.Items.RemoveAt(draggedIndex);
|
||||
listView.Items.Insert(targetIndex - 1, draggedItem);
|
||||
}
|
||||
|
||||
// 重置背景颜色
|
||||
foreach (ListViewItem item in listView.Items)
|
||||
// 重置背景颜色
|
||||
foreach (ListViewItem item in listView.Items)
|
||||
{
|
||||
item.BackColor = listView.BackColor;
|
||||
}
|
||||
}
|
||||
else if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||
{
|
||||
item.BackColor = listView.BackColor;
|
||||
AddFromFileDrop((string[])e.Data.GetData(DataFormats.FileDrop));
|
||||
}
|
||||
}
|
||||
|
||||
private void contextMenuStrip_Opening(object sender, CancelEventArgs e)
|
||||
{
|
||||
var selectedCount = listView.SelectedIndices.Count;
|
||||
var selectedIndices = listView.SelectedIndices;
|
||||
var selectedCount = selectedIndices.Count;
|
||||
var itemsCount = listView.Items.Count;
|
||||
toolStripMenuItem_Insert.Enabled = selectedCount == 1;
|
||||
toolStripMenuItem_Remove.Enabled = selectedCount >= 1;
|
||||
toolStripMenuItem_MoveUp.Enabled = selectedCount == 1 && listView.SelectedIndices[0] != 0;
|
||||
toolStripMenuItem_MoveDown.Enabled = selectedCount == 1 && listView.SelectedIndices[0] != itemsCount - 1;
|
||||
toolStripMenuItem_MoveTop.Enabled = selectedCount == 1 && selectedIndices[0] != 0;
|
||||
toolStripMenuItem_MoveUp.Enabled = selectedCount == 1 && selectedIndices[0] != 0;
|
||||
toolStripMenuItem_MoveDown.Enabled = selectedCount == 1 && selectedIndices[0] != itemsCount - 1;
|
||||
toolStripMenuItem_MoveBottom.Enabled = selectedCount == 1 && selectedIndices[0] != itemsCount - 1;
|
||||
toolStripMenuItem_RemoveAll.Enabled = itemsCount > 0;
|
||||
|
||||
// 视图选项
|
||||
toolStripMenuItem_LargeIconView.Checked = listView.View == View.LargeIcon;
|
||||
toolStripMenuItem_SmallIconView.Checked = listView.View == View.SmallIcon;
|
||||
toolStripMenuItem_ListView.Checked = listView.View == View.List;
|
||||
toolStripMenuItem_DetailsView.Checked = listView.View == View.Details;
|
||||
}
|
||||
|
||||
private void contextMenuStrip_Closed(object sender, ToolStripDropDownClosedEventArgs e)
|
||||
{
|
||||
// 不显示菜单的时候需要把菜单的各项功能启用, 这样才能正常捕获快捷键
|
||||
foreach (var item in contextMenuStrip.Items)
|
||||
if (item is ToolStripMenuItem tsmi)
|
||||
tsmi.Enabled = true;
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_Add_Click(object sender, EventArgs e)
|
||||
{
|
||||
Insert();
|
||||
@@ -238,6 +245,26 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_MoveTop_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (listView.SelectedIndices.Count != 1)
|
||||
return;
|
||||
|
||||
var index = listView.SelectedIndices[0];
|
||||
if (index > 0)
|
||||
{
|
||||
lock (Spines)
|
||||
{
|
||||
var spine = spines[index];
|
||||
spines.RemoveAt(index);
|
||||
spines.Insert(0, spine);
|
||||
}
|
||||
var item = listView.Items[index];
|
||||
listView.Items.RemoveAt(index);
|
||||
listView.Items.Insert(0, item);
|
||||
}
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_MoveUp_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (listView.SelectedIndices.Count != 1)
|
||||
@@ -248,8 +275,10 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
lock (Spines) { (spines[index - 1], spines[index]) = (spines[index], spines[index - 1]); }
|
||||
var item = listView.Items[index];
|
||||
listView.BeginUpdate();
|
||||
listView.Items.RemoveAt(index);
|
||||
listView.Items.Insert(index - 1, item);
|
||||
listView.EndUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,9 +291,31 @@ namespace SpineViewer.Controls
|
||||
if (index < listView.Items.Count - 1)
|
||||
{
|
||||
lock (Spines) { (spines[index], spines[index + 1]) = (spines[index + 1], spines[index]); }
|
||||
var item = listView.Items[index + 1];
|
||||
listView.Items.RemoveAt(index + 1);
|
||||
listView.Items.Insert(index, item);
|
||||
var item = listView.Items[index];
|
||||
listView.BeginUpdate();
|
||||
listView.Items.RemoveAt(index);
|
||||
listView.Items.Insert(index + 1, item);
|
||||
listView.EndUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_MoveBottom_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (listView.SelectedIndices.Count != 1)
|
||||
return;
|
||||
|
||||
var index = listView.SelectedIndices[0];
|
||||
if (index < listView.Items.Count - 1)
|
||||
{
|
||||
lock (Spines)
|
||||
{
|
||||
var spine = spines[index];
|
||||
spines.RemoveAt(index);
|
||||
spines.Add(spine);
|
||||
}
|
||||
var item = listView.Items[index];
|
||||
listView.Items.RemoveAt(index);
|
||||
listView.Items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,14 +340,53 @@ namespace SpineViewer.Controls
|
||||
PropertyGrid.SelectedObject = null;
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_CopyPreview_Click(object sender, EventArgs e)
|
||||
{
|
||||
var fileDropList = new StringCollection();
|
||||
|
||||
lock (Spines)
|
||||
{
|
||||
foreach (int i in listView.SelectedIndices)
|
||||
{
|
||||
var spine = spines[i];
|
||||
var image = spine.Preview;
|
||||
var path = Path.Combine(Program.TempDir, $"{spine.ID}.png");
|
||||
using (var clone = new Bitmap(image))
|
||||
clone.Save(path);
|
||||
fileDropList.Add(path);
|
||||
}
|
||||
}
|
||||
if (fileDropList.Count > 0)
|
||||
Clipboard.SetFileDropList(fileDropList);
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_AddFromClipboard_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (Clipboard.ContainsFileDropList())
|
||||
{
|
||||
var fileDropList = Clipboard.GetFileDropList();
|
||||
var paths = new string[fileDropList.Count];
|
||||
fileDropList.CopyTo(paths, 0);
|
||||
AddFromFileDrop(paths);
|
||||
}
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_SelectAll_Click(object sender, EventArgs e)
|
||||
{
|
||||
listView.BeginUpdate();
|
||||
foreach (ListViewItem item in listView.Items)
|
||||
item.Selected = true;
|
||||
listView.EndUpdate();
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_LargeIconView_Click(object sender, EventArgs e)
|
||||
{
|
||||
listView.View = View.LargeIcon;
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_SmallIconView_Click(object sender, EventArgs e)
|
||||
private void toolStripMenuItem_ListView_Click(object sender, EventArgs e)
|
||||
{
|
||||
listView.View = View.SmallIcon;
|
||||
listView.View = View.List;
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_DetailsView_Click(object sender, EventArgs e)
|
||||
@@ -313,9 +403,14 @@ namespace SpineViewer.Controls
|
||||
if (dialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
Insert(dialog.Result, index);
|
||||
}
|
||||
|
||||
private void Insert(Dialogs.OpenSpineDialogResult result, int index = -1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var spine = Spine.Spine.New(dialog.Version, dialog.SkelPath, dialog.AtlasPath);
|
||||
var spine = Spine.Spine.New(result.Version, result.SkelPath, result.AtlasPath);
|
||||
|
||||
// 如果索引无效则在末尾添加
|
||||
if (index < 0 || index > listView.Items.Count)
|
||||
@@ -337,7 +432,7 @@ namespace SpineViewer.Controls
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
Program.Logger.Error("Failed to load {} {}", dialog.SkelPath, dialog.AtlasPath);
|
||||
Program.Logger.Error("Failed to load {} {}", result.SkelPath, result.AtlasPath);
|
||||
MessageBox.Show(ex.ToString(), "骨骼加载失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
@@ -347,7 +442,7 @@ namespace SpineViewer.Controls
|
||||
private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var arguments = e.Argument as Dialogs.BatchOpenSpineDialog;
|
||||
var arguments = e.Argument as Dialogs.BatchOpenSpineDialogResult;
|
||||
var skelPaths = arguments.SkelPaths;
|
||||
var version = arguments.Version;
|
||||
|
||||
@@ -401,57 +496,42 @@ namespace SpineViewer.Controls
|
||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||
}
|
||||
|
||||
private void ExportPreview_Work(object? sender, DoWorkEventArgs e)
|
||||
private void AddFromFileDrop(string[] paths)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var arguments = e.Argument as Dialogs.ExportPreviewDialog;
|
||||
var outputDir = arguments.OutputDir;
|
||||
var width = arguments.PreviewWidth;
|
||||
var height = arguments.PreviewHeight;
|
||||
|
||||
int success = 0;
|
||||
int error = 0;
|
||||
lock (Spines)
|
||||
List<string> validPaths = [];
|
||||
foreach (var path in paths)
|
||||
{
|
||||
int totalCount = spines.Count;
|
||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
if (File.Exists(path))
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower()))
|
||||
validPaths.Add(path);
|
||||
}
|
||||
else if (Directory.Exists(path))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
||||
validPaths.Add(file);
|
||||
}
|
||||
|
||||
var spine = spines[i];
|
||||
try
|
||||
{
|
||||
var preview = spine.GetPreview(width, height);
|
||||
var savePath = Path.Combine(outputDir, $"{spine.Name}.png");
|
||||
preview.SaveToFile(savePath);
|
||||
success++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
Program.Logger.Error("Failed to save preview {}", spine.SkelPath);
|
||||
error++;
|
||||
}
|
||||
|
||||
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
||||
}
|
||||
}
|
||||
|
||||
if (error > 0)
|
||||
if (validPaths.Count > 1)
|
||||
{
|
||||
Program.Logger.Warn("Preview save {} successfully, {} failed", success, error);
|
||||
if (validPaths.Count > 100)
|
||||
{
|
||||
if (MessageBox.Show($"共发现 {validPaths.Count} 个可加载骨骼,数量较大,是否一次性全部加载?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
|
||||
return;
|
||||
}
|
||||
var progressDialog = new Dialogs.ProgressDialog();
|
||||
progressDialog.DoWork += BatchAdd_Work;
|
||||
progressDialog.RunWorkerAsync(new Dialogs.BatchOpenSpineDialogResult(Spine.Version.Auto, validPaths.ToArray()));
|
||||
progressDialog.ShowDialog();
|
||||
}
|
||||
else
|
||||
else if (validPaths.Count > 0)
|
||||
{
|
||||
Program.Logger.Info("{} preview saved successfully", success);
|
||||
Insert(new Dialogs.OpenSpineDialogResult(Spine.Version.Auto, validPaths[0]));
|
||||
}
|
||||
|
||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,9 +121,9 @@
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="imageList_LargeIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>552, 29</value>
|
||||
<value>511, 20</value>
|
||||
</metadata>
|
||||
<metadata name="imageList_SmallIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>267, 34</value>
|
||||
<value>252, 19</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -458,8 +458,13 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
lock (SpineListView.Spines)
|
||||
{
|
||||
foreach (var spine in SpineListView.Spines.Reverse())
|
||||
var spines = SpineListView.Spines;
|
||||
for (int i = spines.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (cancelToken is not null && cancelToken.IsCancellationRequested)
|
||||
break; // 提前中止
|
||||
|
||||
var spine = spines[i];
|
||||
spine.Update(delta);
|
||||
RenderWindow.Draw(spine);
|
||||
|
||||
|
||||
@@ -13,16 +13,15 @@ namespace SpineViewer.Dialogs
|
||||
{
|
||||
public partial class BatchOpenSpineDialog : Form
|
||||
{
|
||||
public string[] SkelPaths { get; private set; }
|
||||
public Spine.Version Version { get; private set; }
|
||||
public BatchOpenSpineDialogResult Result { get; private set; }
|
||||
|
||||
public BatchOpenSpineDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
comboBox_Version.DataSource = VersionHelper.Versions.ToList();
|
||||
comboBox_Version.DataSource = VersionHelper.Names.ToList();
|
||||
comboBox_Version.DisplayMember = "Value";
|
||||
comboBox_Version.ValueMember = "Key";
|
||||
comboBox_Version.SelectedValue = Spine.Version.V38;
|
||||
comboBox_Version.SelectedValue = Spine.Version.Auto;
|
||||
}
|
||||
|
||||
private void BatchOpenSpineDialog_Load(object sender, EventArgs e)
|
||||
@@ -60,15 +59,13 @@ namespace SpineViewer.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
if (!Spine.Spine.ImplementedVersions.Contains(version))
|
||||
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
||||
{
|
||||
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show($"{version.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
SkelPaths = listBox_FilePath.Items.Cast<string>().ToArray();
|
||||
Version = version;
|
||||
|
||||
Result = new(version, listBox_FilePath.Items.Cast<string>().ToArray());
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
@@ -77,4 +74,10 @@ namespace SpineViewer.Dialogs
|
||||
DialogResult = DialogResult.Cancel;
|
||||
}
|
||||
}
|
||||
|
||||
public class BatchOpenSpineDialogResult(Spine.Version version, string[] skelPaths)
|
||||
{
|
||||
public Spine.Version Version { get; } = version;
|
||||
public string[] SkelPaths { get; } = skelPaths;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,12 @@ namespace SpineViewer.Dialogs
|
||||
public ConvertFileFormatDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
comboBox_SourceVersion.DataSource = VersionHelper.Versions.ToList();
|
||||
|
||||
// XXX: 文件格式转换暂时不支持自动检测版本
|
||||
var impVersions = VersionHelper.Names.ToDictionary();
|
||||
impVersions.Remove(Spine.Version.Auto);
|
||||
|
||||
comboBox_SourceVersion.DataSource = impVersions.ToList();
|
||||
comboBox_SourceVersion.DisplayMember = "Value";
|
||||
comboBox_SourceVersion.ValueMember = "Key";
|
||||
comboBox_SourceVersion.SelectedValue = Spine.Version.V38;
|
||||
@@ -72,13 +77,13 @@ namespace SpineViewer.Dialogs
|
||||
|
||||
if (!SkeletonConverter.ImplementedVersions.Contains(sourceVersion))
|
||||
{
|
||||
MessageBox.Show($"{sourceVersion.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show($"{sourceVersion.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SkeletonConverter.ImplementedVersions.Contains(targetVersion))
|
||||
{
|
||||
MessageBox.Show($"{targetVersion.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show($"{targetVersion.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,17 +12,15 @@ namespace SpineViewer.Dialogs
|
||||
{
|
||||
public partial class OpenSpineDialog : Form
|
||||
{
|
||||
public string SkelPath { get; private set; }
|
||||
public string? AtlasPath { get; private set; }
|
||||
public Spine.Version Version { get; private set; }
|
||||
public OpenSpineDialogResult Result { get; private set; }
|
||||
|
||||
public OpenSpineDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
comboBox_Version.DataSource = VersionHelper.Versions.ToList();
|
||||
comboBox_Version.DataSource = VersionHelper.Names.ToList();
|
||||
comboBox_Version.DisplayMember = "Value";
|
||||
comboBox_Version.ValueMember = "Key";
|
||||
comboBox_Version.SelectedValue = Spine.Version.V38;
|
||||
comboBox_Version.SelectedValue = Spine.Version.Auto;
|
||||
}
|
||||
|
||||
private void OpenSpineDialog_Load(object sender, EventArgs e)
|
||||
@@ -78,16 +76,13 @@ namespace SpineViewer.Dialogs
|
||||
atlasPath = Path.GetFullPath(atlasPath);
|
||||
}
|
||||
|
||||
if (!Spine.Spine.ImplementedVersions.Contains(version))
|
||||
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
||||
{
|
||||
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show($"{version.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
SkelPath = skelPath;
|
||||
AtlasPath = atlasPath;
|
||||
Version = version;
|
||||
|
||||
Result = new(version, skelPath, atlasPath);
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
@@ -96,4 +91,11 @@ namespace SpineViewer.Dialogs
|
||||
DialogResult = DialogResult.Cancel;
|
||||
}
|
||||
}
|
||||
|
||||
public class OpenSpineDialogResult(Spine.Version version, string skelPath, string? atlasPath = null)
|
||||
{
|
||||
public Spine.Version Version { get; } = version;
|
||||
public string SkelPath { get; } = skelPath;
|
||||
public string? AtlasPath { get; } = atlasPath;
|
||||
}
|
||||
}
|
||||
|
||||
44
SpineViewer/MainForm.Designer.cs
generated
44
SpineViewer/MainForm.Designer.cs
generated
@@ -95,7 +95,7 @@
|
||||
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_File, toolStripMenuItem_Function, toolStripMenuItem_Tool, toolStripMenuItem_Download, toolStripMenuItem_Help });
|
||||
menuStrip.Location = new Point(0, 0);
|
||||
menuStrip.Name = "menuStrip";
|
||||
menuStrip.Size = new Size(1741, 32);
|
||||
menuStrip.Size = new Size(1748, 32);
|
||||
menuStrip.TabIndex = 0;
|
||||
menuStrip.Text = "菜单";
|
||||
//
|
||||
@@ -158,8 +158,8 @@
|
||||
//
|
||||
toolStripMenuItem_Function.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ResetAnimation });
|
||||
toolStripMenuItem_Function.Name = "toolStripMenuItem_Function";
|
||||
toolStripMenuItem_Function.Size = new Size(84, 28);
|
||||
toolStripMenuItem_Function.Text = "功能(&F)";
|
||||
toolStripMenuItem_Function.Size = new Size(87, 28);
|
||||
toolStripMenuItem_Function.Text = "功能(&G)";
|
||||
//
|
||||
// toolStripMenuItem_ResetAnimation
|
||||
//
|
||||
@@ -232,7 +232,7 @@
|
||||
rtbLog.Margin = new Padding(3, 2, 3, 2);
|
||||
rtbLog.Name = "rtbLog";
|
||||
rtbLog.ReadOnly = true;
|
||||
rtbLog.Size = new Size(1721, 106);
|
||||
rtbLog.Size = new Size(1728, 114);
|
||||
rtbLog.TabIndex = 0;
|
||||
rtbLog.Text = "";
|
||||
rtbLog.WordWrap = false;
|
||||
@@ -254,8 +254,8 @@
|
||||
//
|
||||
splitContainer_MainForm.Panel2.Controls.Add(rtbLog);
|
||||
splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_MainForm.Size = new Size(1721, 958);
|
||||
splitContainer_MainForm.SplitterDistance = 848;
|
||||
splitContainer_MainForm.Size = new Size(1728, 997);
|
||||
splitContainer_MainForm.SplitterDistance = 879;
|
||||
splitContainer_MainForm.TabIndex = 3;
|
||||
splitContainer_MainForm.TabStop = false;
|
||||
splitContainer_MainForm.SplitterMoved += splitContainer_SplitterMoved;
|
||||
@@ -277,8 +277,8 @@
|
||||
//
|
||||
splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview);
|
||||
splitContainer_Functional.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_Functional.Size = new Size(1721, 848);
|
||||
splitContainer_Functional.SplitterDistance = 744;
|
||||
splitContainer_Functional.Size = new Size(1728, 879);
|
||||
splitContainer_Functional.SplitterDistance = 747;
|
||||
splitContainer_Functional.TabIndex = 2;
|
||||
splitContainer_Functional.TabStop = false;
|
||||
splitContainer_Functional.SplitterMoved += splitContainer_SplitterMoved;
|
||||
@@ -300,8 +300,8 @@
|
||||
//
|
||||
splitContainer_Information.Panel2.Controls.Add(splitContainer_Config);
|
||||
splitContainer_Information.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_Information.Size = new Size(744, 848);
|
||||
splitContainer_Information.SplitterDistance = 398;
|
||||
splitContainer_Information.Size = new Size(747, 879);
|
||||
splitContainer_Information.SplitterDistance = 399;
|
||||
splitContainer_Information.TabIndex = 1;
|
||||
splitContainer_Information.TabStop = false;
|
||||
splitContainer_Information.SplitterMoved += splitContainer_SplitterMoved;
|
||||
@@ -313,7 +313,7 @@
|
||||
groupBox_SkelList.Dock = DockStyle.Fill;
|
||||
groupBox_SkelList.Location = new Point(0, 0);
|
||||
groupBox_SkelList.Name = "groupBox_SkelList";
|
||||
groupBox_SkelList.Size = new Size(398, 848);
|
||||
groupBox_SkelList.Size = new Size(399, 879);
|
||||
groupBox_SkelList.TabIndex = 0;
|
||||
groupBox_SkelList.TabStop = false;
|
||||
groupBox_SkelList.Text = "模型列表";
|
||||
@@ -324,7 +324,7 @@
|
||||
spineListView.Location = new Point(3, 26);
|
||||
spineListView.Name = "spineListView";
|
||||
spineListView.PropertyGrid = propertyGrid_Spine;
|
||||
spineListView.Size = new Size(392, 819);
|
||||
spineListView.Size = new Size(393, 850);
|
||||
spineListView.TabIndex = 0;
|
||||
//
|
||||
// propertyGrid_Spine
|
||||
@@ -333,7 +333,7 @@
|
||||
propertyGrid_Spine.HelpVisible = false;
|
||||
propertyGrid_Spine.Location = new Point(3, 26);
|
||||
propertyGrid_Spine.Name = "propertyGrid_Spine";
|
||||
propertyGrid_Spine.Size = new Size(336, 470);
|
||||
propertyGrid_Spine.Size = new Size(338, 485);
|
||||
propertyGrid_Spine.TabIndex = 0;
|
||||
propertyGrid_Spine.ToolbarVisible = false;
|
||||
propertyGrid_Spine.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
||||
@@ -355,8 +355,8 @@
|
||||
//
|
||||
splitContainer_Config.Panel2.Controls.Add(groupBox_PreviewConfig);
|
||||
splitContainer_Config.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_Config.Size = new Size(342, 848);
|
||||
splitContainer_Config.SplitterDistance = 499;
|
||||
splitContainer_Config.Size = new Size(344, 879);
|
||||
splitContainer_Config.SplitterDistance = 514;
|
||||
splitContainer_Config.TabIndex = 0;
|
||||
splitContainer_Config.TabStop = false;
|
||||
splitContainer_Config.SplitterMoved += splitContainer_SplitterMoved;
|
||||
@@ -368,7 +368,7 @@
|
||||
groupBox_SkelConfig.Dock = DockStyle.Fill;
|
||||
groupBox_SkelConfig.Location = new Point(0, 0);
|
||||
groupBox_SkelConfig.Name = "groupBox_SkelConfig";
|
||||
groupBox_SkelConfig.Size = new Size(342, 499);
|
||||
groupBox_SkelConfig.Size = new Size(344, 514);
|
||||
groupBox_SkelConfig.TabIndex = 0;
|
||||
groupBox_SkelConfig.TabStop = false;
|
||||
groupBox_SkelConfig.Text = "模型参数";
|
||||
@@ -379,7 +379,7 @@
|
||||
groupBox_PreviewConfig.Dock = DockStyle.Fill;
|
||||
groupBox_PreviewConfig.Location = new Point(0, 0);
|
||||
groupBox_PreviewConfig.Name = "groupBox_PreviewConfig";
|
||||
groupBox_PreviewConfig.Size = new Size(342, 345);
|
||||
groupBox_PreviewConfig.Size = new Size(344, 361);
|
||||
groupBox_PreviewConfig.TabIndex = 1;
|
||||
groupBox_PreviewConfig.TabStop = false;
|
||||
groupBox_PreviewConfig.Text = "画面参数";
|
||||
@@ -390,7 +390,7 @@
|
||||
propertyGrid_Previewer.HelpVisible = false;
|
||||
propertyGrid_Previewer.Location = new Point(3, 26);
|
||||
propertyGrid_Previewer.Name = "propertyGrid_Previewer";
|
||||
propertyGrid_Previewer.Size = new Size(336, 316);
|
||||
propertyGrid_Previewer.Size = new Size(338, 332);
|
||||
propertyGrid_Previewer.TabIndex = 1;
|
||||
propertyGrid_Previewer.ToolbarVisible = false;
|
||||
propertyGrid_Previewer.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
||||
@@ -401,7 +401,7 @@
|
||||
groupBox_Preview.Dock = DockStyle.Fill;
|
||||
groupBox_Preview.Location = new Point(0, 0);
|
||||
groupBox_Preview.Name = "groupBox_Preview";
|
||||
groupBox_Preview.Size = new Size(973, 848);
|
||||
groupBox_Preview.Size = new Size(977, 879);
|
||||
groupBox_Preview.TabIndex = 1;
|
||||
groupBox_Preview.TabStop = false;
|
||||
groupBox_Preview.Text = "预览画面";
|
||||
@@ -413,7 +413,7 @@
|
||||
spinePreviewer.Location = new Point(3, 26);
|
||||
spinePreviewer.Name = "spinePreviewer";
|
||||
spinePreviewer.PropertyGrid = propertyGrid_Previewer;
|
||||
spinePreviewer.Size = new Size(967, 819);
|
||||
spinePreviewer.Size = new Size(971, 850);
|
||||
spinePreviewer.SpineListView = spineListView;
|
||||
spinePreviewer.TabIndex = 0;
|
||||
spinePreviewer.MouseUp += spinePreviewer_MouseUp;
|
||||
@@ -425,7 +425,7 @@
|
||||
panel_MainForm.Location = new Point(0, 32);
|
||||
panel_MainForm.Name = "panel_MainForm";
|
||||
panel_MainForm.Padding = new Padding(10, 5, 10, 10);
|
||||
panel_MainForm.Size = new Size(1741, 973);
|
||||
panel_MainForm.Size = new Size(1748, 1012);
|
||||
panel_MainForm.TabIndex = 4;
|
||||
//
|
||||
// toolTip
|
||||
@@ -436,7 +436,7 @@
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(1741, 1005);
|
||||
ClientSize = new Size(1748, 1044);
|
||||
Controls.Add(panel_MainForm);
|
||||
Controls.Add(menuStrip);
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
|
||||
@@ -86,7 +86,23 @@ namespace SpineViewer
|
||||
|
||||
private void toolStripMenuItem_ExportPreview_Click(object sender, EventArgs e)
|
||||
{
|
||||
spineListView.ExportPreviews();
|
||||
lock (spineListView.Spines)
|
||||
{
|
||||
if (spineListView.Spines.Count <= 0)
|
||||
{
|
||||
MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var saveDialog = new Dialogs.ExportPreviewDialog();
|
||||
if (saveDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var progressDialog = new Dialogs.ProgressDialog();
|
||||
progressDialog.DoWork += ExportPreview_Work;
|
||||
progressDialog.RunWorkerAsync(saveDialog);
|
||||
progressDialog.ShowDialog();
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_Exit_Click(object sender, EventArgs e)
|
||||
@@ -200,6 +216,62 @@ namespace SpineViewer
|
||||
spinePreviewer.StartPreview();
|
||||
}
|
||||
|
||||
private void ExportPreview_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var arguments = e.Argument as Dialogs.ExportPreviewDialog;
|
||||
var outputDir = arguments.OutputDir;
|
||||
var width = arguments.PreviewWidth;
|
||||
var height = arguments.PreviewHeight;
|
||||
|
||||
int success = 0;
|
||||
int error = 0;
|
||||
spinePreviewer.StopPreview();
|
||||
lock (spineListView.Spines)
|
||||
{
|
||||
var spines = spineListView.Spines;
|
||||
int totalCount = spines.Count;
|
||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var spine = spines[i];
|
||||
try
|
||||
{
|
||||
var preview = spine.GetPreview(width, height);
|
||||
var savePath = Path.Combine(outputDir, $"{spine.Name}.png");
|
||||
preview.SaveToFile(savePath);
|
||||
success++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
Program.Logger.Error("Failed to save preview {}", spine.SkelPath);
|
||||
error++;
|
||||
}
|
||||
|
||||
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
||||
}
|
||||
}
|
||||
spinePreviewer.StartPreview();
|
||||
|
||||
if (error > 0)
|
||||
{
|
||||
Program.Logger.Warn("Preview save {} successfully, {} failed", success, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.Logger.Info("{} preview saved successfully", success);
|
||||
}
|
||||
|
||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||
}
|
||||
|
||||
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace SpineViewer
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
public const string Name = "SpineViewer";
|
||||
public static readonly string TempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Name)).FullName;
|
||||
public static readonly Process Process = Process.GetCurrentProcess();
|
||||
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
[SkeletonConverterImplementation(Version.V38)]
|
||||
class SkeletonConverter38 : SpineViewer.Spine.SkeletonConverter
|
||||
{
|
||||
private SkeletonReader reader = null;
|
||||
private BinaryReader reader = null;
|
||||
private JsonObject root = null;
|
||||
private bool nonessential = false;
|
||||
|
||||
@@ -51,7 +51,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
{
|
||||
JsonObject skeleton = [];
|
||||
skeleton["hash"] = reader.ReadString();
|
||||
skeleton["spine"] = reader.ReadString();
|
||||
var version = reader.ReadString();
|
||||
if (version == "3.8.75") version = "3.8.76"; // replace 3.8.75 to another version to avoid detection in official runtime
|
||||
skeleton["spine"] = version;
|
||||
skeleton["x"] = reader.ReadFloat();
|
||||
skeleton["y"] = reader.ReadFloat();
|
||||
skeleton["width"] = reader.ReadFloat();
|
||||
@@ -332,7 +334,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
if (nonessential) reader.ReadInt();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid attachment type: {type}");
|
||||
throw new ArgumentOutOfRangeException($"Invalid attachment type: {type}");
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
@@ -435,7 +437,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid slot timeline type: {type}");
|
||||
throw new ArgumentOutOfRangeException($"Invalid slot timeline type: {type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,7 +517,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid bone timeline type: {type}");
|
||||
throw new ArgumentOutOfRangeException($"Invalid bone timeline type: {type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -633,7 +635,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid path timeline type: {type}");
|
||||
throw new ArgumentOutOfRangeException($"Invalid path timeline type: {type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -798,11 +800,11 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
obj["c4"] = reader.ReadFloat();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid curve type: {type}"); ;
|
||||
throw new ArgumentOutOfRangeException($"Invalid curve type: {type}"); ;
|
||||
}
|
||||
}
|
||||
|
||||
private SkeletonWriter writer;
|
||||
private BinaryWriter writer;
|
||||
private readonly Dictionary<string, int> bone2idx = [];
|
||||
private readonly Dictionary<string, int> slot2idx = [];
|
||||
private readonly Dictionary<string, int> ik2idx = [];
|
||||
@@ -842,7 +844,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
{
|
||||
JsonObject skeleton = root["skeleton"].AsObject();
|
||||
writer.WriteString((string)skeleton["hash"]);
|
||||
writer.WriteString((string)skeleton["spine"]);
|
||||
var version = (string)skeleton["spine"];
|
||||
if (version == "3.8.75") version = "3.8.76"; // replace 3.8.75 to another version to avoid detection in official runtime
|
||||
writer.WriteString(version);
|
||||
if (skeleton.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||
if (skeleton.TryGetPropertyValue("y", out var y)) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||
if (skeleton.TryGetPropertyValue("width", out var width)) writer.WriteFloat((float)width); else writer.WriteFloat(0);
|
||||
@@ -1010,6 +1014,18 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
|
||||
private void WriteSkins()
|
||||
{
|
||||
//JsonArray skins = [];
|
||||
|
||||
//// default skin
|
||||
//if (ReadSkin(true) is JsonObject data)
|
||||
// skins.Add(data);
|
||||
|
||||
//// other skins
|
||||
//for (int n = reader.ReadVarInt(); n > 0; n--)
|
||||
// skins.Add(ReadSkin());
|
||||
|
||||
//root["skins"] = skins;
|
||||
|
||||
if (!root.ContainsKey("skins"))
|
||||
{
|
||||
writer.WriteVarInt(0);
|
||||
@@ -1023,6 +1039,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void WriteEvents()
|
||||
{
|
||||
if (!root.ContainsKey("events"))
|
||||
@@ -1076,6 +1093,25 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
writer.WriteVarInt(name2idx[(string)name]);
|
||||
}
|
||||
|
||||
public override JsonObject ReadJson(string jsonPath)
|
||||
{
|
||||
// replace 3.8.75 to another version to avoid detection in official runtime
|
||||
var root = base.ReadJson(jsonPath);
|
||||
var skeleton = root["skeleton"].AsObject();
|
||||
var version = (string)skeleton["spine"];
|
||||
if (version == "3.8.75") skeleton["spine"] = "3.8.76";
|
||||
return root;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonObject root, string jsonPath)
|
||||
{
|
||||
// replace 3.8.75 to another version to avoid detection in official runtime
|
||||
var skeleton = root["skeleton"].AsObject();
|
||||
var version = (string)skeleton["spine"];
|
||||
if (version == "3.8.75") skeleton["spine"] = "3.8.76";
|
||||
base.WriteJson(root, jsonPath);
|
||||
}
|
||||
|
||||
public override JsonObject ToVersion(JsonObject root, Version version)
|
||||
{
|
||||
root = version switch
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace SpineViewer.Spine.Implementations.Spine
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,19 +92,19 @@ namespace SpineViewer.Spine
|
||||
/// <summary>
|
||||
/// 读取 Json 对象
|
||||
/// </summary>
|
||||
public JsonObject ReadJson(string jsonPath)
|
||||
public virtual JsonObject ReadJson(string jsonPath)
|
||||
{
|
||||
using var input = File.OpenRead(jsonPath);
|
||||
if (JsonNode.Parse(input) is JsonObject root)
|
||||
return root;
|
||||
else
|
||||
throw new InvalidOperationException($"{jsonPath} is not a valid json object");
|
||||
throw new InvalidDataException($"{jsonPath} is not a valid json object");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入 Json 对象
|
||||
/// </summary>
|
||||
public void WriteJson(JsonObject root, string jsonPath)
|
||||
public virtual void WriteJson(JsonObject root, string jsonPath)
|
||||
{
|
||||
using var output = File.Create(jsonPath);
|
||||
using var writer = new Utf8JsonWriter(output, jsonWriterOptions);
|
||||
@@ -116,14 +116,17 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
public abstract JsonObject ToVersion(JsonObject root, Version version);
|
||||
|
||||
protected class SkeletonReader
|
||||
/// <summary>
|
||||
/// 二进制骨骼文件读
|
||||
/// </summary>
|
||||
public class BinaryReader
|
||||
{
|
||||
protected byte[] buffer = new byte[32];
|
||||
protected byte[] bytesBigEndian = new byte[8];
|
||||
public readonly List<string> StringTable = new(32);
|
||||
protected Stream input;
|
||||
|
||||
public SkeletonReader(Stream input) { this.input = input; }
|
||||
public BinaryReader(Stream input) { this.input = input; }
|
||||
public int Read()
|
||||
{
|
||||
int val = input.ReadByte();
|
||||
@@ -219,14 +222,17 @@ namespace SpineViewer.Spine
|
||||
}
|
||||
}
|
||||
|
||||
protected class SkeletonWriter
|
||||
/// <summary>
|
||||
/// 二进制骨骼文件写
|
||||
/// </summary>
|
||||
protected class BinaryWriter
|
||||
{
|
||||
protected byte[] buffer = new byte[32];
|
||||
protected byte[] bytesBigEndian = new byte[8];
|
||||
public readonly List<string> StringTable = new(32);
|
||||
protected Stream output;
|
||||
|
||||
public SkeletonWriter(Stream output) { this.output = output; }
|
||||
public BinaryWriter(Stream output) { this.output = output; }
|
||||
public void Write(int val) => output.WriteByte((byte)val);
|
||||
public void WriteByte(byte val) => output.WriteByte(val);
|
||||
public void WriteUByte(byte val) => output.WriteByte(val);
|
||||
|
||||
@@ -14,6 +14,8 @@ using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace SpineViewer.Spine
|
||||
{
|
||||
@@ -36,6 +38,11 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 常规骨骼文件后缀集合
|
||||
/// </summary>
|
||||
public static readonly ImmutableHashSet<string> CommonSkelSuffix = [".skel", ".json"];
|
||||
|
||||
/// <summary>
|
||||
/// 空动画标记
|
||||
/// </summary>
|
||||
@@ -106,11 +113,81 @@ namespace SpineViewer.Spine
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试检测骨骼文件版本
|
||||
/// </summary>
|
||||
public static Version? GetVersion(string skelPath)
|
||||
{
|
||||
string versionString = null;
|
||||
Version? version = null;
|
||||
using var input = File.OpenRead(skelPath);
|
||||
var reader = new SkeletonConverter.BinaryReader(input);
|
||||
|
||||
// try json format
|
||||
try
|
||||
{
|
||||
if (JsonNode.Parse(input) is JsonObject root && root.TryGetPropertyValue("skeleton", out var node) &&
|
||||
node is JsonObject _skeleton && _skeleton.TryGetPropertyValue("spine", out var _version))
|
||||
versionString = (string)_version;
|
||||
}
|
||||
catch { }
|
||||
|
||||
// try v4 binary format
|
||||
if (versionString is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
input.Position = 0;
|
||||
var hash = reader.ReadLong();
|
||||
var versionPosition = input.Position;
|
||||
var versionByteCount = reader.ReadVarInt();
|
||||
input.Position = versionPosition;
|
||||
if (versionByteCount <= 13)
|
||||
versionString = reader.ReadString();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// try v3 binary format
|
||||
if (versionString is null)
|
||||
{
|
||||
try
|
||||
{
|
||||
input.Position = 0;
|
||||
var hash = reader.ReadString();
|
||||
versionString = reader.ReadString();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
if (versionString is not null)
|
||||
{
|
||||
if (versionString.StartsWith("2.1.")) version = Version.V21;
|
||||
else if (versionString.StartsWith("3.6.")) version = Version.V36;
|
||||
else if (versionString.StartsWith("3.7.")) version = Version.V37;
|
||||
else if (versionString.StartsWith("3.8.")) version = Version.V38;
|
||||
else if (versionString.StartsWith("4.0.")) version = Version.V40;
|
||||
else if (versionString.StartsWith("4.1.")) version = Version.V41;
|
||||
else if (versionString.StartsWith("4.2.")) version = Version.V42;
|
||||
else if (versionString.StartsWith("4.3.")) version = Version.V43;
|
||||
else Program.Logger.Error("Unknown verison: {}, {}", versionString, skelPath);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建特定版本的 Spine
|
||||
/// </summary>
|
||||
public static Spine New(Version version, string skelPath, string? atlasPath = null)
|
||||
{
|
||||
if (version == Version.Auto)
|
||||
{
|
||||
if (GetVersion(skelPath) is Version detectedVersion)
|
||||
version = detectedVersion;
|
||||
else
|
||||
throw new InvalidDataException($"Auto version detection failed for {skelPath}, try to use a specific version");
|
||||
}
|
||||
if (!ImplementationTypes.TryGetValue(version, out var spineType))
|
||||
{
|
||||
throw new NotImplementedException($"Not implemented version: {version}");
|
||||
@@ -133,13 +210,14 @@ namespace SpineViewer.Spine
|
||||
var attr = type.GetCustomAttribute<SpineImplementationAttribute>();
|
||||
if (attr is null)
|
||||
{
|
||||
throw new InvalidOperationException($"Class {type.Name} has no SpineImplementationAttribute.");
|
||||
throw new InvalidOperationException($"Class {type.Name} has no SpineImplementationAttribute");
|
||||
}
|
||||
|
||||
atlasPath ??= Path.ChangeExtension(skelPath, ".atlas");
|
||||
|
||||
// 设置 Version
|
||||
Version = attr.Version;
|
||||
AssetsDir = Directory.GetParent(skelPath).FullName;
|
||||
SkelPath = Path.GetFullPath(skelPath);
|
||||
AtlasPath = Path.GetFullPath(atlasPath);
|
||||
Name = Path.GetFileNameWithoutExtension(skelPath);
|
||||
@@ -156,6 +234,12 @@ namespace SpineViewer.Spine
|
||||
[Category("基本信息"), DisplayName("运行时版本")]
|
||||
public Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 资源所在完整目录
|
||||
/// </summary>
|
||||
[Category("基本信息"), DisplayName("资源目录")]
|
||||
public string AssetsDir { get; }
|
||||
|
||||
/// <summary>
|
||||
/// skel 文件完整路径
|
||||
/// </summary>
|
||||
@@ -168,6 +252,9 @@ namespace SpineViewer.Spine
|
||||
[Category("基本信息"), DisplayName("atlas文件路径")]
|
||||
public string AtlasPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[Category("基本信息"), DisplayName("名称")]
|
||||
public string Name { get; }
|
||||
|
||||
@@ -283,7 +370,9 @@ namespace SpineViewer.Spine
|
||||
viewX *= scale;
|
||||
viewY *= scale;
|
||||
|
||||
using var tex = new SFML.Graphics.RenderTexture(width, height);
|
||||
// XXX: 貌似无法使用 using 或者 Dispose 主动释放 tex 资源
|
||||
// 在批量添加的中途, 如果触发 GC? 会卡死, 目前未知原因
|
||||
var tex = new SFML.Graphics.RenderTexture(width, height);
|
||||
var view = tex.GetView();
|
||||
view.Center = new(bounds.X + viewX / 2, bounds.Y + viewY / 2);
|
||||
view.Size = new(viewX, -viewY);
|
||||
@@ -326,5 +415,23 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public bool IsSelected { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 显示调试
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public bool IsDebug { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 显示包围盒
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public bool DebugBounds { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 显示骨骼
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public bool DebugBones { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace SpineViewer.Spine
|
||||
if (destinationType == typeof(string) && value is Version version)
|
||||
{
|
||||
// 调用自定义的 String() 方法
|
||||
return version.String();
|
||||
return version.GetName();
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@@ -11,9 +12,15 @@ namespace SpineViewer.Spine
|
||||
public static class VersionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 描述缓存
|
||||
/// 版本名称
|
||||
/// </summary>
|
||||
public static readonly Dictionary<Version, string> Versions = [];
|
||||
public static readonly ReadOnlyDictionary<Version, string> Names;
|
||||
private static readonly Dictionary<Version, string> names = [];
|
||||
|
||||
/// <summary>
|
||||
/// Runtime 版本字符串
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Version, string> runtimes = [];
|
||||
|
||||
static VersionHelper()
|
||||
{
|
||||
@@ -22,16 +29,33 @@ namespace SpineViewer.Spine
|
||||
{
|
||||
var field = typeof(Version).GetField(value.ToString());
|
||||
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
|
||||
Versions[(Version)value] = attribute?.Description ?? value.ToString();
|
||||
names[(Version)value] = attribute?.Description ?? value.ToString();
|
||||
}
|
||||
Names = names.AsReadOnly();
|
||||
|
||||
runtimes[Version.V21] = Assembly.GetAssembly(typeof(SpineRuntime21.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
runtimes[Version.V36] = Assembly.GetAssembly(typeof(SpineRuntime36.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
runtimes[Version.V37] = Assembly.GetAssembly(typeof(SpineRuntime37.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
runtimes[Version.V38] = Assembly.GetAssembly(typeof(SpineRuntime38.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
runtimes[Version.V40] = Assembly.GetAssembly(typeof(SpineRuntime40.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
runtimes[Version.V41] = Assembly.GetAssembly(typeof(SpineRuntime41.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
runtimes[Version.V42] = Assembly.GetAssembly(typeof(SpineRuntime42.Skeleton)).GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 版本号字符串
|
||||
/// 版本字符串名称
|
||||
/// </summary>
|
||||
public static string String(this Version version)
|
||||
public static string GetName(this Version version)
|
||||
{
|
||||
return Versions.TryGetValue(version, out var description) ? description : version.ToString();
|
||||
return Names.TryGetValue(version, out var val) ? val : version.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runtime 版本字符串名称
|
||||
/// </summary>
|
||||
public static string GetRuntime(this Version version)
|
||||
{
|
||||
return runtimes.TryGetValue(version, out var val) ? val : GetName(version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +64,7 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
public enum Version
|
||||
{
|
||||
[Description("<Auto>")] Auto = 0x0000,
|
||||
[Description("2.1.x")] V21 = 0x0201,
|
||||
[Description("3.6.x")] V36 = 0x0306,
|
||||
[Description("3.7.x")] V37 = 0x0307,
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.10.1</Version>
|
||||
<Version>0.10.6</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||
|
||||
BIN
img/preview.jpg
BIN
img/preview.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 302 KiB |
BIN
img/preview.webp
Normal file
BIN
img/preview.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
Reference in New Issue
Block a user