Compare commits
90 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 | ||
|
|
8de00cad76 | ||
|
|
e4c58f2f4e | ||
|
|
063dba30b6 | ||
|
|
01fa9287a1 | ||
|
|
008067fccb | ||
|
|
091301e945 | ||
|
|
145f4f3265 | ||
|
|
36d4e8c948 | ||
|
|
63de847a57 | ||
|
|
b3e1b7c902 | ||
|
|
2dbc235631 | ||
|
|
4d68b48367 | ||
|
|
65e63e2b2d | ||
|
|
58071e1de1 | ||
|
|
5009ef479f | ||
|
|
e5e9357649 | ||
|
|
a577474772 | ||
|
|
e960a09153 | ||
|
|
13d50f59c3 | ||
|
|
ed4c8475e9 | ||
|
|
2338bf4e15 | ||
|
|
267aa7ee63 | ||
|
|
3df7dbc769 | ||
|
|
5f12ab7e85 | ||
|
|
ac0adc5f95 | ||
|
|
208b702065 | ||
|
|
7e61fbfbac | ||
|
|
0591549727 | ||
|
|
a0833580f8 | ||
|
|
c622b60215 | ||
|
|
c228cf9072 | ||
|
|
4c68dd4904 | ||
|
|
32fde582fc | ||
|
|
2bf2509df7 | ||
|
|
07042189c8 | ||
|
|
d251c94638 | ||
|
|
b4119087fb | ||
|
|
e3959e80fb | ||
|
|
0495a2344c | ||
|
|
c781ec5a4f | ||
|
|
a58566735f | ||
|
|
b37e5c25c3 | ||
|
|
63a937a45b | ||
|
|
c920471c0c | ||
|
|
c4863ee09b | ||
|
|
c0b85c454e | ||
|
|
763a49a4d3 | ||
|
|
0e1540873c | ||
|
|
39dcc636ca | ||
|
|
342778c56e | ||
|
|
fd524891aa | ||
|
|
48cb60020c | ||
|
|
d502c592f7 | ||
|
|
e4377436a7 | ||
|
|
eb44c1271e |
6
.github/workflows/dotnet-desktop.yml
vendored
6
.github/workflows/dotnet-desktop.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build and Release
|
||||
name: Build & Release
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -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
|
||||
|
||||
|
||||
38
CHANGELOG.md
Normal file
38
CHANGELOG.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.10.6
|
||||
|
||||
- 增加文件夹检测
|
||||
- 增加从剪贴板添加(可复制本地文件/文件夹直接打开)
|
||||
- 修复预览图导致的批量添加可能卡死
|
||||
|
||||
## v0.10.5
|
||||
|
||||
- 修复一些问题
|
||||
|
||||
## v0.10.4
|
||||
|
||||
- 修复一些问题
|
||||
|
||||
## v0.10.3
|
||||
|
||||
- 增加自动版本检测
|
||||
- 增加文件拖放打开
|
||||
|
||||
## v0.10.2
|
||||
|
||||
- 增加列表右键菜单快捷键
|
||||
- 增加预览缩略图复制
|
||||
- 增加列表视图切换
|
||||
|
||||
## v0.10.1
|
||||
|
||||
- 增加列表预览图
|
||||
- 增加列表预览图导出
|
||||
|
||||
## v0.10.0
|
||||
|
||||
- 增加了画面和列表的选择联动,并删除了预览画面显示包围盒选项
|
||||
- 增加了骨骼文件格式转换功能,目前仅支持部分版本的不完整功能
|
||||
- 优化了部分使用体验
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SpineViewer
|
||||
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||
|
||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
A simple and user-friendly Spine file viewer and exporter.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
34
README.md
34
README.md
@@ -1,4 +1,4 @@
|
||||
# SpineViewer
|
||||
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
|
||||
|
||||
[](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
一个简单好用的 Spine 文件查看&导出程序.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
@@ -18,21 +18,29 @@
|
||||
|
||||
也可以下载带有 `SelfContained` 后缀的压缩包, 可以独立运行.
|
||||
|
||||
## 功能
|
||||
## 功能支持
|
||||
|
||||
- 支持不同版本 Spine 查看
|
||||
- [x] `v2.1.x`
|
||||
- [x] `v3.6.x`
|
||||
- [x] `v3.7.x`
|
||||
- [x] `v3.8.x`
|
||||
- [x] `v4.0.x`
|
||||
- [x] `v4.1.x`
|
||||
- [x] `v4.2.x`
|
||||
- [ ] `v4.3.x`
|
||||
| 版本 | 查看&导出 | 格式转换 |
|
||||
| :---: | :---: | :---: |
|
||||
| `2.1.x` | :white_check_mark: | |
|
||||
| `3.1.x` | | |
|
||||
| `3.4.x` | | |
|
||||
| `3.5.x` | | |
|
||||
| `3.6.x` | :white_check_mark: | |
|
||||
| `3.7.x` | :white_check_mark: | |
|
||||
| `3.8.x` | :white_check_mark: | :white_check_mark: |
|
||||
| `4.1.x` | :white_check_mark: | |
|
||||
| `4.2.x` | :white_check_mark: | |
|
||||
| `4.3.x` | | |
|
||||
|
||||
- 支持文件拖放/复制到剪贴板打开
|
||||
- 支持自动检测版本
|
||||
- 支持列表缩略图预览
|
||||
- 支持多骨骼文件动画预览
|
||||
- 支持每个骨骼独立参数设置
|
||||
- 支持动画PNG帧序列导出
|
||||
- 支持缩放旋转等导出画面设置
|
||||
- 支持对独立的骨骼文件进行格式转换
|
||||
- Coming soon...
|
||||
|
||||
## 使用方法
|
||||
@@ -41,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);
|
||||
|
||||
@@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.gitignore = .gitignore
|
||||
CHANGELOG.md = CHANGELOG.md
|
||||
README.en.md = README.en.md
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
|
||||
189
SpineViewer/Controls/SpineListView.Designer.cs
generated
189
SpineViewer/Controls/SpineListView.Designer.cs
generated
@@ -36,34 +36,50 @@
|
||||
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_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;
|
||||
listView.Dock = DockStyle.Fill;
|
||||
listView.FullRowSelect = true;
|
||||
listView.GridLines = true;
|
||||
listView.LargeImageList = imageList_LargeIcon;
|
||||
listView.Location = new Point(0, 0);
|
||||
listView.Name = "listView";
|
||||
listView.ShowItemToolTips = true;
|
||||
listView.Size = new Size(336, 445);
|
||||
listView.SmallImageList = imageList_SmallIcon;
|
||||
listView.TabIndex = 1;
|
||||
listView.UseCompatibleStateImageBehavior = false;
|
||||
listView.View = View.Details;
|
||||
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
|
||||
//
|
||||
@@ -73,72 +89,166 @@
|
||||
// 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 });
|
||||
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, 226);
|
||||
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_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;
|
||||
//
|
||||
// toolStripMenuItem_RemoveAll
|
||||
// toolStripSeparator3
|
||||
//
|
||||
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;
|
||||
toolStripSeparator3.Name = "toolStripSeparator3";
|
||||
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_ListView, toolStripMenuItem_DetailsView });
|
||||
toolStripMenuItem_ChangeView.Name = "toolStripMenuItem_ChangeView";
|
||||
toolStripMenuItem_ChangeView.Size = new Size(328, 30);
|
||||
toolStripMenuItem_ChangeView.Text = "切换视图";
|
||||
//
|
||||
// toolStripMenuItem_LargeIconView
|
||||
//
|
||||
toolStripMenuItem_LargeIconView.Name = "toolStripMenuItem_LargeIconView";
|
||||
toolStripMenuItem_LargeIconView.ShortcutKeys = Keys.Alt | Keys.D1;
|
||||
toolStripMenuItem_LargeIconView.Size = new Size(241, 34);
|
||||
toolStripMenuItem_LargeIconView.Text = "大图标";
|
||||
toolStripMenuItem_LargeIconView.Click += toolStripMenuItem_LargeIconView_Click;
|
||||
//
|
||||
// toolStripMenuItem_ListView
|
||||
//
|
||||
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.ShortcutKeys = Keys.Alt | Keys.D3;
|
||||
toolStripMenuItem_DetailsView.Size = new Size(241, 34);
|
||||
toolStripMenuItem_DetailsView.Text = "详细信息";
|
||||
toolStripMenuItem_DetailsView.Click += toolStripMenuItem_DetailsView_Click;
|
||||
//
|
||||
// imageList_LargeIcon
|
||||
//
|
||||
imageList_LargeIcon.ColorDepth = ColorDepth.Depth32Bit;
|
||||
imageList_LargeIcon.ImageSize = new Size(96, 96);
|
||||
imageList_LargeIcon.TransparentColor = Color.Transparent;
|
||||
//
|
||||
// imageList_SmallIcon
|
||||
//
|
||||
imageList_SmallIcon.ColorDepth = ColorDepth.Depth32Bit;
|
||||
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
|
||||
//
|
||||
@@ -164,5 +274,18 @@
|
||||
private ToolStripMenuItem toolStripMenuItem_MoveDown;
|
||||
private ToolStripSeparator toolStripSeparator2;
|
||||
private ColumnHeader columnHeader_Name;
|
||||
private ImageList imageList_SmallIcon;
|
||||
private ImageList imageList_LargeIcon;
|
||||
private ToolStripSeparator toolStripSeparator3;
|
||||
private ToolStripMenuItem toolStripMenuItem_ChangeView;
|
||||
private ToolStripMenuItem toolStripMenuItem_LargeIconView;
|
||||
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
|
||||
{
|
||||
@@ -36,39 +37,9 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹出添加对话框在指定位置之前插入一项
|
||||
/// listView.SelectedIndices
|
||||
/// </summary>
|
||||
private void Insert(int index = -1)
|
||||
{
|
||||
var dialog = new Dialogs.OpenSpineDialog();
|
||||
if (dialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var spine = Spine.Spine.New(dialog.Version, dialog.SkelPath, dialog.AtlasPath);
|
||||
|
||||
// 如果索引无效则在末尾添加
|
||||
if (index < 0 || index > listView.Items.Count)
|
||||
index = listView.Items.Count;
|
||||
|
||||
// 锁定外部的读操作
|
||||
lock (Spines) { spines.Insert(index, spine); }
|
||||
listView.Items.Insert(index, new ListViewItem(spine.Name) { ToolTipText = spine.SkelPath });
|
||||
|
||||
// 选中新增项
|
||||
listView.SelectedIndices.Clear();
|
||||
listView.SelectedIndices.Add(index);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
Program.Logger.Error("Failed to load {} {}", dialog.SkelPath, dialog.AtlasPath);
|
||||
MessageBox.Show(ex.ToString(), "骨骼加载失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||
}
|
||||
public ListView.SelectedIndexCollection SelectedIndices { get => listView.SelectedIndices; }
|
||||
|
||||
/// <summary>
|
||||
/// 弹出添加对话框
|
||||
@@ -89,61 +60,10 @@ namespace SpineViewer.Controls
|
||||
|
||||
var progressDialog = new Dialogs.ProgressDialog();
|
||||
progressDialog.DoWork += BatchAdd_Work;
|
||||
progressDialog.RunWorkerAsync(openDialog);
|
||||
progressDialog.RunWorkerAsync(openDialog.Result);
|
||||
progressDialog.ShowDialog();
|
||||
}
|
||||
|
||||
private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var arguments = e.Argument as Dialogs.BatchOpenSpineDialog;
|
||||
var skelPaths = arguments.SkelPaths;
|
||||
var version = arguments.Version;
|
||||
|
||||
int totalCount = skelPaths.Length;
|
||||
int success = 0;
|
||||
int error = 0;
|
||||
|
||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var skelPath = skelPaths[i];
|
||||
|
||||
try
|
||||
{
|
||||
var spine = Spine.Spine.New(version, skelPath);
|
||||
lock (Spines) { spines.Add(spine); }
|
||||
listView.Invoke(() => listView.Items.Add(new ListViewItem(spine.Name) { ToolTipText = spine.SkelPath }));
|
||||
success++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
Program.Logger.Error("Failed to load {}", skelPath);
|
||||
error++;
|
||||
}
|
||||
|
||||
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
||||
}
|
||||
|
||||
if (error > 0)
|
||||
{
|
||||
Program.Logger.Warn("Batch load {} successfully, {} failed", success, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.Logger.Info("{} skel loaded successfully", success);
|
||||
}
|
||||
|
||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||
}
|
||||
|
||||
private void listView_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (PropertyGrid is not null)
|
||||
@@ -156,21 +76,24 @@ namespace SpineViewer.Controls
|
||||
PropertyGrid.SelectedObject = spines[listView.SelectedIndices[0]];
|
||||
else
|
||||
PropertyGrid.SelectedObjects = listView.SelectedIndices.Cast<int>().Select(index => spines[index]).ToArray();
|
||||
|
||||
// 标记选中的 Spine
|
||||
for (int i = 0; i < spines.Count; i++)
|
||||
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)
|
||||
@@ -178,74 +101,107 @@ 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_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)
|
||||
@@ -279,13 +235,36 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
lock (Spines)
|
||||
{
|
||||
spines[i].Dispose();
|
||||
var spine = spines[i];
|
||||
spines.RemoveAt(i);
|
||||
listView.SmallImageList.Images.RemoveByKey(spine.ID);
|
||||
listView.LargeImageList.Images.RemoveByKey(spine.ID);
|
||||
spine.Dispose();
|
||||
}
|
||||
listView.Items.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -296,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,15 +327,211 @@ namespace SpineViewer.Controls
|
||||
if (MessageBox.Show($"确认移除所有 {listView.Items.Count} 项吗?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
|
||||
return;
|
||||
|
||||
lock (Spines)
|
||||
lock (Spines)
|
||||
{
|
||||
foreach (var spine in spines)
|
||||
spine.Dispose();
|
||||
spines.Clear();
|
||||
spines.Clear();
|
||||
listView.SmallImageList.Images.Clear();
|
||||
listView.LargeImageList.Images.Clear();
|
||||
}
|
||||
listView.Items.Clear();
|
||||
if (PropertyGrid is not null)
|
||||
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_ListView_Click(object sender, EventArgs e)
|
||||
{
|
||||
listView.View = View.List;
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_DetailsView_Click(object sender, EventArgs e)
|
||||
{
|
||||
listView.View = View.Details;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹出添加对话框在指定位置之前插入一项
|
||||
/// </summary>
|
||||
private void Insert(int index = -1)
|
||||
{
|
||||
var dialog = new Dialogs.OpenSpineDialog();
|
||||
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(result.Version, result.SkelPath, result.AtlasPath);
|
||||
|
||||
// 如果索引无效则在末尾添加
|
||||
if (index < 0 || index > listView.Items.Count)
|
||||
index = listView.Items.Count;
|
||||
|
||||
// 锁定外部的读操作
|
||||
lock (Spines)
|
||||
{
|
||||
spines.Insert(index, spine);
|
||||
listView.SmallImageList.Images.Add(spine.ID, spine.Preview);
|
||||
listView.LargeImageList.Images.Add(spine.ID, spine.Preview);
|
||||
}
|
||||
listView.Items.Insert(index, new ListViewItem(spine.Name, spine.ID) { ToolTipText = spine.SkelPath });
|
||||
|
||||
// 选中新增项
|
||||
listView.SelectedIndices.Clear();
|
||||
listView.SelectedIndices.Add(index);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
Program.Logger.Error("Failed to load {} {}", result.SkelPath, result.AtlasPath);
|
||||
MessageBox.Show(ex.ToString(), "骨骼加载失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||
}
|
||||
|
||||
private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var arguments = e.Argument as Dialogs.BatchOpenSpineDialogResult;
|
||||
var skelPaths = arguments.SkelPaths;
|
||||
var version = arguments.Version;
|
||||
|
||||
int totalCount = skelPaths.Length;
|
||||
int success = 0;
|
||||
int error = 0;
|
||||
|
||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var skelPath = skelPaths[i];
|
||||
|
||||
try
|
||||
{
|
||||
var spine = Spine.Spine.New(version, skelPath);
|
||||
var preview = spine.Preview;
|
||||
lock (Spines) { spines.Add(spine); }
|
||||
listView.Invoke(() =>
|
||||
{
|
||||
listView.SmallImageList.Images.Add(spine.ID, preview);
|
||||
listView.LargeImageList.Images.Add(spine.ID, preview);
|
||||
listView.Items.Add(new ListViewItem(spine.Name, spine.ID) { ToolTipText = spine.SkelPath });
|
||||
});
|
||||
success++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
Program.Logger.Error("Failed to load {}", skelPath);
|
||||
error++;
|
||||
}
|
||||
|
||||
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
||||
}
|
||||
|
||||
if (error > 0)
|
||||
{
|
||||
Program.Logger.Warn("Batch load {} successfully, {} failed", success, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.Logger.Info("{} skel loaded successfully", success);
|
||||
}
|
||||
|
||||
Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
|
||||
}
|
||||
|
||||
private void AddFromFileDrop(string[] paths)
|
||||
{
|
||||
List<string> validPaths = [];
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
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))
|
||||
{
|
||||
if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
||||
validPaths.Add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (validPaths.Count > 1)
|
||||
{
|
||||
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 if (validPaths.Count > 0)
|
||||
{
|
||||
Insert(new Dialogs.OpenSpineDialogResult(Spine.Version.Auto, validPaths[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,4 +120,10 @@
|
||||
<metadata name="contextMenuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<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>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>252, 19</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -24,11 +24,11 @@ namespace SpineViewer.Controls
|
||||
|
||||
public PreviewerProperty(SpinePreviewer previewer) { this.previewer = previewer; }
|
||||
|
||||
[TypeConverter(typeof(SizeTypeConverter))]
|
||||
[TypeConverter(typeof(SizeConverter))]
|
||||
[Category("导出"), DisplayName("分辨率")]
|
||||
public Size Resolution { get => previewer.Resolution; set => previewer.Resolution = value; }
|
||||
|
||||
[TypeConverter(typeof(PointFTypeConverter))]
|
||||
[TypeConverter(typeof(PointFConverter))]
|
||||
[Category("导出"), DisplayName("画面中心点")]
|
||||
public PointF Center { get => previewer.Center; set => previewer.Center = value; }
|
||||
|
||||
@@ -47,9 +47,6 @@ namespace SpineViewer.Controls
|
||||
[Category("预览"), DisplayName("显示坐标轴")]
|
||||
public bool ShowAxis { get => previewer.ShowAxis; set => previewer.ShowAxis = value; }
|
||||
|
||||
[Category("预览"), DisplayName("显示包围盒")]
|
||||
public bool ShowBounds { get => previewer.ShowBounds; set => previewer.ShowBounds = value; }
|
||||
|
||||
[Category("预览"), DisplayName("最大帧率")]
|
||||
public uint MaxFps { get => previewer.MaxFps; set => previewer.MaxFps = value; }
|
||||
}
|
||||
@@ -84,7 +81,6 @@ namespace SpineViewer.Controls
|
||||
private readonly SFML.Graphics.RenderWindow RenderWindow;
|
||||
private readonly SFML.System.Clock Clock = new();
|
||||
private SFML.System.Vector2f? draggingSrc = null;
|
||||
private Spine.Spine? draggingSpine = null;
|
||||
|
||||
private Task? task = null;
|
||||
private CancellationTokenSource? cancelToken = null;
|
||||
@@ -236,13 +232,6 @@ namespace SpineViewer.Controls
|
||||
[Browsable(false)]
|
||||
public bool ShowAxis { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 显示包围盒
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
public bool ShowBounds { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 最大帧率
|
||||
/// </summary>
|
||||
@@ -328,7 +317,6 @@ namespace SpineViewer.Controls
|
||||
// 右键优先级高, 进入画面拖动模式, 需要重新记录源点
|
||||
if ((e.Button & MouseButtons.Right) != 0)
|
||||
{
|
||||
draggingSpine = null;
|
||||
draggingSrc = RenderWindow.MapPixelToCoords(new(e.X, e.Y));
|
||||
Cursor = Cursors.Hand;
|
||||
}
|
||||
@@ -342,12 +330,41 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
lock (SpineListView.Spines)
|
||||
{
|
||||
foreach (var spine in SpineListView.Spines)
|
||||
var spines = SpineListView.Spines;
|
||||
|
||||
// 没有按下 Ctrl 键就只选中点击的那个, 所以先清空选中列表
|
||||
if ((ModifierKeys & Keys.Control) == 0)
|
||||
{
|
||||
if (spine.Bounds.Contains(src))
|
||||
bool hit = false;
|
||||
for (int i = 0; i < spines.Count; i++)
|
||||
{
|
||||
draggingSpine = spine;
|
||||
break;
|
||||
if (spines[i].Bounds.Contains(src))
|
||||
{
|
||||
hit = true;
|
||||
|
||||
// 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的
|
||||
if (!SpineListView.SelectedIndices.Contains(i))
|
||||
{
|
||||
SpineListView.SelectedIndices.Clear();
|
||||
SpineListView.SelectedIndices.Add(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果点了空白的地方, 就清空选中列表
|
||||
if (!hit)
|
||||
SpineListView.SelectedIndices.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < spines.Count; i++)
|
||||
{
|
||||
if (spines[i].Bounds.Contains(src))
|
||||
{
|
||||
SpineListView.SelectedIndices.Add(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,8 +388,17 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
else if ((e.Button & MouseButtons.Left) != 0)
|
||||
{
|
||||
if (draggingSpine is not null)
|
||||
draggingSpine.Position += delta;
|
||||
if (SpineListView is not null)
|
||||
{
|
||||
lock (SpineListView.Spines)
|
||||
{
|
||||
foreach (var spine in SpineListView.Spines)
|
||||
{
|
||||
if (spine.IsSelected)
|
||||
spine.Position += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
draggingSrc = dst;
|
||||
}
|
||||
}
|
||||
@@ -382,7 +408,6 @@ namespace SpineViewer.Controls
|
||||
// 右键高优先级, 结束画面拖动模式
|
||||
if ((e.Button & MouseButtons.Right) != 0)
|
||||
{
|
||||
draggingSpine = null;
|
||||
SpineListView?.PropertyGrid?.Refresh();
|
||||
|
||||
draggingSrc = null;
|
||||
@@ -393,7 +418,6 @@ namespace SpineViewer.Controls
|
||||
else if ((e.Button & MouseButtons.Left) != 0 && (MouseButtons & MouseButtons.Right) == 0)
|
||||
{
|
||||
draggingSrc = null;
|
||||
draggingSpine = null;
|
||||
SpineListView?.PropertyGrid?.Refresh();
|
||||
}
|
||||
}
|
||||
@@ -434,12 +458,17 @@ 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);
|
||||
|
||||
if (ShowBounds)
|
||||
if (spine.IsSelected)
|
||||
{
|
||||
var bounds = spine.Bounds;
|
||||
BoundsRect[0] = BoundsRect[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||
|
||||
@@ -194,8 +194,9 @@
|
||||
//
|
||||
openFileDialog_Skel.AddExtension = false;
|
||||
openFileDialog_Skel.AddToRecent = false;
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Multiselect = true;
|
||||
openFileDialog_Skel.Title = "批量选择skel文件";
|
||||
//
|
||||
// BatchOpenSpineDialog
|
||||
//
|
||||
@@ -213,6 +214,7 @@
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Text = "批量打开骨骼";
|
||||
Load += BatchOpenSpineDialog_Load;
|
||||
panel.ResumeLayout(false);
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
|
||||
@@ -13,16 +13,20 @@ 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)
|
||||
{
|
||||
button_SelectSkel_Click(sender, e);
|
||||
}
|
||||
|
||||
private void button_SelectSkel_Click(object sender, EventArgs e)
|
||||
@@ -38,6 +42,8 @@ namespace SpineViewer.Dialogs
|
||||
|
||||
private void button_Ok_Click(object sender, EventArgs e)
|
||||
{
|
||||
var version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||
|
||||
if (listBox_FilePath.Items.Count <= 0)
|
||||
{
|
||||
MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
@@ -53,9 +59,13 @@ namespace SpineViewer.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
SkelPaths = listBox_FilePath.Items.Cast<string>().ToArray();
|
||||
Version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
||||
{
|
||||
MessageBox.Show($"{version.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
Result = new(version, listBox_FilePath.Items.Cast<string>().ToArray());
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
@@ -64,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;
|
||||
}
|
||||
}
|
||||
|
||||
358
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
Normal file
358
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,358 @@
|
||||
namespace SpineViewer.Dialogs
|
||||
{
|
||||
partial class ConvertFileFormatDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConvertFileFormatDialog));
|
||||
panel = new Panel();
|
||||
tableLayoutPanel1 = new TableLayoutPanel();
|
||||
flowLayoutPanel_TargetFormat = new FlowLayoutPanel();
|
||||
radioButton_BinaryTarget = new RadioButton();
|
||||
radioButton_JsonTarget = new RadioButton();
|
||||
label1 = new Label();
|
||||
label4 = new Label();
|
||||
label3 = new Label();
|
||||
comboBox_SourceVersion = new ComboBox();
|
||||
tableLayoutPanel2 = new TableLayoutPanel();
|
||||
button_Ok = new Button();
|
||||
button_Cancel = new Button();
|
||||
listBox_FilePath = new ListBox();
|
||||
button_SelectSkel = new Button();
|
||||
label_Tip = new Label();
|
||||
label2 = new Label();
|
||||
flowLayoutPanel_SourceFormat = new FlowLayoutPanel();
|
||||
radioButton_BinarySource = new RadioButton();
|
||||
radioButton_JsonSource = new RadioButton();
|
||||
openFileDialog_Skel = new OpenFileDialog();
|
||||
panel.SuspendLayout();
|
||||
tableLayoutPanel1.SuspendLayout();
|
||||
flowLayoutPanel_TargetFormat.SuspendLayout();
|
||||
tableLayoutPanel2.SuspendLayout();
|
||||
flowLayoutPanel_SourceFormat.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// panel
|
||||
//
|
||||
panel.Controls.Add(tableLayoutPanel1);
|
||||
panel.Dock = DockStyle.Fill;
|
||||
panel.Location = new Point(0, 0);
|
||||
panel.Name = "panel";
|
||||
panel.Padding = new Padding(50, 15, 50, 10);
|
||||
panel.Size = new Size(1039, 530);
|
||||
panel.TabIndex = 2;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
tableLayoutPanel1.ColumnCount = 2;
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel1.Controls.Add(flowLayoutPanel_TargetFormat, 1, 5);
|
||||
tableLayoutPanel1.Controls.Add(label1, 0, 4);
|
||||
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
||||
tableLayoutPanel1.Controls.Add(label3, 0, 3);
|
||||
tableLayoutPanel1.Controls.Add(comboBox_SourceVersion, 1, 3);
|
||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 6);
|
||||
tableLayoutPanel1.Controls.Add(listBox_FilePath, 0, 2);
|
||||
tableLayoutPanel1.Controls.Add(button_SelectSkel, 0, 1);
|
||||
tableLayoutPanel1.Controls.Add(label_Tip, 1, 1);
|
||||
tableLayoutPanel1.Controls.Add(label2, 0, 5);
|
||||
tableLayoutPanel1.Controls.Add(flowLayoutPanel_SourceFormat, 1, 4);
|
||||
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||
tableLayoutPanel1.Location = new Point(50, 15);
|
||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
tableLayoutPanel1.RowCount = 7;
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.Size = new Size(939, 505);
|
||||
tableLayoutPanel1.TabIndex = 1;
|
||||
//
|
||||
// flowLayoutPanel_TargetFormat
|
||||
//
|
||||
flowLayoutPanel_TargetFormat.AutoSize = true;
|
||||
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_BinaryTarget);
|
||||
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_JsonTarget);
|
||||
flowLayoutPanel_TargetFormat.Dock = DockStyle.Fill;
|
||||
flowLayoutPanel_TargetFormat.Location = new Point(146, 381);
|
||||
flowLayoutPanel_TargetFormat.Name = "flowLayoutPanel_TargetFormat";
|
||||
flowLayoutPanel_TargetFormat.Size = new Size(790, 34);
|
||||
flowLayoutPanel_TargetFormat.TabIndex = 19;
|
||||
//
|
||||
// radioButton_BinaryTarget
|
||||
//
|
||||
radioButton_BinaryTarget.AutoSize = true;
|
||||
radioButton_BinaryTarget.Location = new Point(3, 3);
|
||||
radioButton_BinaryTarget.Name = "radioButton_BinaryTarget";
|
||||
radioButton_BinaryTarget.Size = new Size(151, 28);
|
||||
radioButton_BinaryTarget.TabIndex = 17;
|
||||
radioButton_BinaryTarget.Text = "二进制 (*.skel)";
|
||||
radioButton_BinaryTarget.UseVisualStyleBackColor = true;
|
||||
radioButton_BinaryTarget.CheckedChanged += radioButton_Target_CheckedChanged;
|
||||
//
|
||||
// radioButton_JsonTarget
|
||||
//
|
||||
radioButton_JsonTarget.AutoSize = true;
|
||||
radioButton_JsonTarget.Checked = true;
|
||||
radioButton_JsonTarget.Location = new Point(160, 3);
|
||||
radioButton_JsonTarget.Name = "radioButton_JsonTarget";
|
||||
radioButton_JsonTarget.Size = new Size(135, 28);
|
||||
radioButton_JsonTarget.TabIndex = 18;
|
||||
radioButton_JsonTarget.TabStop = true;
|
||||
radioButton_JsonTarget.Text = "文本 (*.json)";
|
||||
radioButton_JsonTarget.UseVisualStyleBackColor = true;
|
||||
radioButton_JsonTarget.CheckedChanged += radioButton_Target_CheckedChanged;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
label1.Anchor = AnchorStyles.Right;
|
||||
label1.AutoSize = true;
|
||||
label1.Location = new Point(72, 346);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new Size(68, 24);
|
||||
label1.TabIndex = 15;
|
||||
label1.Text = "源格式:";
|
||||
//
|
||||
// label4
|
||||
//
|
||||
label4.AutoSize = true;
|
||||
tableLayoutPanel1.SetColumnSpan(label4, 4);
|
||||
label4.Dock = DockStyle.Fill;
|
||||
label4.Location = new Point(15, 15);
|
||||
label4.Margin = new Padding(15);
|
||||
label4.Name = "label4";
|
||||
label4.Size = new Size(909, 24);
|
||||
label4.TabIndex = 14;
|
||||
label4.Text = "说明:将在每个文件同级目录下生成目标格式后缀的文件,会覆盖已存在文件";
|
||||
label4.TextAlign = ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// label3
|
||||
//
|
||||
label3.Anchor = AnchorStyles.Right;
|
||||
label3.AutoSize = true;
|
||||
label3.Location = new Point(72, 307);
|
||||
label3.Name = "label3";
|
||||
label3.Size = new Size(68, 24);
|
||||
label3.TabIndex = 12;
|
||||
label3.Text = "源版本:";
|
||||
//
|
||||
// comboBox_Version
|
||||
//
|
||||
comboBox_SourceVersion.Anchor = AnchorStyles.Left;
|
||||
comboBox_SourceVersion.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
comboBox_SourceVersion.FormattingEnabled = true;
|
||||
comboBox_SourceVersion.Location = new Point(146, 303);
|
||||
comboBox_SourceVersion.Name = "comboBox_Version";
|
||||
comboBox_SourceVersion.Size = new Size(182, 32);
|
||||
comboBox_SourceVersion.Sorted = true;
|
||||
comboBox_SourceVersion.TabIndex = 13;
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
tableLayoutPanel2.AutoSize = true;
|
||||
tableLayoutPanel2.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
tableLayoutPanel2.ColumnCount = 2;
|
||||
tableLayoutPanel1.SetColumnSpan(tableLayoutPanel2, 4);
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.Controls.Add(button_Ok, 0, 0);
|
||||
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
||||
tableLayoutPanel2.Location = new Point(3, 462);
|
||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
tableLayoutPanel2.RowCount = 1;
|
||||
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel2.Size = new Size(933, 40);
|
||||
tableLayoutPanel2.TabIndex = 11;
|
||||
//
|
||||
// button_Ok
|
||||
//
|
||||
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
button_Ok.Location = new Point(324, 3);
|
||||
button_Ok.Margin = new Padding(3, 3, 30, 3);
|
||||
button_Ok.Name = "button_Ok";
|
||||
button_Ok.Size = new Size(112, 34);
|
||||
button_Ok.TabIndex = 7;
|
||||
button_Ok.Text = "确认";
|
||||
button_Ok.UseVisualStyleBackColor = true;
|
||||
button_Ok.Click += button_Ok_Click;
|
||||
//
|
||||
// button_Cancel
|
||||
//
|
||||
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
||||
button_Cancel.Location = new Point(496, 3);
|
||||
button_Cancel.Margin = new Padding(30, 3, 3, 3);
|
||||
button_Cancel.Name = "button_Cancel";
|
||||
button_Cancel.Size = new Size(112, 34);
|
||||
button_Cancel.TabIndex = 8;
|
||||
button_Cancel.Text = "取消";
|
||||
button_Cancel.UseVisualStyleBackColor = true;
|
||||
button_Cancel.Click += button_Cancel_Click;
|
||||
//
|
||||
// listBox_FilePath
|
||||
//
|
||||
tableLayoutPanel1.SetColumnSpan(listBox_FilePath, 2);
|
||||
listBox_FilePath.Dock = DockStyle.Fill;
|
||||
listBox_FilePath.FormattingEnabled = true;
|
||||
listBox_FilePath.HorizontalScrollbar = true;
|
||||
listBox_FilePath.ItemHeight = 24;
|
||||
listBox_FilePath.Location = new Point(3, 97);
|
||||
listBox_FilePath.Name = "listBox_FilePath";
|
||||
listBox_FilePath.Size = new Size(933, 200);
|
||||
listBox_FilePath.TabIndex = 2;
|
||||
//
|
||||
// button_SelectSkel
|
||||
//
|
||||
button_SelectSkel.Anchor = AnchorStyles.None;
|
||||
button_SelectSkel.Location = new Point(3, 57);
|
||||
button_SelectSkel.Name = "button_SelectSkel";
|
||||
button_SelectSkel.Size = new Size(137, 34);
|
||||
button_SelectSkel.TabIndex = 1;
|
||||
button_SelectSkel.Text = "选择文件...";
|
||||
button_SelectSkel.UseVisualStyleBackColor = true;
|
||||
button_SelectSkel.Click += button_SelectSkel_Click;
|
||||
//
|
||||
// label_Tip
|
||||
//
|
||||
label_Tip.AutoSize = true;
|
||||
label_Tip.Dock = DockStyle.Fill;
|
||||
label_Tip.Location = new Point(146, 54);
|
||||
label_Tip.Name = "label_Tip";
|
||||
label_Tip.Size = new Size(790, 40);
|
||||
label_Tip.TabIndex = 0;
|
||||
label_Tip.Text = "已选择 0 个文件";
|
||||
label_Tip.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
label2.Anchor = AnchorStyles.Right;
|
||||
label2.AutoSize = true;
|
||||
label2.Location = new Point(54, 386);
|
||||
label2.Name = "label2";
|
||||
label2.Size = new Size(86, 24);
|
||||
label2.TabIndex = 16;
|
||||
label2.Text = "目标格式:";
|
||||
//
|
||||
// flowLayoutPanel_SourceFormat
|
||||
//
|
||||
flowLayoutPanel_SourceFormat.AutoSize = true;
|
||||
flowLayoutPanel_SourceFormat.Controls.Add(radioButton_BinarySource);
|
||||
flowLayoutPanel_SourceFormat.Controls.Add(radioButton_JsonSource);
|
||||
flowLayoutPanel_SourceFormat.Dock = DockStyle.Fill;
|
||||
flowLayoutPanel_SourceFormat.Location = new Point(146, 341);
|
||||
flowLayoutPanel_SourceFormat.Name = "flowLayoutPanel_SourceFormat";
|
||||
flowLayoutPanel_SourceFormat.Size = new Size(790, 34);
|
||||
flowLayoutPanel_SourceFormat.TabIndex = 18;
|
||||
//
|
||||
// radioButton_BinarySource
|
||||
//
|
||||
radioButton_BinarySource.AutoSize = true;
|
||||
radioButton_BinarySource.Checked = true;
|
||||
radioButton_BinarySource.Location = new Point(3, 3);
|
||||
radioButton_BinarySource.Name = "radioButton_BinarySource";
|
||||
radioButton_BinarySource.Size = new Size(151, 28);
|
||||
radioButton_BinarySource.TabIndex = 17;
|
||||
radioButton_BinarySource.TabStop = true;
|
||||
radioButton_BinarySource.Text = "二进制 (*.skel)";
|
||||
radioButton_BinarySource.UseVisualStyleBackColor = true;
|
||||
radioButton_BinarySource.CheckedChanged += radioButton_Source_CheckedChanged;
|
||||
//
|
||||
// radioButton_JsonSource
|
||||
//
|
||||
radioButton_JsonSource.AutoSize = true;
|
||||
radioButton_JsonSource.Location = new Point(160, 3);
|
||||
radioButton_JsonSource.Name = "radioButton_JsonSource";
|
||||
radioButton_JsonSource.Size = new Size(135, 28);
|
||||
radioButton_JsonSource.TabIndex = 18;
|
||||
radioButton_JsonSource.Text = "文本 (*.json)";
|
||||
radioButton_JsonSource.UseVisualStyleBackColor = true;
|
||||
radioButton_JsonSource.CheckedChanged += radioButton_Source_CheckedChanged;
|
||||
//
|
||||
// openFileDialog_Skel
|
||||
//
|
||||
openFileDialog_Skel.AddExtension = false;
|
||||
openFileDialog_Skel.AddToRecent = false;
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Multiselect = true;
|
||||
openFileDialog_Skel.Title = "批量选择skel文件";
|
||||
//
|
||||
// ConvertFileFormatDialog
|
||||
//
|
||||
AcceptButton = button_Ok;
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
CancelButton = button_Cancel;
|
||||
ClientSize = new Size(1039, 530);
|
||||
Controls.Add(panel);
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "ConvertFileFormatDialog";
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Text = "骨骼文件格式转换";
|
||||
Load += ConvertFileFormatDialog_Load;
|
||||
panel.ResumeLayout(false);
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
flowLayoutPanel_TargetFormat.ResumeLayout(false);
|
||||
flowLayoutPanel_TargetFormat.PerformLayout();
|
||||
tableLayoutPanel2.ResumeLayout(false);
|
||||
flowLayoutPanel_SourceFormat.ResumeLayout(false);
|
||||
flowLayoutPanel_SourceFormat.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Panel panel;
|
||||
private TableLayoutPanel tableLayoutPanel1;
|
||||
private Label label4;
|
||||
private Label label3;
|
||||
private ComboBox comboBox_SourceVersion;
|
||||
private TableLayoutPanel tableLayoutPanel2;
|
||||
private Button button_Ok;
|
||||
private Button button_Cancel;
|
||||
private ListBox listBox_FilePath;
|
||||
private Button button_SelectSkel;
|
||||
private Label label_Tip;
|
||||
private OpenFileDialog openFileDialog_Skel;
|
||||
private Label label1;
|
||||
private Label label2;
|
||||
private RadioButton radioButton_BinarySource;
|
||||
private FlowLayoutPanel flowLayoutPanel_SourceFormat;
|
||||
private RadioButton radioButton_JsonSource;
|
||||
private FlowLayoutPanel flowLayoutPanel_TargetFormat;
|
||||
private RadioButton radioButton_BinaryTarget;
|
||||
private RadioButton radioButton_JsonTarget;
|
||||
}
|
||||
}
|
||||
126
SpineViewer/Dialogs/ConvertFileFormatDialog.cs
Normal file
126
SpineViewer/Dialogs/ConvertFileFormatDialog.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using SpineViewer.Spine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace SpineViewer.Dialogs
|
||||
{
|
||||
public partial class ConvertFileFormatDialog : Form
|
||||
{
|
||||
public string[] SkelPaths { get; private set; }
|
||||
public Spine.Version SourceVersion { get; private set; }
|
||||
public Spine.Version TargetVersion { get; private set; }
|
||||
public bool JsonSource { get; private set; }
|
||||
public bool JsonTarget { get; private set; }
|
||||
|
||||
public ConvertFileFormatDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 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;
|
||||
//comboBox_TargetVersion.DataSource = VersionHelper.Versions.ToList();
|
||||
//comboBox_TargetVersion.DisplayMember = "Value";
|
||||
//comboBox_TargetVersion.ValueMember = "Key";
|
||||
//comboBox_TargetVersion.SelectedValue = Spine.Version.V38;
|
||||
}
|
||||
|
||||
private void ConvertFileFormatDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
button_SelectSkel_Click(sender, e);
|
||||
}
|
||||
|
||||
private void button_SelectSkel_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (openFileDialog_Skel.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
listBox_FilePath.Items.Clear();
|
||||
foreach (var p in openFileDialog_Skel.FileNames)
|
||||
listBox_FilePath.Items.Add(Path.GetFullPath(p));
|
||||
label_Tip.Text = $"已选择 {listBox_FilePath.Items.Count} 个文件";
|
||||
}
|
||||
}
|
||||
|
||||
private void button_Ok_Click(object sender, EventArgs e)
|
||||
{
|
||||
var sourceVersion = (Spine.Version)comboBox_SourceVersion.SelectedValue;
|
||||
var targetVersion = (Spine.Version)comboBox_SourceVersion.SelectedValue; // TODO: 增加目标版本
|
||||
var jsonSource = radioButton_JsonSource.Checked;
|
||||
var jsonTarget = radioButton_JsonTarget.Checked;
|
||||
|
||||
if (listBox_FilePath.Items.Count <= 0)
|
||||
{
|
||||
MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string p in listBox_FilePath.Items)
|
||||
{
|
||||
if (!File.Exists(p))
|
||||
{
|
||||
MessageBox.Show($"{p}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SkeletonConverter.ImplementedVersions.Contains(sourceVersion))
|
||||
{
|
||||
MessageBox.Show($"{sourceVersion.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SkeletonConverter.ImplementedVersions.Contains(targetVersion))
|
||||
{
|
||||
MessageBox.Show($"{targetVersion.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsonSource == jsonTarget && sourceVersion == targetVersion)
|
||||
{
|
||||
MessageBox.Show($"不需要转换相同的格式和版本", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
SkelPaths = listBox_FilePath.Items.Cast<string>().ToArray();
|
||||
SourceVersion = sourceVersion;
|
||||
TargetVersion = targetVersion;
|
||||
JsonSource = jsonSource;
|
||||
JsonTarget = jsonTarget;
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
private void button_Cancel_Click(object sender, EventArgs e)
|
||||
{
|
||||
DialogResult = DialogResult.Cancel;
|
||||
}
|
||||
|
||||
private void radioButton_Source_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (radioButton_BinarySource.Checked)
|
||||
radioButton_JsonTarget.Checked = true;
|
||||
else
|
||||
radioButton_BinaryTarget.Checked = true;
|
||||
}
|
||||
|
||||
private void radioButton_Target_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (radioButton_BinaryTarget.Checked)
|
||||
radioButton_JsonSource.Checked = true;
|
||||
else
|
||||
radioButton_BinarySource.Checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
3270
SpineViewer/Dialogs/ConvertFileFormatDialog.resx
Normal file
3270
SpineViewer/Dialogs/ConvertFileFormatDialog.resx
Normal file
File diff suppressed because it is too large
Load Diff
1
SpineViewer/Dialogs/ExportPngDialog.Designer.cs
generated
1
SpineViewer/Dialogs/ExportPngDialog.Designer.cs
generated
@@ -239,6 +239,7 @@
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Text = "导出PNG序列";
|
||||
Load += ExportPngDialog_Load;
|
||||
panel1.ResumeLayout(false);
|
||||
panel1.PerformLayout();
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
|
||||
@@ -21,6 +21,11 @@ namespace SpineViewer.Dialogs
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ExportPngDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
button_SelectOutputDir_Click(sender, e);
|
||||
}
|
||||
|
||||
private void button_SelectOutputDir_Click(object sender, EventArgs e)
|
||||
{
|
||||
folderBrowserDialog.InitialDirectory = textBox_OutputDir.Text;
|
||||
|
||||
270
SpineViewer/Dialogs/ExportPreviewDialog.Designer.cs
generated
Normal file
270
SpineViewer/Dialogs/ExportPreviewDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,270 @@
|
||||
namespace SpineViewer.Dialogs
|
||||
{
|
||||
partial class ExportPreviewDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ExportPreviewDialog));
|
||||
panel1 = new Panel();
|
||||
tableLayoutPanel1 = new TableLayoutPanel();
|
||||
label4 = new Label();
|
||||
label1 = new Label();
|
||||
label2 = new Label();
|
||||
label3 = new Label();
|
||||
textBox_OutputDir = new TextBox();
|
||||
button_SelectOutputDir = new Button();
|
||||
tableLayoutPanel2 = new TableLayoutPanel();
|
||||
button_Ok = new Button();
|
||||
button_Cancel = new Button();
|
||||
numericUpDown_Width = new NumericUpDown();
|
||||
numericUpDown_Height = new NumericUpDown();
|
||||
folderBrowserDialog = new FolderBrowserDialog();
|
||||
panel1.SuspendLayout();
|
||||
tableLayoutPanel1.SuspendLayout();
|
||||
tableLayoutPanel2.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Width).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Height).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
panel1.Controls.Add(tableLayoutPanel1);
|
||||
panel1.Dock = DockStyle.Fill;
|
||||
panel1.Location = new Point(0, 0);
|
||||
panel1.Name = "panel1";
|
||||
panel1.Padding = new Padding(50, 15, 50, 10);
|
||||
panel1.Size = new Size(919, 276);
|
||||
panel1.TabIndex = 2;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
tableLayoutPanel1.AutoSize = true;
|
||||
tableLayoutPanel1.ColumnCount = 4;
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
||||
tableLayoutPanel1.Controls.Add(label1, 0, 1);
|
||||
tableLayoutPanel1.Controls.Add(label2, 0, 2);
|
||||
tableLayoutPanel1.Controls.Add(label3, 0, 3);
|
||||
tableLayoutPanel1.Controls.Add(textBox_OutputDir, 1, 1);
|
||||
tableLayoutPanel1.Controls.Add(button_SelectOutputDir, 3, 1);
|
||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 4);
|
||||
tableLayoutPanel1.Controls.Add(numericUpDown_Width, 1, 2);
|
||||
tableLayoutPanel1.Controls.Add(numericUpDown_Height, 1, 3);
|
||||
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||
tableLayoutPanel1.Location = new Point(50, 15);
|
||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
tableLayoutPanel1.RowCount = 5;
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.Size = new Size(819, 251);
|
||||
tableLayoutPanel1.TabIndex = 0;
|
||||
//
|
||||
// label4
|
||||
//
|
||||
label4.AutoSize = true;
|
||||
tableLayoutPanel1.SetColumnSpan(label4, 4);
|
||||
label4.Dock = DockStyle.Fill;
|
||||
label4.Location = new Point(15, 15);
|
||||
label4.Margin = new Padding(15);
|
||||
label4.Name = "label4";
|
||||
label4.Size = new Size(789, 24);
|
||||
label4.TabIndex = 11;
|
||||
label4.Text = "说明:导出的文件名与骨骼文件名相同";
|
||||
label4.TextAlign = ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
label1.Anchor = AnchorStyles.Right;
|
||||
label1.AutoSize = true;
|
||||
label1.Location = new Point(3, 62);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new Size(104, 24);
|
||||
label1.TabIndex = 0;
|
||||
label1.Text = "输出文件夹:";
|
||||
//
|
||||
// label2
|
||||
//
|
||||
label2.Anchor = AnchorStyles.Right;
|
||||
label2.AutoSize = true;
|
||||
label2.Location = new Point(75, 100);
|
||||
label2.Name = "label2";
|
||||
label2.Size = new Size(32, 24);
|
||||
label2.TabIndex = 1;
|
||||
label2.Text = "宽:";
|
||||
//
|
||||
// label3
|
||||
//
|
||||
label3.Anchor = AnchorStyles.Right;
|
||||
label3.AutoSize = true;
|
||||
label3.Location = new Point(75, 136);
|
||||
label3.Name = "label3";
|
||||
label3.Size = new Size(32, 24);
|
||||
label3.TabIndex = 2;
|
||||
label3.Text = "高:";
|
||||
//
|
||||
// textBox_OutputDir
|
||||
//
|
||||
tableLayoutPanel1.SetColumnSpan(textBox_OutputDir, 2);
|
||||
textBox_OutputDir.Dock = DockStyle.Fill;
|
||||
textBox_OutputDir.Location = new Point(113, 57);
|
||||
textBox_OutputDir.Name = "textBox_OutputDir";
|
||||
textBox_OutputDir.Size = new Size(664, 30);
|
||||
textBox_OutputDir.TabIndex = 3;
|
||||
//
|
||||
// button_SelectOutputDir
|
||||
//
|
||||
button_SelectOutputDir.AutoSize = true;
|
||||
button_SelectOutputDir.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
button_SelectOutputDir.Location = new Point(783, 57);
|
||||
button_SelectOutputDir.Name = "button_SelectOutputDir";
|
||||
button_SelectOutputDir.Size = new Size(32, 34);
|
||||
button_SelectOutputDir.TabIndex = 5;
|
||||
button_SelectOutputDir.Text = "...";
|
||||
button_SelectOutputDir.UseVisualStyleBackColor = true;
|
||||
button_SelectOutputDir.Click += button_SelectOutputDir_Click;
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
tableLayoutPanel2.AutoSize = true;
|
||||
tableLayoutPanel2.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
tableLayoutPanel2.ColumnCount = 2;
|
||||
tableLayoutPanel1.SetColumnSpan(tableLayoutPanel2, 4);
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||
tableLayoutPanel2.Controls.Add(button_Ok, 0, 0);
|
||||
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
||||
tableLayoutPanel2.Location = new Point(3, 208);
|
||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
tableLayoutPanel2.RowCount = 1;
|
||||
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel2.Size = new Size(813, 40);
|
||||
tableLayoutPanel2.TabIndex = 10;
|
||||
//
|
||||
// button_Ok
|
||||
//
|
||||
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
button_Ok.Location = new Point(264, 3);
|
||||
button_Ok.Margin = new Padding(3, 3, 30, 3);
|
||||
button_Ok.Name = "button_Ok";
|
||||
button_Ok.Size = new Size(112, 34);
|
||||
button_Ok.TabIndex = 7;
|
||||
button_Ok.Text = "确认";
|
||||
button_Ok.UseVisualStyleBackColor = true;
|
||||
button_Ok.Click += button_Ok_Click;
|
||||
//
|
||||
// button_Cancel
|
||||
//
|
||||
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
||||
button_Cancel.Location = new Point(436, 3);
|
||||
button_Cancel.Margin = new Padding(30, 3, 3, 3);
|
||||
button_Cancel.Name = "button_Cancel";
|
||||
button_Cancel.Size = new Size(112, 34);
|
||||
button_Cancel.TabIndex = 8;
|
||||
button_Cancel.Text = "取消";
|
||||
button_Cancel.UseVisualStyleBackColor = true;
|
||||
button_Cancel.Click += button_Cancel_Click;
|
||||
//
|
||||
// numericUpDown_Width
|
||||
//
|
||||
numericUpDown_Width.Anchor = AnchorStyles.Left;
|
||||
numericUpDown_Width.Location = new Point(113, 97);
|
||||
numericUpDown_Width.Maximum = new decimal(new int[] { 4096, 0, 0, 0 });
|
||||
numericUpDown_Width.Minimum = new decimal(new int[] { 32, 0, 0, 0 });
|
||||
numericUpDown_Width.Name = "numericUpDown_Width";
|
||||
numericUpDown_Width.Size = new Size(180, 30);
|
||||
numericUpDown_Width.TabIndex = 12;
|
||||
numericUpDown_Width.TextAlign = HorizontalAlignment.Right;
|
||||
numericUpDown_Width.Value = new decimal(new int[] { 256, 0, 0, 0 });
|
||||
//
|
||||
// numericUpDown_Height
|
||||
//
|
||||
numericUpDown_Height.Anchor = AnchorStyles.Left;
|
||||
numericUpDown_Height.Location = new Point(113, 133);
|
||||
numericUpDown_Height.Maximum = new decimal(new int[] { 4096, 0, 0, 0 });
|
||||
numericUpDown_Height.Minimum = new decimal(new int[] { 32, 0, 0, 0 });
|
||||
numericUpDown_Height.Name = "numericUpDown_Height";
|
||||
numericUpDown_Height.Size = new Size(180, 30);
|
||||
numericUpDown_Height.TabIndex = 13;
|
||||
numericUpDown_Height.TextAlign = HorizontalAlignment.Right;
|
||||
numericUpDown_Height.Value = new decimal(new int[] { 256, 0, 0, 0 });
|
||||
//
|
||||
// folderBrowserDialog
|
||||
//
|
||||
folderBrowserDialog.AddToRecent = false;
|
||||
//
|
||||
// ExportPreviewDialog
|
||||
//
|
||||
AcceptButton = button_Ok;
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
CancelButton = button_Cancel;
|
||||
ClientSize = new Size(919, 276);
|
||||
Controls.Add(panel1);
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "ExportPreviewDialog";
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Text = "导出预览图";
|
||||
Load += ExportPreviewDialog_Load;
|
||||
panel1.ResumeLayout(false);
|
||||
panel1.PerformLayout();
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
tableLayoutPanel2.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Width).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Height).EndInit();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Panel panel1;
|
||||
private TableLayoutPanel tableLayoutPanel1;
|
||||
private Label label4;
|
||||
private Label label1;
|
||||
private Label label2;
|
||||
private Label label3;
|
||||
private TextBox textBox_OutputDir;
|
||||
private Button button_SelectOutputDir;
|
||||
private TableLayoutPanel tableLayoutPanel2;
|
||||
private Button button_Ok;
|
||||
private Button button_Cancel;
|
||||
private NumericUpDown numericUpDown_Width;
|
||||
private NumericUpDown numericUpDown_Height;
|
||||
private FolderBrowserDialog folderBrowserDialog;
|
||||
}
|
||||
}
|
||||
80
SpineViewer/Dialogs/ExportPreviewDialog.cs
Normal file
80
SpineViewer/Dialogs/ExportPreviewDialog.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace SpineViewer.Dialogs
|
||||
{
|
||||
public partial class ExportPreviewDialog: Form
|
||||
{
|
||||
public string OutputDir { get; private set; }
|
||||
public uint PreviewWidth { get; private set; }
|
||||
public uint PreviewHeight { get; private set; }
|
||||
|
||||
public ExportPreviewDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ExportPreviewDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
button_SelectOutputDir_Click(sender, e);
|
||||
}
|
||||
|
||||
private void button_SelectOutputDir_Click(object sender, EventArgs e)
|
||||
{
|
||||
folderBrowserDialog.InitialDirectory = textBox_OutputDir.Text;
|
||||
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
textBox_OutputDir.Text = Path.GetFullPath(folderBrowserDialog.SelectedPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void button_Ok_Click(object sender, EventArgs e)
|
||||
{
|
||||
var outputDir = textBox_OutputDir.Text;
|
||||
if (File.Exists(outputDir))
|
||||
{
|
||||
MessageBox.Show("输出文件夹无效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(outputDir))
|
||||
{
|
||||
if (MessageBox.Show($"文件夹 {outputDir} 不存在,是否创建?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(outputDir);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
MessageBox.Show(ex.ToString(), "文件夹创建失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OutputDir = Path.GetFullPath(outputDir);
|
||||
PreviewWidth = (uint)numericUpDown_Width.Value;
|
||||
PreviewHeight = (uint)numericUpDown_Height.Value;
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
private void button_Cancel_Click(object sender, EventArgs e)
|
||||
{
|
||||
DialogResult = DialogResult.Cancel;
|
||||
}
|
||||
}
|
||||
}
|
||||
3270
SpineViewer/Dialogs/ExportPreviewDialog.resx
Normal file
3270
SpineViewer/Dialogs/ExportPreviewDialog.resx
Normal file
File diff suppressed because it is too large
Load Diff
5
SpineViewer/Dialogs/OpenSpineDialog.Designer.cs
generated
5
SpineViewer/Dialogs/OpenSpineDialog.Designer.cs
generated
@@ -232,13 +232,15 @@
|
||||
//
|
||||
openFileDialog_Skel.AddExtension = false;
|
||||
openFileDialog_Skel.AddToRecent = false;
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Title = "选择skel文件";
|
||||
//
|
||||
// openFileDialog_Atlas
|
||||
//
|
||||
openFileDialog_Atlas.AddExtension = false;
|
||||
openFileDialog_Atlas.AddToRecent = false;
|
||||
openFileDialog_Atlas.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Atlas.Title = "选择atlas文件";
|
||||
//
|
||||
// OpenSpineDialog
|
||||
//
|
||||
@@ -256,6 +258,7 @@
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Text = "打开骨骼";
|
||||
Load += OpenSpineDialog_Load;
|
||||
panel1.ResumeLayout(false);
|
||||
panel1.PerformLayout();
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
|
||||
@@ -8,22 +8,24 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
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)
|
||||
{
|
||||
button_SelectSkel_Click(sender, e);
|
||||
}
|
||||
|
||||
private void button_SelectSkel_Click(object sender, EventArgs e)
|
||||
@@ -48,6 +50,7 @@ namespace SpineViewer.Dialogs
|
||||
{
|
||||
var skelPath = textBox_SkelPath.Text;
|
||||
var atlasPath = textBox_AtlasPath.Text;
|
||||
var version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||
|
||||
if (!File.Exists(skelPath))
|
||||
{
|
||||
@@ -73,10 +76,13 @@ namespace SpineViewer.Dialogs
|
||||
atlasPath = Path.GetFullPath(atlasPath);
|
||||
}
|
||||
|
||||
SkelPath = skelPath;
|
||||
AtlasPath = atlasPath;
|
||||
Version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
||||
{
|
||||
MessageBox.Show($"{version.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
Result = new(version, skelPath, atlasPath);
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
@@ -85,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,10 +118,10 @@
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="openFileDialog_Skel.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>75, 24</value>
|
||||
<value>58, 25</value>
|
||||
</metadata>
|
||||
<metadata name="openFileDialog_Atlas.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>317, 22</value>
|
||||
<value>349, 29</value>
|
||||
</metadata>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
|
||||
93
SpineViewer/MainForm.Designer.cs
generated
93
SpineViewer/MainForm.Designer.cs
generated
@@ -36,10 +36,15 @@
|
||||
toolStripMenuItem_BatchOpen = new ToolStripMenuItem();
|
||||
toolStripSeparator1 = new ToolStripSeparator();
|
||||
toolStripMenuItem_Export = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ExportPreview = new ToolStripMenuItem();
|
||||
toolStripSeparator2 = new ToolStripSeparator();
|
||||
toolStripMenuItem_Exit = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Function = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ResetAnimation = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Tool = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ConvertFileFormat = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Download = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ManageResource = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Help = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Diagnostics = new ToolStripMenuItem();
|
||||
toolStripSeparator3 = new ToolStripSeparator();
|
||||
@@ -87,16 +92,16 @@
|
||||
//
|
||||
menuStrip.BackColor = SystemColors.Control;
|
||||
menuStrip.ImageScalingSize = new Size(24, 24);
|
||||
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_File, toolStripMenuItem_Function, toolStripMenuItem_Help });
|
||||
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 = "菜单";
|
||||
//
|
||||
// toolStripMenuItem_File
|
||||
//
|
||||
toolStripMenuItem_File.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Open, toolStripMenuItem_BatchOpen, toolStripSeparator1, toolStripMenuItem_Export, toolStripSeparator2, toolStripMenuItem_Exit });
|
||||
toolStripMenuItem_File.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Open, toolStripMenuItem_BatchOpen, toolStripSeparator1, toolStripMenuItem_Export, toolStripMenuItem_ExportPreview, toolStripSeparator2, toolStripMenuItem_Exit });
|
||||
toolStripMenuItem_File.Name = "toolStripMenuItem_File";
|
||||
toolStripMenuItem_File.Size = new Size(84, 28);
|
||||
toolStripMenuItem_File.Text = "文件(&F)";
|
||||
@@ -129,6 +134,13 @@
|
||||
toolStripMenuItem_Export.Text = "导出(&E)...";
|
||||
toolStripMenuItem_Export.Click += toolStripMenuItem_Export_Click;
|
||||
//
|
||||
// toolStripMenuItem_ExportPreview
|
||||
//
|
||||
toolStripMenuItem_ExportPreview.Name = "toolStripMenuItem_ExportPreview";
|
||||
toolStripMenuItem_ExportPreview.Size = new Size(254, 34);
|
||||
toolStripMenuItem_ExportPreview.Text = "导出预览图(&P)...";
|
||||
toolStripMenuItem_ExportPreview.Click += toolStripMenuItem_ExportPreview_Click;
|
||||
//
|
||||
// toolStripSeparator2
|
||||
//
|
||||
toolStripSeparator2.Name = "toolStripSeparator2";
|
||||
@@ -146,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
|
||||
//
|
||||
@@ -156,6 +168,34 @@
|
||||
toolStripMenuItem_ResetAnimation.Text = "重置动画时间(&R)";
|
||||
toolStripMenuItem_ResetAnimation.Click += toolStripMenuItem_ResetAnimation_Click;
|
||||
//
|
||||
// toolStripMenuItem_Tool
|
||||
//
|
||||
toolStripMenuItem_Tool.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ConvertFileFormat });
|
||||
toolStripMenuItem_Tool.Name = "toolStripMenuItem_Tool";
|
||||
toolStripMenuItem_Tool.Size = new Size(84, 28);
|
||||
toolStripMenuItem_Tool.Text = "工具(&T)";
|
||||
//
|
||||
// toolStripMenuItem_ConvertFileFormat
|
||||
//
|
||||
toolStripMenuItem_ConvertFileFormat.Name = "toolStripMenuItem_ConvertFileFormat";
|
||||
toolStripMenuItem_ConvertFileFormat.Size = new Size(254, 34);
|
||||
toolStripMenuItem_ConvertFileFormat.Text = "转换文件格式(&C)...";
|
||||
toolStripMenuItem_ConvertFileFormat.Click += toolStripMenuItem_ConvertFileFormat_Click;
|
||||
//
|
||||
// toolStripMenuItem_Download
|
||||
//
|
||||
toolStripMenuItem_Download.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ManageResource });
|
||||
toolStripMenuItem_Download.Name = "toolStripMenuItem_Download";
|
||||
toolStripMenuItem_Download.Size = new Size(88, 28);
|
||||
toolStripMenuItem_Download.Text = "下载(&D)";
|
||||
//
|
||||
// toolStripMenuItem_ManageResource
|
||||
//
|
||||
toolStripMenuItem_ManageResource.Name = "toolStripMenuItem_ManageResource";
|
||||
toolStripMenuItem_ManageResource.Size = new Size(260, 34);
|
||||
toolStripMenuItem_ManageResource.Text = "管理下载资源(&M)...";
|
||||
toolStripMenuItem_ManageResource.Click += toolStripMenuItem_ManageResource_Click;
|
||||
//
|
||||
// toolStripMenuItem_Help
|
||||
//
|
||||
toolStripMenuItem_Help.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Diagnostics, toolStripSeparator3, toolStripMenuItem_About });
|
||||
@@ -192,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;
|
||||
@@ -214,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;
|
||||
@@ -237,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;
|
||||
@@ -260,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 = 327;
|
||||
splitContainer_Information.Size = new Size(747, 879);
|
||||
splitContainer_Information.SplitterDistance = 399;
|
||||
splitContainer_Information.TabIndex = 1;
|
||||
splitContainer_Information.TabStop = false;
|
||||
splitContainer_Information.SplitterMoved += splitContainer_SplitterMoved;
|
||||
@@ -273,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(327, 848);
|
||||
groupBox_SkelList.Size = new Size(399, 879);
|
||||
groupBox_SkelList.TabIndex = 0;
|
||||
groupBox_SkelList.TabStop = false;
|
||||
groupBox_SkelList.Text = "模型列表";
|
||||
@@ -284,7 +324,7 @@
|
||||
spineListView.Location = new Point(3, 26);
|
||||
spineListView.Name = "spineListView";
|
||||
spineListView.PropertyGrid = propertyGrid_Spine;
|
||||
spineListView.Size = new Size(321, 819);
|
||||
spineListView.Size = new Size(393, 850);
|
||||
spineListView.TabIndex = 0;
|
||||
//
|
||||
// propertyGrid_Spine
|
||||
@@ -293,7 +333,7 @@
|
||||
propertyGrid_Spine.HelpVisible = false;
|
||||
propertyGrid_Spine.Location = new Point(3, 26);
|
||||
propertyGrid_Spine.Name = "propertyGrid_Spine";
|
||||
propertyGrid_Spine.Size = new Size(407, 470);
|
||||
propertyGrid_Spine.Size = new Size(338, 485);
|
||||
propertyGrid_Spine.TabIndex = 0;
|
||||
propertyGrid_Spine.ToolbarVisible = false;
|
||||
propertyGrid_Spine.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
||||
@@ -315,8 +355,8 @@
|
||||
//
|
||||
splitContainer_Config.Panel2.Controls.Add(groupBox_PreviewConfig);
|
||||
splitContainer_Config.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_Config.Size = new Size(413, 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;
|
||||
@@ -328,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(413, 499);
|
||||
groupBox_SkelConfig.Size = new Size(344, 514);
|
||||
groupBox_SkelConfig.TabIndex = 0;
|
||||
groupBox_SkelConfig.TabStop = false;
|
||||
groupBox_SkelConfig.Text = "模型参数";
|
||||
@@ -339,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(413, 345);
|
||||
groupBox_PreviewConfig.Size = new Size(344, 361);
|
||||
groupBox_PreviewConfig.TabIndex = 1;
|
||||
groupBox_PreviewConfig.TabStop = false;
|
||||
groupBox_PreviewConfig.Text = "画面参数";
|
||||
@@ -350,7 +390,7 @@
|
||||
propertyGrid_Previewer.HelpVisible = false;
|
||||
propertyGrid_Previewer.Location = new Point(3, 26);
|
||||
propertyGrid_Previewer.Name = "propertyGrid_Previewer";
|
||||
propertyGrid_Previewer.Size = new Size(407, 316);
|
||||
propertyGrid_Previewer.Size = new Size(338, 332);
|
||||
propertyGrid_Previewer.TabIndex = 1;
|
||||
propertyGrid_Previewer.ToolbarVisible = false;
|
||||
propertyGrid_Previewer.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
||||
@@ -361,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 = "预览画面";
|
||||
@@ -373,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;
|
||||
@@ -385,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
|
||||
@@ -396,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");
|
||||
@@ -465,5 +505,10 @@
|
||||
private ToolStripMenuItem toolStripMenuItem_ResetAnimation;
|
||||
private ToolStripMenuItem toolStripMenuItem_Diagnostics;
|
||||
private ToolStripSeparator toolStripSeparator3;
|
||||
private ToolStripMenuItem toolStripMenuItem_Download;
|
||||
private ToolStripMenuItem toolStripMenuItem_ManageResource;
|
||||
private ToolStripMenuItem toolStripMenuItem_Tool;
|
||||
private ToolStripMenuItem toolStripMenuItem_ConvertFileFormat;
|
||||
private ToolStripMenuItem toolStripMenuItem_ExportPreview;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using SpineViewer.Spine;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace SpineViewer
|
||||
{
|
||||
@@ -14,11 +16,11 @@ namespace SpineViewer
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־<EFBFBD><EFBFBD>
|
||||
/// 初始化窗口日志器
|
||||
/// </summary>
|
||||
private void InitializeLogConfiguration()
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־
|
||||
// 窗口日志
|
||||
var rtbTarget = new NLog.Windows.Forms.RichTextBoxTarget
|
||||
{
|
||||
Name = "rtbTarget",
|
||||
@@ -41,68 +43,6 @@ namespace SpineViewer
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
|
||||
private void ExportPng_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var arguments = e.Argument as Dialogs.ExportPngDialog;
|
||||
var outputDir = arguments.OutputDir;
|
||||
var duration = arguments.Duration;
|
||||
var fps = arguments.Fps;
|
||||
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
||||
|
||||
var resolution = spinePreviewer.Resolution;
|
||||
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
||||
tex.SetView(spinePreviewer.View);
|
||||
var delta = 1f / fps;
|
||||
var frameCount = 1 + (int)(duration / delta); // <20><>֡<EFBFBD><D6A1>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>
|
||||
|
||||
spinePreviewer.StopPreview();
|
||||
|
||||
lock (spineListView.Spines)
|
||||
{
|
||||
var spinesReverse = spineListView.Spines.Reverse();
|
||||
|
||||
// <20><><EFBFBD>ö<EFBFBD><C3B6><EFBFBD>ʱ<EFBFBD><CAB1>
|
||||
foreach (var spine in spinesReverse)
|
||||
spine.CurrentAnimation = spine.CurrentAnimation;
|
||||
|
||||
Program.Logger.Info(
|
||||
"Begin exporting png frames to output dir {}, duration: {}, fps: {}, totally {} spines",
|
||||
[outputDir, duration, fps, spinesReverse.Count()]
|
||||
);
|
||||
|
||||
// <20><>֡<EFBFBD><D6A1><EFBFBD><EFBFBD>
|
||||
var success = 0;
|
||||
worker.ReportProgress(0, $"<22>Ѵ<EFBFBD><D1B4><EFBFBD> 0/{frameCount}");
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
break;
|
||||
|
||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||
|
||||
foreach (var spine in spinesReverse)
|
||||
{
|
||||
tex.Draw(spine);
|
||||
spine.Update(delta);
|
||||
}
|
||||
|
||||
tex.Display();
|
||||
using (var img = tex.Texture.CopyToImage())
|
||||
{
|
||||
img.SaveToFile(Path.Combine(outputDir, $"{timestamp}_{fps}_{frameIndex:d6}.png"));
|
||||
}
|
||||
|
||||
success++;
|
||||
worker.ReportProgress((int)((frameIndex + 1) * 100.0) / frameCount, $"<22>Ѵ<EFBFBD><D1B4><EFBFBD> {frameIndex + 1}/{frameCount}");
|
||||
}
|
||||
|
||||
Program.Logger.Info("Exporting done: {}/{}", success, frameCount);
|
||||
}
|
||||
|
||||
spinePreviewer.StartPreview();
|
||||
}
|
||||
|
||||
private void MainForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
spinePreviewer.StartPreview();
|
||||
@@ -129,7 +69,7 @@ namespace SpineViewer
|
||||
{
|
||||
if (spineListView.Spines.Count <= 0)
|
||||
{
|
||||
MessageBox.Show("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ٴ<EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>", "<EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD>Ϣ", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -144,6 +84,27 @@ namespace SpineViewer
|
||||
progressDialog.ShowDialog();
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_ExportPreview_Click(object sender, EventArgs e)
|
||||
{
|
||||
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)
|
||||
{
|
||||
Close();
|
||||
@@ -158,6 +119,23 @@ namespace SpineViewer
|
||||
}
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_ConvertFileFormat_Click(object sender, EventArgs e)
|
||||
{
|
||||
var openDialog = new Dialogs.ConvertFileFormatDialog();
|
||||
if (openDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var progressDialog = new Dialogs.ProgressDialog();
|
||||
progressDialog.DoWork += ConvertFileFormat_Work;
|
||||
progressDialog.RunWorkerAsync(openDialog);
|
||||
progressDialog.ShowDialog();
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_ManageResource_Click(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
||||
{
|
||||
(new Dialogs.AboutDialog()).ShowDialog();
|
||||
@@ -175,5 +153,188 @@ namespace SpineViewer
|
||||
private void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e) { (sender as PropertyGrid)?.Refresh(); }
|
||||
|
||||
private void spinePreviewer_MouseUp(object sender, MouseEventArgs e) { propertyGrid_Spine.Refresh(); }
|
||||
|
||||
private void ExportPng_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var arguments = e.Argument as Dialogs.ExportPngDialog;
|
||||
var outputDir = arguments.OutputDir;
|
||||
var duration = arguments.Duration;
|
||||
var fps = arguments.Fps;
|
||||
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
||||
|
||||
var resolution = spinePreviewer.Resolution;
|
||||
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
||||
tex.SetView(spinePreviewer.View);
|
||||
var delta = 1f / fps;
|
||||
var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
|
||||
|
||||
spinePreviewer.StopPreview();
|
||||
|
||||
lock (spineListView.Spines)
|
||||
{
|
||||
var spinesReverse = spineListView.Spines.Reverse();
|
||||
|
||||
// 重置动画时间
|
||||
foreach (var spine in spinesReverse)
|
||||
spine.CurrentAnimation = spine.CurrentAnimation;
|
||||
|
||||
Program.Logger.Info(
|
||||
"Begin exporting png frames to output dir {}, duration: {}, fps: {}, totally {} spines",
|
||||
[outputDir, duration, fps, spinesReverse.Count()]
|
||||
);
|
||||
|
||||
// 逐帧导出
|
||||
var success = 0;
|
||||
worker.ReportProgress(0, $"已处理 0/{frameCount}");
|
||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
break;
|
||||
|
||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||
|
||||
foreach (var spine in spinesReverse)
|
||||
{
|
||||
tex.Draw(spine);
|
||||
spine.Update(delta);
|
||||
}
|
||||
|
||||
tex.Display();
|
||||
using (var img = tex.Texture.CopyToImage())
|
||||
{
|
||||
img.SaveToFile(Path.Combine(outputDir, $"{timestamp}_{fps}_{frameIndex:d6}.png"));
|
||||
}
|
||||
|
||||
success++;
|
||||
worker.ReportProgress((int)((frameIndex + 1) * 100.0) / frameCount, $"已处理 {frameIndex + 1}/{frameCount}");
|
||||
}
|
||||
|
||||
Program.Logger.Info("Exporting done: {}/{}", success, frameCount);
|
||||
}
|
||||
|
||||
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;
|
||||
var arguments = e.Argument as Dialogs.ConvertFileFormatDialog;
|
||||
var skelPaths = arguments.SkelPaths;
|
||||
var srcVersion = arguments.SourceVersion;
|
||||
var tgtVersion = arguments.TargetVersion;
|
||||
var jsonSource = arguments.JsonSource;
|
||||
var jsonTarget = arguments.JsonTarget;
|
||||
var newSuffix = jsonTarget ? ".json" : ".skel";
|
||||
|
||||
if (jsonTarget == jsonSource)
|
||||
{
|
||||
if (tgtVersion == srcVersion)
|
||||
return;
|
||||
else
|
||||
newSuffix += $".{tgtVersion.ToString().ToLower()}"; // TODO: 仅转换版本的情况下考虑文件覆盖问题
|
||||
}
|
||||
|
||||
int totalCount = skelPaths.Length;
|
||||
int success = 0;
|
||||
int error = 0;
|
||||
|
||||
SkeletonConverter srcCvter = SkeletonConverter.New(srcVersion);
|
||||
SkeletonConverter tgtCvter = tgtVersion == srcVersion ? srcCvter : SkeletonConverter.New(tgtVersion);
|
||||
|
||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
{
|
||||
e.Cancel = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var skelPath = skelPaths[i];
|
||||
var newPath = Path.ChangeExtension(skelPath, newSuffix);
|
||||
|
||||
try
|
||||
{
|
||||
var root = jsonSource ? srcCvter.ReadJson(skelPath) : srcCvter.ReadBinary(skelPath);
|
||||
if (tgtVersion != srcVersion) root = srcCvter.ToVersion(root, tgtVersion);
|
||||
if (jsonTarget) tgtCvter.WriteJson(root, newPath); else tgtCvter.WriteBinary(root, newPath);
|
||||
success++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Program.Logger.Error(ex.ToString());
|
||||
Program.Logger.Error("Failed to convert {}", skelPath);
|
||||
error++;
|
||||
}
|
||||
|
||||
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
||||
}
|
||||
|
||||
if (error > 0)
|
||||
{
|
||||
Program.Logger.Warn("Batch convert {} successfully, {} failed", success, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Program.Logger.Info("{} skel converted successfully", success);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using NLog;
|
||||
using NLog;
|
||||
using System.Diagnostics;
|
||||
|
||||
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();
|
||||
|
||||
@@ -28,18 +30,18 @@ namespace SpineViewer
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Fatal(ex.ToString());
|
||||
MessageBox.Show(ex.ToString(), "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѱ<EFBFBD><EFBFBD><EFBFBD>", MessageBoxButtons.OK, MessageBoxIcon.Stop);
|
||||
MessageBox.Show(ex.ToString(), "程序已崩溃", MessageBoxButtons.OK, MessageBoxIcon.Stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>־<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 初始化日志配置
|
||||
/// </summary>
|
||||
private static void InitializeLogConfiguration()
|
||||
{
|
||||
var config = new NLog.Config.LoggingConfiguration();
|
||||
|
||||
// <EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>־
|
||||
// 文件日志
|
||||
var fileTarget = new NLog.Targets.FileTarget("fileTarget")
|
||||
{
|
||||
Encoding = System.Text.Encoding.UTF8,
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace SpineViewer.Spine
|
||||
/// <summary>
|
||||
/// SFML 混合模式
|
||||
/// </summary>
|
||||
public static class BlendMode
|
||||
public static class BlendModeSFML
|
||||
{
|
||||
/// <summary>
|
||||
/// Alpha Blend
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,11 +8,13 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime21;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V21)]
|
||||
internal class Spine21 : Spine
|
||||
internal class Spine21 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private class TextureLoader : SpineRuntime21.TextureLoader
|
||||
{
|
||||
public void Load(AtlasPage page, string path)
|
||||
@@ -69,7 +71,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +90,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -170,8 +174,15 @@ namespace SpineViewer.Spine.Implementations
|
||||
|
||||
public override string CurrentAnimation
|
||||
{
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||
set
|
||||
{
|
||||
if (value == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(value))
|
||||
animationState.SetAnimation(0, value, true);
|
||||
Update(0);
|
||||
}
|
||||
}
|
||||
|
||||
public override RectangleF Bounds
|
||||
@@ -234,14 +245,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
//private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime21.BlendMode spineBlendMode)
|
||||
//private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
//{
|
||||
// return spineBlendMode switch
|
||||
// {
|
||||
// SpineRuntime21.BlendMode.Normal => BlendMode.Normal,
|
||||
// SpineRuntime21.BlendMode.Additive => BlendMode.Additive,
|
||||
// SpineRuntime21.BlendMode.Multiply => BlendMode.Multiply,
|
||||
// SpineRuntime21.BlendMode.Screen => BlendMode.Screen,
|
||||
// BlendMode.Normal => BlendMode.Normal,
|
||||
// BlendMode.Additive => BlendMode.Additive,
|
||||
// BlendMode.Multiply => BlendMode.Multiply,
|
||||
// BlendMode.Screen => BlendMode.Screen,
|
||||
// _ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
// };
|
||||
//}
|
||||
@@ -311,14 +322,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
}
|
||||
|
||||
// 似乎 2.1.x 也没有 BlendMode
|
||||
SFML.Graphics.BlendMode blendMode = slot.Data.AdditiveBlending ? BlendMode.Additive : BlendMode.Normal;
|
||||
SFML.Graphics.BlendMode blendMode = slot.Data.AdditiveBlending ? BlendModeSFML.Additive : BlendModeSFML.Normal;
|
||||
|
||||
states.Texture ??= texture;
|
||||
if (states.BlendMode != blendMode || states.Texture != texture)
|
||||
{
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -363,7 +374,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
//clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -8,11 +8,13 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime36;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V36)]
|
||||
internal class Spine36 : Spine
|
||||
internal class Spine36 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private class TextureLoader : SpineRuntime36.TextureLoader
|
||||
{
|
||||
public void Load(AtlasPage page, string path)
|
||||
@@ -68,7 +70,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +89,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -168,8 +172,15 @@ namespace SpineViewer.Spine.Implementations
|
||||
|
||||
public override string CurrentAnimation
|
||||
{
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||
set
|
||||
{
|
||||
if (value == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(value))
|
||||
animationState.SetAnimation(0, value, true);
|
||||
Update(0);
|
||||
}
|
||||
}
|
||||
|
||||
public override RectangleF Bounds
|
||||
@@ -192,14 +203,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime36.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime36.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime36.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime36.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime36.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -274,7 +285,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
{
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -319,7 +330,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -5,11 +5,13 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime37;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V37)]
|
||||
internal class Spine37 : Spine
|
||||
internal class Spine37 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private class TextureLoader : SpineRuntime37.TextureLoader
|
||||
{
|
||||
public void Load(AtlasPage page, string path)
|
||||
@@ -66,7 +68,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +88,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -175,8 +179,15 @@ namespace SpineViewer.Spine.Implementations
|
||||
|
||||
public override string CurrentAnimation
|
||||
{
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||
set
|
||||
{
|
||||
if (value == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(value))
|
||||
animationState.SetAnimation(0, value, true);
|
||||
Update(0);
|
||||
}
|
||||
}
|
||||
|
||||
public override RectangleF Bounds
|
||||
@@ -199,14 +210,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime37.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime37.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime37.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime37.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime37.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -282,7 +293,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -327,7 +338,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -7,13 +7,14 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime38;
|
||||
using SpineRuntime38.Attachments;
|
||||
using SpineViewer.Spine;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V38)]
|
||||
internal class Spine38 : Spine
|
||||
internal class Spine38 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private class TextureLoader : SpineRuntime38.TextureLoader
|
||||
{
|
||||
public void Load(AtlasPage page, string path)
|
||||
@@ -70,7 +71,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +91,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -179,8 +182,15 @@ namespace SpineViewer.Spine.Implementations
|
||||
|
||||
public override string CurrentAnimation
|
||||
{
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||
set
|
||||
{
|
||||
if (value == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(value))
|
||||
animationState.SetAnimation(0, value, true);
|
||||
Update(0);
|
||||
}
|
||||
}
|
||||
|
||||
public override RectangleF Bounds
|
||||
@@ -203,14 +213,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime38.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime38.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime38.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime38.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime38.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -286,7 +296,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -331,7 +341,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -7,11 +7,13 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime40;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V40)]
|
||||
internal class Spine40 : Spine
|
||||
internal class Spine40 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private class TextureLoader : SpineRuntime40.TextureLoader
|
||||
{
|
||||
public void Load(AtlasPage page, string path)
|
||||
@@ -68,7 +70,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +90,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -177,8 +181,15 @@ namespace SpineViewer.Spine.Implementations
|
||||
|
||||
public override string CurrentAnimation
|
||||
{
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||
set
|
||||
{
|
||||
if (value == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(value))
|
||||
animationState.SetAnimation(0, value, true);
|
||||
Update(0);
|
||||
}
|
||||
}
|
||||
|
||||
public override RectangleF Bounds
|
||||
@@ -201,14 +212,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime40.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime40.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime40.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime40.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime40.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -284,7 +295,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -329,7 +340,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -7,11 +7,13 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime41;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V41)]
|
||||
internal class Spine41 : Spine
|
||||
internal class Spine41 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private class TextureLoader : SpineRuntime41.TextureLoader
|
||||
{
|
||||
public void Load(AtlasPage page, string path)
|
||||
@@ -68,7 +70,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +90,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -177,8 +181,15 @@ namespace SpineViewer.Spine.Implementations
|
||||
|
||||
public override string CurrentAnimation
|
||||
{
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||
set
|
||||
{
|
||||
if (value == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(value))
|
||||
animationState.SetAnimation(0, value, true);
|
||||
Update(0);
|
||||
}
|
||||
}
|
||||
|
||||
public override RectangleF Bounds
|
||||
@@ -201,14 +212,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime41.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime41.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime41.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime41.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime41.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -284,7 +295,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -329,7 +340,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -7,11 +7,13 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime42;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations
|
||||
namespace SpineViewer.Spine.Implementations.Spine
|
||||
{
|
||||
[SpineImplementation(Version.V42)]
|
||||
internal class Spine42 : Spine
|
||||
internal class Spine42 : SpineViewer.Spine.Spine
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private class TextureLoader : SpineRuntime42.TextureLoader
|
||||
{
|
||||
public void Load(AtlasPage page, string path)
|
||||
@@ -68,7 +70,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
catch
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new ArgumentException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +90,8 @@ namespace SpineViewer.Spine.Implementations
|
||||
atlas.Dispose();
|
||||
}
|
||||
|
||||
public override string FileVersion { get => skeletonData.Version; }
|
||||
|
||||
public override float Scale
|
||||
{
|
||||
get
|
||||
@@ -177,8 +181,15 @@ namespace SpineViewer.Spine.Implementations
|
||||
|
||||
public override string CurrentAnimation
|
||||
{
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? DefaultAnimationName;
|
||||
set { if (animationNames.Contains(value)) { animationState.SetAnimation(0, value, true); Update(0); } }
|
||||
get => animationState.GetCurrent(0)?.Animation.Name ?? EMPTY_ANIMATION;
|
||||
set
|
||||
{
|
||||
if (value == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(0, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(value))
|
||||
animationState.SetAnimation(0, value, true);
|
||||
Update(0);
|
||||
}
|
||||
}
|
||||
|
||||
public override RectangleF Bounds
|
||||
@@ -201,14 +212,14 @@ namespace SpineViewer.Spine.Implementations
|
||||
skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(SpineRuntime42.BlendMode spineBlendMode)
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
SpineRuntime42.BlendMode.Normal => BlendMode.Normal,
|
||||
SpineRuntime42.BlendMode.Additive => BlendMode.Additive,
|
||||
SpineRuntime42.BlendMode.Multiply => BlendMode.Multiply,
|
||||
SpineRuntime42.BlendMode.Screen => BlendMode.Screen,
|
||||
BlendMode.Normal => BlendModeSFML.Normal,
|
||||
BlendMode.Additive => BlendModeSFML.Additive,
|
||||
BlendMode.Multiply => BlendModeSFML.Multiply,
|
||||
BlendMode.Screen => BlendModeSFML.Screen,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
@@ -284,7 +295,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
if (vertexArray.VertexCount > 0)
|
||||
{
|
||||
// XXX: 实测不用设置 sampler2D 的值也正确
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
@@ -329,7 +340,7 @@ namespace SpineViewer.Spine.Implementations
|
||||
clipping.ClipEnd(slot);
|
||||
}
|
||||
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendMode.Normal || states.BlendMode == BlendMode.Additive))
|
||||
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
|
||||
states.Shader = FragmentShader;
|
||||
else
|
||||
states.Shader = null;
|
||||
339
SpineViewer/Spine/SkeletonConverter.cs
Normal file
339
SpineViewer/Spine/SkeletonConverter.cs
Normal file
@@ -0,0 +1,339 @@
|
||||
using Microsoft.VisualBasic;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace SpineViewer.Spine
|
||||
{
|
||||
/// <summary>
|
||||
/// SkeletonConverter 实现类标记
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class SkeletonConverterImplementationAttribute : Attribute
|
||||
{
|
||||
public Version Version { get; }
|
||||
|
||||
public SkeletonConverterImplementationAttribute(Version version)
|
||||
{
|
||||
Version = version;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SkeletonConverter 基类, 使用静态方法 New 来创建具体版本对象
|
||||
/// </summary>
|
||||
public abstract class SkeletonConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// 实现类缓存
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Version, Type> ImplementationTypes = [];
|
||||
public static readonly Dictionary<Version, Type>.KeyCollection ImplementedVersions;
|
||||
|
||||
/// <summary>
|
||||
/// 静态构造函数
|
||||
/// </summary>
|
||||
static SkeletonConverter()
|
||||
{
|
||||
// 遍历并缓存标记了 SkeletonConverterImplementationAttribute 的类型
|
||||
var impTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => typeof(SkeletonConverter).IsAssignableFrom(t) && !t.IsAbstract);
|
||||
foreach (var type in impTypes)
|
||||
{
|
||||
var attr = type.GetCustomAttribute<SkeletonConverterImplementationAttribute>();
|
||||
if (attr is not null)
|
||||
{
|
||||
if (ImplementationTypes.ContainsKey(attr.Version))
|
||||
throw new InvalidOperationException($"Multiple implementations found: {attr.Version}");
|
||||
ImplementationTypes[attr.Version] = type;
|
||||
}
|
||||
}
|
||||
Program.Logger.Debug("Find SkeletonConverter implementations: [{}]", string.Join(", ", ImplementationTypes.Keys));
|
||||
ImplementedVersions = ImplementationTypes.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建特定版本的 SkeletonConverter
|
||||
/// </summary>
|
||||
public static SkeletonConverter New(Version version)
|
||||
{
|
||||
if (!ImplementationTypes.TryGetValue(version, out var cvterType))
|
||||
{
|
||||
throw new NotImplementedException($"Not implemented version: {version}");
|
||||
}
|
||||
return (SkeletonConverter)Activator.CreateInstance(cvterType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json 格式控制
|
||||
/// </summary>
|
||||
private static readonly JsonWriterOptions jsonWriterOptions = new()
|
||||
{
|
||||
Indented = true,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 读取二进制骨骼文件并构造 Json 对象
|
||||
/// </summary>
|
||||
public abstract JsonObject ReadBinary(string binPath);
|
||||
|
||||
/// <summary>
|
||||
/// 将 Json 对象写入二进制骨骼文件
|
||||
/// </summary>
|
||||
public abstract void WriteBinary(JsonObject root, string binPath, bool nonessential = false);
|
||||
|
||||
/// <summary>
|
||||
/// 读取 Json 对象
|
||||
/// </summary>
|
||||
public virtual JsonObject ReadJson(string jsonPath)
|
||||
{
|
||||
using var input = File.OpenRead(jsonPath);
|
||||
if (JsonNode.Parse(input) is JsonObject root)
|
||||
return root;
|
||||
else
|
||||
throw new InvalidDataException($"{jsonPath} is not a valid json object");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入 Json 对象
|
||||
/// </summary>
|
||||
public virtual void WriteJson(JsonObject root, string jsonPath)
|
||||
{
|
||||
using var output = File.Create(jsonPath);
|
||||
using var writer = new Utf8JsonWriter(output, jsonWriterOptions);
|
||||
root.WriteTo(writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换到目标版本
|
||||
/// </summary>
|
||||
public abstract JsonObject ToVersion(JsonObject root, Version version);
|
||||
|
||||
/// <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 BinaryReader(Stream input) { this.input = input; }
|
||||
public int Read()
|
||||
{
|
||||
int val = input.ReadByte();
|
||||
if (val == -1) throw new EndOfStreamException();
|
||||
return val;
|
||||
}
|
||||
public byte ReadByte() => (byte)Read();
|
||||
public byte ReadUByte() => (byte)Read();
|
||||
public sbyte ReadSByte() => (sbyte)ReadByte();
|
||||
public bool ReadBoolean() => Read() != 0;
|
||||
public float ReadFloat()
|
||||
{
|
||||
if (input.Read(bytesBigEndian, 0, 4) < 4) throw new EndOfStreamException();
|
||||
buffer[3] = bytesBigEndian[0];
|
||||
buffer[2] = bytesBigEndian[1];
|
||||
buffer[1] = bytesBigEndian[2];
|
||||
buffer[0] = bytesBigEndian[3];
|
||||
return BitConverter.ToSingle(buffer, 0);
|
||||
}
|
||||
public int ReadInt()
|
||||
{
|
||||
if (input.Read(bytesBigEndian, 0, 4) < 4) throw new EndOfStreamException();
|
||||
return (bytesBigEndian[0] << 24)
|
||||
| (bytesBigEndian[1] << 16)
|
||||
| (bytesBigEndian[2] << 8)
|
||||
| bytesBigEndian[3];
|
||||
}
|
||||
public long ReadLong()
|
||||
{
|
||||
if (input.Read(bytesBigEndian, 0, 8) < 8) throw new EndOfStreamException();
|
||||
return ((long)(bytesBigEndian[0]) << 56)
|
||||
| ((long)(bytesBigEndian[1]) << 48)
|
||||
| ((long)(bytesBigEndian[2]) << 40)
|
||||
| ((long)(bytesBigEndian[3]) << 32)
|
||||
| ((long)(bytesBigEndian[4]) << 24)
|
||||
| ((long)(bytesBigEndian[5]) << 16)
|
||||
| ((long)(bytesBigEndian[6]) << 8)
|
||||
| (long)(bytesBigEndian[7]);
|
||||
}
|
||||
public int ReadVarInt(bool optimizePositive = true)
|
||||
{
|
||||
byte b = ReadByte();
|
||||
int val = b & 0x7F;
|
||||
if ((b & 0x80) != 0)
|
||||
{
|
||||
b = ReadByte();
|
||||
val |= (b & 0x7F) << 7;
|
||||
if ((b & 0x80) != 0)
|
||||
{
|
||||
b = ReadByte();
|
||||
val |= (b & 0x7F) << 14;
|
||||
if ((b & 0x80) != 0)
|
||||
{
|
||||
b = ReadByte();
|
||||
val |= (b & 0x7F) << 21;
|
||||
if ((b & 0x80) != 0)
|
||||
val |= (ReadByte() & 0x7F) << 28;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 最低位是符号, 根据符号得到全 1 或全 0
|
||||
// 无符号右移, 符号按原样设置在最高位, 其他位与符号异或
|
||||
return optimizePositive ? val : (val >>> 1) ^ -(val & 1);
|
||||
}
|
||||
public string ReadString()
|
||||
{
|
||||
int byteCount = ReadVarInt();
|
||||
switch (byteCount)
|
||||
{
|
||||
case 0: return null;
|
||||
case 1: return "";
|
||||
}
|
||||
byteCount--;
|
||||
if (buffer.Length < byteCount) buffer = new byte[byteCount];
|
||||
ReadFully(buffer, 0, byteCount);
|
||||
return System.Text.Encoding.UTF8.GetString(buffer, 0, byteCount);
|
||||
}
|
||||
public string ReadStringRef()
|
||||
{
|
||||
int index = ReadVarInt();
|
||||
return index == 0 ? null : StringTable[index - 1];
|
||||
}
|
||||
public void ReadFully(byte[] buffer, int offset, int length)
|
||||
{
|
||||
while (length > 0)
|
||||
{
|
||||
int count = input.Read(buffer, offset, length);
|
||||
if (count <= 0) throw new EndOfStreamException();
|
||||
offset += count;
|
||||
length -= count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 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);
|
||||
public void WriteSByte(sbyte val) => output.WriteByte((byte)val);
|
||||
public void WriteBoolean(bool val) => output.WriteByte((byte)(val ? 1 : 0));
|
||||
public void WriteFloat(float val)
|
||||
{
|
||||
uint v = BitConverter.SingleToUInt32Bits(val);
|
||||
bytesBigEndian[0] = (byte)(v >> 24);
|
||||
bytesBigEndian[1] = (byte)(v >> 16);
|
||||
bytesBigEndian[2] = (byte)(v >> 8);
|
||||
bytesBigEndian[3] = (byte)v;
|
||||
output.Write(bytesBigEndian, 0, 4);
|
||||
}
|
||||
public void WriteInt(int val)
|
||||
{
|
||||
bytesBigEndian[0] = (byte)(val >> 24);
|
||||
bytesBigEndian[1] = (byte)(val >> 16);
|
||||
bytesBigEndian[2] = (byte)(val >> 8);
|
||||
bytesBigEndian[3] = (byte)val;
|
||||
output.Write(bytesBigEndian, 0, 4);
|
||||
}
|
||||
public void WriteLong(long val)
|
||||
{
|
||||
bytesBigEndian[0] = (byte)(val >> 56);
|
||||
bytesBigEndian[1] = (byte)(val >> 48);
|
||||
bytesBigEndian[2] = (byte)(val >> 40);
|
||||
bytesBigEndian[3] = (byte)(val >> 32);
|
||||
bytesBigEndian[4] = (byte)(val >> 24);
|
||||
bytesBigEndian[5] = (byte)(val >> 16);
|
||||
bytesBigEndian[6] = (byte)(val >> 8);
|
||||
bytesBigEndian[7] = (byte)val;
|
||||
output.Write(bytesBigEndian, 0, 8);
|
||||
}
|
||||
public void WriteVarInt(int val, bool optimizePositive = true)
|
||||
{
|
||||
// 有符号右移, 会变成全 1 或者全 0 符号
|
||||
// 其他位与符号异或, 符号按原样设置在最低位
|
||||
if (!optimizePositive) val = (val << 1) ^ (val >> 31);
|
||||
|
||||
byte b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
val >>>= 7;
|
||||
if (val != 0)
|
||||
{
|
||||
output.WriteByte((byte)(b | 0x80));
|
||||
b = (byte)(val & 0x7F);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output.WriteByte(b);
|
||||
}
|
||||
public void WriteString(string val)
|
||||
{
|
||||
if (val == null)
|
||||
{
|
||||
WriteVarInt(0);
|
||||
return;
|
||||
}
|
||||
if (val.Length == 0)
|
||||
{
|
||||
WriteVarInt(1);
|
||||
return;
|
||||
}
|
||||
int byteCount = System.Text.Encoding.UTF8.GetByteCount(val);
|
||||
WriteVarInt(byteCount + 1);
|
||||
if (buffer.Length < byteCount) buffer = new byte[byteCount];
|
||||
System.Text.Encoding.UTF8.GetBytes(val, 0, val.Length, buffer, 0);
|
||||
WriteFully(buffer, 0, byteCount);
|
||||
}
|
||||
public void WriteStringRef(string val)
|
||||
{
|
||||
if (val is null)
|
||||
{
|
||||
WriteVarInt(0);
|
||||
return;
|
||||
}
|
||||
int index = StringTable.IndexOf(val);
|
||||
if (index < 0)
|
||||
{
|
||||
StringTable.Add(val);
|
||||
index = StringTable.Count - 1;
|
||||
}
|
||||
WriteVarInt(index + 1);
|
||||
}
|
||||
public void WriteFully(byte[] buffer, int offset, int length) => output.Write(buffer, offset, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,11 @@ 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
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Spine 实现类标记
|
||||
/// </summary>
|
||||
@@ -38,10 +38,31 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 常规骨骼文件后缀集合
|
||||
/// </summary>
|
||||
public static readonly ImmutableHashSet<string> CommonSkelSuffix = [".skel", ".json"];
|
||||
|
||||
/// <summary>
|
||||
/// 空动画标记
|
||||
/// </summary>
|
||||
public const string EMPTY_ANIMATION = "<Empty>";
|
||||
|
||||
/// <summary>
|
||||
/// 预览图大小
|
||||
/// </summary>
|
||||
public static readonly Size PREVIEW_SIZE = new(256, 256);
|
||||
|
||||
/// <summary>
|
||||
/// 缩放最小值
|
||||
/// </summary>
|
||||
public const float SCALE_MIN = 0.001f;
|
||||
|
||||
/// <summary>
|
||||
/// 实现类缓存
|
||||
/// </summary>
|
||||
private static readonly Dictionary<Version, Type> ImplementationTypes = [];
|
||||
public static readonly Dictionary<Version, Type>.KeyCollection ImplementedVersions;
|
||||
|
||||
/// <summary>
|
||||
/// 用于解决 PMA 和渐变动画问题的片段着色器
|
||||
@@ -70,10 +91,13 @@ namespace SpineViewer.Spine
|
||||
var attr = type.GetCustomAttribute<SpineImplementationAttribute>();
|
||||
if (attr is not null)
|
||||
{
|
||||
if (ImplementationTypes.ContainsKey(attr.Version))
|
||||
throw new InvalidOperationException($"Multiple implementations found: {attr.Version}");
|
||||
ImplementationTypes[attr.Version] = type;
|
||||
}
|
||||
}
|
||||
Program.Logger.Debug("Find Spine implementations: [{}]", string.Join(", ", ImplementationTypes.Keys));
|
||||
ImplementedVersions = ImplementationTypes.Keys;
|
||||
|
||||
// 加载 FragmentShader
|
||||
try
|
||||
@@ -89,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}");
|
||||
@@ -101,6 +195,11 @@ namespace SpineViewer.Spine
|
||||
return (Spine)Activator.CreateInstance(spineType, skelPath, atlasPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标识符
|
||||
/// </summary>
|
||||
public readonly string ID = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
@@ -111,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);
|
||||
@@ -125,21 +225,21 @@ namespace SpineViewer.Spine
|
||||
|
||||
~Spine() { Dispose(false); }
|
||||
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
|
||||
/// <summary>
|
||||
/// 缩放最小值
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public const float SCALE_MIN = 0.001f;
|
||||
protected virtual void Dispose(bool disposing) { preview?.Dispose(); }
|
||||
|
||||
/// <summary>
|
||||
/// 获取所属版本
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(VersionTypeConverter))]
|
||||
[Category("基本信息"), DisplayName("版本")]
|
||||
[TypeConverter(typeof(VersionConverter))]
|
||||
[Category("基本信息"), DisplayName("运行时版本")]
|
||||
public Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 资源所在完整目录
|
||||
/// </summary>
|
||||
[Category("基本信息"), DisplayName("资源目录")]
|
||||
public string AssetsDir { get; }
|
||||
|
||||
/// <summary>
|
||||
/// skel 文件完整路径
|
||||
/// </summary>
|
||||
@@ -152,9 +252,18 @@ namespace SpineViewer.Spine
|
||||
[Category("基本信息"), DisplayName("atlas文件路径")]
|
||||
public string AtlasPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[Category("基本信息"), DisplayName("名称")]
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取所属文件版本
|
||||
/// </summary>
|
||||
[Category("基本信息"), DisplayName("文件版本")]
|
||||
public abstract string FileVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 缩放比例
|
||||
/// </summary>
|
||||
@@ -164,7 +273,7 @@ namespace SpineViewer.Spine
|
||||
/// <summary>
|
||||
/// 位置
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(PointFTypeConverter))]
|
||||
[TypeConverter(typeof(PointFConverter))]
|
||||
[Category("变换"), DisplayName("位置")]
|
||||
public abstract PointF Position { get; set; }
|
||||
|
||||
@@ -191,7 +300,7 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public ReadOnlyCollection<string> AnimationNames { get => animationNames.AsReadOnly(); }
|
||||
protected List<string> animationNames = [];
|
||||
protected List<string> animationNames = [EMPTY_ANIMATION];
|
||||
|
||||
/// <summary>
|
||||
/// 默认动画名称
|
||||
@@ -202,7 +311,7 @@ namespace SpineViewer.Spine
|
||||
/// <summary>
|
||||
/// 当前动画名称
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(AnimationTypeConverter))]
|
||||
[TypeConverter(typeof(AnimationConverter))]
|
||||
[Category("动画"), DisplayName("当前动画")]
|
||||
public abstract string CurrentAnimation { get; set; }
|
||||
|
||||
@@ -218,6 +327,63 @@ namespace SpineViewer.Spine
|
||||
[Browsable(false)]
|
||||
public abstract RectangleF Bounds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 骨骼预览图
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
public Image Preview
|
||||
{
|
||||
get
|
||||
{
|
||||
if (preview is null)
|
||||
{
|
||||
using var img = GetPreview((uint)PREVIEW_SIZE.Width, (uint)PREVIEW_SIZE.Height);
|
||||
img.SaveToMemory(out var imgBuffer, "bmp");
|
||||
using var stream = new MemoryStream(imgBuffer);
|
||||
preview = new Bitmap(stream);
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
}
|
||||
private Image preview = null;
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定尺寸的预览图
|
||||
/// </summary>
|
||||
public SFML.Graphics.Image GetPreview(uint width, uint height)
|
||||
{
|
||||
var curAnimation = CurrentAnimation;
|
||||
CurrentAnimation = EMPTY_ANIMATION;
|
||||
var bounds = Bounds;
|
||||
|
||||
float viewX = width;
|
||||
float viewY = height;
|
||||
float sizeX = bounds.Width;
|
||||
float sizeY = bounds.Height;
|
||||
|
||||
var scale = 1f;
|
||||
if ((sizeY / sizeX) < (viewY / viewX))
|
||||
scale = sizeX / viewX;// 相同的 X, 视窗 Y 更大
|
||||
else
|
||||
scale = sizeY / viewY;// 相同的 Y, 视窗 X 更大
|
||||
|
||||
viewX *= scale;
|
||||
viewY *= scale;
|
||||
|
||||
// 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);
|
||||
tex.SetView(view);
|
||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||
tex.Draw(this);
|
||||
tex.Display();
|
||||
CurrentAnimation = curAnimation;
|
||||
return tex.Texture.CopyToImage();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取动画时长, 如果动画不存在则返回 0
|
||||
/// </summary>
|
||||
@@ -243,5 +409,29 @@ namespace SpineViewer.Spine
|
||||
/// SFML.Graphics.Drawable 接口实现
|
||||
/// </summary>
|
||||
public abstract void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
|
||||
|
||||
/// <summary>
|
||||
/// 是否被选中
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,23 +9,23 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer.Spine
|
||||
{
|
||||
public class VersionTypeConverter : EnumConverter
|
||||
public class VersionConverter : EnumConverter
|
||||
{
|
||||
public VersionTypeConverter() : base(typeof(Version)) { }
|
||||
public VersionConverter() : base(typeof(Version)) { }
|
||||
|
||||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type? destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string) && value is Version version)
|
||||
{
|
||||
// 调用自定义的 String() 方法
|
||||
return version.String();
|
||||
return version.GetName();
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
|
||||
public class AnimationTypeConverter : StringConverter
|
||||
public class AnimationConverter : StringConverter
|
||||
{
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
|
||||
{
|
||||
@@ -42,11 +42,7 @@ namespace SpineViewer.Spine
|
||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||
{
|
||||
if (context?.Instance is Spine obj)
|
||||
{
|
||||
// 返回 AnimationNames 作为下拉选项
|
||||
return new StandardValuesCollection(obj.AnimationNames);
|
||||
}
|
||||
|
||||
return base.GetStandardValues(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,13 +64,14 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
public enum Version
|
||||
{
|
||||
[Description("v2.1.x")] V21 = 0x0201,
|
||||
[Description("v3.6.x")] V36 = 0x0306,
|
||||
[Description("v3.7.x")] V37 = 0x0307,
|
||||
[Description("v3.8.x")] V38 = 0x0308,
|
||||
[Description("v4.0.x")] V40 = 0x0400,
|
||||
[Description("v4.1.x")] V41 = 0x0401,
|
||||
[Description("v4.2.x")] V42 = 0x0402,
|
||||
[Description("v4.3.x")] V43 = 0x0403,
|
||||
[Description("<Auto>")] Auto = 0x0000,
|
||||
[Description("2.1.x")] V21 = 0x0201,
|
||||
[Description("3.6.x")] V36 = 0x0306,
|
||||
[Description("3.7.x")] V37 = 0x0307,
|
||||
[Description("3.8.x")] V38 = 0x0308,
|
||||
[Description("4.0.x")] V40 = 0x0400,
|
||||
[Description("4.1.x")] V41 = 0x0401,
|
||||
[Description("4.2.x")] V42 = 0x0402,
|
||||
[Description("4.3.x")] V43 = 0x0403,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.9.6</Version>
|
||||
<Version>0.10.6</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer
|
||||
{
|
||||
public class PointFTypeConverter : ExpandableObjectConverter
|
||||
public class PointFConverter : ExpandableObjectConverter
|
||||
{
|
||||
public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
|
||||
{
|
||||
@@ -52,48 +52,4 @@ namespace SpineViewer
|
||||
|
||||
public override bool GetPropertiesSupported(ITypeDescriptorContext? context) => true;
|
||||
}
|
||||
|
||||
public class SizeTypeConverter : ExpandableObjectConverter
|
||||
{
|
||||
public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType)
|
||||
{
|
||||
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string) && value is Size size)
|
||||
{
|
||||
return $"{size.Width}, {size.Height}";
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
var parts = str.Split(',');
|
||||
if (parts.Length == 2 &&
|
||||
int.TryParse(parts[0], out var width) &&
|
||||
int.TryParse(parts[1], out var height))
|
||||
{
|
||||
return new Size(width, height);
|
||||
}
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext? context, object value, Attribute[]? attributes)
|
||||
{
|
||||
return TypeDescriptor.GetProperties(typeof(Size), attributes);
|
||||
}
|
||||
|
||||
public override bool GetPropertiesSupported(ITypeDescriptorContext? context) => true;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
img/preview.webp
Normal file
BIN
img/preview.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 234 KiB |
Reference in New Issue
Block a user