Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e293daf62 | ||
|
|
f9d7fdc516 | ||
|
|
6a04f3955c | ||
|
|
dce3b1780c | ||
|
|
f47f3e9db6 | ||
|
|
4ac74acaf7 | ||
|
|
cf7588c288 | ||
|
|
ec7bdf4000 | ||
|
|
51cd97f782 | ||
|
|
a16f2f096d | ||
|
|
4e92f14551 | ||
|
|
8f6cc9ff44 | ||
|
|
f885df5c67 | ||
|
|
0ccb110e36 | ||
|
|
2c238dca9b | ||
|
|
3e0aa53fca | ||
|
|
12b4e44296 | ||
|
|
9a2cf4aefe | ||
|
|
0e2a116e0a | ||
|
|
7bf30eb54a | ||
|
|
8dda8c8ff3 | ||
|
|
988fdb22be | ||
|
|
1dd2c8fb4d | ||
|
|
2b39384b28 | ||
|
|
28d1275023 | ||
|
|
979181fc3b | ||
|
|
b374b88ad5 | ||
|
|
6643c19a20 | ||
|
|
7460874c81 | ||
|
|
13dd7511f6 | ||
|
|
f153d251c8 | ||
|
|
3442ace981 | ||
|
|
547cebf5a9 | ||
|
|
7a24d22bc6 | ||
|
|
8f5728afe4 | ||
|
|
41b5ac2c61 | ||
|
|
694ca3bf25 | ||
|
|
674d314b55 | ||
|
|
08a35cc5d1 | ||
|
|
176e5db4d9 | ||
|
|
2535a9ebf9 | ||
|
|
8ff99ee925 | ||
|
|
abc8218487 |
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,32 +1,52 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v0.10.9
|
||||||
|
|
||||||
|
- 预览图导出增加名称后缀参数
|
||||||
|
|
||||||
|
## v0.10.8
|
||||||
|
|
||||||
|
- 完善预览图导出
|
||||||
|
- 优化骨骼文件选择
|
||||||
|
|
||||||
|
## v0.10.7
|
||||||
|
|
||||||
|
- 增加仅导出选中
|
||||||
|
- 增加模型调试属性
|
||||||
|
|
||||||
|
## v0.10.6
|
||||||
|
|
||||||
|
- 增加文件夹检测
|
||||||
|
- 增加从剪贴板添加(可复制本地文件/文件夹直接打开)
|
||||||
|
- 修复预览图导致的批量添加可能卡死
|
||||||
|
|
||||||
## v0.10.5
|
## v0.10.5
|
||||||
|
|
||||||
- <EFBFBD><EFBFBD>һЩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 修复一些问题
|
||||||
|
|
||||||
## v0.10.4
|
## v0.10.4
|
||||||
|
|
||||||
- <EFBFBD><EFBFBD>һЩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 修复一些问题
|
||||||
|
|
||||||
## v0.10.3
|
## v0.10.3
|
||||||
|
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD>汾<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 增加自动版本检测
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD>ϷŴ<EFBFBD><EFBFBD><EFBFBD>
|
- 增加文件拖放打开
|
||||||
|
|
||||||
## v0.10.2
|
## v0.10.2
|
||||||
|
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD>Ҽ<EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݼ<EFBFBD>
|
- 增加列表右键菜单快捷键
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 增加预览缩略图复制
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD><EFBFBD>ͼ<EFBFBD>л<EFBFBD>
|
- 增加列表视图切换
|
||||||
|
|
||||||
## v0.10.1
|
## v0.10.1
|
||||||
|
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><EFBFBD>ͼ
|
- 增加列表预览图
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD>Ԥ<EFBFBD><EFBFBD>ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 增加列表预览图导出
|
||||||
|
|
||||||
## v0.10.0
|
## v0.10.0
|
||||||
|
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD><EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɾ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ԥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><EFBFBD>Χ<EFBFBD><EFBFBD>ѡ<EFBFBD><EFBFBD>
|
- 增加了画面和列表的选择联动,并删除了预览画面显示包围盒选项
|
||||||
- <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˹<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><EFBFBD><EFBFBD>ʽת<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܣ<EFBFBD>Ŀǰ<EFBFBD><EFBFBD>֧<EFBFBD>ֲ<EFBFBD><EFBFBD>ְ汾<EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 增加了骨骼文件格式转换功能,目前仅支持部分版本的不完整功能
|
||||||
- <EFBFBD>Ż<EFBFBD><EFBFBD>˲<EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
- 优化了部分使用体验
|
||||||
|
|
||||||
|
|||||||
90
README.en.md
90
README.en.md
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
[中文](README.md) | [English](README.en.md)
|
[中文](README.md) | [English](README.en.md)
|
||||||
|
|
||||||
A simple and user-friendly Spine file viewer and exporter.
|
A simple and user-friendly tool for viewing and exporting Spine files.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -12,59 +12,81 @@ A simple and user-friendly Spine file viewer and exporter.
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Download the zip package from the [Releases](https://github.com/ww-rm/SpineViewer/releases) page.
|
Go to the [Release](https://github.com/ww-rm/SpineViewer/releases) page to download the compressed package.
|
||||||
|
|
||||||
The application requires the [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/en-us/download/dotnet/8.0).
|
The software requires the dependency framework [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0).
|
||||||
|
|
||||||
Alternatively, you can download the zip package with the `SelfContained` suffix, which can run independently.
|
You can also download the package with the `SelfContained` suffix, which can run independently.
|
||||||
|
|
||||||
## Features
|
## Version Support
|
||||||
|
|
||||||
- Supports viewing Spine files of different versions:
|
| Version | View & Export | Format Conversion | Version Conversion |
|
||||||
- [x] `v2.1.x`
|
| :-------: | :--------------------: | :--------------------: | :----------------: |
|
||||||
- [x] `v3.6.x`
|
| `2.1.x` | :white_check_mark: | | |
|
||||||
- [x] `v3.7.x`
|
| `3.1.x` | | | |
|
||||||
- [x] `v3.8.x`
|
| `3.4.x` | | | |
|
||||||
- [x] `v4.0.x`
|
| `3.5.x` | | | |
|
||||||
- [x] `v4.1.x`
|
| `3.6.x` | :white_check_mark: | | |
|
||||||
- [x] `v4.2.x`
|
| `3.7.x` | :white_check_mark: | | |
|
||||||
- [ ] `v4.3.x`
|
| `3.8.x` | :white_check_mark: | :white_check_mark: | |
|
||||||
- Supports animation preview for multi-skeleton files
|
| `4.1.x` | :white_check_mark: | | |
|
||||||
- Allows independent parameter settings for each skeleton
|
| `4.2.x` | :white_check_mark: | | |
|
||||||
- Supports exporting animation as PNG frame sequences
|
| `4.3.x` | | | |
|
||||||
- Provides export settings such as zoom and rotation
|
|
||||||
- More features coming soon...
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Importing Skeletons
|
### Importing Skeletons
|
||||||
|
|
||||||
Use the **File** menu to select **Open** or **Batch Open** to import skeleton files.
|
There are 3 ways to import skeleton files:
|
||||||
|
|
||||||
### Adjusting Skeletons
|
- **Drag & Drop/Paste:**
|
||||||
|
Drag and drop or paste the skeleton file/directory into the model list.
|
||||||
|
This method automatically searches through the provided files and subdirectories. Although convenient, it relies on the file structure and has its limitations.
|
||||||
|
|
||||||
Select one or more items in the **Model List** to display adjustable parameters in the **Model Parameters** panel.
|
- Only standard files with `*.json`, `*.skel`, or `.atlas` extensions are automatically detected.
|
||||||
|
- The skeleton file and atlas file must have the same name.
|
||||||
|
- The version string in the skeleton file must not be modified.
|
||||||
|
|
||||||
Right-clicking in the **Model List** allows you to add, delete, or adjust list items. You can also drag items with the left mouse button to rearrange them.
|
- **Batch Open from the File Menu:**
|
||||||
|
This method offers more file flexibility. You can drag and drop or paste files into the file selection dialog, and additional options are available.
|
||||||
|
|
||||||
### Adjusting the View
|
- The filename restrictions are similar to the above, but you can use the panel’s file selection button to choose skeleton files with non-standard extensions.
|
||||||
|
- You can set a fixed load version to handle cases where the version number has been modified.
|
||||||
|
|
||||||
Mouse operations supported in the **Preview** window:
|
- **Open a Single Model:**
|
||||||
|
This method offers the highest degree of freedom, allowing you to select any skeleton file and atlas file without filename restrictions. You can also set the load version.
|
||||||
|
|
||||||
- Left-click to drag the skeleton
|
### Adjusting Preview Content
|
||||||
- Right-click to drag the view
|
|
||||||
- Scroll wheel to zoom in/out
|
|
||||||
|
|
||||||
Additionally, you can adjust export and preview parameters through the **View Parameters** panel.
|
The model list supports right-click menus and various shortcut keys, and you can select multiple models to adjust their parameters in bulk.
|
||||||
|
|
||||||
In the **Functions** menu, you can reset and synchronize the animation time for all skeletons.
|
In addition to the parameter panel, the preview area supports several mouse actions:
|
||||||
|
|
||||||
### Exporting Animations
|
- **Left-click:** Select and drag models. Holding down the `Ctrl` key enables multi-selection, which syncs with the model list.
|
||||||
|
- **Right-click:** Drag the overall canvas.
|
||||||
|
- **Scroll wheel:** Zoom the view.
|
||||||
|
- **Selective Rendering:** The preview area supports a mode to render only the selected models. In this mode, only the selected models are displayed, and selection changes must be made through the model list.
|
||||||
|
|
||||||
Select **Export** from the **File** menu to export all loaded skeleton animations as PNG frame sequences, based on the current preview settings.
|
In the function menu, you can reset and synchronize the animation time for all skeletons.
|
||||||
|
|
||||||
You can view the full duration of each animation in the **Model Parameters** of each skeleton.
|
### Exporting Preview Content
|
||||||
|
|
||||||
|
Both preview images and videos can be exported.
|
||||||
|
|
||||||
|
- **Preview Image:**
|
||||||
|
The exported preview image shows the model in its default state, with one image per model.
|
||||||
|
|
||||||
|
- **Video (TODO: Currently only supports frame sequence export):**
|
||||||
|
The complete animation duration for each skeleton can be viewed in the model parameters.
|
||||||
|
|
||||||
|
When the preview area is set to render only the selected models, the exported content will include only the models that are displayed.
|
||||||
|
|
||||||
|
### Format & Version Conversion
|
||||||
|
|
||||||
|
You can use the tools menu to convert skeleton files. This feature supports conversion between binary and text formats, as well as between different versions.
|
||||||
|
|
||||||
|
Currently under development, it only supports converting `3.8.x` binary format to text format.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*If you find this project helpful, please give it a :star: and share it with others! :)*
|
*If you like this project, please give it a :star: and share it with more people! :)*
|
||||||
|
|||||||
86
README.md
86
README.md
@@ -18,62 +18,72 @@
|
|||||||
|
|
||||||
也可以下载带有 `SelfContained` 后缀的压缩包, 可以独立运行.
|
也可以下载带有 `SelfContained` 后缀的压缩包, 可以独立运行.
|
||||||
|
|
||||||
## 功能支持
|
## 版本支持
|
||||||
|
|
||||||
| 版本 | 查看&导出 | 格式转换 |
|
| 版本 | 查看&导出 | 格式转换 | 版本转换 |
|
||||||
| :---: | :---: | :---: |
|
| :---: | :---: | :---: | :---: |
|
||||||
| `2.1.x` | :white_check_mark: | |
|
| `2.1.x` | :white_check_mark: | | |
|
||||||
| `3.1.x` | | |
|
| `3.1.x` | | | |
|
||||||
| `3.4.x` | | |
|
| `3.4.x` | | | |
|
||||||
| `3.5.x` | | |
|
| `3.5.x` | | | |
|
||||||
| `3.6.x` | :white_check_mark: | |
|
| `3.6.x` | :white_check_mark: | | |
|
||||||
| `3.7.x` | :white_check_mark: | |
|
| `3.7.x` | :white_check_mark: | | |
|
||||||
| `3.8.x` | :white_check_mark: | :white_check_mark: |
|
| `3.8.x` | :white_check_mark: | :white_check_mark: | |
|
||||||
| `4.1.x` | :white_check_mark: | |
|
| `4.1.x` | :white_check_mark: | | |
|
||||||
| `4.2.x` | :white_check_mark: | |
|
| `4.2.x` | :white_check_mark: | | |
|
||||||
| `4.3.x` | | |
|
| `4.3.x` | | | |
|
||||||
|
|
||||||
- 支持文件拖放打开
|
|
||||||
- 支持自动检测版本
|
|
||||||
- 支持列表缩略图预览
|
|
||||||
- 支持多骨骼文件动画预览
|
|
||||||
- 支持每个骨骼独立参数设置
|
|
||||||
- 支持动画PNG帧序列导出
|
|
||||||
- 支持缩放旋转等导出画面设置
|
|
||||||
- 支持对独立的骨骼文件进行格式转换
|
|
||||||
- Coming soon...
|
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
### 骨骼导入
|
### 骨骼导入
|
||||||
|
|
||||||
**文件**菜单可以选择**打开**或者**批量打开**进行骨骼文件导入.
|
有 3 种模式导入骨骼文件:
|
||||||
|
|
||||||
或者直接把要打开的骨骼文件拖进列表, 这种方式只支持 `.json` 和 `.skel` 后缀的文件, 其他的会被忽略.
|
- 拖放/粘贴需要导入的骨骼文件/目录到模型列表
|
||||||
|
|
||||||
### 骨骼调整
|
这种方式会自动查找传入的文件列表以及目录内的子级文件列表, 虽然方便但是依赖模型文件结构, 限制最多.
|
||||||
|
|
||||||
在**模型列表**中选择一项或多项, 将会在**模型参数**面板显示可供调节的参数.
|
- 仅支持自动发现标准的 `*.json`/`*.skel`/`.atlas` 后缀文件.
|
||||||
|
- 骨骼文件和 atlas 文件需要是同名.
|
||||||
|
- 需要保证骨骼文件里的版本字符串不是魔改过的.
|
||||||
|
- 从文件菜单里批量打开骨骼文件
|
||||||
|
|
||||||
**模型列表**右键菜单可以对列表项进行增删调整, 也可以使用鼠标左键拖动调整顺序.
|
这种方式提供一定程度的文件自由度, 文件选择框里同样支持拖放/粘贴, 但是多一些额外选项.
|
||||||
|
|
||||||
### 画面调整
|
- 文件名限制条件与上面类似, 但是可以通过面板的选择文件按钮选择非标准后缀的骨骼文件.
|
||||||
|
- 可以设置固定加载版本, 便于应对魔改过的版本号.
|
||||||
|
- 选择单个模型打开
|
||||||
|
|
||||||
**预览画面**支持的鼠标操作:
|
这种方式自由度最高, 允许选择任意的骨骼文件和 atlas 文件, 可以没有文件名限制, 并且也可以设置加载版本.
|
||||||
|
|
||||||
- 左键可以对骨骼进行拖动
|
### 预览内容调整
|
||||||
- 右键对画面进行拖动
|
|
||||||
- 滚轮进行画面缩放
|
|
||||||
|
|
||||||
除此之外, 也可以通过**画面参数**面板调节导出和预览时的画面参数.
|
模型列表支持右键菜单以及部分快捷键, 并且可以多选进行模型参数的批量调整.
|
||||||
|
|
||||||
在**功能**菜单中, 可以重置同步所有骨骼动画时间.
|
预览画面除了使用面板进行参数设置外, 支持部分鼠标动作:
|
||||||
|
|
||||||
### 动画导出
|
- 左键可以选择和拖拽模型, 按下 `Ctrl` 键可以实现多选, 与左侧列表选择是联动的.
|
||||||
|
- 右键对整体画面进行拖动.
|
||||||
|
- 滚轮进行画面缩放.
|
||||||
|
- 预览画面支持仅渲染选中, 在该模式下, 画面仅显示被选中的模型, 并且只能通过左侧列表改变选中状态.
|
||||||
|
|
||||||
**文件**菜单中选择**导出**可以将目前加载的所有骨骼动画按照预览时的画面进行PNG帧序列导出.
|
在功能菜单中, 可以重置同步所有骨骼动画时间.
|
||||||
|
|
||||||
可以在每个骨骼的**模型参数**中查看动画完整时长.
|
### 预览内容导出
|
||||||
|
|
||||||
|
支持预览图和视频的导出.
|
||||||
|
|
||||||
|
预览图导出的内容是模型的默认状态画面, 每个模型一张单独的预览图.
|
||||||
|
|
||||||
|
视频(TODO: 目前仅支持帧序列导出), 可以在每个骨骼的模型参数中查看动画完整时长.
|
||||||
|
|
||||||
|
当预览画面处于仅渲染选中状态时, 导出的内容仅包含被选中的模型, 也就是在画面中显示的内容.
|
||||||
|
|
||||||
|
### 格式与版本转换
|
||||||
|
|
||||||
|
可以通过工具菜单进行骨骼文件转换, 允许二进制和文本格式之间的转换, 以及不同版本间的转换.
|
||||||
|
|
||||||
|
目前处于施工中, 仅支持转换 `3.8.x` 二进制到文本格式.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
197
SpineViewer/Controls/SkelFileListBox.Designer.cs
generated
Normal file
197
SpineViewer/Controls/SkelFileListBox.Designer.cs
generated
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
namespace SpineViewer.Controls
|
||||||
|
{
|
||||||
|
partial class SkelFileListBox
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 必需的设计器变量。
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清理所有正在使用的资源。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 组件设计器生成的代码
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设计器支持所需的方法 - 不要修改
|
||||||
|
/// 使用代码编辑器修改此方法的内容。
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
components = new System.ComponentModel.Container();
|
||||||
|
tableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
flowLayoutPanel1 = new FlowLayoutPanel();
|
||||||
|
button_AddFolder = new Button();
|
||||||
|
button_AddFile = new Button();
|
||||||
|
label_Tip = new Label();
|
||||||
|
listBox = new ListBox();
|
||||||
|
contextMenuStrip = new ContextMenuStrip(components);
|
||||||
|
toolStripMenuItem_SelectAll = new ToolStripMenuItem();
|
||||||
|
toolStripMenuItem_Paste = new ToolStripMenuItem();
|
||||||
|
toolStripMenuItem_Remove = new ToolStripMenuItem();
|
||||||
|
folderBrowserDialog = new FolderBrowserDialog();
|
||||||
|
openFileDialog_Skel = new OpenFileDialog();
|
||||||
|
tableLayoutPanel1.SuspendLayout();
|
||||||
|
flowLayoutPanel1.SuspendLayout();
|
||||||
|
contextMenuStrip.SuspendLayout();
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// tableLayoutPanel1
|
||||||
|
//
|
||||||
|
tableLayoutPanel1.ColumnCount = 1;
|
||||||
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.Controls.Add(flowLayoutPanel1, 0, 0);
|
||||||
|
tableLayoutPanel1.Controls.Add(listBox, 0, 1);
|
||||||
|
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
tableLayoutPanel1.Location = new Point(0, 0);
|
||||||
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
|
tableLayoutPanel1.RowCount = 2;
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
|
tableLayoutPanel1.Size = new Size(801, 394);
|
||||||
|
tableLayoutPanel1.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// flowLayoutPanel1
|
||||||
|
//
|
||||||
|
flowLayoutPanel1.AutoSize = true;
|
||||||
|
flowLayoutPanel1.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||||
|
flowLayoutPanel1.Controls.Add(button_AddFolder);
|
||||||
|
flowLayoutPanel1.Controls.Add(button_AddFile);
|
||||||
|
flowLayoutPanel1.Controls.Add(label_Tip);
|
||||||
|
flowLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
|
flowLayoutPanel1.Location = new Point(3, 3);
|
||||||
|
flowLayoutPanel1.Name = "flowLayoutPanel1";
|
||||||
|
flowLayoutPanel1.Size = new Size(795, 40);
|
||||||
|
flowLayoutPanel1.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// button_AddFolder
|
||||||
|
//
|
||||||
|
button_AddFolder.AutoSize = true;
|
||||||
|
button_AddFolder.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||||
|
button_AddFolder.Location = new Point(3, 3);
|
||||||
|
button_AddFolder.Name = "button_AddFolder";
|
||||||
|
button_AddFolder.Size = new Size(122, 34);
|
||||||
|
button_AddFolder.TabIndex = 0;
|
||||||
|
button_AddFolder.Text = "添加文件夹...";
|
||||||
|
button_AddFolder.UseVisualStyleBackColor = true;
|
||||||
|
button_AddFolder.Click += button_AddFolder_Click;
|
||||||
|
//
|
||||||
|
// button_AddFile
|
||||||
|
//
|
||||||
|
button_AddFile.AutoSize = true;
|
||||||
|
button_AddFile.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||||
|
button_AddFile.Location = new Point(131, 3);
|
||||||
|
button_AddFile.Name = "button_AddFile";
|
||||||
|
button_AddFile.Size = new Size(104, 34);
|
||||||
|
button_AddFile.TabIndex = 1;
|
||||||
|
button_AddFile.Text = "添加文件...";
|
||||||
|
button_AddFile.UseVisualStyleBackColor = true;
|
||||||
|
button_AddFile.Click += button_AddFile_Click;
|
||||||
|
//
|
||||||
|
// label_Tip
|
||||||
|
//
|
||||||
|
label_Tip.Anchor = AnchorStyles.Left;
|
||||||
|
label_Tip.AutoSize = true;
|
||||||
|
label_Tip.Location = new Point(241, 8);
|
||||||
|
label_Tip.Name = "label_Tip";
|
||||||
|
label_Tip.Size = new Size(139, 24);
|
||||||
|
label_Tip.TabIndex = 3;
|
||||||
|
label_Tip.Text = "已添加 0 个文件";
|
||||||
|
label_Tip.TextAlign = ContentAlignment.MiddleCenter;
|
||||||
|
//
|
||||||
|
// listBox
|
||||||
|
//
|
||||||
|
listBox.AllowDrop = true;
|
||||||
|
listBox.ContextMenuStrip = contextMenuStrip;
|
||||||
|
listBox.Dock = DockStyle.Fill;
|
||||||
|
listBox.FormattingEnabled = true;
|
||||||
|
listBox.HorizontalScrollbar = true;
|
||||||
|
listBox.ItemHeight = 24;
|
||||||
|
listBox.Location = new Point(3, 49);
|
||||||
|
listBox.Name = "listBox";
|
||||||
|
listBox.SelectionMode = SelectionMode.MultiExtended;
|
||||||
|
listBox.Size = new Size(795, 342);
|
||||||
|
listBox.TabIndex = 0;
|
||||||
|
listBox.DragDrop += listBox_DragDrop;
|
||||||
|
listBox.DragEnter += listBox_DragEnter;
|
||||||
|
//
|
||||||
|
// contextMenuStrip
|
||||||
|
//
|
||||||
|
contextMenuStrip.ImageScalingSize = new Size(24, 24);
|
||||||
|
contextMenuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_SelectAll, toolStripMenuItem_Paste, toolStripMenuItem_Remove });
|
||||||
|
contextMenuStrip.Name = "contextMenuStrip";
|
||||||
|
contextMenuStrip.Size = new Size(184, 94);
|
||||||
|
//
|
||||||
|
// toolStripMenuItem_SelectAll
|
||||||
|
//
|
||||||
|
toolStripMenuItem_SelectAll.Name = "toolStripMenuItem_SelectAll";
|
||||||
|
toolStripMenuItem_SelectAll.ShortcutKeys = Keys.Control | Keys.A;
|
||||||
|
toolStripMenuItem_SelectAll.Size = new Size(183, 30);
|
||||||
|
toolStripMenuItem_SelectAll.Text = "全选";
|
||||||
|
toolStripMenuItem_SelectAll.Click += toolStripMenuItem_SelectAll_Click;
|
||||||
|
//
|
||||||
|
// toolStripMenuItem_Paste
|
||||||
|
//
|
||||||
|
toolStripMenuItem_Paste.Name = "toolStripMenuItem_Paste";
|
||||||
|
toolStripMenuItem_Paste.ShortcutKeys = Keys.Control | Keys.V;
|
||||||
|
toolStripMenuItem_Paste.Size = new Size(183, 30);
|
||||||
|
toolStripMenuItem_Paste.Text = "粘贴";
|
||||||
|
toolStripMenuItem_Paste.Click += toolStripMenuItem_Paste_Click;
|
||||||
|
//
|
||||||
|
// toolStripMenuItem_Remove
|
||||||
|
//
|
||||||
|
toolStripMenuItem_Remove.Name = "toolStripMenuItem_Remove";
|
||||||
|
toolStripMenuItem_Remove.ShortcutKeys = Keys.Delete;
|
||||||
|
toolStripMenuItem_Remove.Size = new Size(183, 30);
|
||||||
|
toolStripMenuItem_Remove.Text = "移除";
|
||||||
|
toolStripMenuItem_Remove.Click += toolStripMenuItem_Remove_Click;
|
||||||
|
//
|
||||||
|
// 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文件";
|
||||||
|
//
|
||||||
|
// SkelFileListBox
|
||||||
|
//
|
||||||
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
Controls.Add(tableLayoutPanel1);
|
||||||
|
Name = "SkelFileListBox";
|
||||||
|
Size = new Size(801, 394);
|
||||||
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
|
tableLayoutPanel1.PerformLayout();
|
||||||
|
flowLayoutPanel1.ResumeLayout(false);
|
||||||
|
flowLayoutPanel1.PerformLayout();
|
||||||
|
contextMenuStrip.ResumeLayout(false);
|
||||||
|
ResumeLayout(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private TableLayoutPanel tableLayoutPanel1;
|
||||||
|
private ListBox listBox;
|
||||||
|
private FlowLayoutPanel flowLayoutPanel1;
|
||||||
|
private Button button_AddFolder;
|
||||||
|
private Button button_AddFile;
|
||||||
|
private FolderBrowserDialog folderBrowserDialog;
|
||||||
|
private Label label_Tip;
|
||||||
|
private ContextMenuStrip contextMenuStrip;
|
||||||
|
private OpenFileDialog openFileDialog_Skel;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_SelectAll;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_Paste;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_Remove;
|
||||||
|
}
|
||||||
|
}
|
||||||
123
SpineViewer/Controls/SkelFileListBox.cs
Normal file
123
SpineViewer/Controls/SkelFileListBox.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace SpineViewer.Controls
|
||||||
|
{
|
||||||
|
public partial class SkelFileListBox : UserControl
|
||||||
|
{
|
||||||
|
public SkelFileListBox()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Items = listBox.Items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ListBox.Items
|
||||||
|
/// </summary>
|
||||||
|
public readonly ListBox.ObjectCollection Items;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从路径列表添加
|
||||||
|
/// </summary>
|
||||||
|
private void AddFromFileDrop(string[] paths)
|
||||||
|
{
|
||||||
|
foreach (var path in paths)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower()))
|
||||||
|
listBox.Items.Add(Path.GetFullPath(path));
|
||||||
|
}
|
||||||
|
else if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
||||||
|
listBox.Items.Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_AddFolder_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (folderBrowserDialog.ShowDialog() != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var path = folderBrowserDialog.SelectedPath;
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
|
||||||
|
listBox.Items.Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label_Tip.Text = $"已选择 {listBox.Items.Count} 个文件";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void button_AddFile_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (openFileDialog_Skel.ShowDialog() != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var p in openFileDialog_Skel.FileNames)
|
||||||
|
listBox.Items.Add(Path.GetFullPath(p));
|
||||||
|
|
||||||
|
label_Tip.Text = $"已选择 {listBox.Items.Count} 个文件";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listBox_DragEnter(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||||
|
e.Effect = DragDropEffects.Copy;
|
||||||
|
else
|
||||||
|
e.Effect = DragDropEffects.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listBox_DragDrop(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddFromFileDrop((string[])e.Data.GetData(DataFormats.FileDrop));
|
||||||
|
label_Tip.Text = $"已选择 {listBox.Items.Count} 个文件";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_SelectAll_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < listBox.Items.Count; i++)
|
||||||
|
listBox.SelectedIndices.Add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_Paste_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!Clipboard.ContainsFileDropList())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var fileDropList = Clipboard.GetFileDropList();
|
||||||
|
var paths = new string[fileDropList.Count];
|
||||||
|
fileDropList.CopyTo(paths, 0);
|
||||||
|
AddFromFileDrop(paths);
|
||||||
|
label_Tip.Text = $"已选择 {listBox.Items.Count} 个文件";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toolStripMenuItem_Remove_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var indices = new int[listBox.SelectedIndices.Count];
|
||||||
|
listBox.SelectedIndices.CopyTo(indices, 0);
|
||||||
|
for (int i = indices.Length - 1; i >= 0; i--)
|
||||||
|
listBox.Items.RemoveAt(indices[i]);
|
||||||
|
label_Tip.Text = $"已选择 {listBox.Items.Count} 个文件";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
129
SpineViewer/Controls/SkelFileListBox.resx
Normal file
129
SpineViewer/Controls/SkelFileListBox.resx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<metadata name="contextMenuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<value>556, 18</value>
|
||||||
|
</metadata>
|
||||||
|
<metadata name="folderBrowserDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<value>286, 21</value>
|
||||||
|
</metadata>
|
||||||
|
<metadata name="openFileDialog_Skel.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||||
|
<value>31, 27</value>
|
||||||
|
</metadata>
|
||||||
|
</root>
|
||||||
14
SpineViewer/Controls/SpineListView.Designer.cs
generated
14
SpineViewer/Controls/SpineListView.Designer.cs
generated
@@ -53,6 +53,7 @@
|
|||||||
toolStripMenuItem_DetailsView = new ToolStripMenuItem();
|
toolStripMenuItem_DetailsView = new ToolStripMenuItem();
|
||||||
imageList_LargeIcon = new ImageList(components);
|
imageList_LargeIcon = new ImageList(components);
|
||||||
imageList_SmallIcon = new ImageList(components);
|
imageList_SmallIcon = new ImageList(components);
|
||||||
|
toolStripMenuItem_AddFromClipboard = new ToolStripMenuItem();
|
||||||
contextMenuStrip.SuspendLayout();
|
contextMenuStrip.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
@@ -88,9 +89,9 @@
|
|||||||
// contextMenuStrip
|
// contextMenuStrip
|
||||||
//
|
//
|
||||||
contextMenuStrip.ImageScalingSize = new Size(24, 24);
|
contextMenuStrip.ImageScalingSize = new Size(24, 24);
|
||||||
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_SelectAll, toolStripMenuItem_CopyPreview, toolStripSeparator4, toolStripMenuItem_ChangeView });
|
contextMenuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_Add, toolStripMenuItem_Insert, toolStripMenuItem_Remove, toolStripSeparator1, toolStripMenuItem_BatchAdd, toolStripMenuItem_RemoveAll, toolStripSeparator2, toolStripMenuItem_MoveUp, toolStripMenuItem_MoveDown, toolStripMenuItem_MoveTop, toolStripMenuItem_MoveBottom, toolStripSeparator3, toolStripMenuItem_CopyPreview, toolStripMenuItem_AddFromClipboard, toolStripMenuItem_SelectAll, toolStripSeparator4, toolStripMenuItem_ChangeView });
|
||||||
contextMenuStrip.Name = "contextMenuStrip";
|
contextMenuStrip.Name = "contextMenuStrip";
|
||||||
contextMenuStrip.Size = new Size(329, 388);
|
contextMenuStrip.Size = new Size(329, 451);
|
||||||
contextMenuStrip.Closed += contextMenuStrip_Closed;
|
contextMenuStrip.Closed += contextMenuStrip_Closed;
|
||||||
contextMenuStrip.Opening += contextMenuStrip_Opening;
|
contextMenuStrip.Opening += contextMenuStrip_Opening;
|
||||||
//
|
//
|
||||||
@@ -241,6 +242,14 @@
|
|||||||
imageList_SmallIcon.ImageSize = new Size(48, 48);
|
imageList_SmallIcon.ImageSize = new Size(48, 48);
|
||||||
imageList_SmallIcon.TransparentColor = Color.Transparent;
|
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
|
// SpineListView
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
@@ -277,5 +286,6 @@
|
|||||||
private ToolStripMenuItem toolStripMenuItem_CopyPreview;
|
private ToolStripMenuItem toolStripMenuItem_CopyPreview;
|
||||||
private ToolStripMenuItem toolStripMenuItem_SelectAll;
|
private ToolStripMenuItem toolStripMenuItem_SelectAll;
|
||||||
private ToolStripSeparator toolStripSeparator4;
|
private ToolStripSeparator toolStripSeparator4;
|
||||||
|
private ToolStripMenuItem toolStripMenuItem_AddFromClipboard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,19 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
public partial class SpineListView : UserControl
|
public partial class SpineListView : UserControl
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 显示骨骼信息的属性面板
|
||||||
|
/// </summary>
|
||||||
[Category("自定义"), Description("用于显示骨骼属性的属性页")]
|
[Category("自定义"), Description("用于显示骨骼属性的属性页")]
|
||||||
public PropertyGrid? PropertyGrid { get; set; }
|
public PropertyGrid? PropertyGrid { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取数组快照, 访问时必须使用 lock 语句锁定对象本身
|
/// Spine 列表只读视图, 访问时必须使用 lock 语句锁定视图本身
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly ReadOnlyCollection<Spine.Spine> Spines;
|
public readonly ReadOnlyCollection<Spine.Spine> Spines;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spine 列表, 访问时必须使用 lock 语句锁定 Spines
|
/// Spine 列表, 访问时必须使用 lock 语句锁定只读视图 Spines
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<Spine.Spine> spines = [];
|
private readonly List<Spine.Spine> spines = [];
|
||||||
|
|
||||||
@@ -37,16 +40,60 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// listView.SelectedIndices
|
/// 选中的索引
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ListView.SelectedIndexCollection SelectedIndices { get => listView.SelectedIndices; }
|
public ListView.SelectedIndexCollection SelectedIndices => listView.SelectedIndices;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 弹出添加对话框
|
/// 弹出添加对话框在末尾添加
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Add()
|
public void Add() => Insert();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 弹出添加对话框在指定位置之前插入一项, 如果索引无效则在末尾添加
|
||||||
|
/// </summary>
|
||||||
|
private void Insert(int index = -1)
|
||||||
{
|
{
|
||||||
Insert();
|
var dialog = new Dialogs.OpenSpineDialog();
|
||||||
|
if (dialog.ShowDialog() != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
Insert(dialog.Result, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从结果在指定位置之前插入一项, 如果索引无效则在末尾添加
|
||||||
|
/// </summary>
|
||||||
|
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.Error(ex.ToString(), "骨骼加载失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.LogCurrentMemoryUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,42 +104,123 @@ namespace SpineViewer.Controls
|
|||||||
var openDialog = new Dialogs.BatchOpenSpineDialog();
|
var openDialog = new Dialogs.BatchOpenSpineDialog();
|
||||||
if (openDialog.ShowDialog() != DialogResult.OK)
|
if (openDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
BatchAdd(openDialog.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从结果批量添加
|
||||||
|
/// </summary>
|
||||||
|
public void BatchAdd(Dialogs.BatchOpenSpineDialogResult result)
|
||||||
|
{
|
||||||
var progressDialog = new Dialogs.ProgressDialog();
|
var progressDialog = new Dialogs.ProgressDialog();
|
||||||
progressDialog.DoWork += BatchAdd_Work;
|
progressDialog.DoWork += BatchAdd_Work;
|
||||||
progressDialog.RunWorkerAsync(openDialog.Result);
|
progressDialog.RunWorkerAsync(result);
|
||||||
progressDialog.ShowDialog();
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 弹出对话框导出列表预览图
|
/// 批量添加后台任务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ExportPreviews()
|
private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
|
||||||
{
|
{
|
||||||
lock (Spines)
|
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 (spines.Count <= 0)
|
if (worker.CancellationPending)
|
||||||
{
|
{
|
||||||
MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
e.Cancel = true;
|
||||||
return;
|
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.LogCurrentMemoryUsage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从拖放/复制的路径列表添加
|
||||||
|
/// </summary>
|
||||||
|
private void AddFromFileDrop(IEnumerable<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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var saveDialog = new Dialogs.ExportPreviewDialog();
|
if (validPaths.Count > 1)
|
||||||
if (saveDialog.ShowDialog() != DialogResult.OK)
|
{
|
||||||
return;
|
if (validPaths.Count > 100)
|
||||||
|
{
|
||||||
var progressDialog = new Dialogs.ProgressDialog();
|
if (MessageBox.Quest($"共发现 {validPaths.Count} 个可加载骨骼,数量较多,是否一次性全部加载?") == DialogResult.Cancel)
|
||||||
progressDialog.DoWork += ExportPreview_Work;
|
return;
|
||||||
progressDialog.RunWorkerAsync(saveDialog);
|
}
|
||||||
progressDialog.ShowDialog();
|
BatchAdd(new Dialogs.BatchOpenSpineDialogResult(Spine.Version.Auto, validPaths.ToArray()));
|
||||||
|
}
|
||||||
|
else if (validPaths.Count > 0)
|
||||||
|
{
|
||||||
|
Insert(new Dialogs.OpenSpineDialogResult(Spine.Version.Auto, validPaths[0]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void listView_SelectedIndexChanged(object sender, EventArgs e)
|
private void listView_SelectedIndexChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (PropertyGrid is not null)
|
lock (Spines)
|
||||||
{
|
{
|
||||||
lock (Spines)
|
if (PropertyGrid is not null)
|
||||||
{
|
{
|
||||||
if (listView.SelectedIndices.Count <= 0)
|
if (listView.SelectedIndices.Count <= 0)
|
||||||
PropertyGrid.SelectedObject = null;
|
PropertyGrid.SelectedObject = null;
|
||||||
@@ -100,19 +228,21 @@ namespace SpineViewer.Controls
|
|||||||
PropertyGrid.SelectedObject = spines[listView.SelectedIndices[0]];
|
PropertyGrid.SelectedObject = spines[listView.SelectedIndices[0]];
|
||||||
else
|
else
|
||||||
PropertyGrid.SelectedObjects = listView.SelectedIndices.Cast<int>().Select(index => spines[index]).ToArray();
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 标记选中的 Spine
|
||||||
|
for (int i = 0; i < spines.Count; i++)
|
||||||
|
spines[i].IsSelected = listView.SelectedIndices.Contains(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// BUG: 图标显示的时候没法自动刷新顺序, 只能切换视图刷新, 不知道什么原理
|
// XXX: 图标显示的时候没法自动刷新顺序, 只能切换视图刷新, 不知道什么原理
|
||||||
listView.BeginUpdate();
|
if (listView.View == View.LargeIcon)
|
||||||
var tmp = listView.View;
|
{
|
||||||
listView.View = View.List;
|
listView.BeginUpdate();
|
||||||
listView.View = tmp;
|
listView.View = View.List;
|
||||||
listView.EndUpdate();
|
listView.View = View.LargeIcon;
|
||||||
|
listView.EndUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
if (listView.SelectedItems.Count > 0)
|
if (listView.SelectedItems.Count > 0)
|
||||||
listView.SelectedItems[0].EnsureVisible();
|
listView.SelectedItems[0].EnsureVisible();
|
||||||
@@ -195,23 +325,7 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
else if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
else if (e.Data.GetDataPresent(DataFormats.FileDrop))
|
||||||
{
|
{
|
||||||
var validPaths = ((string[])e.Data.GetData(DataFormats.FileDrop)).Where(
|
AddFromFileDrop((string[])e.Data.GetData(DataFormats.FileDrop));
|
||||||
path => File.Exists(path) &&
|
|
||||||
(Path.GetExtension(path).Equals(".skel", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
Path.GetExtension(path).Equals(".json", StringComparison.OrdinalIgnoreCase))
|
|
||||||
).ToArray();
|
|
||||||
|
|
||||||
if (validPaths.Length > 1)
|
|
||||||
{
|
|
||||||
var progressDialog = new Dialogs.ProgressDialog();
|
|
||||||
progressDialog.DoWork += BatchAdd_Work;
|
|
||||||
progressDialog.RunWorkerAsync(new Dialogs.BatchOpenSpineDialogResult(Spine.Version.Auto, validPaths));
|
|
||||||
progressDialog.ShowDialog();
|
|
||||||
}
|
|
||||||
else if (validPaths.Length > 0)
|
|
||||||
{
|
|
||||||
Insert(new Dialogs.OpenSpineDialogResult(Spine.Version.Auto, validPaths[0]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,12 +379,13 @@ namespace SpineViewer.Controls
|
|||||||
|
|
||||||
if (listView.SelectedIndices.Count > 1)
|
if (listView.SelectedIndices.Count > 1)
|
||||||
{
|
{
|
||||||
if (MessageBox.Show($"确定移除所选 {listView.SelectedIndices.Count} 项吗?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
|
if (MessageBox.Quest($"确定移除所选 {listView.SelectedIndices.Count} 项吗?") != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var i in listView.SelectedIndices.Cast<int>().OrderByDescending(x => x))
|
foreach (var i in listView.SelectedIndices.Cast<int>().OrderByDescending(x => x))
|
||||||
{
|
{
|
||||||
|
listView.Items.RemoveAt(i);
|
||||||
lock (Spines)
|
lock (Spines)
|
||||||
{
|
{
|
||||||
var spine = spines[i];
|
var spine = spines[i];
|
||||||
@@ -279,7 +394,6 @@ namespace SpineViewer.Controls
|
|||||||
listView.LargeImageList.Images.RemoveByKey(spine.ID);
|
listView.LargeImageList.Images.RemoveByKey(spine.ID);
|
||||||
spine.Dispose();
|
spine.Dispose();
|
||||||
}
|
}
|
||||||
listView.Items.RemoveAt(i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,12 +461,9 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
lock (Spines)
|
lock (Spines)
|
||||||
{
|
{
|
||||||
lock (Spines)
|
var spine = spines[index];
|
||||||
{
|
spines.RemoveAt(index);
|
||||||
var spine = spines[index];
|
spines.Add(spine);
|
||||||
spines.RemoveAt(index);
|
|
||||||
spines.Add(spine);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var item = listView.Items[index];
|
var item = listView.Items[index];
|
||||||
listView.Items.RemoveAt(index);
|
listView.Items.RemoveAt(index);
|
||||||
@@ -365,30 +476,21 @@ namespace SpineViewer.Controls
|
|||||||
if (listView.Items.Count <= 0)
|
if (listView.Items.Count <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (MessageBox.Show($"确认移除所有 {listView.Items.Count} 项吗?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
|
if (MessageBox.Quest($"确认移除所有 {listView.Items.Count} 项吗?") != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
listView.Items.Clear();
|
||||||
lock (Spines)
|
lock (Spines)
|
||||||
{
|
{
|
||||||
foreach (var spine in spines)
|
foreach (var spine in spines) spine.Dispose();
|
||||||
spine.Dispose();
|
|
||||||
spines.Clear();
|
spines.Clear();
|
||||||
listView.SmallImageList.Images.Clear();
|
listView.SmallImageList.Images.Clear();
|
||||||
listView.LargeImageList.Images.Clear();
|
listView.LargeImageList.Images.Clear();
|
||||||
}
|
}
|
||||||
listView.Items.Clear();
|
|
||||||
if (PropertyGrid is not null)
|
if (PropertyGrid is not null)
|
||||||
PropertyGrid.SelectedObject = null;
|
PropertyGrid.SelectedObject = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
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_CopyPreview_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_CopyPreview_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var fileDropList = new StringCollection();
|
var fileDropList = new StringCollection();
|
||||||
@@ -409,6 +511,25 @@ namespace SpineViewer.Controls
|
|||||||
Clipboard.SetFileDropList(fileDropList);
|
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)
|
private void toolStripMenuItem_LargeIconView_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
listView.View = View.LargeIcon;
|
listView.View = View.LargeIcon;
|
||||||
@@ -423,160 +544,5 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
listView.View = View.Details;
|
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 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;
|
|
||||||
lock (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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,10 @@ namespace SpineViewer.Controls
|
|||||||
public partial class SpinePreviewer : UserControl
|
public partial class SpinePreviewer : UserControl
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包装类, 用于 PropertyGrid 显示
|
/// 包装类, 用于属性面板显示
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private class PreviewerProperty
|
private class PreviewerProperty(SpinePreviewer previewer)
|
||||||
{
|
{
|
||||||
private readonly SpinePreviewer previewer;
|
|
||||||
|
|
||||||
public PreviewerProperty(SpinePreviewer previewer) { this.previewer = previewer; }
|
|
||||||
|
|
||||||
[TypeConverter(typeof(SizeConverter))]
|
[TypeConverter(typeof(SizeConverter))]
|
||||||
[Category("导出"), DisplayName("分辨率")]
|
[Category("导出"), DisplayName("分辨率")]
|
||||||
public Size Resolution { get => previewer.Resolution; set => previewer.Resolution = value; }
|
public Size Resolution { get => previewer.Resolution; set => previewer.Resolution = value; }
|
||||||
@@ -44,6 +40,9 @@ namespace SpineViewer.Controls
|
|||||||
[Category("导出"), DisplayName("垂直翻转")]
|
[Category("导出"), DisplayName("垂直翻转")]
|
||||||
public bool FlipY { get => previewer.FlipY; set => previewer.FlipY = value; }
|
public bool FlipY { get => previewer.FlipY; set => previewer.FlipY = value; }
|
||||||
|
|
||||||
|
[Category("导出"), DisplayName("仅渲染选中")]
|
||||||
|
public bool RenderSelectedOnly { get => previewer.RenderSelectedOnly; set => previewer.RenderSelectedOnly = value; }
|
||||||
|
|
||||||
[Category("预览"), DisplayName("显示坐标轴")]
|
[Category("预览"), DisplayName("显示坐标轴")]
|
||||||
public bool ShowAxis { get => previewer.ShowAxis; set => previewer.ShowAxis = value; }
|
public bool ShowAxis { get => previewer.ShowAxis; set => previewer.ShowAxis = value; }
|
||||||
|
|
||||||
@@ -51,9 +50,15 @@ namespace SpineViewer.Controls
|
|||||||
public uint MaxFps { get => previewer.MaxFps; set => previewer.MaxFps = value; }
|
public uint MaxFps { get => previewer.MaxFps; set => previewer.MaxFps = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 要绑定的 Spine 列表控件
|
||||||
|
/// </summary>
|
||||||
[Category("自定义"), Description("相关联的 SpineListView")]
|
[Category("自定义"), Description("相关联的 SpineListView")]
|
||||||
public SpineListView? SpineListView { get; set; }
|
public SpineListView? SpineListView { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 属性信息面板
|
||||||
|
/// </summary>
|
||||||
[Category("自定义"), Description("用于显示画面属性的属性页")]
|
[Category("自定义"), Description("用于显示画面属性的属性页")]
|
||||||
public PropertyGrid? PropertyGrid
|
public PropertyGrid? PropertyGrid
|
||||||
{
|
{
|
||||||
@@ -67,21 +72,49 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
private PropertyGrid? propertyGrid;
|
private PropertyGrid? propertyGrid;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 画面缩放最大值
|
||||||
|
/// </summary>
|
||||||
public const float ZOOM_MAX = 1000f;
|
public const float ZOOM_MAX = 1000f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 画面缩放最小值
|
||||||
|
/// </summary>
|
||||||
public const float ZOOM_MIN = 0.001f;
|
public const float ZOOM_MIN = 0.001f;
|
||||||
public const int BACKGROUND_CELL_SIZE = 10;
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览画面背景色
|
||||||
|
/// </summary>
|
||||||
private static readonly SFML.Graphics.Color BackgroundColor = new(105, 105, 105);
|
private static readonly SFML.Graphics.Color BackgroundColor = new(105, 105, 105);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览画面坐标轴颜色
|
||||||
|
/// </summary>
|
||||||
private static readonly SFML.Graphics.Color AxisColor = new(220, 220, 220);
|
private static readonly SFML.Graphics.Color AxisColor = new(220, 220, 220);
|
||||||
private static readonly SFML.Graphics.Color BoundsColor = new(120, 200, 0);
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 坐标轴顶点缓冲区
|
||||||
|
/// </summary>
|
||||||
private readonly SFML.Graphics.VertexArray AxisVertex = new(SFML.Graphics.PrimitiveType.Lines, 2);
|
private readonly SFML.Graphics.VertexArray AxisVertex = new(SFML.Graphics.PrimitiveType.Lines, 2);
|
||||||
private readonly SFML.Graphics.VertexArray BoundsRect = new(SFML.Graphics.PrimitiveType.LineStrip, 5);
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渲染窗口
|
||||||
|
/// </summary>
|
||||||
private readonly SFML.Graphics.RenderWindow RenderWindow;
|
private readonly SFML.Graphics.RenderWindow RenderWindow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 帧间隔计时器
|
||||||
|
/// </summary>
|
||||||
private readonly SFML.System.Clock Clock = new();
|
private readonly SFML.System.Clock Clock = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 画面拖放对象世界坐标源点
|
||||||
|
/// </summary>
|
||||||
private SFML.System.Vector2f? draggingSrc = null;
|
private SFML.System.Vector2f? draggingSrc = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渲染任务
|
||||||
|
/// </summary>
|
||||||
private Task? task = null;
|
private Task? task = null;
|
||||||
private CancellationTokenSource? cancelToken = null;
|
private CancellationTokenSource? cancelToken = null;
|
||||||
|
|
||||||
@@ -225,6 +258,13 @@ namespace SpineViewer.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 仅渲染选中
|
||||||
|
/// </summary>
|
||||||
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||||
|
[Browsable(false)]
|
||||||
|
public bool RenderSelectedOnly { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 显示坐标轴
|
/// 显示坐标轴
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -240,13 +280,6 @@ namespace SpineViewer.Controls
|
|||||||
public uint MaxFps { get => maxFps; set { RenderWindow.SetFramerateLimit(value); maxFps = value; } }
|
public uint MaxFps { get => maxFps; set { RenderWindow.SetFramerateLimit(value); maxFps = value; } }
|
||||||
private uint maxFps = 60;
|
private uint maxFps = 60;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// RenderWindow.View
|
|
||||||
/// </summary>
|
|
||||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
||||||
[Browsable(false)]
|
|
||||||
public SFML.Graphics.View View { get => RenderWindow.GetView(); }
|
|
||||||
|
|
||||||
public SpinePreviewer()
|
public SpinePreviewer()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -260,6 +293,11 @@ namespace SpineViewer.Controls
|
|||||||
MaxFps = 30;
|
MaxFps = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览画面帧参数
|
||||||
|
/// </summary>
|
||||||
|
public SpinePreviewerFrameArgs GetFrameArgs() => new(Resolution, RenderWindow.GetView(), RenderSelectedOnly);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开始预览
|
/// 开始预览
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -284,6 +322,67 @@ namespace SpineViewer.Controls
|
|||||||
task = null;
|
task = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渲染任务
|
||||||
|
/// </summary>
|
||||||
|
private void RenderTask()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RenderWindow.SetActive(true);
|
||||||
|
|
||||||
|
float delta;
|
||||||
|
while (cancelToken is not null && !cancelToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
delta = Clock.ElapsedTime.AsSeconds();
|
||||||
|
Clock.Restart();
|
||||||
|
|
||||||
|
RenderWindow.Clear(BackgroundColor);
|
||||||
|
|
||||||
|
if (ShowAxis)
|
||||||
|
{
|
||||||
|
// 画一个很长的坐标轴, 用 1e9 比较合适
|
||||||
|
AxisVertex[0] = new(new(-1e9f, 0), AxisColor);
|
||||||
|
AxisVertex[1] = new(new(1e9f, 0), AxisColor);
|
||||||
|
RenderWindow.Draw(AxisVertex);
|
||||||
|
AxisVertex[0] = new(new(0, -1e9f), AxisColor);
|
||||||
|
AxisVertex[1] = new(new(0, 1e9f), AxisColor);
|
||||||
|
RenderWindow.Draw(AxisVertex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染 Spine
|
||||||
|
if (SpineListView is not null)
|
||||||
|
{
|
||||||
|
lock (SpineListView.Spines)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (RenderSelectedOnly && !spine.IsSelected)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
spine.IsDebug = true;
|
||||||
|
RenderWindow.Draw(spine);
|
||||||
|
spine.IsDebug = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderWindow.Display();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RenderWindow.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SpinePreviewer_SizeChanged(object sender, EventArgs e)
|
private void SpinePreviewer_SizeChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (RenderWindow is null)
|
if (RenderWindow is null)
|
||||||
@@ -326,45 +425,60 @@ namespace SpineViewer.Controls
|
|||||||
draggingSrc = RenderWindow.MapPixelToCoords(new(e.X, e.Y));
|
draggingSrc = RenderWindow.MapPixelToCoords(new(e.X, e.Y));
|
||||||
var src = new PointF(((SFML.System.Vector2f)draggingSrc).X, ((SFML.System.Vector2f)draggingSrc).Y);
|
var src = new PointF(((SFML.System.Vector2f)draggingSrc).X, ((SFML.System.Vector2f)draggingSrc).Y);
|
||||||
|
|
||||||
if (SpineListView is not null)
|
if (SpineListView is null)
|
||||||
{
|
return;
|
||||||
lock (SpineListView.Spines)
|
|
||||||
{
|
|
||||||
var spines = SpineListView.Spines;
|
|
||||||
|
|
||||||
|
lock (SpineListView.Spines)
|
||||||
|
{
|
||||||
|
var spines = SpineListView.Spines;
|
||||||
|
|
||||||
|
// 仅渲染选中模式禁止在画面里选择对象
|
||||||
|
if (RenderSelectedOnly)
|
||||||
|
{
|
||||||
|
bool hit = false;
|
||||||
|
foreach (int i in SpineListView.SelectedIndices)
|
||||||
|
{
|
||||||
|
if (!spines[i].Bounds.Contains(src)) continue;
|
||||||
|
hit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没点到被选中的模型, 则不允许拖动
|
||||||
|
if (!hit) draggingSrc = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// 没有按下 Ctrl 键就只选中点击的那个, 所以先清空选中列表
|
// 没有按下 Ctrl 键就只选中点击的那个, 所以先清空选中列表
|
||||||
if ((ModifierKeys & Keys.Control) == 0)
|
if ((ModifierKeys & Keys.Control) == 0)
|
||||||
{
|
{
|
||||||
bool hit = false;
|
bool hit = false;
|
||||||
for (int i = 0; i < spines.Count; i++)
|
for (int i = 0; i < spines.Count; i++)
|
||||||
{
|
{
|
||||||
if (spines[i].Bounds.Contains(src))
|
if (!spines[i].Bounds.Contains(src)) continue;
|
||||||
{
|
|
||||||
hit = true;
|
|
||||||
|
|
||||||
// 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的
|
hit = true;
|
||||||
if (!SpineListView.SelectedIndices.Contains(i))
|
|
||||||
{
|
// 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的
|
||||||
SpineListView.SelectedIndices.Clear();
|
if (!SpineListView.SelectedIndices.Contains(i))
|
||||||
SpineListView.SelectedIndices.Add(i);
|
{
|
||||||
}
|
SpineListView.SelectedIndices.Clear();
|
||||||
break;
|
SpineListView.SelectedIndices.Add(i);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果点了空白的地方, 就清空选中列表
|
// 如果点了空白的地方, 就清空选中列表
|
||||||
if (!hit)
|
if (!hit) SpineListView.SelectedIndices.Clear();
|
||||||
SpineListView.SelectedIndices.Clear();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (int i = 0; i < spines.Count; i++)
|
for (int i = 0; i < spines.Count; i++)
|
||||||
{
|
{
|
||||||
if (spines[i].Bounds.Contains(src))
|
if (!spines[i].Bounds.Contains(src))
|
||||||
{
|
continue;
|
||||||
SpineListView.SelectedIndices.Add(i);
|
|
||||||
break;
|
SpineListView.SelectedIndices.Add(i);
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,11 +506,8 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
lock (SpineListView.Spines)
|
lock (SpineListView.Spines)
|
||||||
{
|
{
|
||||||
foreach (var spine in SpineListView.Spines)
|
foreach (int i in SpineListView.SelectedIndices)
|
||||||
{
|
SpineListView.Spines[i].Position += delta;
|
||||||
if (spine.IsSelected)
|
|
||||||
spine.Position += delta;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
draggingSrc = dst;
|
draggingSrc = dst;
|
||||||
@@ -427,63 +538,27 @@ namespace SpineViewer.Controls
|
|||||||
Zoom *= (e.Delta > 0 ? 1.1f : 0.9f);
|
Zoom *= (e.Delta > 0 ? 1.1f : 0.9f);
|
||||||
PropertyGrid?.Refresh();
|
PropertyGrid?.Refresh();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void RenderTask()
|
/// <summary>
|
||||||
{
|
/// 预览画面帧参数
|
||||||
try
|
/// </summary>
|
||||||
{
|
public class SpinePreviewerFrameArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly)
|
||||||
RenderWindow.SetActive(true);
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 分辨率
|
||||||
|
/// </summary>
|
||||||
|
public Size Resolution => resolution;
|
||||||
|
|
||||||
float delta;
|
/// <summary>
|
||||||
while (cancelToken is not null && !cancelToken.IsCancellationRequested)
|
/// 渲染视窗
|
||||||
{
|
/// </summary>
|
||||||
delta = Clock.ElapsedTime.AsSeconds();
|
public SFML.Graphics.View View => view;
|
||||||
Clock.Restart();
|
|
||||||
|
|
||||||
RenderWindow.Clear(BackgroundColor);
|
/// <summary>
|
||||||
|
/// 是否仅渲染/导出选中骨骼
|
||||||
if (ShowAxis)
|
/// </summary>
|
||||||
{
|
public bool RenderSelectedOnly => renderSelectedOnly;
|
||||||
// 画一个很长的坐标轴, 用 1e9 比较合适
|
|
||||||
AxisVertex[0] = new(new(-1e9f, 0), AxisColor);
|
|
||||||
AxisVertex[1] = new(new(1e9f, 0), AxisColor);
|
|
||||||
RenderWindow.Draw(AxisVertex);
|
|
||||||
AxisVertex[0] = new(new(0, -1e9f), AxisColor);
|
|
||||||
AxisVertex[1] = new(new(0, 1e9f), AxisColor);
|
|
||||||
RenderWindow.Draw(AxisVertex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 渲染 Spine
|
|
||||||
if (SpineListView is not null)
|
|
||||||
{
|
|
||||||
lock (SpineListView.Spines)
|
|
||||||
{
|
|
||||||
foreach (var spine in SpineListView.Spines.Reverse())
|
|
||||||
{
|
|
||||||
spine.Update(delta);
|
|
||||||
RenderWindow.Draw(spine);
|
|
||||||
|
|
||||||
if (spine.IsSelected)
|
|
||||||
{
|
|
||||||
var bounds = spine.Bounds;
|
|
||||||
BoundsRect[0] = BoundsRect[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
|
||||||
BoundsRect[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
|
||||||
BoundsRect[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
|
||||||
BoundsRect[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
|
||||||
RenderWindow.Draw(BoundsRect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderWindow.Display();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
RenderWindow.SetActive(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,12 @@ namespace SpineViewer.Dialogs
|
|||||||
public AboutDialog()
|
public AboutDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
this.label_Version.Text = $"v{InformationalVersion}";
|
Text = $"关于 {Program.Name}";
|
||||||
|
label_Version.Text = $"v{InformationalVersion}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string InformationalVersion
|
public string InformationalVersion =>
|
||||||
{
|
Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
get
|
|
||||||
{
|
|
||||||
return Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void linkLabel_RepoUrl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
private void linkLabel_RepoUrl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -36,7 +32,7 @@ namespace SpineViewer.Dialogs
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Clipboard.SetText(url);
|
Clipboard.SetText(url);
|
||||||
MessageBox.Show(this, "链接已复制到剪贴板,请前往浏览器进行访问", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("链接已复制到剪贴板,请前往浏览器进行访问");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
SpineViewer/Dialogs/BatchOpenSpineDialog.Designer.cs
generated
98
SpineViewer/Dialogs/BatchOpenSpineDialog.Designer.cs
generated
@@ -37,10 +37,7 @@
|
|||||||
tableLayoutPanel2 = new TableLayoutPanel();
|
tableLayoutPanel2 = new TableLayoutPanel();
|
||||||
button_Ok = new Button();
|
button_Ok = new Button();
|
||||||
button_Cancel = new Button();
|
button_Cancel = new Button();
|
||||||
listBox_FilePath = new ListBox();
|
skelFileListBox = new SpineViewer.Controls.SkelFileListBox();
|
||||||
button_SelectSkel = new Button();
|
|
||||||
label_Tip = new Label();
|
|
||||||
openFileDialog_Skel = new OpenFileDialog();
|
|
||||||
panel.SuspendLayout();
|
panel.SuspendLayout();
|
||||||
tableLayoutPanel1.SuspendLayout();
|
tableLayoutPanel1.SuspendLayout();
|
||||||
tableLayoutPanel2.SuspendLayout();
|
tableLayoutPanel2.SuspendLayout();
|
||||||
@@ -53,7 +50,7 @@
|
|||||||
panel.Location = new Point(0, 0);
|
panel.Location = new Point(0, 0);
|
||||||
panel.Name = "panel";
|
panel.Name = "panel";
|
||||||
panel.Padding = new Padding(50, 15, 50, 10);
|
panel.Padding = new Padding(50, 15, 50, 10);
|
||||||
panel.Size = new Size(1126, 449);
|
panel.Size = new Size(1042, 472);
|
||||||
panel.TabIndex = 1;
|
panel.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// tableLayoutPanel1
|
// tableLayoutPanel1
|
||||||
@@ -62,22 +59,19 @@
|
|||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
||||||
tableLayoutPanel1.Controls.Add(label3, 0, 3);
|
tableLayoutPanel1.Controls.Add(label3, 0, 2);
|
||||||
tableLayoutPanel1.Controls.Add(comboBox_Version, 1, 3);
|
tableLayoutPanel1.Controls.Add(comboBox_Version, 1, 2);
|
||||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 4);
|
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 3);
|
||||||
tableLayoutPanel1.Controls.Add(listBox_FilePath, 0, 2);
|
tableLayoutPanel1.Controls.Add(skelFileListBox, 0, 1);
|
||||||
tableLayoutPanel1.Controls.Add(button_SelectSkel, 0, 1);
|
|
||||||
tableLayoutPanel1.Controls.Add(label_Tip, 1, 1);
|
|
||||||
tableLayoutPanel1.Dock = DockStyle.Fill;
|
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
tableLayoutPanel1.Location = new Point(50, 15);
|
tableLayoutPanel1.Location = new Point(50, 15);
|
||||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
tableLayoutPanel1.RowCount = 5;
|
tableLayoutPanel1.RowCount = 3;
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
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(942, 447);
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.Size = new Size(1026, 424);
|
|
||||||
tableLayoutPanel1.TabIndex = 1;
|
tableLayoutPanel1.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// label4
|
// label4
|
||||||
@@ -88,7 +82,7 @@
|
|||||||
label4.Location = new Point(15, 15);
|
label4.Location = new Point(15, 15);
|
||||||
label4.Margin = new Padding(15);
|
label4.Margin = new Padding(15);
|
||||||
label4.Name = "label4";
|
label4.Name = "label4";
|
||||||
label4.Size = new Size(996, 24);
|
label4.Size = new Size(912, 24);
|
||||||
label4.TabIndex = 14;
|
label4.TabIndex = 14;
|
||||||
label4.Text = "说明:批量导入只需要选择skel文件,atlas文件需要在同目录下并且与skel文件名相同";
|
label4.Text = "说明:批量导入只需要选择skel文件,atlas文件需要在同目录下并且与skel文件名相同";
|
||||||
label4.TextAlign = ContentAlignment.MiddleCenter;
|
label4.TextAlign = ContentAlignment.MiddleCenter;
|
||||||
@@ -97,7 +91,7 @@
|
|||||||
//
|
//
|
||||||
label3.Anchor = AnchorStyles.Right;
|
label3.Anchor = AnchorStyles.Right;
|
||||||
label3.AutoSize = true;
|
label3.AutoSize = true;
|
||||||
label3.Location = new Point(90, 307);
|
label3.Location = new Point(3, 343);
|
||||||
label3.Name = "label3";
|
label3.Name = "label3";
|
||||||
label3.Size = new Size(50, 24);
|
label3.Size = new Size(50, 24);
|
||||||
label3.TabIndex = 12;
|
label3.TabIndex = 12;
|
||||||
@@ -108,7 +102,7 @@
|
|||||||
comboBox_Version.Anchor = AnchorStyles.Left;
|
comboBox_Version.Anchor = AnchorStyles.Left;
|
||||||
comboBox_Version.DropDownStyle = ComboBoxStyle.DropDownList;
|
comboBox_Version.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||||
comboBox_Version.FormattingEnabled = true;
|
comboBox_Version.FormattingEnabled = true;
|
||||||
comboBox_Version.Location = new Point(146, 303);
|
comboBox_Version.Location = new Point(59, 339);
|
||||||
comboBox_Version.Name = "comboBox_Version";
|
comboBox_Version.Name = "comboBox_Version";
|
||||||
comboBox_Version.Size = new Size(182, 32);
|
comboBox_Version.Size = new Size(182, 32);
|
||||||
comboBox_Version.Sorted = true;
|
comboBox_Version.Sorted = true;
|
||||||
@@ -124,18 +118,19 @@
|
|||||||
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_Ok, 0, 0);
|
||||||
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
||||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
tableLayoutPanel2.Dock = DockStyle.Fill;
|
||||||
tableLayoutPanel2.Location = new Point(3, 381);
|
tableLayoutPanel2.Location = new Point(3, 404);
|
||||||
|
tableLayoutPanel2.Margin = new Padding(3, 30, 3, 3);
|
||||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||||
tableLayoutPanel2.RowCount = 1;
|
tableLayoutPanel2.RowCount = 1;
|
||||||
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
||||||
tableLayoutPanel2.Size = new Size(1020, 40);
|
tableLayoutPanel2.Size = new Size(936, 40);
|
||||||
tableLayoutPanel2.TabIndex = 11;
|
tableLayoutPanel2.TabIndex = 11;
|
||||||
//
|
//
|
||||||
// button_Ok
|
// button_Ok
|
||||||
//
|
//
|
||||||
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||||
button_Ok.Location = new Point(368, 3);
|
button_Ok.Location = new Point(326, 3);
|
||||||
button_Ok.Margin = new Padding(3, 3, 30, 3);
|
button_Ok.Margin = new Padding(3, 3, 30, 3);
|
||||||
button_Ok.Name = "button_Ok";
|
button_Ok.Name = "button_Ok";
|
||||||
button_Ok.Size = new Size(112, 34);
|
button_Ok.Size = new Size(112, 34);
|
||||||
@@ -147,7 +142,7 @@
|
|||||||
// button_Cancel
|
// button_Cancel
|
||||||
//
|
//
|
||||||
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
||||||
button_Cancel.Location = new Point(540, 3);
|
button_Cancel.Location = new Point(498, 3);
|
||||||
button_Cancel.Margin = new Padding(30, 3, 3, 3);
|
button_Cancel.Margin = new Padding(30, 3, 3, 3);
|
||||||
button_Cancel.Name = "button_Cancel";
|
button_Cancel.Name = "button_Cancel";
|
||||||
button_Cancel.Size = new Size(112, 34);
|
button_Cancel.Size = new Size(112, 34);
|
||||||
@@ -156,47 +151,14 @@
|
|||||||
button_Cancel.UseVisualStyleBackColor = true;
|
button_Cancel.UseVisualStyleBackColor = true;
|
||||||
button_Cancel.Click += button_Cancel_Click;
|
button_Cancel.Click += button_Cancel_Click;
|
||||||
//
|
//
|
||||||
// listBox_FilePath
|
// skelFileListBox
|
||||||
//
|
//
|
||||||
tableLayoutPanel1.SetColumnSpan(listBox_FilePath, 2);
|
tableLayoutPanel1.SetColumnSpan(skelFileListBox, 2);
|
||||||
listBox_FilePath.Dock = DockStyle.Fill;
|
skelFileListBox.Dock = DockStyle.Fill;
|
||||||
listBox_FilePath.FormattingEnabled = true;
|
skelFileListBox.Location = new Point(3, 57);
|
||||||
listBox_FilePath.HorizontalScrollbar = true;
|
skelFileListBox.Name = "skelFileListBox";
|
||||||
listBox_FilePath.ItemHeight = 24;
|
skelFileListBox.Size = new Size(936, 276);
|
||||||
listBox_FilePath.Location = new Point(3, 97);
|
skelFileListBox.TabIndex = 15;
|
||||||
listBox_FilePath.Name = "listBox_FilePath";
|
|
||||||
listBox_FilePath.Size = new Size(1020, 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(877, 40);
|
|
||||||
label_Tip.TabIndex = 0;
|
|
||||||
label_Tip.Text = "已选择 0 个文件";
|
|
||||||
label_Tip.TextAlign = ContentAlignment.MiddleLeft;
|
|
||||||
//
|
|
||||||
// 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文件";
|
|
||||||
//
|
//
|
||||||
// BatchOpenSpineDialog
|
// BatchOpenSpineDialog
|
||||||
//
|
//
|
||||||
@@ -204,7 +166,7 @@
|
|||||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
CancelButton = button_Cancel;
|
CancelButton = button_Cancel;
|
||||||
ClientSize = new Size(1126, 449);
|
ClientSize = new Size(1042, 472);
|
||||||
Controls.Add(panel);
|
Controls.Add(panel);
|
||||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||||
@@ -214,7 +176,6 @@
|
|||||||
ShowInTaskbar = false;
|
ShowInTaskbar = false;
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
Text = "批量打开骨骼";
|
Text = "批量打开骨骼";
|
||||||
Load += BatchOpenSpineDialog_Load;
|
|
||||||
panel.ResumeLayout(false);
|
panel.ResumeLayout(false);
|
||||||
tableLayoutPanel1.ResumeLayout(false);
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
tableLayoutPanel1.PerformLayout();
|
tableLayoutPanel1.PerformLayout();
|
||||||
@@ -225,15 +186,12 @@
|
|||||||
#endregion
|
#endregion
|
||||||
private Panel panel;
|
private Panel panel;
|
||||||
private TableLayoutPanel tableLayoutPanel1;
|
private TableLayoutPanel tableLayoutPanel1;
|
||||||
private Label label_Tip;
|
|
||||||
private ListBox listBox_FilePath;
|
|
||||||
private Button button_SelectSkel;
|
|
||||||
private TableLayoutPanel tableLayoutPanel2;
|
private TableLayoutPanel tableLayoutPanel2;
|
||||||
private Button button_Ok;
|
private Button button_Ok;
|
||||||
private Button button_Cancel;
|
private Button button_Cancel;
|
||||||
private Label label3;
|
private Label label3;
|
||||||
private ComboBox comboBox_Version;
|
private ComboBox comboBox_Version;
|
||||||
private OpenFileDialog openFileDialog_Skel;
|
|
||||||
private Label label4;
|
private Label label4;
|
||||||
|
private Controls.SkelFileListBox skelFileListBox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,59 +13,48 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class BatchOpenSpineDialog : Form
|
public partial class BatchOpenSpineDialog : Form
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 对话框结果, 取消时为 null
|
||||||
|
/// </summary>
|
||||||
public BatchOpenSpineDialogResult Result { get; private set; }
|
public BatchOpenSpineDialogResult Result { get; private set; }
|
||||||
|
|
||||||
public BatchOpenSpineDialog()
|
public BatchOpenSpineDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
comboBox_Version.DataSource = VersionHelper.Versions.ToList();
|
comboBox_Version.DataSource = VersionHelper.Names.ToList();
|
||||||
comboBox_Version.DisplayMember = "Value";
|
comboBox_Version.DisplayMember = "Value";
|
||||||
comboBox_Version.ValueMember = "Key";
|
comboBox_Version.ValueMember = "Key";
|
||||||
comboBox_Version.SelectedValue = Spine.Version.Auto;
|
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)
|
|
||||||
{
|
|
||||||
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)
|
private void button_Ok_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var version = (Spine.Version)comboBox_Version.SelectedValue;
|
var version = (Spine.Version)comboBox_Version.SelectedValue;
|
||||||
|
|
||||||
if (listBox_FilePath.Items.Count <= 0)
|
var items = skelFileListBox.Items;
|
||||||
|
|
||||||
|
if (items.Count <= 0)
|
||||||
{
|
{
|
||||||
MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("未选择任何文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string p in listBox_FilePath.Items)
|
foreach (string p in items)
|
||||||
{
|
{
|
||||||
if (!File.Exists(p))
|
if (!File.Exists(p))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{p}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{p}", "skel文件不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{version.GetName()} 版本尚未实现(咕咕咕~)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result = new(version, listBox_FilePath.Items.Cast<string>().ToArray());
|
Result = new(version, items.Cast<string>().ToArray());
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,9 +64,19 @@ namespace SpineViewer.Dialogs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量打开对话框结果
|
||||||
|
/// </summary>
|
||||||
public class BatchOpenSpineDialogResult(Spine.Version version, string[] skelPaths)
|
public class BatchOpenSpineDialogResult(Spine.Version version, string[] skelPaths)
|
||||||
{
|
{
|
||||||
public Spine.Version Version { get; } = version;
|
/// <summary>
|
||||||
public string[] SkelPaths { get; } = skelPaths;
|
/// 版本
|
||||||
|
/// </summary>
|
||||||
|
public Spine.Version Version => version;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 路径列表
|
||||||
|
/// </summary>
|
||||||
|
public string[] SkelPaths => skelPaths;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,9 +117,6 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<metadata name="openFileDialog_Skel.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
|
||||||
<value>92, 26</value>
|
|
||||||
</metadata>
|
|
||||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
<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">
|
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
<value>
|
<value>
|
||||||
|
|||||||
171
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
171
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
@@ -31,6 +31,7 @@
|
|||||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConvertFileFormatDialog));
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConvertFileFormatDialog));
|
||||||
panel = new Panel();
|
panel = new Panel();
|
||||||
tableLayoutPanel1 = new TableLayoutPanel();
|
tableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
comboBox_TargetVersion = new ComboBox();
|
||||||
flowLayoutPanel_TargetFormat = new FlowLayoutPanel();
|
flowLayoutPanel_TargetFormat = new FlowLayoutPanel();
|
||||||
radioButton_BinaryTarget = new RadioButton();
|
radioButton_BinaryTarget = new RadioButton();
|
||||||
radioButton_JsonTarget = new RadioButton();
|
radioButton_JsonTarget = new RadioButton();
|
||||||
@@ -41,19 +42,13 @@
|
|||||||
tableLayoutPanel2 = new TableLayoutPanel();
|
tableLayoutPanel2 = new TableLayoutPanel();
|
||||||
button_Ok = new Button();
|
button_Ok = new Button();
|
||||||
button_Cancel = new Button();
|
button_Cancel = new Button();
|
||||||
listBox_FilePath = new ListBox();
|
|
||||||
button_SelectSkel = new Button();
|
|
||||||
label_Tip = new Label();
|
|
||||||
label2 = new Label();
|
label2 = new Label();
|
||||||
flowLayoutPanel_SourceFormat = new FlowLayoutPanel();
|
skelFileListBox = new SpineViewer.Controls.SkelFileListBox();
|
||||||
radioButton_BinarySource = new RadioButton();
|
|
||||||
radioButton_JsonSource = new RadioButton();
|
|
||||||
openFileDialog_Skel = new OpenFileDialog();
|
openFileDialog_Skel = new OpenFileDialog();
|
||||||
panel.SuspendLayout();
|
panel.SuspendLayout();
|
||||||
tableLayoutPanel1.SuspendLayout();
|
tableLayoutPanel1.SuspendLayout();
|
||||||
flowLayoutPanel_TargetFormat.SuspendLayout();
|
flowLayoutPanel_TargetFormat.SuspendLayout();
|
||||||
tableLayoutPanel2.SuspendLayout();
|
tableLayoutPanel2.SuspendLayout();
|
||||||
flowLayoutPanel_SourceFormat.SuspendLayout();
|
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// panel
|
// panel
|
||||||
@@ -63,7 +58,7 @@
|
|||||||
panel.Location = new Point(0, 0);
|
panel.Location = new Point(0, 0);
|
||||||
panel.Name = "panel";
|
panel.Name = "panel";
|
||||||
panel.Padding = new Padding(50, 15, 50, 10);
|
panel.Padding = new Padding(50, 15, 50, 10);
|
||||||
panel.Size = new Size(1039, 530);
|
panel.Size = new Size(1051, 538);
|
||||||
panel.TabIndex = 2;
|
panel.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// tableLayoutPanel1
|
// tableLayoutPanel1
|
||||||
@@ -71,40 +66,48 @@
|
|||||||
tableLayoutPanel1.ColumnCount = 2;
|
tableLayoutPanel1.ColumnCount = 2;
|
||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||||
tableLayoutPanel1.Controls.Add(flowLayoutPanel_TargetFormat, 1, 5);
|
tableLayoutPanel1.Controls.Add(comboBox_TargetVersion, 1, 3);
|
||||||
tableLayoutPanel1.Controls.Add(label1, 0, 4);
|
tableLayoutPanel1.Controls.Add(flowLayoutPanel_TargetFormat, 1, 4);
|
||||||
|
tableLayoutPanel1.Controls.Add(label1, 0, 3);
|
||||||
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
||||||
tableLayoutPanel1.Controls.Add(label3, 0, 3);
|
tableLayoutPanel1.Controls.Add(label3, 0, 2);
|
||||||
tableLayoutPanel1.Controls.Add(comboBox_SourceVersion, 1, 3);
|
tableLayoutPanel1.Controls.Add(comboBox_SourceVersion, 1, 2);
|
||||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 6);
|
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 5);
|
||||||
tableLayoutPanel1.Controls.Add(listBox_FilePath, 0, 2);
|
tableLayoutPanel1.Controls.Add(label2, 0, 4);
|
||||||
tableLayoutPanel1.Controls.Add(button_SelectSkel, 0, 1);
|
tableLayoutPanel1.Controls.Add(skelFileListBox, 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.Dock = DockStyle.Fill;
|
||||||
tableLayoutPanel1.Location = new Point(50, 15);
|
tableLayoutPanel1.Location = new Point(50, 15);
|
||||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
tableLayoutPanel1.RowCount = 7;
|
tableLayoutPanel1.RowCount = 6;
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
|
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||||
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.RowStyles.Add(new RowStyle());
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
tableLayoutPanel1.Size = new Size(951, 513);
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.Size = new Size(939, 505);
|
|
||||||
tableLayoutPanel1.TabIndex = 1;
|
tableLayoutPanel1.TabIndex = 1;
|
||||||
//
|
//
|
||||||
|
// comboBox_TargetVersion
|
||||||
|
//
|
||||||
|
comboBox_TargetVersion.Anchor = AnchorStyles.Left;
|
||||||
|
comboBox_TargetVersion.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||||
|
comboBox_TargetVersion.FormattingEnabled = true;
|
||||||
|
comboBox_TargetVersion.Location = new Point(95, 365);
|
||||||
|
comboBox_TargetVersion.Name = "comboBox_TargetVersion";
|
||||||
|
comboBox_TargetVersion.Size = new Size(182, 32);
|
||||||
|
comboBox_TargetVersion.Sorted = true;
|
||||||
|
comboBox_TargetVersion.TabIndex = 21;
|
||||||
|
//
|
||||||
// flowLayoutPanel_TargetFormat
|
// flowLayoutPanel_TargetFormat
|
||||||
//
|
//
|
||||||
flowLayoutPanel_TargetFormat.AutoSize = true;
|
flowLayoutPanel_TargetFormat.AutoSize = true;
|
||||||
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_BinaryTarget);
|
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_BinaryTarget);
|
||||||
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_JsonTarget);
|
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_JsonTarget);
|
||||||
flowLayoutPanel_TargetFormat.Dock = DockStyle.Fill;
|
flowLayoutPanel_TargetFormat.Dock = DockStyle.Fill;
|
||||||
flowLayoutPanel_TargetFormat.Location = new Point(146, 381);
|
flowLayoutPanel_TargetFormat.Location = new Point(95, 403);
|
||||||
flowLayoutPanel_TargetFormat.Name = "flowLayoutPanel_TargetFormat";
|
flowLayoutPanel_TargetFormat.Name = "flowLayoutPanel_TargetFormat";
|
||||||
flowLayoutPanel_TargetFormat.Size = new Size(790, 34);
|
flowLayoutPanel_TargetFormat.Size = new Size(853, 34);
|
||||||
flowLayoutPanel_TargetFormat.TabIndex = 19;
|
flowLayoutPanel_TargetFormat.TabIndex = 19;
|
||||||
//
|
//
|
||||||
// radioButton_BinaryTarget
|
// radioButton_BinaryTarget
|
||||||
@@ -116,7 +119,6 @@
|
|||||||
radioButton_BinaryTarget.TabIndex = 17;
|
radioButton_BinaryTarget.TabIndex = 17;
|
||||||
radioButton_BinaryTarget.Text = "二进制 (*.skel)";
|
radioButton_BinaryTarget.Text = "二进制 (*.skel)";
|
||||||
radioButton_BinaryTarget.UseVisualStyleBackColor = true;
|
radioButton_BinaryTarget.UseVisualStyleBackColor = true;
|
||||||
radioButton_BinaryTarget.CheckedChanged += radioButton_Target_CheckedChanged;
|
|
||||||
//
|
//
|
||||||
// radioButton_JsonTarget
|
// radioButton_JsonTarget
|
||||||
//
|
//
|
||||||
@@ -129,17 +131,16 @@
|
|||||||
radioButton_JsonTarget.TabStop = true;
|
radioButton_JsonTarget.TabStop = true;
|
||||||
radioButton_JsonTarget.Text = "文本 (*.json)";
|
radioButton_JsonTarget.Text = "文本 (*.json)";
|
||||||
radioButton_JsonTarget.UseVisualStyleBackColor = true;
|
radioButton_JsonTarget.UseVisualStyleBackColor = true;
|
||||||
radioButton_JsonTarget.CheckedChanged += radioButton_Target_CheckedChanged;
|
|
||||||
//
|
//
|
||||||
// label1
|
// label1
|
||||||
//
|
//
|
||||||
label1.Anchor = AnchorStyles.Right;
|
label1.Anchor = AnchorStyles.Right;
|
||||||
label1.AutoSize = true;
|
label1.AutoSize = true;
|
||||||
label1.Location = new Point(72, 346);
|
label1.Location = new Point(3, 369);
|
||||||
label1.Name = "label1";
|
label1.Name = "label1";
|
||||||
label1.Size = new Size(68, 24);
|
label1.Size = new Size(86, 24);
|
||||||
label1.TabIndex = 15;
|
label1.TabIndex = 15;
|
||||||
label1.Text = "源格式:";
|
label1.Text = "目标版本:";
|
||||||
//
|
//
|
||||||
// label4
|
// label4
|
||||||
//
|
//
|
||||||
@@ -149,7 +150,7 @@
|
|||||||
label4.Location = new Point(15, 15);
|
label4.Location = new Point(15, 15);
|
||||||
label4.Margin = new Padding(15);
|
label4.Margin = new Padding(15);
|
||||||
label4.Name = "label4";
|
label4.Name = "label4";
|
||||||
label4.Size = new Size(909, 24);
|
label4.Size = new Size(921, 24);
|
||||||
label4.TabIndex = 14;
|
label4.TabIndex = 14;
|
||||||
label4.Text = "说明:将在每个文件同级目录下生成目标格式后缀的文件,会覆盖已存在文件";
|
label4.Text = "说明:将在每个文件同级目录下生成目标格式后缀的文件,会覆盖已存在文件";
|
||||||
label4.TextAlign = ContentAlignment.MiddleCenter;
|
label4.TextAlign = ContentAlignment.MiddleCenter;
|
||||||
@@ -158,19 +159,19 @@
|
|||||||
//
|
//
|
||||||
label3.Anchor = AnchorStyles.Right;
|
label3.Anchor = AnchorStyles.Right;
|
||||||
label3.AutoSize = true;
|
label3.AutoSize = true;
|
||||||
label3.Location = new Point(72, 307);
|
label3.Location = new Point(21, 331);
|
||||||
label3.Name = "label3";
|
label3.Name = "label3";
|
||||||
label3.Size = new Size(68, 24);
|
label3.Size = new Size(68, 24);
|
||||||
label3.TabIndex = 12;
|
label3.TabIndex = 12;
|
||||||
label3.Text = "源版本:";
|
label3.Text = "源版本:";
|
||||||
//
|
//
|
||||||
// comboBox_Version
|
// comboBox_SourceVersion
|
||||||
//
|
//
|
||||||
comboBox_SourceVersion.Anchor = AnchorStyles.Left;
|
comboBox_SourceVersion.Anchor = AnchorStyles.Left;
|
||||||
comboBox_SourceVersion.DropDownStyle = ComboBoxStyle.DropDownList;
|
comboBox_SourceVersion.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||||
comboBox_SourceVersion.FormattingEnabled = true;
|
comboBox_SourceVersion.FormattingEnabled = true;
|
||||||
comboBox_SourceVersion.Location = new Point(146, 303);
|
comboBox_SourceVersion.Location = new Point(95, 327);
|
||||||
comboBox_SourceVersion.Name = "comboBox_Version";
|
comboBox_SourceVersion.Name = "comboBox_SourceVersion";
|
||||||
comboBox_SourceVersion.Size = new Size(182, 32);
|
comboBox_SourceVersion.Size = new Size(182, 32);
|
||||||
comboBox_SourceVersion.Sorted = true;
|
comboBox_SourceVersion.Sorted = true;
|
||||||
comboBox_SourceVersion.TabIndex = 13;
|
comboBox_SourceVersion.TabIndex = 13;
|
||||||
@@ -185,18 +186,19 @@
|
|||||||
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_Ok, 0, 0);
|
||||||
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
||||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
tableLayoutPanel2.Dock = DockStyle.Fill;
|
||||||
tableLayoutPanel2.Location = new Point(3, 462);
|
tableLayoutPanel2.Location = new Point(3, 470);
|
||||||
|
tableLayoutPanel2.Margin = new Padding(3, 30, 3, 3);
|
||||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||||
tableLayoutPanel2.RowCount = 1;
|
tableLayoutPanel2.RowCount = 1;
|
||||||
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
||||||
tableLayoutPanel2.Size = new Size(933, 40);
|
tableLayoutPanel2.Size = new Size(945, 40);
|
||||||
tableLayoutPanel2.TabIndex = 11;
|
tableLayoutPanel2.TabIndex = 11;
|
||||||
//
|
//
|
||||||
// button_Ok
|
// button_Ok
|
||||||
//
|
//
|
||||||
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||||
button_Ok.Location = new Point(324, 3);
|
button_Ok.Location = new Point(330, 3);
|
||||||
button_Ok.Margin = new Padding(3, 3, 30, 3);
|
button_Ok.Margin = new Padding(3, 3, 30, 3);
|
||||||
button_Ok.Name = "button_Ok";
|
button_Ok.Name = "button_Ok";
|
||||||
button_Ok.Size = new Size(112, 34);
|
button_Ok.Size = new Size(112, 34);
|
||||||
@@ -208,7 +210,7 @@
|
|||||||
// button_Cancel
|
// button_Cancel
|
||||||
//
|
//
|
||||||
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
||||||
button_Cancel.Location = new Point(496, 3);
|
button_Cancel.Location = new Point(502, 3);
|
||||||
button_Cancel.Margin = new Padding(30, 3, 3, 3);
|
button_Cancel.Margin = new Padding(30, 3, 3, 3);
|
||||||
button_Cancel.Name = "button_Cancel";
|
button_Cancel.Name = "button_Cancel";
|
||||||
button_Cancel.Size = new Size(112, 34);
|
button_Cancel.Size = new Size(112, 34);
|
||||||
@@ -217,84 +219,24 @@
|
|||||||
button_Cancel.UseVisualStyleBackColor = true;
|
button_Cancel.UseVisualStyleBackColor = true;
|
||||||
button_Cancel.Click += button_Cancel_Click;
|
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
|
||||||
//
|
//
|
||||||
label2.Anchor = AnchorStyles.Right;
|
label2.Anchor = AnchorStyles.Right;
|
||||||
label2.AutoSize = true;
|
label2.AutoSize = true;
|
||||||
label2.Location = new Point(54, 386);
|
label2.Location = new Point(3, 408);
|
||||||
label2.Name = "label2";
|
label2.Name = "label2";
|
||||||
label2.Size = new Size(86, 24);
|
label2.Size = new Size(86, 24);
|
||||||
label2.TabIndex = 16;
|
label2.TabIndex = 16;
|
||||||
label2.Text = "目标格式:";
|
label2.Text = "目标格式:";
|
||||||
//
|
//
|
||||||
// flowLayoutPanel_SourceFormat
|
// skelFileListBox
|
||||||
//
|
//
|
||||||
flowLayoutPanel_SourceFormat.AutoSize = true;
|
tableLayoutPanel1.SetColumnSpan(skelFileListBox, 2);
|
||||||
flowLayoutPanel_SourceFormat.Controls.Add(radioButton_BinarySource);
|
skelFileListBox.Dock = DockStyle.Fill;
|
||||||
flowLayoutPanel_SourceFormat.Controls.Add(radioButton_JsonSource);
|
skelFileListBox.Location = new Point(3, 57);
|
||||||
flowLayoutPanel_SourceFormat.Dock = DockStyle.Fill;
|
skelFileListBox.Name = "skelFileListBox";
|
||||||
flowLayoutPanel_SourceFormat.Location = new Point(146, 341);
|
skelFileListBox.Size = new Size(945, 264);
|
||||||
flowLayoutPanel_SourceFormat.Name = "flowLayoutPanel_SourceFormat";
|
skelFileListBox.TabIndex = 20;
|
||||||
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
|
||||||
//
|
//
|
||||||
@@ -310,7 +252,7 @@
|
|||||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
CancelButton = button_Cancel;
|
CancelButton = button_Cancel;
|
||||||
ClientSize = new Size(1039, 530);
|
ClientSize = new Size(1051, 538);
|
||||||
Controls.Add(panel);
|
Controls.Add(panel);
|
||||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||||
@@ -320,15 +262,12 @@
|
|||||||
ShowInTaskbar = false;
|
ShowInTaskbar = false;
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
Text = "骨骼文件格式转换";
|
Text = "骨骼文件格式转换";
|
||||||
Load += ConvertFileFormatDialog_Load;
|
|
||||||
panel.ResumeLayout(false);
|
panel.ResumeLayout(false);
|
||||||
tableLayoutPanel1.ResumeLayout(false);
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
tableLayoutPanel1.PerformLayout();
|
tableLayoutPanel1.PerformLayout();
|
||||||
flowLayoutPanel_TargetFormat.ResumeLayout(false);
|
flowLayoutPanel_TargetFormat.ResumeLayout(false);
|
||||||
flowLayoutPanel_TargetFormat.PerformLayout();
|
flowLayoutPanel_TargetFormat.PerformLayout();
|
||||||
tableLayoutPanel2.ResumeLayout(false);
|
tableLayoutPanel2.ResumeLayout(false);
|
||||||
flowLayoutPanel_SourceFormat.ResumeLayout(false);
|
|
||||||
flowLayoutPanel_SourceFormat.PerformLayout();
|
|
||||||
ResumeLayout(false);
|
ResumeLayout(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,17 +281,13 @@
|
|||||||
private TableLayoutPanel tableLayoutPanel2;
|
private TableLayoutPanel tableLayoutPanel2;
|
||||||
private Button button_Ok;
|
private Button button_Ok;
|
||||||
private Button button_Cancel;
|
private Button button_Cancel;
|
||||||
private ListBox listBox_FilePath;
|
|
||||||
private Button button_SelectSkel;
|
|
||||||
private Label label_Tip;
|
|
||||||
private OpenFileDialog openFileDialog_Skel;
|
private OpenFileDialog openFileDialog_Skel;
|
||||||
private Label label1;
|
private Label label1;
|
||||||
private Label label2;
|
private Label label2;
|
||||||
private RadioButton radioButton_BinarySource;
|
|
||||||
private FlowLayoutPanel flowLayoutPanel_SourceFormat;
|
|
||||||
private RadioButton radioButton_JsonSource;
|
|
||||||
private FlowLayoutPanel flowLayoutPanel_TargetFormat;
|
private FlowLayoutPanel flowLayoutPanel_TargetFormat;
|
||||||
private RadioButton radioButton_BinaryTarget;
|
private RadioButton radioButton_BinaryTarget;
|
||||||
private RadioButton radioButton_JsonTarget;
|
private RadioButton radioButton_JsonTarget;
|
||||||
|
private Controls.SkelFileListBox skelFileListBox;
|
||||||
|
private ComboBox comboBox_TargetVersion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,92 +13,65 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class ConvertFileFormatDialog : Form
|
public partial class ConvertFileFormatDialog : Form
|
||||||
{
|
{
|
||||||
public string[] SkelPaths { get; private set; }
|
/// <summary>
|
||||||
public Spine.Version SourceVersion { get; private set; }
|
/// 对话框结果, 取消时为 null
|
||||||
public Spine.Version TargetVersion { get; private set; }
|
/// </summary>
|
||||||
public bool JsonSource { get; private set; }
|
public ConvertFileFormatDialogResult Result { get; private set; }
|
||||||
public bool JsonTarget { get; private set; }
|
|
||||||
|
|
||||||
public ConvertFileFormatDialog()
|
public ConvertFileFormatDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
// XXX: 文件格式转换暂时不支持自动检测版本
|
comboBox_SourceVersion.DataSource = VersionHelper.Names.ToList();
|
||||||
var impVersions = VersionHelper.Versions.ToDictionary();
|
|
||||||
impVersions.Remove(Spine.Version.Auto);
|
|
||||||
|
|
||||||
comboBox_SourceVersion.DataSource = impVersions.ToList();
|
|
||||||
comboBox_SourceVersion.DisplayMember = "Value";
|
comboBox_SourceVersion.DisplayMember = "Value";
|
||||||
comboBox_SourceVersion.ValueMember = "Key";
|
comboBox_SourceVersion.ValueMember = "Key";
|
||||||
comboBox_SourceVersion.SelectedValue = Spine.Version.V38;
|
comboBox_SourceVersion.SelectedValue = Spine.Version.Auto;
|
||||||
//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)
|
// 目标版本不包含自动
|
||||||
{
|
var versionsWithoutAuto = VersionHelper.Names.ToDictionary();
|
||||||
button_SelectSkel_Click(sender, e);
|
versionsWithoutAuto.Remove(Spine.Version.Auto);
|
||||||
}
|
comboBox_TargetVersion.DataSource = versionsWithoutAuto.ToList();
|
||||||
|
comboBox_TargetVersion.DisplayMember = "Value";
|
||||||
private void button_SelectSkel_Click(object sender, EventArgs e)
|
comboBox_TargetVersion.ValueMember = "Key";
|
||||||
{
|
comboBox_TargetVersion.SelectedValue = Spine.Version.V38;
|
||||||
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)
|
private void button_Ok_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var sourceVersion = (Spine.Version)comboBox_SourceVersion.SelectedValue;
|
var sourceVersion = (Spine.Version)comboBox_SourceVersion.SelectedValue;
|
||||||
var targetVersion = (Spine.Version)comboBox_SourceVersion.SelectedValue; // TODO: 增加目标版本
|
var targetVersion = (Spine.Version)comboBox_TargetVersion.SelectedValue;
|
||||||
var jsonSource = radioButton_JsonSource.Checked;
|
|
||||||
var jsonTarget = radioButton_JsonTarget.Checked;
|
var jsonTarget = radioButton_JsonTarget.Checked;
|
||||||
|
|
||||||
if (listBox_FilePath.Items.Count <= 0)
|
var items = skelFileListBox.Items;
|
||||||
|
|
||||||
|
if (items.Count <= 0)
|
||||||
{
|
{
|
||||||
MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("未选择任何文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string p in listBox_FilePath.Items)
|
foreach (string p in items)
|
||||||
{
|
{
|
||||||
if (!File.Exists(p))
|
if (!File.Exists(p))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{p}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{p}", "skel文件不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SkeletonConverter.ImplementedVersions.Contains(sourceVersion))
|
if (sourceVersion != Spine.Version.Auto && !SkeletonConverter.ImplementedVersions.Contains(sourceVersion))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{sourceVersion.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{sourceVersion.GetName()} 版本尚未实现(咕咕咕~)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SkeletonConverter.ImplementedVersions.Contains(targetVersion))
|
if (!SkeletonConverter.ImplementedVersions.Contains(targetVersion))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{targetVersion.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{targetVersion.GetName()} 版本尚未实现(咕咕咕~)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jsonSource == jsonTarget && sourceVersion == targetVersion)
|
Result = new(items.Cast<string>().ToArray(), sourceVersion, targetVersion, jsonTarget);
|
||||||
{
|
|
||||||
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;
|
DialogResult = DialogResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,21 +79,31 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
DialogResult = DialogResult.Cancel;
|
DialogResult = DialogResult.Cancel;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void radioButton_Source_CheckedChanged(object sender, EventArgs e)
|
/// <summary>
|
||||||
{
|
/// 文件格式转换对话框结果包装类
|
||||||
if (radioButton_BinarySource.Checked)
|
/// </summary>
|
||||||
radioButton_JsonTarget.Checked = true;
|
public class ConvertFileFormatDialogResult(string[] skelPaths, Spine.Version sourceVersion, Spine.Version targetVersion, bool jsonTarget)
|
||||||
else
|
{
|
||||||
radioButton_BinaryTarget.Checked = true;
|
/// <summary>
|
||||||
}
|
/// 骨骼文件路径列表
|
||||||
|
/// </summary>
|
||||||
|
public string[] SkelPaths => skelPaths;
|
||||||
|
|
||||||
private void radioButton_Target_CheckedChanged(object sender, EventArgs e)
|
/// <summary>
|
||||||
{
|
/// 源版本
|
||||||
if (radioButton_BinaryTarget.Checked)
|
/// </summary>
|
||||||
radioButton_JsonSource.Checked = true;
|
public Spine.Version SourceVersion => sourceVersion;
|
||||||
else
|
|
||||||
radioButton_BinarySource.Checked = true;
|
/// <summary>
|
||||||
}
|
/// 目标版本
|
||||||
|
/// </summary>
|
||||||
|
public Spine.Version TargetVersion => targetVersion;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标格式是否为 Json
|
||||||
|
/// </summary>
|
||||||
|
public bool JsonTarget => jsonTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,11 +82,11 @@ namespace SpineViewer.Dialogs
|
|||||||
|
|
||||||
private void button_Copy_Click(object sender, EventArgs e)
|
private void button_Copy_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var selectedObject = propertyGrid.SelectedObject as DiagnosticsInformation;
|
var selectedObject = (DiagnosticsInformation)propertyGrid.SelectedObject;
|
||||||
var properties = selectedObject.GetType().GetProperties();
|
var properties = selectedObject.GetType().GetProperties();
|
||||||
var result = string.Join(Environment.NewLine, properties.Select(p => $"{p.Name}\t{p.GetValue(selectedObject)?.ToString()}"));
|
var result = string.Join(Environment.NewLine, properties.Select(p => $"{p.Name}\t{p.GetValue(selectedObject)?.ToString()}"));
|
||||||
Clipboard.SetText(result);
|
Clipboard.SetText(result);
|
||||||
MessageBox.Show(this, "已复制", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("已复制");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class ExportPngDialog : Form
|
public partial class ExportPngDialog : Form
|
||||||
{
|
{
|
||||||
|
// TODO: 该对话框要合并到统一的导出参数对话框
|
||||||
|
// TODO: 使用结果包装类
|
||||||
public string OutputDir { get; private set; }
|
public string OutputDir { get; private set; }
|
||||||
public float Duration { get; private set; }
|
public float Duration { get; private set; }
|
||||||
public uint Fps { get; private set; }
|
public uint Fps { get; private set; }
|
||||||
@@ -40,27 +42,23 @@ namespace SpineViewer.Dialogs
|
|||||||
var outputDir = textBox_OutputDir.Text;
|
var outputDir = textBox_OutputDir.Text;
|
||||||
if (File.Exists(outputDir))
|
if (File.Exists(outputDir))
|
||||||
{
|
{
|
||||||
MessageBox.Show("输出文件夹无效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("输出文件夹无效");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Directory.Exists(outputDir))
|
if (!Directory.Exists(outputDir))
|
||||||
{
|
{
|
||||||
if (MessageBox.Show($"文件夹 {outputDir} 不存在,是否创建?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
|
if (MessageBox.Quest($"文件夹 {outputDir} 不存在,是否创建?") != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
Directory.CreateDirectory(outputDir);
|
||||||
{
|
|
||||||
Directory.CreateDirectory(outputDir);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Program.Logger.Error(ex.ToString());
|
|
||||||
MessageBox.Show(ex.ToString(), "文件夹创建失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
MessageBox.Error(ex.ToString(), "文件夹创建失败");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
105
SpineViewer/Dialogs/ExportPreviewDialog.Designer.cs
generated
105
SpineViewer/Dialogs/ExportPreviewDialog.Designer.cs
generated
@@ -31,23 +31,18 @@
|
|||||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ExportPreviewDialog));
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ExportPreviewDialog));
|
||||||
panel1 = new Panel();
|
panel1 = new Panel();
|
||||||
tableLayoutPanel1 = new TableLayoutPanel();
|
tableLayoutPanel1 = new TableLayoutPanel();
|
||||||
|
propertyGrid = new PropertyGrid();
|
||||||
label4 = new Label();
|
label4 = new Label();
|
||||||
label1 = new Label();
|
label1 = new Label();
|
||||||
label2 = new Label();
|
|
||||||
label3 = new Label();
|
|
||||||
textBox_OutputDir = new TextBox();
|
textBox_OutputDir = new TextBox();
|
||||||
button_SelectOutputDir = new Button();
|
button_SelectOutputDir = new Button();
|
||||||
tableLayoutPanel2 = new TableLayoutPanel();
|
tableLayoutPanel2 = new TableLayoutPanel();
|
||||||
button_Ok = new Button();
|
button_Ok = new Button();
|
||||||
button_Cancel = new Button();
|
button_Cancel = new Button();
|
||||||
numericUpDown_Width = new NumericUpDown();
|
|
||||||
numericUpDown_Height = new NumericUpDown();
|
|
||||||
folderBrowserDialog = new FolderBrowserDialog();
|
folderBrowserDialog = new FolderBrowserDialog();
|
||||||
panel1.SuspendLayout();
|
panel1.SuspendLayout();
|
||||||
tableLayoutPanel1.SuspendLayout();
|
tableLayoutPanel1.SuspendLayout();
|
||||||
tableLayoutPanel2.SuspendLayout();
|
tableLayoutPanel2.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Width).BeginInit();
|
|
||||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Height).BeginInit();
|
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// panel1
|
// panel1
|
||||||
@@ -57,7 +52,7 @@
|
|||||||
panel1.Location = new Point(0, 0);
|
panel1.Location = new Point(0, 0);
|
||||||
panel1.Name = "panel1";
|
panel1.Name = "panel1";
|
||||||
panel1.Padding = new Padding(50, 15, 50, 10);
|
panel1.Padding = new Padding(50, 15, 50, 10);
|
||||||
panel1.Size = new Size(919, 276);
|
panel1.Size = new Size(914, 482);
|
||||||
panel1.TabIndex = 2;
|
panel1.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// tableLayoutPanel1
|
// tableLayoutPanel1
|
||||||
@@ -68,27 +63,34 @@
|
|||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||||
|
tableLayoutPanel1.Controls.Add(propertyGrid, 0, 2);
|
||||||
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
||||||
tableLayoutPanel1.Controls.Add(label1, 0, 1);
|
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(textBox_OutputDir, 1, 1);
|
||||||
tableLayoutPanel1.Controls.Add(button_SelectOutputDir, 3, 1);
|
tableLayoutPanel1.Controls.Add(button_SelectOutputDir, 3, 1);
|
||||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 4);
|
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 3);
|
||||||
tableLayoutPanel1.Controls.Add(numericUpDown_Width, 1, 2);
|
|
||||||
tableLayoutPanel1.Controls.Add(numericUpDown_Height, 1, 3);
|
|
||||||
tableLayoutPanel1.Dock = DockStyle.Fill;
|
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||||
tableLayoutPanel1.Location = new Point(50, 15);
|
tableLayoutPanel1.Location = new Point(50, 15);
|
||||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||||
tableLayoutPanel1.RowCount = 5;
|
tableLayoutPanel1.RowCount = 4;
|
||||||
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(SizeType.Percent, 100F));
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
tableLayoutPanel1.Size = new Size(814, 457);
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.Size = new Size(819, 251);
|
|
||||||
tableLayoutPanel1.TabIndex = 0;
|
tableLayoutPanel1.TabIndex = 0;
|
||||||
//
|
//
|
||||||
|
// propertyGrid
|
||||||
|
//
|
||||||
|
tableLayoutPanel1.SetColumnSpan(propertyGrid, 4);
|
||||||
|
propertyGrid.Dock = DockStyle.Fill;
|
||||||
|
propertyGrid.HelpVisible = false;
|
||||||
|
propertyGrid.Location = new Point(3, 97);
|
||||||
|
propertyGrid.Name = "propertyGrid";
|
||||||
|
propertyGrid.Size = new Size(808, 284);
|
||||||
|
propertyGrid.TabIndex = 1;
|
||||||
|
propertyGrid.ToolbarVisible = false;
|
||||||
|
//
|
||||||
// label4
|
// label4
|
||||||
//
|
//
|
||||||
label4.AutoSize = true;
|
label4.AutoSize = true;
|
||||||
@@ -97,9 +99,9 @@
|
|||||||
label4.Location = new Point(15, 15);
|
label4.Location = new Point(15, 15);
|
||||||
label4.Margin = new Padding(15);
|
label4.Margin = new Padding(15);
|
||||||
label4.Name = "label4";
|
label4.Name = "label4";
|
||||||
label4.Size = new Size(789, 24);
|
label4.Size = new Size(784, 24);
|
||||||
label4.TabIndex = 11;
|
label4.TabIndex = 11;
|
||||||
label4.Text = "说明:导出的文件名与骨骼文件名相同";
|
label4.Text = "说明:输出文件夹为可选项,留空则将预览图输出到每个骨骼文件所在目录";
|
||||||
label4.TextAlign = ContentAlignment.MiddleCenter;
|
label4.TextAlign = ContentAlignment.MiddleCenter;
|
||||||
//
|
//
|
||||||
// label1
|
// label1
|
||||||
@@ -112,40 +114,20 @@
|
|||||||
label1.TabIndex = 0;
|
label1.TabIndex = 0;
|
||||||
label1.Text = "输出文件夹:";
|
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
|
// textBox_OutputDir
|
||||||
//
|
//
|
||||||
tableLayoutPanel1.SetColumnSpan(textBox_OutputDir, 2);
|
tableLayoutPanel1.SetColumnSpan(textBox_OutputDir, 2);
|
||||||
textBox_OutputDir.Dock = DockStyle.Fill;
|
textBox_OutputDir.Dock = DockStyle.Fill;
|
||||||
textBox_OutputDir.Location = new Point(113, 57);
|
textBox_OutputDir.Location = new Point(113, 57);
|
||||||
textBox_OutputDir.Name = "textBox_OutputDir";
|
textBox_OutputDir.Name = "textBox_OutputDir";
|
||||||
textBox_OutputDir.Size = new Size(664, 30);
|
textBox_OutputDir.Size = new Size(660, 30);
|
||||||
textBox_OutputDir.TabIndex = 3;
|
textBox_OutputDir.TabIndex = 3;
|
||||||
//
|
//
|
||||||
// button_SelectOutputDir
|
// button_SelectOutputDir
|
||||||
//
|
//
|
||||||
button_SelectOutputDir.AutoSize = true;
|
button_SelectOutputDir.AutoSize = true;
|
||||||
button_SelectOutputDir.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
button_SelectOutputDir.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||||
button_SelectOutputDir.Location = new Point(783, 57);
|
button_SelectOutputDir.Location = new Point(779, 57);
|
||||||
button_SelectOutputDir.Name = "button_SelectOutputDir";
|
button_SelectOutputDir.Name = "button_SelectOutputDir";
|
||||||
button_SelectOutputDir.Size = new Size(32, 34);
|
button_SelectOutputDir.Size = new Size(32, 34);
|
||||||
button_SelectOutputDir.TabIndex = 5;
|
button_SelectOutputDir.TabIndex = 5;
|
||||||
@@ -164,17 +146,18 @@
|
|||||||
tableLayoutPanel2.Controls.Add(button_Ok, 0, 0);
|
tableLayoutPanel2.Controls.Add(button_Ok, 0, 0);
|
||||||
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
||||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
||||||
tableLayoutPanel2.Location = new Point(3, 208);
|
tableLayoutPanel2.Location = new Point(3, 414);
|
||||||
|
tableLayoutPanel2.Margin = new Padding(3, 30, 3, 3);
|
||||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||||
tableLayoutPanel2.RowCount = 1;
|
tableLayoutPanel2.RowCount = 1;
|
||||||
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
||||||
tableLayoutPanel2.Size = new Size(813, 40);
|
tableLayoutPanel2.Size = new Size(808, 40);
|
||||||
tableLayoutPanel2.TabIndex = 10;
|
tableLayoutPanel2.TabIndex = 10;
|
||||||
//
|
//
|
||||||
// button_Ok
|
// button_Ok
|
||||||
//
|
//
|
||||||
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||||
button_Ok.Location = new Point(264, 3);
|
button_Ok.Location = new Point(262, 3);
|
||||||
button_Ok.Margin = new Padding(3, 3, 30, 3);
|
button_Ok.Margin = new Padding(3, 3, 30, 3);
|
||||||
button_Ok.Name = "button_Ok";
|
button_Ok.Name = "button_Ok";
|
||||||
button_Ok.Size = new Size(112, 34);
|
button_Ok.Size = new Size(112, 34);
|
||||||
@@ -186,7 +169,7 @@
|
|||||||
// button_Cancel
|
// button_Cancel
|
||||||
//
|
//
|
||||||
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
||||||
button_Cancel.Location = new Point(436, 3);
|
button_Cancel.Location = new Point(434, 3);
|
||||||
button_Cancel.Margin = new Padding(30, 3, 3, 3);
|
button_Cancel.Margin = new Padding(30, 3, 3, 3);
|
||||||
button_Cancel.Name = "button_Cancel";
|
button_Cancel.Name = "button_Cancel";
|
||||||
button_Cancel.Size = new Size(112, 34);
|
button_Cancel.Size = new Size(112, 34);
|
||||||
@@ -195,30 +178,6 @@
|
|||||||
button_Cancel.UseVisualStyleBackColor = true;
|
button_Cancel.UseVisualStyleBackColor = true;
|
||||||
button_Cancel.Click += button_Cancel_Click;
|
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
|
||||||
//
|
//
|
||||||
folderBrowserDialog.AddToRecent = false;
|
folderBrowserDialog.AddToRecent = false;
|
||||||
@@ -229,7 +188,7 @@
|
|||||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
CancelButton = button_Cancel;
|
CancelButton = button_Cancel;
|
||||||
ClientSize = new Size(919, 276);
|
ClientSize = new Size(914, 482);
|
||||||
Controls.Add(panel1);
|
Controls.Add(panel1);
|
||||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||||
@@ -239,14 +198,11 @@
|
|||||||
ShowInTaskbar = false;
|
ShowInTaskbar = false;
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
StartPosition = FormStartPosition.CenterScreen;
|
||||||
Text = "导出预览图";
|
Text = "导出预览图";
|
||||||
Load += ExportPreviewDialog_Load;
|
|
||||||
panel1.ResumeLayout(false);
|
panel1.ResumeLayout(false);
|
||||||
panel1.PerformLayout();
|
panel1.PerformLayout();
|
||||||
tableLayoutPanel1.ResumeLayout(false);
|
tableLayoutPanel1.ResumeLayout(false);
|
||||||
tableLayoutPanel1.PerformLayout();
|
tableLayoutPanel1.PerformLayout();
|
||||||
tableLayoutPanel2.ResumeLayout(false);
|
tableLayoutPanel2.ResumeLayout(false);
|
||||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Width).EndInit();
|
|
||||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Height).EndInit();
|
|
||||||
ResumeLayout(false);
|
ResumeLayout(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,15 +212,12 @@
|
|||||||
private TableLayoutPanel tableLayoutPanel1;
|
private TableLayoutPanel tableLayoutPanel1;
|
||||||
private Label label4;
|
private Label label4;
|
||||||
private Label label1;
|
private Label label1;
|
||||||
private Label label2;
|
|
||||||
private Label label3;
|
|
||||||
private TextBox textBox_OutputDir;
|
private TextBox textBox_OutputDir;
|
||||||
private Button button_SelectOutputDir;
|
private Button button_SelectOutputDir;
|
||||||
private TableLayoutPanel tableLayoutPanel2;
|
private TableLayoutPanel tableLayoutPanel2;
|
||||||
private Button button_Ok;
|
private Button button_Ok;
|
||||||
private Button button_Cancel;
|
private Button button_Cancel;
|
||||||
private NumericUpDown numericUpDown_Width;
|
|
||||||
private NumericUpDown numericUpDown_Height;
|
|
||||||
private FolderBrowserDialog folderBrowserDialog;
|
private FolderBrowserDialog folderBrowserDialog;
|
||||||
|
private PropertyGrid propertyGrid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -12,42 +13,45 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class ExportPreviewDialog: Form
|
public partial class ExportPreviewDialog: Form
|
||||||
{
|
{
|
||||||
public string OutputDir { get; private set; }
|
/// <summary>
|
||||||
public uint PreviewWidth { get; private set; }
|
/// 对话框结果
|
||||||
public uint PreviewHeight { get; private set; }
|
/// </summary>
|
||||||
|
public readonly ExportPreviewDialogResult Result = new();
|
||||||
|
|
||||||
public ExportPreviewDialog()
|
public ExportPreviewDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
propertyGrid.SelectedObject = Result;
|
||||||
|
|
||||||
private void ExportPreviewDialog_Load(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
button_SelectOutputDir_Click(sender, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void button_SelectOutputDir_Click(object sender, EventArgs e)
|
private void button_SelectOutputDir_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
folderBrowserDialog.InitialDirectory = textBox_OutputDir.Text;
|
folderBrowserDialog.InitialDirectory = textBox_OutputDir.Text;
|
||||||
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
if (folderBrowserDialog.ShowDialog() != DialogResult.OK)
|
||||||
{
|
return;
|
||||||
textBox_OutputDir.Text = Path.GetFullPath(folderBrowserDialog.SelectedPath);
|
textBox_OutputDir.Text = Path.GetFullPath(folderBrowserDialog.SelectedPath);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void button_Ok_Click(object sender, EventArgs e)
|
private void button_Ok_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var outputDir = textBox_OutputDir.Text;
|
var outputDir = textBox_OutputDir.Text;
|
||||||
if (File.Exists(outputDir))
|
if (string.IsNullOrEmpty(outputDir))
|
||||||
{
|
{
|
||||||
MessageBox.Show("输出文件夹无效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
outputDir = null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (!Directory.Exists(outputDir))
|
|
||||||
{
|
{
|
||||||
if (MessageBox.Show($"文件夹 {outputDir} 不存在,是否创建?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
|
if (File.Exists(outputDir))
|
||||||
{
|
{
|
||||||
|
MessageBox.Info("输出文件夹无效");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(outputDir))
|
||||||
|
{
|
||||||
|
if (MessageBox.Quest($"文件夹 {outputDir} 不存在,是否创建?") != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(outputDir);
|
Directory.CreateDirectory(outputDir);
|
||||||
@@ -55,20 +59,20 @@ namespace SpineViewer.Dialogs
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Program.Logger.Error(ex.ToString());
|
Program.Logger.Error(ex.ToString());
|
||||||
MessageBox.Show(ex.ToString(), "文件夹创建失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Error(ex.ToString(), "文件夹创建失败");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
outputDir = Path.GetFullPath(outputDir);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OutputDir = Path.GetFullPath(outputDir);
|
if (outputDir is null && string.IsNullOrEmpty(Result.NameSuffix))
|
||||||
PreviewWidth = (uint)numericUpDown_Width.Value;
|
{
|
||||||
PreviewHeight = (uint)numericUpDown_Height.Value;
|
MessageBox.Info("输出文件夹和名称后缀不可同时为空,存在文件覆盖风险");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.OutputDir = outputDir;
|
||||||
DialogResult = DialogResult.OK;
|
DialogResult = DialogResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,4 +81,89 @@ namespace SpineViewer.Dialogs
|
|||||||
DialogResult = DialogResult.Cancel;
|
DialogResult = DialogResult.Cancel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ExportPreviewDialogResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 输出路径
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public string? OutputDir { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览图格式
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(ImageFormatConverter))]
|
||||||
|
[Category("图像"), DisplayName("预览图格式")]
|
||||||
|
public ImageFormat ImageFormat
|
||||||
|
{
|
||||||
|
get => imageFormat;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == ImageFormat.MemoryBmp) value = ImageFormat.Bmp;
|
||||||
|
imageFormat = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private ImageFormat imageFormat = ImageFormat.Png;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览图分辨率
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(SizeConverter))]
|
||||||
|
[Category("图像"), DisplayName("分辨率")]
|
||||||
|
public Size Resolution
|
||||||
|
{
|
||||||
|
get => resolution;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.Width <= 0) value.Width = 128;
|
||||||
|
if (value.Height <= 0) value.Height = 128;
|
||||||
|
resolution = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Size resolution = new(512, 512);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 四周填充像素值
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(PaddingConverter))]
|
||||||
|
[Category("图像"), DisplayName("四周填充像素值")]
|
||||||
|
public Padding Padding
|
||||||
|
{
|
||||||
|
get => padding;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.Left <= 0) value.Left = 10;
|
||||||
|
if (value.Right <= 0) value.Right = 10;
|
||||||
|
if (value.Top <= 0) value.Top = 10;
|
||||||
|
if (value.Bottom <= 0) value.Bottom = 10;
|
||||||
|
padding = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Padding padding = new(1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DPI
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(SizeFConverter))]
|
||||||
|
[Category("图像"), DisplayName("DPI")]
|
||||||
|
public SizeF DPI
|
||||||
|
{
|
||||||
|
get => dpi;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value.Width <= 0) value.Width = 144;
|
||||||
|
if (value.Height <= 0) value.Height = 144;
|
||||||
|
dpi = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private SizeF dpi = new(144, 144);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 名称后缀
|
||||||
|
/// </summary>
|
||||||
|
[Category("其他"), DisplayName("名称后缀")]
|
||||||
|
public string NameSuffix { get; set; } = "(preview)";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,15 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class OpenSpineDialog : Form
|
public partial class OpenSpineDialog : Form
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 对话框结果
|
||||||
|
/// </summary>
|
||||||
public OpenSpineDialogResult Result { get; private set; }
|
public OpenSpineDialogResult Result { get; private set; }
|
||||||
|
|
||||||
public OpenSpineDialog()
|
public OpenSpineDialog()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
comboBox_Version.DataSource = VersionHelper.Versions.ToList();
|
comboBox_Version.DataSource = VersionHelper.Names.ToList();
|
||||||
comboBox_Version.DisplayMember = "Value";
|
comboBox_Version.DisplayMember = "Value";
|
||||||
comboBox_Version.ValueMember = "Key";
|
comboBox_Version.ValueMember = "Key";
|
||||||
comboBox_Version.SelectedValue = Spine.Version.Auto;
|
comboBox_Version.SelectedValue = Spine.Version.Auto;
|
||||||
@@ -54,7 +57,7 @@ namespace SpineViewer.Dialogs
|
|||||||
|
|
||||||
if (!File.Exists(skelPath))
|
if (!File.Exists(skelPath))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{skelPath}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{skelPath}", "skel文件不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -68,7 +71,7 @@ namespace SpineViewer.Dialogs
|
|||||||
}
|
}
|
||||||
else if (!File.Exists(atlasPath))
|
else if (!File.Exists(atlasPath))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{atlasPath}", "atlas文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{atlasPath}", "atlas文件不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -78,7 +81,7 @@ namespace SpineViewer.Dialogs
|
|||||||
|
|
||||||
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
|
||||||
{
|
{
|
||||||
MessageBox.Show($"{version.String()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info($"{version.GetName()} 版本尚未实现(咕咕咕~)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,10 +95,24 @@ namespace SpineViewer.Dialogs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 打开骨骼对话框结果
|
||||||
|
/// </summary>
|
||||||
public class OpenSpineDialogResult(Spine.Version version, string skelPath, string? atlasPath = null)
|
public class OpenSpineDialogResult(Spine.Version version, string skelPath, string? atlasPath = null)
|
||||||
{
|
{
|
||||||
public Spine.Version Version { get; } = version;
|
/// <summary>
|
||||||
public string SkelPath { get; } = skelPath;
|
/// 版本
|
||||||
public string? AtlasPath { get; } = atlasPath;
|
/// </summary>
|
||||||
|
public Spine.Version Version => version;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// skel 文件路径
|
||||||
|
/// </summary>
|
||||||
|
public string SkelPath => skelPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// atlas 文件路径
|
||||||
|
/// </summary>
|
||||||
|
public string? AtlasPath => atlasPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,15 +12,25 @@ namespace SpineViewer.Dialogs
|
|||||||
{
|
{
|
||||||
public partial class ProgressDialog : Form
|
public partial class ProgressDialog : Form
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// BackgroundWorker.DoWork 接口暴露
|
||||||
|
/// </summary>
|
||||||
[Category("自定义"), Description("BackgroundWorker 的 DoWork 事件")]
|
[Category("自定义"), Description("BackgroundWorker 的 DoWork 事件")]
|
||||||
public event DoWorkEventHandler? DoWork
|
public event DoWorkEventHandler? DoWork
|
||||||
{
|
{
|
||||||
add { backgroundWorker.DoWork += value; }
|
add => backgroundWorker.DoWork += value;
|
||||||
remove { backgroundWorker.DoWork -= value; }
|
remove => backgroundWorker.DoWork -= value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunWorkerAsync() { backgroundWorker.RunWorkerAsync(); }
|
/// <summary>
|
||||||
public void RunWorkerAsync(object? argument) { backgroundWorker.RunWorkerAsync(argument); }
|
/// 启动后台执行
|
||||||
|
/// </summary>
|
||||||
|
public void RunWorkerAsync() => backgroundWorker.RunWorkerAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用给定参数启动后台执行
|
||||||
|
/// </summary>
|
||||||
|
public void RunWorkerAsync(object? argument) => backgroundWorker.RunWorkerAsync(argument);
|
||||||
|
|
||||||
public ProgressDialog()
|
public ProgressDialog()
|
||||||
{
|
{
|
||||||
@@ -38,7 +48,7 @@ namespace SpineViewer.Dialogs
|
|||||||
if (e.Error != null)
|
if (e.Error != null)
|
||||||
{
|
{
|
||||||
Program.Logger.Error(e.Error.ToString());
|
Program.Logger.Error(e.Error.ToString());
|
||||||
MessageBox.Show(e.Error.ToString(), "执行出错", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
MessageBox.Error(e.Error.ToString(), "执行出错");
|
||||||
DialogResult = DialogResult.Abort;
|
DialogResult = DialogResult.Abort;
|
||||||
}
|
}
|
||||||
else if (e.Cancelled)
|
else if (e.Cancelled)
|
||||||
|
|||||||
64
SpineViewer/ExportHelper.cs
Normal file
64
SpineViewer/ExportHelper.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using FFMpegCore.Pipes;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
|
||||||
|
|
||||||
|
namespace SpineViewer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SFML.Graphics.Image 帧对象包装类
|
||||||
|
/// </summary>
|
||||||
|
public class SFMLImageVideoFrame(SFML.Graphics.Image image) : IVideoFrame, IDisposable
|
||||||
|
{
|
||||||
|
public int Width => (int)image.Size.X;
|
||||||
|
public int Height => (int)image.Size.Y;
|
||||||
|
public string Format => "rgba";
|
||||||
|
public void Serialize(Stream pipe) => pipe.Write(image.Pixels);
|
||||||
|
public async Task SerializeAsync(Stream pipe, CancellationToken token) => await pipe.WriteAsync(image.Pixels, token);
|
||||||
|
public void Dispose() => image.Dispose();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the contents of the image to a file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">Path of the file to save (overwritten if already exist)</param>
|
||||||
|
/// <returns>True if saving was successful</returns>
|
||||||
|
public bool SaveToFile(string filename) => image.SaveToFile(filename);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the image to a buffer in memory The format of the image must be specified.
|
||||||
|
/// The supported image formats are bmp, png, tga and jpg. This function fails if
|
||||||
|
/// the image is empty, or if the format was invalid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="output">Byte array filled with encoded data</param>
|
||||||
|
/// <param name="format">Encoding format to use</param>
|
||||||
|
/// <returns>True if saving was successful</returns>
|
||||||
|
public bool SaveToMemory(out byte[] output, string format) => image.SaveToMemory(out output, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 为帧导出创建的辅助类
|
||||||
|
/// </summary>
|
||||||
|
public static class ExportHelper
|
||||||
|
{
|
||||||
|
public static Bitmap CopyToBitmap(this SFML.Graphics.Texture tex)
|
||||||
|
{
|
||||||
|
using var img = tex.CopyToImage();
|
||||||
|
img.SaveToMemory(out var imgBuffer, "bmp");
|
||||||
|
using var stream = new MemoryStream(imgBuffer);
|
||||||
|
return new Bitmap(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SFMLImageVideoFrame CopyToFrame(this SFML.Graphics.Texture tex) => new(tex.CopyToImage());
|
||||||
|
|
||||||
|
public static string GetSuffix(this ImageFormat imageFormat)
|
||||||
|
{
|
||||||
|
if (imageFormat == ImageFormat.Icon) return ".ico";
|
||||||
|
else if (imageFormat == ImageFormat.Exif) return ".jpg";
|
||||||
|
else return $".{imageFormat.ToString().ToLower()}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
using NLog;
|
using FFMpegCore.Pipes;
|
||||||
|
using FFMpegCore;
|
||||||
|
using NLog;
|
||||||
|
using SFML.System;
|
||||||
using SpineViewer.Spine;
|
using SpineViewer.Spine;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
namespace SpineViewer
|
namespace SpineViewer
|
||||||
{
|
{
|
||||||
@@ -65,11 +69,12 @@ namespace SpineViewer
|
|||||||
|
|
||||||
private void toolStripMenuItem_Export_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_Export_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
// TODO: 改成统一导出调用
|
||||||
lock (spineListView.Spines)
|
lock (spineListView.Spines)
|
||||||
{
|
{
|
||||||
if (spineListView.Spines.Count <= 0)
|
if (spineListView.Spines.Count <= 0)
|
||||||
{
|
{
|
||||||
MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Info("请至少打开一个骨骼文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +91,23 @@ namespace SpineViewer
|
|||||||
|
|
||||||
private void toolStripMenuItem_ExportPreview_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_ExportPreview_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
spineListView.ExportPreviews();
|
lock (spineListView.Spines)
|
||||||
|
{
|
||||||
|
if (spineListView.Spines.Count <= 0)
|
||||||
|
{
|
||||||
|
MessageBox.Info("请至少打开一个骨骼文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var saveDialog = new Dialogs.ExportPreviewDialog();
|
||||||
|
if (saveDialog.ShowDialog() != DialogResult.OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var progressDialog = new Dialogs.ProgressDialog();
|
||||||
|
progressDialog.DoWork += ExportPreview_Work;
|
||||||
|
progressDialog.RunWorkerAsync(saveDialog.Result);
|
||||||
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toolStripMenuItem_Exit_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_Exit_Click(object sender, EventArgs e)
|
||||||
@@ -111,13 +132,74 @@ namespace SpineViewer
|
|||||||
|
|
||||||
var progressDialog = new Dialogs.ProgressDialog();
|
var progressDialog = new Dialogs.ProgressDialog();
|
||||||
progressDialog.DoWork += ConvertFileFormat_Work;
|
progressDialog.DoWork += ConvertFileFormat_Work;
|
||||||
progressDialog.RunWorkerAsync(openDialog);
|
progressDialog.RunWorkerAsync(openDialog.Result);
|
||||||
progressDialog.ShowDialog();
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//IEnumerable<IVideoFrame> testExport(int fps)
|
||||||
|
//{
|
||||||
|
// var duration = 2f;
|
||||||
|
// var resolution = spinePreviewer.Resolution;
|
||||||
|
// var delta = 1f / fps;
|
||||||
|
// var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
|
||||||
|
|
||||||
|
// var spinesReverse = spineListView.Spines.Reverse();
|
||||||
|
|
||||||
|
// // 重置动画时间
|
||||||
|
// foreach (var spine in spinesReverse)
|
||||||
|
// spine.CurrentAnimation = spine.CurrentAnimation;
|
||||||
|
|
||||||
|
// // 逐帧导出
|
||||||
|
// var success = 0;
|
||||||
|
// for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
||||||
|
// {
|
||||||
|
// using var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
||||||
|
// tex.SetView(spinePreviewer.View);
|
||||||
|
// tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
|
||||||
|
// foreach (var spine in spinesReverse)
|
||||||
|
// {
|
||||||
|
// tex.Draw(spine);
|
||||||
|
// spine.Update(delta);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// tex.Display();
|
||||||
|
// Debug.WriteLine($"ThreadID: {Environment.CurrentManagedThreadId}");
|
||||||
|
// var frame = tex.Texture.CopyToFrame();
|
||||||
|
// tex.Dispose();
|
||||||
|
// yield return frame;
|
||||||
|
|
||||||
|
// success++;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Program.Logger.Info("Exporting done: {}/{}", success, frameCount);
|
||||||
|
//}
|
||||||
|
|
||||||
private void toolStripMenuItem_ManageResource_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_ManageResource_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
//spinePreviewer.StopPreview();
|
||||||
|
|
||||||
|
//lock (spineListView.Spines)
|
||||||
|
//{
|
||||||
|
// //var fps = 24;
|
||||||
|
// ////foreach (var i in testExport(fps))
|
||||||
|
// //// _ = i;
|
||||||
|
// ////var t = testExport(fps).ToArray();
|
||||||
|
// ////var a = testExport(fps).GetEnumerator();
|
||||||
|
// ////while (a.MoveNext());
|
||||||
|
// //var videoFramesSource = new RawVideoPipeSource(testExport(fps)) { FrameRate = fps };
|
||||||
|
// //var outputPath = @"C:\Users\ljh\Desktop\test\a.mov";
|
||||||
|
// //var task = FFMpegArguments
|
||||||
|
// // .FromPipeInput(videoFramesSource)
|
||||||
|
// // .OutputToFile(outputPath, true
|
||||||
|
// // , options => options
|
||||||
|
// // //.WithCustomArgument("-vf \"split[s0][s1];[s0]palettegen=reserve_transparent=1[p];[s1][p]paletteuse=alpha_threshold=128\""))
|
||||||
|
// // .WithCustomArgument("-c:v prores_ks -profile:v 4444 -pix_fmt yuva444p10le"))
|
||||||
|
// // .ProcessAsynchronously();
|
||||||
|
// //task.Wait();
|
||||||
|
//}
|
||||||
|
|
||||||
|
//spinePreviewer.StartPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
|
||||||
@@ -130,13 +212,16 @@ namespace SpineViewer
|
|||||||
(new Dialogs.DiagnosticsDialog()).ShowDialog();
|
(new Dialogs.DiagnosticsDialog()).ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void splitContainer_SplitterMoved(object sender, SplitterEventArgs e) { ActiveControl = null; }
|
private void splitContainer_SplitterMoved(object sender, SplitterEventArgs e) => ActiveControl = null;
|
||||||
|
|
||||||
private void splitContainer_MouseUp(object sender, MouseEventArgs e) { ActiveControl = null; }
|
private void splitContainer_MouseUp(object sender, MouseEventArgs e) => ActiveControl = null;
|
||||||
|
|
||||||
private void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e) { (sender as PropertyGrid)?.Refresh(); }
|
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 spinePreviewer_MouseUp(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
propertyGrid_Spine.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
private void ExportPng_Work(object? sender, DoWorkEventArgs e)
|
private void ExportPng_Work(object? sender, DoWorkEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -147,9 +232,12 @@ namespace SpineViewer
|
|||||||
var fps = arguments.Fps;
|
var fps = arguments.Fps;
|
||||||
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
||||||
|
|
||||||
var resolution = spinePreviewer.Resolution;
|
var frameArgs = spinePreviewer.GetFrameArgs();
|
||||||
|
var renderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
||||||
|
|
||||||
|
var resolution = frameArgs.Resolution;
|
||||||
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
||||||
tex.SetView(spinePreviewer.View);
|
tex.SetView(frameArgs.View);
|
||||||
var delta = 1f / fps;
|
var delta = 1f / fps;
|
||||||
var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
|
var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
|
||||||
|
|
||||||
@@ -180,6 +268,9 @@ namespace SpineViewer
|
|||||||
|
|
||||||
foreach (var spine in spinesReverse)
|
foreach (var spine in spinesReverse)
|
||||||
{
|
{
|
||||||
|
if (renderSelectedOnly && !spine.IsSelected)
|
||||||
|
continue;
|
||||||
|
|
||||||
tex.Draw(spine);
|
tex.Draw(spine);
|
||||||
spine.Update(delta);
|
spine.Update(delta);
|
||||||
}
|
}
|
||||||
@@ -200,31 +291,101 @@ namespace SpineViewer
|
|||||||
spinePreviewer.StartPreview();
|
spinePreviewer.StartPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ExportPreview_Work(object? sender, DoWorkEventArgs e)
|
||||||
|
{
|
||||||
|
var worker = sender as BackgroundWorker;
|
||||||
|
var arguments = e.Argument as Dialogs.ExportPreviewDialogResult;
|
||||||
|
|
||||||
|
var outputDir = arguments.OutputDir;
|
||||||
|
var imageFormat = arguments.ImageFormat;
|
||||||
|
var resolution = arguments.Resolution;
|
||||||
|
var padding = arguments.Padding;
|
||||||
|
var dpi = arguments.DPI;
|
||||||
|
var nameSuffix = arguments.NameSuffix;
|
||||||
|
var renderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
||||||
|
|
||||||
|
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
||||||
|
|
||||||
|
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];
|
||||||
|
if (renderSelectedOnly && !spine.IsSelected)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var filename = $"{spine.Name}{nameSuffix}{imageFormat.GetSuffix()}";
|
||||||
|
var savePath = outputDir is null ? Path.Combine(spine.AssetsDir, filename) : Path.Combine(outputDir, filename);
|
||||||
|
|
||||||
|
var tmp = spine.CurrentAnimation;
|
||||||
|
spine.CurrentAnimation = Spine.Spine.EMPTY_ANIMATION;
|
||||||
|
tex.SetView(spine.GetInitView(resolution, padding));
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
tex.Draw(spine);
|
||||||
|
tex.Display();
|
||||||
|
spine.CurrentAnimation = tmp;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var img = new Bitmap(tex.Texture.CopyToBitmap()))
|
||||||
|
{
|
||||||
|
img.SetResolution(dpi.Width, dpi.Height);
|
||||||
|
img.Save(savePath, imageFormat);
|
||||||
|
}
|
||||||
|
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.LogCurrentMemoryUsage();
|
||||||
|
}
|
||||||
|
|
||||||
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
|
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
|
||||||
{
|
{
|
||||||
var worker = sender as BackgroundWorker;
|
var worker = sender as BackgroundWorker;
|
||||||
var arguments = e.Argument as Dialogs.ConvertFileFormatDialog;
|
var arguments = e.Argument as Dialogs.ConvertFileFormatDialogResult;
|
||||||
var skelPaths = arguments.SkelPaths;
|
var skelPaths = arguments.SkelPaths;
|
||||||
var srcVersion = arguments.SourceVersion;
|
var srcVersion = arguments.SourceVersion;
|
||||||
var tgtVersion = arguments.TargetVersion;
|
var tgtVersion = arguments.TargetVersion;
|
||||||
var jsonSource = arguments.JsonSource;
|
|
||||||
var jsonTarget = arguments.JsonTarget;
|
var jsonTarget = arguments.JsonTarget;
|
||||||
var newSuffix = jsonTarget ? ".json" : ".skel";
|
var newSuffix = jsonTarget ? ".json" : ".skel";
|
||||||
|
|
||||||
if (jsonTarget == jsonSource)
|
|
||||||
{
|
|
||||||
if (tgtVersion == srcVersion)
|
|
||||||
return;
|
|
||||||
else
|
|
||||||
newSuffix += $".{tgtVersion.ToString().ToLower()}"; // TODO: 仅转换版本的情况下考虑文件覆盖问题
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalCount = skelPaths.Length;
|
int totalCount = skelPaths.Length;
|
||||||
int success = 0;
|
int success = 0;
|
||||||
int error = 0;
|
int error = 0;
|
||||||
|
|
||||||
SkeletonConverter srcCvter = SkeletonConverter.New(srcVersion);
|
SkeletonConverter srcCvter = srcVersion != Spine.Version.Auto ? SkeletonConverter.New(srcVersion) : null;
|
||||||
SkeletonConverter tgtCvter = tgtVersion == srcVersion ? srcCvter : SkeletonConverter.New(tgtVersion);
|
SkeletonConverter tgtCvter = SkeletonConverter.New(tgtVersion);
|
||||||
|
|
||||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||||
for (int i = 0; i < totalCount; i++)
|
for (int i = 0; i < totalCount; i++)
|
||||||
@@ -240,8 +401,15 @@ namespace SpineViewer
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var root = jsonSource ? srcCvter.ReadJson(skelPath) : srcCvter.ReadBinary(skelPath);
|
if (srcVersion == Spine.Version.Auto)
|
||||||
if (tgtVersion != srcVersion) root = srcCvter.ToVersion(root, tgtVersion);
|
{
|
||||||
|
if (Spine.Spine.GetVersion(skelPath) is Spine.Version detectedSrcVersion)
|
||||||
|
srcCvter = SkeletonConverter.New(detectedSrcVersion);
|
||||||
|
else
|
||||||
|
throw new InvalidDataException($"Auto version detection failed for {skelPath}, try to use a specific version");
|
||||||
|
}
|
||||||
|
var root = srcCvter.Read(skelPath);
|
||||||
|
root = srcCvter.ToVersion(root, tgtVersion);
|
||||||
if (jsonTarget) tgtCvter.WriteJson(root, newPath); else tgtCvter.WriteBinary(root, newPath);
|
if (jsonTarget) tgtCvter.WriteJson(root, newPath); else tgtCvter.WriteBinary(root, newPath);
|
||||||
success++;
|
success++;
|
||||||
}
|
}
|
||||||
|
|||||||
39
SpineViewer/MessageBox.cs
Normal file
39
SpineViewer/MessageBox.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace SpineViewer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 弹窗消息静态类
|
||||||
|
/// </summary>
|
||||||
|
public static class MessageBox
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 提示弹窗
|
||||||
|
/// </summary>
|
||||||
|
public static void Info(string text, string title = "提示信息") =>
|
||||||
|
System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 警告弹窗
|
||||||
|
/// </summary>
|
||||||
|
public static void Warn(string text, string title = "警告信息") =>
|
||||||
|
System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 错误弹窗
|
||||||
|
/// </summary>
|
||||||
|
public static void Error(string text, string title = "错误信息") =>
|
||||||
|
System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作确认弹窗
|
||||||
|
/// </summary>
|
||||||
|
public static DialogResult Quest(string text, string title = "操作确认") =>
|
||||||
|
System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,13 +5,38 @@ namespace SpineViewer
|
|||||||
{
|
{
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
public const string Name = "SpineViewer";
|
/// <summary>
|
||||||
|
/// 程序路径
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string FilePath = Environment.ProcessPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序名
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string Name = Path.GetFileNameWithoutExtension(FilePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序目录
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string RootDir = Path.GetDirectoryName(FilePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序临时目录
|
||||||
|
/// </summary>
|
||||||
public static readonly string TempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Name)).FullName;
|
public static readonly string TempDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Name)).FullName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序进程
|
||||||
|
/// </summary>
|
||||||
public static readonly Process Process = Process.GetCurrentProcess();
|
public static readonly Process Process = Process.GetCurrentProcess();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 程序日志器
|
||||||
|
/// </summary>
|
||||||
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main entry point for the application.
|
/// 应用入口点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
@@ -30,7 +55,7 @@ namespace SpineViewer
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Fatal(ex.ToString());
|
Logger.Fatal(ex.ToString());
|
||||||
MessageBox.Show(ex.ToString(), "程序已崩溃", MessageBoxButtons.OK, MessageBoxIcon.Stop);
|
MessageBox.Error(ex.ToString(), "程序已崩溃");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,5 +83,9 @@ namespace SpineViewer
|
|||||||
LogManager.Configuration = config;
|
LogManager.Configuration = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出当前内存使用情况
|
||||||
|
/// </summary>
|
||||||
|
public static void LogCurrentMemoryUsage() => Logger.Info("Current memory usage: {:F2} MB", Process.WorkingSet64 / 1024.0 / 1024.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@ using System.Text.Json;
|
|||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using SpineRuntime38.Attachments;
|
using SpineRuntime38.Attachments;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||||
{
|
{
|
||||||
@@ -225,8 +227,8 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
skin["name"] = reader.ReadStringRef();
|
skin["name"] = reader.ReadStringRef();
|
||||||
skin["bones"] = ReadNames(root["bones"].AsArray());
|
skin["bones"] = ReadNames(root["bones"].AsArray());
|
||||||
skin["ik"] = ReadNames(root["ik"].AsArray());
|
skin["ik"] = ReadNames(root["ik"].AsArray());
|
||||||
skin["transform"] = ReadNames(root["transform"].AsArray()); ;
|
skin["transform"] = ReadNames(root["transform"].AsArray());
|
||||||
skin["path"] = ReadNames(root["path"].AsArray()); ;
|
skin["path"] = ReadNames(root["path"].AsArray());
|
||||||
slotCount = reader.ReadVarInt();
|
slotCount = reader.ReadVarInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1014,31 +1016,163 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
|
|
||||||
private void WriteSkins()
|
private void WriteSkins()
|
||||||
{
|
{
|
||||||
//JsonArray skins = [];
|
|
||||||
|
|
||||||
//// default skin
|
|
||||||
//if (ReadSkin(true) is JsonObject data)
|
|
||||||
// skins.Add(data);
|
|
||||||
|
|
||||||
//// other skins
|
|
||||||
//for (int n = reader.ReadVarInt(); n > 0; n--)
|
|
||||||
// skins.Add(ReadSkin());
|
|
||||||
|
|
||||||
//root["skins"] = skins;
|
|
||||||
|
|
||||||
if (!root.ContainsKey("skins"))
|
if (!root.ContainsKey("skins"))
|
||||||
|
{
|
||||||
|
writer.WriteVarInt(0); // default 的 slotCount
|
||||||
|
writer.WriteVarInt(0); // 其他皮肤数量
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray skins = root["skins"].AsArray();
|
||||||
|
bool hasDefault = false;
|
||||||
|
foreach (JsonObject skin in skins)
|
||||||
|
{
|
||||||
|
if ((string)skin["name"] == "default")
|
||||||
|
{
|
||||||
|
hasDefault = true;
|
||||||
|
WriteSkin(skin, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasDefault) writer.WriteVarInt(0);
|
||||||
|
|
||||||
|
int skinCount = hasDefault ? skins.Count - 1 : skins.Count;
|
||||||
|
if (skinCount <= 0)
|
||||||
{
|
{
|
||||||
writer.WriteVarInt(0);
|
writer.WriteVarInt(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JsonArray skins = root["skins"].AsArray();
|
|
||||||
writer.WriteVarInt(skins.Count);
|
writer.WriteVarInt(skinCount);
|
||||||
for (int i = 0, n = skins.Count; i < n; i++)
|
foreach (JsonObject skin in skins)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if ((string)skin["name"] != "default")
|
||||||
|
WriteSkin(skin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WriteSkin(JsonObject skin, bool isDefault = false)
|
||||||
|
{
|
||||||
|
JsonObject skinAttachments = null;
|
||||||
|
if (isDefault)
|
||||||
|
{
|
||||||
|
// 这里固定有一个给 default 的 count 值, 算是占位符, 如果是 0 则表示没有 default 的 skin
|
||||||
|
if (skin.TryGetPropertyValue("attachments", out var attachments)) skinAttachments = attachments.AsObject();
|
||||||
|
writer.WriteVarInt(skinAttachments?.Count ?? 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteStringRef((string)skin["name"]);
|
||||||
|
if (skin.TryGetPropertyValue("bones", out var bones)) WriteNames(bone2idx, bones.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (skin.TryGetPropertyValue("ik", out var ik)) WriteNames(ik2idx, ik.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (skin.TryGetPropertyValue("transform", out var transform)) WriteNames(transform2idx, transform.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (skin.TryGetPropertyValue("path", out var path)) WriteNames(path2idx, path.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (skin.TryGetPropertyValue("attachments", out var attachments)) skinAttachments = attachments.AsObject();
|
||||||
|
writer.WriteVarInt(skinAttachments?.Count ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skinAttachments is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (slotName, _slotAttachments) in skinAttachments)
|
||||||
|
{
|
||||||
|
JsonObject slotAttachments = _slotAttachments.AsObject();
|
||||||
|
writer.WriteVarInt(slot2idx[slotName]);
|
||||||
|
writer.WriteVarInt(slotAttachments.Count);
|
||||||
|
foreach (var (attachmentKey, attachment) in slotAttachments)
|
||||||
|
{
|
||||||
|
writer.WriteStringRef(attachmentKey);
|
||||||
|
WriteAttachment(attachment.AsObject(), attachmentKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteAttachment(JsonObject attachment, string keyName)
|
||||||
|
{
|
||||||
|
int vertexCount;
|
||||||
|
|
||||||
|
string name = keyName;
|
||||||
|
AttachmentType type = AttachmentType.Region;
|
||||||
|
|
||||||
|
if (attachment.TryGetPropertyValue("name", out var _name)) name = (string)_name;
|
||||||
|
if (attachment.TryGetPropertyValue("type", out var _type)) type = Enum.Parse<AttachmentType>((string)_type, true);
|
||||||
|
writer.WriteStringRef(name);
|
||||||
|
writer.WriteByte((byte)type);
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case AttachmentType.Region:
|
||||||
|
if (attachment.TryGetPropertyValue("path", out var path1)) writer.WriteStringRef((string)path1); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("rotation", out var rotation1)) writer.WriteFloat((float)rotation1); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("x", out var x1)) writer.WriteFloat((float)x1); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("y", out var y1)) writer.WriteFloat((float)y1); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("scaleX", out var scaleX)) writer.WriteFloat((float)scaleX); else writer.WriteFloat(1);
|
||||||
|
if (attachment.TryGetPropertyValue("scaleY", out var scaleY)) writer.WriteFloat((float)scaleY); else writer.WriteFloat(1);
|
||||||
|
if (attachment.TryGetPropertyValue("width", out var width)) writer.WriteFloat((float)width); else writer.WriteFloat(32);
|
||||||
|
if (attachment.TryGetPropertyValue("height", out var height)) writer.WriteFloat((float)height); else writer.WriteFloat(32);
|
||||||
|
if (attachment.TryGetPropertyValue("color", out var color1)) writer.WriteInt(int.Parse((string)color1, NumberStyles.HexNumber)); else writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
case AttachmentType.Boundingbox:
|
||||||
|
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount1)) vertexCount = (int)_vertexCount1; else vertexCount = 0;
|
||||||
|
writer.WriteVarInt(vertexCount);
|
||||||
|
WriteVertices(attachment["vertices"].AsArray(), vertexCount);
|
||||||
|
if (nonessential) writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
case AttachmentType.Mesh:
|
||||||
|
if (attachment.TryGetPropertyValue("path", out var path2)) writer.WriteStringRef((string)path2); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("color", out var color2)) writer.WriteInt(int.Parse((string)color2, NumberStyles.HexNumber)); else writer.WriteInt(0);
|
||||||
|
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount2)) vertexCount = (int)_vertexCount2; else vertexCount = 0;
|
||||||
|
writer.WriteVarInt(vertexCount);
|
||||||
|
WriteFloatArray(attachment["uvs"].AsArray(), vertexCount << 1); // vertexCount = uvs.Length
|
||||||
|
WriteShortArray(attachment["triangles"].AsArray());
|
||||||
|
WriteVertices(attachment["vertices"].AsArray(), vertexCount);
|
||||||
|
if (attachment.TryGetPropertyValue("hull", out var hull)) writer.WriteVarInt((int)hull); else writer.WriteVarInt(0);
|
||||||
|
if (nonessential)
|
||||||
|
{
|
||||||
|
if (attachment.TryGetPropertyValue("edges", out var edges)) WriteShortArray(edges.AsArray()); else writer.WriteVarInt(0);
|
||||||
|
if (attachment.TryGetPropertyValue("width", out var _width)) writer.WriteFloat((float)_width); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("height", out var _height)) writer.WriteFloat((float)_height); else writer.WriteFloat(0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AttachmentType.Linkedmesh:
|
||||||
|
if (attachment.TryGetPropertyValue("path", out var path3)) writer.WriteStringRef((string)path3); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("color", out var color3)) writer.WriteInt(int.Parse((string)color3, NumberStyles.HexNumber)); else writer.WriteInt(0);
|
||||||
|
if (attachment.TryGetPropertyValue("skin", out var skin)) writer.WriteStringRef((string)skin); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("parent", out var parent)) writer.WriteStringRef((string)parent); else writer.WriteStringRef(null);
|
||||||
|
if (attachment.TryGetPropertyValue("deform", out var deform)) writer.WriteBoolean((bool)deform); else writer.WriteBoolean(true);
|
||||||
|
if (nonessential)
|
||||||
|
{
|
||||||
|
if (attachment.TryGetPropertyValue("width", out var _width)) writer.WriteFloat((float)_width); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("height", out var _height)) writer.WriteFloat((float)_height); else writer.WriteFloat(0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AttachmentType.Path:
|
||||||
|
if (attachment.TryGetPropertyValue("closed", out var closed)) writer.WriteBoolean((bool)closed); else writer.WriteBoolean(false);
|
||||||
|
if (attachment.TryGetPropertyValue("constantSpeed", out var constantSpeed)) writer.WriteBoolean((bool)constantSpeed); else writer.WriteBoolean(true);
|
||||||
|
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount3)) vertexCount = (int)_vertexCount3; else vertexCount = 0;
|
||||||
|
writer.WriteVarInt(vertexCount);
|
||||||
|
WriteVertices(attachment["vertices"].AsArray(), vertexCount);
|
||||||
|
WriteFloatArray(attachment["lengths"].AsArray(), vertexCount / 3);
|
||||||
|
if (nonessential) writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
case AttachmentType.Point:
|
||||||
|
if (attachment.TryGetPropertyValue("rotation", out var rotation2)) writer.WriteFloat((float)rotation2); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("x", out var x2)) writer.WriteFloat((float)x2); else writer.WriteFloat(0);
|
||||||
|
if (attachment.TryGetPropertyValue("y", out var y2)) writer.WriteFloat((float)y2); else writer.WriteFloat(0);
|
||||||
|
if (nonessential) writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
case AttachmentType.Clipping:
|
||||||
|
writer.WriteVarInt(slot2idx[(string)attachment["end"]]);
|
||||||
|
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount4)) vertexCount = (int)_vertexCount4; else vertexCount = 0;
|
||||||
|
writer.WriteVarInt(vertexCount);
|
||||||
|
WriteVertices(attachment["vertices"].AsArray(), vertexCount);
|
||||||
|
if (nonessential) writer.WriteInt(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException($"Invalid attachment type: {type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void WriteEvents()
|
private void WriteEvents()
|
||||||
{
|
{
|
||||||
@@ -1089,8 +1223,69 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
private void WriteNames(Dictionary<string, int> name2idx, JsonArray names)
|
private void WriteNames(Dictionary<string, int> name2idx, JsonArray names)
|
||||||
{
|
{
|
||||||
writer.WriteVarInt(names.Count);
|
writer.WriteVarInt(names.Count);
|
||||||
foreach (var name in names)
|
foreach (string name in names)
|
||||||
writer.WriteVarInt(name2idx[(string)name]);
|
writer.WriteVarInt(name2idx[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteFloatArray(JsonArray array, int n)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
writer.WriteFloat((float)array[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteShortArray(JsonArray array)
|
||||||
|
{
|
||||||
|
writer.WriteVarInt(array.Count);
|
||||||
|
foreach (uint i in array)
|
||||||
|
{
|
||||||
|
writer.WriteByte((byte)(i >> 8));
|
||||||
|
writer.WriteByte((byte)i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteVertices(JsonArray vertices, int vertexCount)
|
||||||
|
{
|
||||||
|
bool hasWeight = vertices.Count != (vertexCount << 1);
|
||||||
|
writer.WriteBoolean(hasWeight);
|
||||||
|
if (!hasWeight)
|
||||||
|
{
|
||||||
|
WriteFloatArray(vertices, vertexCount << 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
for (int i = 0; i < vertexCount; i++)
|
||||||
|
{
|
||||||
|
var bonesCount = (int)vertices[idx++];
|
||||||
|
writer.WriteVarInt(bonesCount);
|
||||||
|
for (int j = 0; j < bonesCount; j++)
|
||||||
|
{
|
||||||
|
writer.WriteVarInt((int)vertices[idx++]);
|
||||||
|
writer.WriteFloat((float)vertices[idx++]);
|
||||||
|
writer.WriteFloat((float)vertices[idx++]);
|
||||||
|
writer.WriteFloat((float)vertices[idx++]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override JsonObject ReadJson(string jsonPath)
|
||||||
|
{
|
||||||
|
// replace 3.8.75 to another version to avoid detection in official runtime
|
||||||
|
var root = base.ReadJson(jsonPath);
|
||||||
|
var skeleton = root["skeleton"].AsObject();
|
||||||
|
var version = (string)skeleton["spine"];
|
||||||
|
if (version == "3.8.75") skeleton["spine"] = "3.8.76";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteJson(JsonObject root, string jsonPath)
|
||||||
|
{
|
||||||
|
// replace 3.8.75 to another version to avoid detection in official runtime
|
||||||
|
var skeleton = root["skeleton"].AsObject();
|
||||||
|
var version = (string)skeleton["spine"];
|
||||||
|
if (version == "3.8.75") skeleton["spine"] = "3.8.76";
|
||||||
|
base.WriteJson(root, jsonPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override JsonObject ToVersion(JsonObject root, Version version)
|
public override JsonObject ToVersion(JsonObject root, Version version)
|
||||||
@@ -1103,19 +1298,5 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
//public void WriteFloatArray(float[] array)
|
|
||||||
//{
|
|
||||||
// foreach (var i in array)
|
|
||||||
// writer.WriteFloat(i);
|
|
||||||
//}
|
|
||||||
|
|
||||||
//public void WriteShortArray(int[] array)
|
|
||||||
//{
|
|
||||||
// foreach (var i in array)
|
|
||||||
// {
|
|
||||||
// writer.WriteByte((byte)(i >> 8));
|
|
||||||
// writer.WriteByte((byte)i);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,6 +380,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
//clipping.ClipEnd();
|
//clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,6 +336,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,6 +344,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,6 +347,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,6 +346,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,6 +346,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -346,6 +346,17 @@ namespace SpineViewer.Spine.Implementations.Spine
|
|||||||
states.Shader = null;
|
states.Shader = null;
|
||||||
target.Draw(vertexArray, states);
|
target.Draw(vertexArray, states);
|
||||||
clipping.ClipEnd();
|
clipping.ClipEnd();
|
||||||
|
|
||||||
|
// 包围盒
|
||||||
|
if (IsDebug && IsSelected && DebugBounds)
|
||||||
|
{
|
||||||
|
var bounds = Bounds;
|
||||||
|
boundsVertices[0] = boundsVertices[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
|
||||||
|
boundsVertices[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
|
||||||
|
boundsVertices[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
|
||||||
|
target.Draw(boundsVertices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ namespace SpineViewer.Spine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取 Json 对象
|
/// 读取 Json 对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public JsonObject ReadJson(string jsonPath)
|
public virtual JsonObject ReadJson(string jsonPath)
|
||||||
{
|
{
|
||||||
using var input = File.OpenRead(jsonPath);
|
using var input = File.OpenRead(jsonPath);
|
||||||
if (JsonNode.Parse(input) is JsonObject root)
|
if (JsonNode.Parse(input) is JsonObject root)
|
||||||
@@ -104,13 +104,36 @@ namespace SpineViewer.Spine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 写入 Json 对象
|
/// 写入 Json 对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void WriteJson(JsonObject root, string jsonPath)
|
public virtual void WriteJson(JsonObject root, string jsonPath)
|
||||||
{
|
{
|
||||||
using var output = File.Create(jsonPath);
|
using var output = File.Create(jsonPath);
|
||||||
using var writer = new Utf8JsonWriter(output, jsonWriterOptions);
|
using var writer = new Utf8JsonWriter(output, jsonWriterOptions);
|
||||||
root.WriteTo(writer);
|
root.WriteTo(writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取骨骼文件
|
||||||
|
/// </summary>
|
||||||
|
public JsonObject Read(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ReadBinary(path);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ReadJson(path);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 都不行就报错
|
||||||
|
throw new InvalidDataException($"Unknown skeleton file format {path}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 转换到目标版本
|
/// 转换到目标版本
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,13 +8,12 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using SFML.System;
|
|
||||||
using SFML.Window;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
namespace SpineViewer.Spine
|
namespace SpineViewer.Spine
|
||||||
{
|
{
|
||||||
@@ -37,15 +36,25 @@ namespace SpineViewer.Spine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
|
public abstract class Spine : SFML.Graphics.Drawable, IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 常规骨骼文件后缀集合
|
||||||
|
/// </summary>
|
||||||
|
public static readonly ImmutableHashSet<string> CommonSkelSuffix = [".skel", ".json"];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 空动画标记
|
/// 空动画标记
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string EMPTY_ANIMATION = "<Empty>";
|
public const string EMPTY_ANIMATION = "<Empty>";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 预览图大小
|
/// 预览图宽
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Size PREVIEW_SIZE = new(256, 256);
|
public const uint PREVIEW_WIDTH = 256;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预览图高
|
||||||
|
/// </summary>
|
||||||
|
public const uint PREVIEW_HEIGHT = 256;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 缩放最小值
|
/// 缩放最小值
|
||||||
@@ -103,7 +112,7 @@ namespace SpineViewer.Spine
|
|||||||
FragmentShader = null;
|
FragmentShader = null;
|
||||||
Program.Logger.Error(ex.ToString());
|
Program.Logger.Error(ex.ToString());
|
||||||
Program.Logger.Error("Failed to load fragment shader");
|
Program.Logger.Error("Failed to load fragment shader");
|
||||||
MessageBox.Show("Fragment shader 加载失败,预乘Alpha通道属性失效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
MessageBox.Warn("Fragment shader 加载失败,预乘Alpha通道属性失效");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +173,7 @@ namespace SpineViewer.Spine
|
|||||||
else if (versionString.StartsWith("4.1.")) version = Version.V41;
|
else if (versionString.StartsWith("4.1.")) version = Version.V41;
|
||||||
else if (versionString.StartsWith("4.2.")) version = Version.V42;
|
else if (versionString.StartsWith("4.2.")) version = Version.V42;
|
||||||
else if (versionString.StartsWith("4.3.")) version = Version.V43;
|
else if (versionString.StartsWith("4.3.")) version = Version.V43;
|
||||||
|
else Program.Logger.Error("Unknown verison: {}, {}", versionString, skelPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return version;
|
return version;
|
||||||
@@ -210,6 +220,7 @@ namespace SpineViewer.Spine
|
|||||||
|
|
||||||
// 设置 Version
|
// 设置 Version
|
||||||
Version = attr.Version;
|
Version = attr.Version;
|
||||||
|
AssetsDir = Directory.GetParent(skelPath).FullName;
|
||||||
SkelPath = Path.GetFullPath(skelPath);
|
SkelPath = Path.GetFullPath(skelPath);
|
||||||
AtlasPath = Path.GetFullPath(atlasPath);
|
AtlasPath = Path.GetFullPath(atlasPath);
|
||||||
Name = Path.GetFileNameWithoutExtension(skelPath);
|
Name = Path.GetFileNameWithoutExtension(skelPath);
|
||||||
@@ -219,6 +230,8 @@ namespace SpineViewer.Spine
|
|||||||
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
|
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
|
||||||
protected virtual void Dispose(bool disposing) { preview?.Dispose(); }
|
protected virtual void Dispose(bool disposing) { preview?.Dispose(); }
|
||||||
|
|
||||||
|
#region 属性 | 基本信息
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取所属版本
|
/// 获取所属版本
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -226,6 +239,12 @@ namespace SpineViewer.Spine
|
|||||||
[Category("基本信息"), DisplayName("运行时版本")]
|
[Category("基本信息"), DisplayName("运行时版本")]
|
||||||
public Version Version { get; }
|
public Version Version { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 资源所在完整目录
|
||||||
|
/// </summary>
|
||||||
|
[Category("基本信息"), DisplayName("资源目录")]
|
||||||
|
public string AssetsDir { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// skel 文件完整路径
|
/// skel 文件完整路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -238,6 +257,9 @@ namespace SpineViewer.Spine
|
|||||||
[Category("基本信息"), DisplayName("atlas文件路径")]
|
[Category("基本信息"), DisplayName("atlas文件路径")]
|
||||||
public string AtlasPath { get; }
|
public string AtlasPath { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 名称
|
||||||
|
/// </summary>
|
||||||
[Category("基本信息"), DisplayName("名称")]
|
[Category("基本信息"), DisplayName("名称")]
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
@@ -247,6 +269,10 @@ namespace SpineViewer.Spine
|
|||||||
[Category("基本信息"), DisplayName("文件版本")]
|
[Category("基本信息"), DisplayName("文件版本")]
|
||||||
public abstract string FileVersion { get; }
|
public abstract string FileVersion { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 属性 | 变换
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 缩放比例
|
/// 缩放比例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -272,12 +298,18 @@ namespace SpineViewer.Spine
|
|||||||
[Category("变换"), DisplayName("垂直翻转")]
|
[Category("变换"), DisplayName("垂直翻转")]
|
||||||
public abstract bool FlipY { get; set; }
|
public abstract bool FlipY { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 属性 | 画面
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是否使用预乘Alpha
|
/// 是否使用预乘Alpha
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Category("画面"), DisplayName("预乘Alpha通道")]
|
[Category("画面"), DisplayName("预乘Alpha通道")]
|
||||||
public bool UsePremultipliedAlpha { get; set; } = true;
|
public bool UsePremultipliedAlpha { get; set; } = true;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含的所有动画名称
|
/// 包含的所有动画名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -291,8 +323,10 @@ namespace SpineViewer.Spine
|
|||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public string DefaultAnimationName { get => animationNames.Last(); }
|
public string DefaultAnimationName { get => animationNames.Last(); }
|
||||||
|
|
||||||
|
#region 属性 | 动画
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 当前动画名称
|
/// 当前动画名称, 如果设置的动画不存在则忽略
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(AnimationConverter))]
|
[TypeConverter(typeof(AnimationConverter))]
|
||||||
[Category("动画"), DisplayName("当前动画")]
|
[Category("动画"), DisplayName("当前动画")]
|
||||||
@@ -304,6 +338,8 @@ namespace SpineViewer.Spine
|
|||||||
[Category("动画"), DisplayName("当前动画时长")]
|
[Category("动画"), DisplayName("当前动画时长")]
|
||||||
public float CurrentAnimationDuration { get => GetAnimationDuration(CurrentAnimation); }
|
public float CurrentAnimationDuration { get => GetAnimationDuration(CurrentAnimation); }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 骨骼包围盒
|
/// 骨骼包围盒
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -320,7 +356,21 @@ namespace SpineViewer.Spine
|
|||||||
{
|
{
|
||||||
if (preview is null)
|
if (preview is null)
|
||||||
{
|
{
|
||||||
using var img = GetPreview((uint)PREVIEW_SIZE.Width, (uint)PREVIEW_SIZE.Height);
|
// XXX: tex 没办法在这里主动 Dispose
|
||||||
|
// 批量添加在获取预览图的时候极大概率会和预览线程死锁
|
||||||
|
// 虽然两边不会同时调用 Draw, 但是死锁似乎和 Draw 函数有关
|
||||||
|
// 除此之外, 似乎还和 tex 的 Dispose 有关
|
||||||
|
// 如果不对 tex 进行 Dispose, 那么不管是否 Draw 都正常不会死锁
|
||||||
|
var tex = new SFML.Graphics.RenderTexture(PREVIEW_WIDTH, PREVIEW_HEIGHT);
|
||||||
|
tex.SetView(GetInitView(PREVIEW_WIDTH, PREVIEW_HEIGHT));
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
var tmp = CurrentAnimation;
|
||||||
|
CurrentAnimation = EMPTY_ANIMATION;
|
||||||
|
tex.Draw(this);
|
||||||
|
CurrentAnimation = tmp;
|
||||||
|
tex.Display();
|
||||||
|
|
||||||
|
using var img = tex.Texture.CopyToImage();
|
||||||
img.SaveToMemory(out var imgBuffer, "bmp");
|
img.SaveToMemory(out var imgBuffer, "bmp");
|
||||||
using var stream = new MemoryStream(imgBuffer);
|
using var stream = new MemoryStream(imgBuffer);
|
||||||
preview = new Bitmap(stream);
|
preview = new Bitmap(stream);
|
||||||
@@ -330,41 +380,6 @@ namespace SpineViewer.Spine
|
|||||||
}
|
}
|
||||||
private Image preview = null;
|
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;
|
|
||||||
|
|
||||||
using 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>
|
/// <summary>
|
||||||
/// 获取动画时长, 如果动画不存在则返回 0
|
/// 获取动画时长, 如果动画不存在则返回 0
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -376,6 +391,89 @@ namespace SpineViewer.Spine
|
|||||||
/// <param name="delta">时间间隔</param>
|
/// <param name="delta">时间间隔</param>
|
||||||
public abstract void Update(float delta);
|
public abstract void Update(float delta);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取初始状态下合适的 View, 参数单位为像素
|
||||||
|
/// </summary>
|
||||||
|
public SFML.Graphics.View GetInitView(Size resolution, Padding padding) =>
|
||||||
|
GetInitView((uint)resolution.Width, (uint)resolution.Height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取初始状态下合适的 View, 参数单位为像素
|
||||||
|
/// </summary>
|
||||||
|
public SFML.Graphics.View GetInitView(uint width, uint height, Padding padding) =>
|
||||||
|
GetInitView(width, height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取初始状态下合适的 View, 参数单位为像素
|
||||||
|
/// </summary>
|
||||||
|
public SFML.Graphics.View GetInitView(Size resolution, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1) =>
|
||||||
|
GetInitView((uint)resolution.Width, (uint)resolution.Height, paddingL, paddingR, paddingT, paddingB);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取初始状态下合适的 View, 参数单位为像素
|
||||||
|
/// </summary>
|
||||||
|
public SFML.Graphics.View GetInitView(uint width, uint height, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1)
|
||||||
|
{
|
||||||
|
var tmp = CurrentAnimation;
|
||||||
|
CurrentAnimation = EMPTY_ANIMATION;
|
||||||
|
var bounds = Bounds;
|
||||||
|
CurrentAnimation = tmp;
|
||||||
|
|
||||||
|
float sizeX = bounds.Width;
|
||||||
|
float sizeY = bounds.Height;
|
||||||
|
float innerW = width - paddingL - paddingR;
|
||||||
|
float innerH = height - paddingT - paddingB;
|
||||||
|
|
||||||
|
float scale = 1;
|
||||||
|
if ((sizeY / sizeX) < (innerH / innerW))
|
||||||
|
scale = sizeX / innerW; // 相同的 X, 视窗 Y 更大
|
||||||
|
else
|
||||||
|
scale = sizeY / innerH; // 相同的 Y, 视窗 X 更大
|
||||||
|
|
||||||
|
var x = bounds.X + bounds.Width / 2 + ((float)paddingL - (float)paddingR) * scale;
|
||||||
|
var y = bounds.Y + bounds.Height / 2 + ((float)paddingT - (float)paddingB) * scale;
|
||||||
|
var viewX = width * scale;
|
||||||
|
var viewY = height * scale;
|
||||||
|
|
||||||
|
return new(new(x, y), new(viewX, -viewY));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否被选中
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public bool IsSelected { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示调试
|
||||||
|
/// </summary>
|
||||||
|
[Browsable(false)]
|
||||||
|
public bool IsDebug { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包围盒颜色
|
||||||
|
/// </summary>
|
||||||
|
protected static readonly SFML.Graphics.Color BoundsColor = new(120, 200, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 包围盒顶点数组
|
||||||
|
/// </summary>
|
||||||
|
protected readonly SFML.Graphics.VertexArray boundsVertices = new(SFML.Graphics.PrimitiveType.LineStrip, 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示包围盒
|
||||||
|
/// </summary>
|
||||||
|
[Category("调试"), DisplayName("显示包围盒")]
|
||||||
|
public bool DebugBounds { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示骨骼
|
||||||
|
/// </summary>
|
||||||
|
[Category("调试"), DisplayName("显示骨骼(TODO)")]
|
||||||
|
public bool DebugBones { get; set; } = false;
|
||||||
|
|
||||||
|
#region SFML.Graphics.Drawable 接口实现
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 顶点坐标缓冲区
|
/// 顶点坐标缓冲区
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -384,17 +482,13 @@ namespace SpineViewer.Spine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 顶点缓冲区
|
/// 顶点缓冲区
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected SFML.Graphics.VertexArray vertexArray = new(SFML.Graphics.PrimitiveType.Triangles);
|
protected readonly SFML.Graphics.VertexArray vertexArray = new(SFML.Graphics.PrimitiveType.Triangles);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SFML.Graphics.Drawable 接口实现
|
/// SFML.Graphics.Drawable 接口实现
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
|
public abstract void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
|
||||||
|
|
||||||
/// <summary>
|
#endregion
|
||||||
/// 是否被选中
|
|
||||||
/// </summary>
|
|
||||||
[Browsable(false)]
|
|
||||||
public bool IsSelected { get; set; } = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace SpineViewer.Spine
|
|||||||
if (destinationType == typeof(string) && value is Version version)
|
if (destinationType == typeof(string) && value is Version version)
|
||||||
{
|
{
|
||||||
// 调用自定义的 String() 方法
|
// 调用自定义的 String() 方法
|
||||||
return version.String();
|
return version.GetName();
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.ConvertTo(context, culture, value, destinationType);
|
return base.ConvertTo(context, culture, value, destinationType);
|
||||||
@@ -42,7 +42,19 @@ namespace SpineViewer.Spine
|
|||||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||||
{
|
{
|
||||||
if (context?.Instance is Spine obj)
|
if (context?.Instance is Spine obj)
|
||||||
|
{
|
||||||
return new StandardValuesCollection(obj.AnimationNames);
|
return new StandardValuesCollection(obj.AnimationNames);
|
||||||
|
}
|
||||||
|
else if (context?.Instance is Spine[] spines)
|
||||||
|
{
|
||||||
|
if (spines.Length > 0)
|
||||||
|
{
|
||||||
|
IEnumerable<string> common = spines[0].AnimationNames;
|
||||||
|
foreach (var spine in spines.Skip(1))
|
||||||
|
common = common.Intersect(spine.AnimationNames);
|
||||||
|
return new StandardValuesCollection(common.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
return base.GetStandardValues(context);
|
return base.GetStandardValues(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,21 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace SpineViewer.Spine
|
namespace SpineViewer.Spine
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Spine 版本静态辅助类
|
||||||
|
/// </summary>
|
||||||
public static class VersionHelper
|
public static class VersionHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 描述缓存
|
/// 版本名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly ReadOnlyDictionary<Version, string> Versions;
|
public static readonly ReadOnlyDictionary<Version, string> Names;
|
||||||
private static readonly Dictionary<Version, string> versions = [];
|
private static readonly Dictionary<Version, string> names = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runtime 版本字符串
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<Version, string> runtimes = [];
|
||||||
|
|
||||||
static VersionHelper()
|
static VersionHelper()
|
||||||
{
|
{
|
||||||
@@ -24,17 +32,33 @@ namespace SpineViewer.Spine
|
|||||||
{
|
{
|
||||||
var field = typeof(Version).GetField(value.ToString());
|
var field = typeof(Version).GetField(value.ToString());
|
||||||
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
|
var attribute = field?.GetCustomAttribute<DescriptionAttribute>();
|
||||||
versions[(Version)value] = attribute?.Description ?? value.ToString();
|
names[(Version)value] = attribute?.Description ?? value.ToString();
|
||||||
}
|
}
|
||||||
Versions = versions.AsReadOnly();
|
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>
|
||||||
/// 版本号字符串
|
/// 版本字符串名称
|
||||||
/// </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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
<Version>0.10.5</Version>
|
<Version>0.10.9</Version>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FFMpegCore" Version="5.2.0" />
|
||||||
<PackageReference Include="NLog.Windows.Forms" Version="5.2.3" />
|
<PackageReference Include="NLog.Windows.Forms" Version="5.2.3" />
|
||||||
<PackageReference Include="SFML.Net" Version="2.6.1" />
|
<PackageReference Include="SFML.Net" Version="2.6.1" />
|
||||||
<PackageReference Include="System.Management" Version="9.0.2" />
|
<PackageReference Include="System.Management" Version="9.0.2" />
|
||||||
|
|||||||
Reference in New Issue
Block a user