Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
165be38cfe | ||
|
|
f1fb5fe5e1 | ||
|
|
17554d3a8e | ||
|
|
054ac3939b | ||
|
|
9566c0a6f5 | ||
|
|
b8509ccb69 | ||
|
|
1e043e4faa | ||
|
|
5b1c177f58 | ||
|
|
794de783db | ||
|
|
7f6aa26986 | ||
|
|
fcd809fda5 | ||
|
|
71ff8007fb | ||
|
|
a8261f4f58 | ||
|
|
bb6a6f3664 | ||
|
|
9c6af07d32 | ||
|
|
afc011adbb | ||
|
|
da42c14d35 | ||
|
|
a7438e2026 | ||
|
|
47e5314bb3 | ||
|
|
1978c1da11 | ||
|
|
914c9d0ea3 | ||
|
|
7134aebb7f | ||
|
|
abb32e9ed2 | ||
|
|
2c77e385c5 | ||
|
|
ba0f5ac124 | ||
|
|
c4f17e3f06 | ||
|
|
b802ec252a | ||
|
|
68779caab0 | ||
|
|
5d819114d0 | ||
|
|
46b3937236 | ||
|
|
6803b8cf4a | ||
|
|
bd9c5a176b | ||
|
|
76c1d96c87 | ||
|
|
304af805cb | ||
|
|
027d3af619 | ||
|
|
c612c01ac7 | ||
|
|
cd7855a877 | ||
|
|
cdd81e0bfb | ||
|
|
750a8b8aff | ||
|
|
cd86155878 | ||
|
|
16739c39d6 | ||
|
|
c7971a9829 | ||
|
|
44c4fc4b21 | ||
|
|
6f1c8e3320 | ||
|
|
8f818416ba | ||
|
|
de6858ca48 | ||
|
|
3fd3d2a378 | ||
|
|
706c9125e6 | ||
|
|
5f026b000c | ||
|
|
0b0d036f08 | ||
|
|
6b9017d535 | ||
|
|
5eb47e33ac | ||
|
|
4d31335da0 | ||
|
|
0b5e76a448 | ||
|
|
775268c01a | ||
|
|
b0b1c85047 | ||
|
|
5f08fc6695 | ||
|
|
2de3bdf12b | ||
|
|
3a424c7dc1 | ||
|
|
c3e2b37072 | ||
|
|
65bd11a346 | ||
|
|
e6e7fc539f | ||
|
|
6522d415b7 | ||
|
|
378c66a333 | ||
|
|
07204417a5 | ||
|
|
c9c909cdf9 | ||
|
|
a9f59a4d2f | ||
|
|
1d2513cef5 | ||
|
|
febb797ae2 | ||
|
|
68d279a7c3 | ||
|
|
d2d8b7955c | ||
|
|
2a55fd9c36 | ||
|
|
695d3c0735 | ||
|
|
ce95db469b | ||
|
|
5d187cf80f | ||
|
|
e704ebc224 | ||
|
|
ee36f8981c | ||
|
|
09dd220abf | ||
|
|
15bc2dc3b8 | ||
|
|
1deb74eca9 | ||
|
|
de76ce64ab | ||
|
|
94b4ba33e6 | ||
|
|
7ce8a115f4 | ||
|
|
c036a4bb45 | ||
|
|
aa62f30b05 | ||
|
|
3d967c9812 | ||
|
|
e87e9efb99 |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,5 +1,32 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.12.7
|
||||
|
||||
- 修复一些问题
|
||||
|
||||
## v0.12.6
|
||||
|
||||
- 增加全屏预览
|
||||
- 增加桌面投影 (实验性功能)
|
||||
- 增加预览画面背景色设置
|
||||
- 增加分辨率和颜色预设列表
|
||||
- 皮肤面板显示 default
|
||||
|
||||
## v0.12.5
|
||||
|
||||
- 增加插槽属性面板
|
||||
- 修改皮肤属性面板设置方式为True/False
|
||||
|
||||
## v0.12.4
|
||||
|
||||
- 增加导出自动分辨率参数
|
||||
- 增加导出边缘和填充参数
|
||||
- 增加导出内容溢出参数
|
||||
- 支持3.7及以下版本多皮肤功能
|
||||
- 增加3.8版本的骨骼文件二进制和文本格式互转
|
||||
- 增加格式转换输出文件夹参数
|
||||
- 修改打开对话框的默认文件后缀筛选为所有类型
|
||||
|
||||
## v0.12.3
|
||||
|
||||
- 增加按住 ctrl 缩放选中模型
|
||||
|
||||
137
README.en.md
137
README.en.md
@@ -6,92 +6,97 @@
|
||||
|
||||
[中文](README.md) | [English](README.en.md)
|
||||
|
||||
*A WYSIWYG Spine file viewer and exporter.*
|
||||
A *WYSIWYG* Spine file viewer & exporter.
|
||||
|
||||

|
||||
|
||||
---
|
||||
## Features
|
||||
|
||||
:sparkles: v0.12.x New Feature: Support for multi-track animations and multi-skin list management :sparkles:
|
||||
- Supports multiple Spine file versions
|
||||
- Drag & drop or copy/paste to open files in batch
|
||||
- List-based skeleton view with render layer management
|
||||
- Multi-select list to batch-adjust skeleton parameters
|
||||
- Multi-track animation support
|
||||
- Skin / custom slot attachment configuration
|
||||
- Debug rendering mode
|
||||
- Fullscreen preview
|
||||
- Export to single-frame image, animated GIF/WebP/AVIF, video formats
|
||||
- Batch export at multiple resolutions
|
||||
- Custom FFmpeg export parameters
|
||||
- …and more
|
||||
|
||||
---
|
||||
### Spine Version Support
|
||||
|
||||
| Version | View & Export | Format Conversion | Version Conversion |
|
||||
| :------: | :-----------: | :---------------: | :----------------: |
|
||||
| `2.1.x` | :white_check_mark: | | |
|
||||
| `3.6.x` | :white_check_mark: | | |
|
||||
| `3.7.x` | :white_check_mark: | | |
|
||||
| `3.8.x` | :white_check_mark: | :white_check_mark: | |
|
||||
| `4.0.x` | :white_check_mark: | | |
|
||||
| `4.1.x` | :white_check_mark: | | |
|
||||
| `4.2.x` | :white_check_mark: | | |
|
||||
| `4.3.x` | | | |
|
||||
|
||||
More versions coming soon 🚀🚀🚀
|
||||
|
||||
### Supported Export Formats
|
||||
|
||||
| Export Format | Use Case |
|
||||
| --------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| Single Frame | Generate high‑resolution still images; pick any frame manually. |
|
||||
| Frame Sequence (PNG) | Lossless PNG sequences with alpha channel preserved. |
|
||||
| GIF / WebP / AVIF | Perfect for quick animated previews. |
|
||||
| MP4 | The most widely compatible video format. |
|
||||
| WebM | Browser‑friendly streaming with optional transparency. |
|
||||
| MKV / MOV | For those who like to tinker. |
|
||||
| Custom FFmpeg Command | Use any FFmpeg arguments for complex, tailored export workflows. |
|
||||
|
||||
## Installation
|
||||
|
||||
Head over to the [Release](https://github.com/ww-rm/SpineViewer/releases) page to download the zip package.
|
||||
1. Go to the [Releases](https://github.com/ww-rm/SpineViewer/releases) page and download the ZIP.
|
||||
2. Make sure you have the [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/download/dotnet/8.0) installed.
|
||||
3. Alternatively, download the `SelfContained` ZIP, which runs standalone without any .NET prerequisites.
|
||||
4. To export GIF or other video formats, install the `ffmpeg` CLI and add it to your PATH.
|
||||
- Windows builds: see the [FFmpeg download page](https://ffmpeg.org/download.html#build-windows)
|
||||
- Direct download: [ffmpeg-release-full.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z)
|
||||
|
||||
The software requires the dependency framework [.NET Desktop Runtime 8.0.x](https://dotnet.microsoft.com/en-us/download/dotnet/8.0).
|
||||
## Usage
|
||||
|
||||
Alternatively, you can download the package with the `SelfContained` suffix, which can run independently.
|
||||
### Importing Skeletons
|
||||
|
||||
Exporting video formats such as GIF requires that ffmpeg is installed locally and added to your system’s PATH. You can [click here to go to the FFmpeg-Windows download page](https://ffmpeg.org/download.html#build-windows) or directly download the latest version [ffmpeg-release-full.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z).
|
||||
You can import Spine skeletons in three ways:
|
||||
|
||||
## Supported Export Formats
|
||||
- Drag & drop or paste skeleton files or folders onto the model list.
|
||||
- Use **File > Open** to batch‑open multiple skeleton files.
|
||||
- Use **File > Open Single Model** to open one at a time.
|
||||
|
||||
| Export Format | Suitable for Scenario |
|
||||
| ------------ | ------------------------------------------------------------------------------------|
|
||||
| Single Frame | Supports generating high-definition model snapshots; you can manually adjust the frame. |
|
||||
| Frame Sequence | Supports png sequence output with transparency and lossless compression. |
|
||||
| GIF | Ideal for generating preview animations. |
|
||||
| MP4 | The most common video format with the best compatibility. |
|
||||
| WebM | Suitable for browser-based playback and supports transparent backgrounds. |
|
||||
| MKV | For more experimental use. |
|
||||
| MOV | For more experimental use. |
|
||||
| Custom Export | In addition to the above presets, you can provide any FFmpeg parameters to meet complex custom needs. |
|
||||
### Adjusting Content
|
||||
|
||||
## Supported Spine Versions
|
||||
- Right‑click menu and keyboard shortcuts are available in the model list. You can multi‑select to adjust parameters in batch.
|
||||
- In the preview pane, you can also use mouse controls:
|
||||
- **Left‑click & drag** to move a model; hold **Ctrl** to multi‑select (synced with the list).
|
||||
- **Right‑click & drag** to pan the entire scene.
|
||||
- **Mouse wheel** to zoom; hold **Ctrl** to zoom all selected models proportionally.
|
||||
- **“Render Selected Only”** mode shows only the selected models in preview; use the list to change selection.
|
||||
|
||||
| Version | View & Export | Format Conversion | Version Conversion |
|
||||
| :------: | :-------------------: | :------------------: | :-----------------: |
|
||||
| `2.1.x` | :white_check_mark: | | |
|
||||
| `3.1.x` | | | |
|
||||
| `3.4.x` | | | |
|
||||
| `3.5.x` | | | |
|
||||
| `3.6.x` | :white_check_mark: | | |
|
||||
| `3.7.x` | :white_check_mark: | | |
|
||||
| `3.8.x` | :white_check_mark: | :white_check_mark: | |
|
||||
| `4.1.x` | :white_check_mark: | | |
|
||||
| `4.2.x` | :white_check_mark: | | |
|
||||
| `4.3.x` | | | |
|
||||
Below the preview, playback controls let you scrub through the timeline like a basic player.
|
||||
|
||||
More versions are under development :rocket: :rocket: :rocket:
|
||||
### Exporting Content
|
||||
|
||||
## How to Use
|
||||
Exports follow the “what you see is what you get” principle—your real‑time preview is exactly what gets exported.
|
||||
|
||||
### Importing Skeleton Files
|
||||
Key export options:
|
||||
|
||||
There are three ways to import skeleton files:
|
||||
- **Render Selected Only**: includes only the selected models in both preview and export.
|
||||
- **Output Folder**: if unspecified, exports go into each model’s source folder; otherwise, everything exports to the chosen folder.
|
||||
- **Export Single**: by default, each model is exported separately; enable this to render all selected models together into a single output.
|
||||
- **Auto Resolution**: ignores preview resolution and viewport size—exports at the content’s actual bounds; for animations, matches the full animation area.
|
||||
|
||||
- Drag and drop or paste the skeleton file/directory into the model list.
|
||||
- Batch open skeleton files from the File menu.
|
||||
- Select a single model to open from the File menu.
|
||||
## More
|
||||
|
||||
### Adjusting the Preview
|
||||
|
||||
The model list supports context menus and some shortcuts, and you can multi-select to adjust parameters in bulk.
|
||||
|
||||
In addition to using the panel for parameter settings, the preview screen supports several mouse actions:
|
||||
|
||||
- Left-click to select and drag models; hold the `Ctrl` key for multi-selection (which is synchronized with the list on the left).
|
||||
- Right-click to drag the overall view.
|
||||
- Use the mouse wheel to zoom in and out.
|
||||
- “Render Selected” mode: in this mode, the preview screen only shows the selected models and the selection state can only be changed from the list on the left.
|
||||
|
||||
The buttons below the preview allow you to adjust the timeline, acting as a simple media player.
|
||||
|
||||
### Exporting the Preview
|
||||
|
||||
Exporting follows the “What You See Is What You Get” principle – the preview exactly reflects the output.
|
||||
|
||||
There are several key parameters for export:
|
||||
|
||||
- Render Selected Only: This option affects both the preview and export. If enabled, only the selected models will be considered during export while ignoring the others.
|
||||
- Output Folder: This parameter is optional in some cases. If not provided, the output files will be saved in each model’s own folder; otherwise, all outputs will be saved to the specified folder.
|
||||
- Single Export: By default, each model is exported separately (i.e., batch operation on the model list). If “Single Export” is selected, all the exported models will be rendered on the same canvas, producing only one output file.
|
||||
|
||||
### More Information
|
||||
|
||||
For detailed instructions and usage notes, please see the [Wiki](https://github.com/ww-rm/SpineViewer/wiki). If you encounter any issues or bugs, feel free to open an [Issue](https://github.com/ww-rm/SpineViewer/issues).
|
||||
Detailed usage and advanced tips are in the [Wiki](https://github.com/ww-rm/SpineViewer/wiki).
|
||||
Encounter a bug or have a feature request? Open an [Issue](https://github.com/ww-rm/SpineViewer/issues).
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
@@ -101,6 +106,6 @@ For detailed instructions and usage notes, please see the [Wiki](https://github.
|
||||
|
||||
---
|
||||
|
||||
*If you like this project, please give it a :star: and share it with others!*
|
||||
If you find this project useful, please give it a ⭐ and share it with others!
|
||||
|
||||
[](https://starchart.cc/ww-rm/SpineViewer)
|
||||
68
README.md
68
README.md
@@ -10,23 +10,37 @@
|
||||
|
||||

|
||||
|
||||
---
|
||||
## 功能
|
||||
|
||||
:sparkles: v0.12.x 新增功能: 支持多轨道动画以及多皮肤列表管理 :sparkles:
|
||||
- 支持多版本 spine 文件
|
||||
- 支持拖拽/复制粘贴批量打开文件
|
||||
- 支持列表式多骨骼查看和渲染层级管理
|
||||
- 支持列表多选批量设置骨骼参数
|
||||
- 支持多轨道动画设置
|
||||
- 支持皮肤/自定义插槽附件设置
|
||||
- 支持调试渲染
|
||||
- 支持全屏预览
|
||||
- 支持单帧/动图/视频文件导出
|
||||
- 支持自动分辨率批量导出
|
||||
- 支持 FFmpeg 自定义导出
|
||||
- ...
|
||||
|
||||
---
|
||||
### Spine 版本支持
|
||||
|
||||
## 安装
|
||||
| 版本 | 查看&导出 | 格式转换 | 版本转换 |
|
||||
| :---: | :---: | :---: | :---: |
|
||||
| `2.1.x` | :white_check_mark: | | |
|
||||
| `3.6.x` | :white_check_mark: | | |
|
||||
| `3.7.x` | :white_check_mark: | | |
|
||||
| `3.8.x` | :white_check_mark: | :white_check_mark: | |
|
||||
| `4.0.x` | :white_check_mark: | | |
|
||||
| `4.1.x` | :white_check_mark: | | |
|
||||
| `4.2.x` | :white_check_mark: | | |
|
||||
| `4.3.x` | | | |
|
||||
|
||||
前往 [Release](https://github.com/ww-rm/SpineViewer/releases) 界面下载压缩包.
|
||||
更多版本正在施工 :rocket: :rocket: :rocket:
|
||||
|
||||
软件需要安装依赖框架 [.NET 桌面运行时 8.0.x](https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0).
|
||||
|
||||
也可以下载带有 `SelfContained` 后缀的压缩包, 可以独立运行.
|
||||
|
||||
导出 GIF 等视频格式需要在本地安装 ffmpeg 命令行, 并且添加至环境变量, [点击前往 FFmpeg-Windows 下载页面](https://ffmpeg.org/download.html#build-windows), 也可以点这个下载最新版本 [ffmpeg-release-full.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z).
|
||||
|
||||
## 导出格式支持
|
||||
### 导出格式支持
|
||||
|
||||
| 导出格式 | 适用场景 |
|
||||
| --- | --- |
|
||||
@@ -38,34 +52,27 @@
|
||||
| MKV/MOV | 适合折腾. |
|
||||
| 自定义导出 | 除上述预设方案, 支持提供任意 FFmpeg 参数进行导出, 满足自定义复杂需求. |
|
||||
|
||||
## Spine 版本支持
|
||||
## 安装
|
||||
|
||||
| 版本 | 查看&导出 | 格式转换 | 版本转换 |
|
||||
| :---: | :---: | :---: | :---: |
|
||||
| `2.1.x` | :white_check_mark: | | |
|
||||
| `3.1.x` | | | |
|
||||
| `3.4.x` | | | |
|
||||
| `3.5.x` | | | |
|
||||
| `3.6.x` | :white_check_mark: | | |
|
||||
| `3.7.x` | :white_check_mark: | | |
|
||||
| `3.8.x` | :white_check_mark: | :white_check_mark: | |
|
||||
| `4.1.x` | :white_check_mark: | | |
|
||||
| `4.2.x` | :white_check_mark: | | |
|
||||
| `4.3.x` | | | |
|
||||
前往 [Release](https://github.com/ww-rm/SpineViewer/releases) 界面下载压缩包.
|
||||
|
||||
更多版本正在施工 :rocket: :rocket: :rocket:
|
||||
软件需要安装依赖框架 [.NET 桌面运行时 8.0.x](https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0).
|
||||
|
||||
也可以下载带有 `SelfContained` 后缀的压缩包, 可以独立运行.
|
||||
|
||||
导出 GIF 等视频格式需要在本地安装 ffmpeg 命令行, 并且添加至环境变量, [点击前往 FFmpeg-Windows 下载页面](https://ffmpeg.org/download.html#build-windows), 也可以点这个下载最新版本 [ffmpeg-release-full.7z](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z).
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 骨骼导入
|
||||
|
||||
有 3 种模式导入骨骼文件:
|
||||
有 3 种方式导入骨骼文件:
|
||||
|
||||
- 拖放/粘贴需要导入的骨骼文件/目录到模型列表
|
||||
- 从文件菜单里批量打开骨骼文件
|
||||
- 从文件菜单选择单个模型打开
|
||||
|
||||
### 预览内容调整
|
||||
### 内容调整
|
||||
|
||||
模型列表支持右键菜单以及部分快捷键, 并且可以多选进行模型参数的批量调整.
|
||||
|
||||
@@ -73,12 +80,12 @@
|
||||
|
||||
- 左键可以选择和拖拽模型, 按下 `Ctrl` 键可以实现多选, 与左侧列表选择是联动的.
|
||||
- 右键对整体画面进行拖动.
|
||||
- 滚轮进行画面缩放.
|
||||
- 滚轮进行画面缩放, 按住 `Ctrl` 可以对选中的模型进行批量缩放.
|
||||
- 仅渲染选中模式, 在该模式下, 预览画面仅包含被选中的模型, 并且只能通过左侧列表改变选中状态.
|
||||
|
||||
预览画面下方按钮支持对画面时间进行调整, 可以当作一个简易的播放器.
|
||||
|
||||
### 预览内容导出
|
||||
### 内容导出
|
||||
|
||||
导出遵循 "所见即所得" 原则, 即实时预览的画面就是你导出的画面.
|
||||
|
||||
@@ -87,6 +94,7 @@
|
||||
- 仅渲染选中. 这个参数不仅影响预览模式, 也影响导出, 如果仅渲染选中, 那么在导出时只有被选中的模型会被考虑, 忽略其他模型.
|
||||
- 输出文件夹. 这个参数某些时候可选, 当不提供时, 则将输出产物输出到每个模型各自的模型文件夹, 否则输出产物全部输出到提供的输出文件夹.
|
||||
- 导出单个. 默认是每个模型独立导出, 即对模型列表进行批量操作, 如果选择仅导出单个, 那么被导出的所有模型将在同一个画面上被渲染, 输出产物只有一份.
|
||||
- 自动分辨率. 该模式会忽略预览画面的分辨率和视区参数, 导出产物的分辨率与被导出内容的实际大小一致, 如果是动图或者视频则会与完整显示动画的必需大小一致.
|
||||
|
||||
### 更多
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace SpineRuntime21 {
|
||||
public class Bone{
|
||||
static public bool yDown;
|
||||
static readonly public bool yDown = false;
|
||||
|
||||
internal BoneData data;
|
||||
internal Skeleton skeleton;
|
||||
@@ -84,7 +84,8 @@ namespace SpineRuntime21 {
|
||||
|
||||
/// <summary>Computes the world SRT using the parent bone and the local SRT.</summary>
|
||||
public void UpdateWorldTransform () {
|
||||
Bone parent = this.parent;
|
||||
float sx = skeleton.scaleX, sy = skeleton.scaleY;
|
||||
Bone parent = this.parent;
|
||||
float x = this.x, y = this.y;
|
||||
if (parent != null) {
|
||||
worldX = x * parent.m00 + y * parent.m01 + parent.worldX;
|
||||
@@ -100,34 +101,22 @@ namespace SpineRuntime21 {
|
||||
worldFlipX = parent.worldFlipX != flipX;
|
||||
worldFlipY = parent.worldFlipY != flipY;
|
||||
} else {
|
||||
Skeleton skeleton = this.skeleton;
|
||||
bool skeletonFlipX = skeleton.flipX, skeletonFlipY = skeleton.flipY;
|
||||
worldX = skeletonFlipX ? -x : x;
|
||||
worldY = skeletonFlipY != yDown ? -y : y;
|
||||
worldX = x * sx;
|
||||
worldY = y * sy;
|
||||
worldScaleX = scaleX;
|
||||
worldScaleY = scaleY;
|
||||
worldRotation = rotationIK;
|
||||
worldFlipX = skeletonFlipX != flipX;
|
||||
worldFlipY = skeletonFlipY != flipY;
|
||||
worldFlipX = (sx < 0) != flipX;
|
||||
worldFlipY = (sy < 0) != flipY;
|
||||
}
|
||||
float radians = worldRotation * (float)Math.PI / 180;
|
||||
float cos = (float)Math.Cos(radians);
|
||||
float sin = (float)Math.Sin(radians);
|
||||
if (worldFlipX) {
|
||||
m00 = -cos * worldScaleX;
|
||||
m01 = sin * worldScaleY;
|
||||
} else {
|
||||
m00 = cos * worldScaleX;
|
||||
m01 = -sin * worldScaleY;
|
||||
}
|
||||
if (worldFlipY != yDown) {
|
||||
m10 = -sin * worldScaleX;
|
||||
m11 = -cos * worldScaleY;
|
||||
} else {
|
||||
m10 = sin * worldScaleX;
|
||||
m11 = cos * worldScaleY;
|
||||
}
|
||||
}
|
||||
m00 = cos * worldScaleX * sx;
|
||||
m01 = -sin * worldScaleY * sx;
|
||||
m10 = sin * worldScaleX * sy;
|
||||
m11 = cos * worldScaleY * sy;
|
||||
}
|
||||
|
||||
public void SetToSetupPose () {
|
||||
BoneData data = this.data;
|
||||
|
||||
@@ -42,8 +42,8 @@ namespace SpineRuntime21 {
|
||||
internal Skin skin;
|
||||
internal float r = 1, g = 1, b = 1, a = 1;
|
||||
internal float time;
|
||||
internal bool flipX, flipY;
|
||||
internal float x, y;
|
||||
internal float scaleX = 1, scaleY = 1;
|
||||
internal float x, y;
|
||||
|
||||
public SkeletonData Data { get { return data; } }
|
||||
public List<Bone> Bones { get { return bones; } }
|
||||
@@ -58,10 +58,16 @@ namespace SpineRuntime21 {
|
||||
public float Time { get { return time; } set { time = value; } }
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
public bool FlipX { get { return flipX; } set { flipX = value; } }
|
||||
public bool FlipY { get { return flipY; } set { flipY = value; } }
|
||||
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
|
||||
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
|
||||
|
||||
public Bone RootBone {
|
||||
[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
|
||||
public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
|
||||
|
||||
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
|
||||
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
|
||||
|
||||
public Bone RootBone {
|
||||
get {
|
||||
return bones.Count == 0 ? null : bones[0];
|
||||
}
|
||||
@@ -302,5 +308,50 @@ namespace SpineRuntime21 {
|
||||
public void Update (float delta) {
|
||||
time += delta;
|
||||
}
|
||||
}
|
||||
|
||||
public void GetBounds(out float x, out float y, out float width, out float height)
|
||||
{
|
||||
float[] temp = new float[8];
|
||||
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
|
||||
for (int i = 0, n = drawOrder.Count; i < n; i++)
|
||||
{
|
||||
Slot slot = drawOrder[i];
|
||||
int verticesLength = 0;
|
||||
float[] vertices = null;
|
||||
Attachment attachment = slot.Attachment;
|
||||
if (attachment is RegionAttachment regionAttachment)
|
||||
{
|
||||
verticesLength = 8;
|
||||
vertices = temp;
|
||||
if (vertices.Length < 8) vertices = temp = new float[8];
|
||||
regionAttachment.ComputeWorldVertices(slot.Bone, temp);
|
||||
}
|
||||
else if (attachment is MeshAttachment meshAttachment)
|
||||
{
|
||||
|
||||
MeshAttachment mesh = meshAttachment;
|
||||
verticesLength = mesh.Vertices.Length;
|
||||
vertices = temp;
|
||||
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
|
||||
mesh.ComputeWorldVertices(slot, temp);
|
||||
}
|
||||
|
||||
if (vertices != null)
|
||||
{
|
||||
for (int ii = 0; ii < verticesLength; ii += 2)
|
||||
{
|
||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||
minX = Math.Min(minX, vx);
|
||||
minY = Math.Min(minY, vy);
|
||||
maxX = Math.Max(maxX, vx);
|
||||
maxY = Math.Max(maxY, vy);
|
||||
}
|
||||
}
|
||||
}
|
||||
x = minX;
|
||||
y = minY;
|
||||
width = maxX - minX;
|
||||
height = maxY - minY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,9 @@ namespace SpineRuntime21 {
|
||||
new Dictionary<KeyValuePair<int, String>, Attachment>(AttachmentComparer.Instance);
|
||||
|
||||
public String Name { get { return name; } }
|
||||
public Dictionary<KeyValuePair<int, String>, Attachment> Attachments { get { return attachments; } }
|
||||
|
||||
public Skin (String name) {
|
||||
public Skin (String name) {
|
||||
if (name == null) throw new ArgumentNullException("name cannot be null.");
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@@ -152,27 +152,13 @@ namespace SpineRuntime36 {
|
||||
|
||||
Bone parent = this.parent;
|
||||
if (parent == null) { // Root bone.
|
||||
float rotationY = rotation + 90 + shearY;
|
||||
float la = MathUtils.CosDeg(rotation + shearX) * scaleX;
|
||||
float lb = MathUtils.CosDeg(rotationY) * scaleY;
|
||||
float lc = MathUtils.SinDeg(rotation + shearX) * scaleX;
|
||||
float ld = MathUtils.SinDeg(rotationY) * scaleY;
|
||||
if (skeleton.flipX) {
|
||||
x = -x;
|
||||
la = -la;
|
||||
lb = -lb;
|
||||
}
|
||||
if (skeleton.flipY != yDown) {
|
||||
y = -y;
|
||||
lc = -lc;
|
||||
ld = -ld;
|
||||
}
|
||||
a = la;
|
||||
b = lb;
|
||||
c = lc;
|
||||
d = ld;
|
||||
worldX = x + skeleton.x;
|
||||
worldY = y + skeleton.y;
|
||||
float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY;
|
||||
a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx;
|
||||
b = MathUtils.CosDeg(rotationY) * scaleY * sx;
|
||||
c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy;
|
||||
d = MathUtils.SinDeg(rotationY) * scaleY * sy;
|
||||
worldX = x * sx + skeleton.x;
|
||||
worldY = y * sy + skeleton.y;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,13 +214,16 @@ namespace SpineRuntime36 {
|
||||
case TransformMode.NoScale:
|
||||
case TransformMode.NoScaleOrReflection: {
|
||||
float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
|
||||
float za = pa * cos + pb * sin;
|
||||
float zc = pc * cos + pd * sin;
|
||||
float za = (pa * cos + pb * sin) / skeleton.scaleX;
|
||||
float zc = (pc * cos + pd * sin) / skeleton.scaleY;
|
||||
float s = (float)Math.Sqrt(za * za + zc * zc);
|
||||
if (s > 0.00001f) s = 1 / s;
|
||||
za *= s;
|
||||
zc *= s;
|
||||
s = (float)Math.Sqrt(za * za + zc * zc);
|
||||
if (data.transformMode == TransformMode.NoScale
|
||||
&& (pa * pd - pb * pc < 0) != (skeleton.scaleX < 0 != skeleton.scaleY < 0)) s = -s;
|
||||
|
||||
float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
|
||||
float zb = MathUtils.Cos(r) * s;
|
||||
float zd = MathUtils.Sin(r) * s;
|
||||
@@ -242,26 +231,18 @@ namespace SpineRuntime36 {
|
||||
float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
|
||||
float lc = MathUtils.SinDeg(shearX) * scaleX;
|
||||
float ld = MathUtils.SinDeg(90 + shearY) * scaleY;
|
||||
if (data.transformMode != TransformMode.NoScaleOrReflection? pa * pd - pb* pc< 0 : skeleton.flipX != skeleton.flipY) {
|
||||
zb = -zb;
|
||||
zd = -zd;
|
||||
}
|
||||
a = za * la + zb * lc;
|
||||
b = za * lb + zb * ld;
|
||||
c = zc * la + zd * lc;
|
||||
d = zc * lb + zd * ld;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (skeleton.flipX) {
|
||||
a = -a;
|
||||
b = -b;
|
||||
}
|
||||
if (skeleton.flipY != Bone.yDown) {
|
||||
c = -c;
|
||||
d = -d;
|
||||
}
|
||||
a *= skeleton.scaleX;
|
||||
b *= skeleton.scaleX;
|
||||
c *= skeleton.scaleY;
|
||||
d *= skeleton.scaleY;
|
||||
}
|
||||
|
||||
public void SetToSetupPose () {
|
||||
|
||||
@@ -45,8 +45,8 @@ namespace SpineRuntime36 {
|
||||
internal Skin skin;
|
||||
internal float r = 1, g = 1, b = 1, a = 1;
|
||||
internal float time;
|
||||
internal bool flipX, flipY;
|
||||
internal float x, y;
|
||||
internal float scaleX = 1, scaleY = 1;
|
||||
internal float x, y;
|
||||
|
||||
public SkeletonData Data { get { return data; } }
|
||||
public ExposedList<Bone> Bones { get { return bones; } }
|
||||
@@ -64,10 +64,16 @@ namespace SpineRuntime36 {
|
||||
public float Time { get { return time; } set { time = value; } }
|
||||
public float X { get { return x; } set { x = value; } }
|
||||
public float Y { get { return y; } set { y = value; } }
|
||||
public bool FlipX { get { return flipX; } set { flipX = value; } }
|
||||
public bool FlipY { get { return flipY; } set { flipY = value; } }
|
||||
public float ScaleX { get { return scaleX; } set { scaleX = value; } }
|
||||
public float ScaleY { get { return scaleY; } set { scaleY = value; } }
|
||||
|
||||
public Bone RootBone {
|
||||
[Obsolete("Use ScaleX instead. FlipX is when ScaleX is negative.")]
|
||||
public bool FlipX { get { return scaleX < 0; } set { scaleX = value ? -1f : 1f; } }
|
||||
|
||||
[Obsolete("Use ScaleY instead. FlipY is when ScaleY is negative.")]
|
||||
public bool FlipY { get { return scaleY < 0; } set { scaleY = value ? -1f : 1f; } }
|
||||
|
||||
public Bone RootBone {
|
||||
get { return bones.Count == 0 ? null : bones.Items[0]; }
|
||||
}
|
||||
|
||||
|
||||
2
SpineViewer/Controls/SkelFileListBox.Designer.cs
generated
2
SpineViewer/Controls/SkelFileListBox.Designer.cs
generated
@@ -160,7 +160,7 @@
|
||||
//
|
||||
openFileDialog_Skel.AddExtension = false;
|
||||
openFileDialog_Skel.AddToRecent = false;
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Filter = "所有文件 (*.*)|*.*|skel 文件 (*.skel; *.json)|*.skel;*.json";
|
||||
openFileDialog_Skel.Multiselect = true;
|
||||
openFileDialog_Skel.Title = "批量选择skel文件";
|
||||
//
|
||||
|
||||
@@ -17,13 +17,14 @@ namespace SpineViewer.Controls
|
||||
public SkelFileListBox()
|
||||
{
|
||||
InitializeComponent();
|
||||
Items = listBox.Items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ListBox.Items
|
||||
/// </summary>
|
||||
public readonly ListBox.ObjectCollection Items;
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
public ListBox.ObjectCollection Items { get => listBox.Items; }
|
||||
|
||||
/// <summary>
|
||||
/// 从路径列表添加
|
||||
|
||||
@@ -29,12 +29,12 @@ namespace SpineViewer.Controls
|
||||
/// <summary>
|
||||
/// Spine 列表只读视图, 访问时必须使用 lock 语句锁定视图本身
|
||||
/// </summary>
|
||||
public readonly ReadOnlyCollection<Spine.SpineObject> Spines;
|
||||
public readonly ReadOnlyCollection<SpineObject> Spines;
|
||||
|
||||
/// <summary>
|
||||
/// Spine 列表, 访问时必须使用 lock 语句锁定只读视图 Spines
|
||||
/// </summary>
|
||||
private readonly List<Spine.SpineObject> spines = [];
|
||||
private readonly List<SpineObject> spines = [];
|
||||
|
||||
/// <summary>
|
||||
/// 用于属性页显示模型参数的包装类
|
||||
@@ -80,7 +80,7 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
try
|
||||
{
|
||||
var spine = Spine.SpineObject.New(result.Version, result.SkelPath, result.AtlasPath);
|
||||
var spine = SpineObject.New(result.Version, result.SkelPath, result.AtlasPath);
|
||||
|
||||
// 如果索引无效则在末尾添加
|
||||
if (index < 0 || index > listView.Items.Count)
|
||||
@@ -155,7 +155,7 @@ namespace SpineViewer.Controls
|
||||
|
||||
try
|
||||
{
|
||||
var spine = Spine.SpineObject.New(version, skelPath);
|
||||
var spine = SpineObject.New(version, skelPath);
|
||||
var preview = spine.Preview;
|
||||
lock (Spines) { spines.Add(spine); }
|
||||
spinePropertyWrappers[spine.ID] = new(spine);
|
||||
|
||||
118
SpineViewer/Controls/SpinePreviewPanel.Designer.cs
generated
118
SpineViewer/Controls/SpinePreviewPanel.Designer.cs
generated
@@ -32,7 +32,6 @@
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SpinePreviewPanel));
|
||||
panel_Render = new Panel();
|
||||
tableLayoutPanel1 = new TableLayoutPanel();
|
||||
panel_Container = new Panel();
|
||||
flowLayoutPanel1 = new FlowLayoutPanel();
|
||||
button_Stop = new Button();
|
||||
imageList = new ImageList(components);
|
||||
@@ -40,10 +39,16 @@
|
||||
button_Start = new Button();
|
||||
button_ForwardStep = new Button();
|
||||
button_ForwardFast = new Button();
|
||||
button_FullScreen = new Button();
|
||||
panel_ViewContainer = new Panel();
|
||||
panel_RenderContainer = new Panel();
|
||||
toolTip = new ToolTip(components);
|
||||
spinePreviewFullScreenForm = new SpineViewer.Forms.SpinePreviewFullScreenForm();
|
||||
wallpaperForm = new WallpaperForm();
|
||||
tableLayoutPanel1.SuspendLayout();
|
||||
panel_Container.SuspendLayout();
|
||||
flowLayoutPanel1.SuspendLayout();
|
||||
panel_ViewContainer.SuspendLayout();
|
||||
panel_RenderContainer.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// panel_Render
|
||||
@@ -63,8 +68,8 @@
|
||||
//
|
||||
tableLayoutPanel1.ColumnCount = 1;
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel1.Controls.Add(panel_Container, 0, 0);
|
||||
tableLayoutPanel1.Controls.Add(flowLayoutPanel1, 0, 1);
|
||||
tableLayoutPanel1.Controls.Add(panel_ViewContainer, 0, 0);
|
||||
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||
tableLayoutPanel1.Location = new Point(0, 0);
|
||||
tableLayoutPanel1.Margin = new Padding(0);
|
||||
@@ -75,17 +80,6 @@
|
||||
tableLayoutPanel1.Size = new Size(641, 636);
|
||||
tableLayoutPanel1.TabIndex = 2;
|
||||
//
|
||||
// panel_Container
|
||||
//
|
||||
panel_Container.BackColor = SystemColors.ControlDark;
|
||||
panel_Container.Controls.Add(panel_Render);
|
||||
panel_Container.Dock = DockStyle.Fill;
|
||||
panel_Container.Location = new Point(0, 0);
|
||||
panel_Container.Margin = new Padding(0);
|
||||
panel_Container.Name = "panel_Container";
|
||||
panel_Container.Size = new Size(641, 594);
|
||||
panel_Container.TabIndex = 0;
|
||||
//
|
||||
// flowLayoutPanel1
|
||||
//
|
||||
flowLayoutPanel1.Anchor = AnchorStyles.None;
|
||||
@@ -96,10 +90,11 @@
|
||||
flowLayoutPanel1.Controls.Add(button_Start);
|
||||
flowLayoutPanel1.Controls.Add(button_ForwardStep);
|
||||
flowLayoutPanel1.Controls.Add(button_ForwardFast);
|
||||
flowLayoutPanel1.Location = new Point(138, 594);
|
||||
flowLayoutPanel1.Controls.Add(button_FullScreen);
|
||||
flowLayoutPanel1.Location = new Point(101, 594);
|
||||
flowLayoutPanel1.Margin = new Padding(0);
|
||||
flowLayoutPanel1.Name = "flowLayoutPanel1";
|
||||
flowLayoutPanel1.Size = new Size(365, 42);
|
||||
flowLayoutPanel1.Size = new Size(438, 42);
|
||||
flowLayoutPanel1.TabIndex = 1;
|
||||
//
|
||||
// button_Stop
|
||||
@@ -122,18 +117,19 @@
|
||||
imageList.ColorDepth = ColorDepth.Depth32Bit;
|
||||
imageList.ImageStream = (ImageListStreamer)resources.GetObject("imageList.ImageStream");
|
||||
imageList.TransparentColor = Color.Transparent;
|
||||
imageList.Images.SetKeyName(0, "stop");
|
||||
imageList.Images.SetKeyName(1, "restart");
|
||||
imageList.Images.SetKeyName(2, "start");
|
||||
imageList.Images.SetKeyName(0, "arrows-maximize");
|
||||
imageList.Images.SetKeyName(1, "forward-fast");
|
||||
imageList.Images.SetKeyName(2, "forward-step");
|
||||
imageList.Images.SetKeyName(3, "pause");
|
||||
imageList.Images.SetKeyName(4, "forward-step");
|
||||
imageList.Images.SetKeyName(5, "forward-fast");
|
||||
imageList.Images.SetKeyName(4, "rotate-left");
|
||||
imageList.Images.SetKeyName(5, "start");
|
||||
imageList.Images.SetKeyName(6, "stop");
|
||||
//
|
||||
// button_Restart
|
||||
//
|
||||
button_Restart.AutoSize = true;
|
||||
button_Restart.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
button_Restart.ImageKey = "restart";
|
||||
button_Restart.ImageKey = "rotate-left";
|
||||
button_Restart.ImageList = imageList;
|
||||
button_Restart.Location = new Point(76, 3);
|
||||
button_Restart.Name = "button_Restart";
|
||||
@@ -190,6 +186,74 @@
|
||||
button_ForwardFast.UseVisualStyleBackColor = true;
|
||||
button_ForwardFast.Click += button_ForwardFast_Click;
|
||||
//
|
||||
// button_FullScreen
|
||||
//
|
||||
button_FullScreen.AutoSize = true;
|
||||
button_FullScreen.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
button_FullScreen.ImageKey = "arrows-maximize";
|
||||
button_FullScreen.ImageList = imageList;
|
||||
button_FullScreen.Location = new Point(368, 3);
|
||||
button_FullScreen.Name = "button_FullScreen";
|
||||
button_FullScreen.Padding = new Padding(15, 3, 15, 3);
|
||||
button_FullScreen.Size = new Size(67, 36);
|
||||
button_FullScreen.TabIndex = 5;
|
||||
toolTip.SetToolTip(button_FullScreen, "全屏预览");
|
||||
button_FullScreen.UseVisualStyleBackColor = true;
|
||||
button_FullScreen.Click += button_FullScreen_Click;
|
||||
//
|
||||
// panel_ViewContainer
|
||||
//
|
||||
panel_ViewContainer.Controls.Add(panel_RenderContainer);
|
||||
panel_ViewContainer.Dock = DockStyle.Fill;
|
||||
panel_ViewContainer.Location = new Point(0, 0);
|
||||
panel_ViewContainer.Margin = new Padding(0);
|
||||
panel_ViewContainer.Name = "panel_ViewContainer";
|
||||
panel_ViewContainer.Size = new Size(641, 594);
|
||||
panel_ViewContainer.TabIndex = 6;
|
||||
//
|
||||
// panel_RenderContainer
|
||||
//
|
||||
panel_RenderContainer.BackColor = SystemColors.ControlDark;
|
||||
panel_RenderContainer.Controls.Add(panel_Render);
|
||||
panel_RenderContainer.Dock = DockStyle.Fill;
|
||||
panel_RenderContainer.Location = new Point(0, 0);
|
||||
panel_RenderContainer.Margin = new Padding(0);
|
||||
panel_RenderContainer.Name = "panel_RenderContainer";
|
||||
panel_RenderContainer.Size = new Size(641, 594);
|
||||
panel_RenderContainer.TabIndex = 0;
|
||||
panel_RenderContainer.SizeChanged += panel_RenderContainer_SizeChanged;
|
||||
//
|
||||
// spinePreviewFullScreenForm
|
||||
//
|
||||
spinePreviewFullScreenForm.ClientSize = new Size(2560, 1440);
|
||||
spinePreviewFullScreenForm.ControlBox = false;
|
||||
spinePreviewFullScreenForm.FormBorderStyle = FormBorderStyle.None;
|
||||
spinePreviewFullScreenForm.MaximizeBox = false;
|
||||
spinePreviewFullScreenForm.MinimizeBox = false;
|
||||
spinePreviewFullScreenForm.Name = "SpinePreviewFullScreenForm";
|
||||
spinePreviewFullScreenForm.ShowIcon = false;
|
||||
spinePreviewFullScreenForm.ShowInTaskbar = false;
|
||||
spinePreviewFullScreenForm.StartPosition = FormStartPosition.Manual;
|
||||
spinePreviewFullScreenForm.TopMost = true;
|
||||
spinePreviewFullScreenForm.Visible = false;
|
||||
spinePreviewFullScreenForm.FormClosing += spinePreviewFullScreenForm_FormClosing;
|
||||
spinePreviewFullScreenForm.KeyDown += spinePreviewFullScreenForm_KeyDown;
|
||||
//
|
||||
// wallpaperForm
|
||||
//
|
||||
wallpaperForm.ClientSize = new Size(0, 0);
|
||||
wallpaperForm.ControlBox = false;
|
||||
wallpaperForm.FormBorderStyle = FormBorderStyle.None;
|
||||
wallpaperForm.MaximizeBox = false;
|
||||
wallpaperForm.MinimizeBox = false;
|
||||
wallpaperForm.Name = "WallpaperForm";
|
||||
wallpaperForm.ShowIcon = false;
|
||||
wallpaperForm.ShowInTaskbar = false;
|
||||
wallpaperForm.StartPosition = FormStartPosition.Manual;
|
||||
wallpaperForm.Visible = false;
|
||||
wallpaperForm.WindowState = FormWindowState.Minimized;
|
||||
wallpaperForm.FormClosing += wallpaperForm_FormClosing;
|
||||
//
|
||||
// SpinePreviewPanel
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
@@ -197,12 +261,12 @@
|
||||
Controls.Add(tableLayoutPanel1);
|
||||
Name = "SpinePreviewPanel";
|
||||
Size = new Size(641, 636);
|
||||
SizeChanged += SpinePreviewPanel_SizeChanged;
|
||||
tableLayoutPanel1.ResumeLayout(false);
|
||||
tableLayoutPanel1.PerformLayout();
|
||||
panel_Container.ResumeLayout(false);
|
||||
flowLayoutPanel1.ResumeLayout(false);
|
||||
flowLayoutPanel1.PerformLayout();
|
||||
panel_ViewContainer.ResumeLayout(false);
|
||||
panel_RenderContainer.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
@@ -210,7 +274,7 @@
|
||||
|
||||
private Panel panel_Render;
|
||||
private TableLayoutPanel tableLayoutPanel1;
|
||||
private Panel panel_Container;
|
||||
private Panel panel_RenderContainer;
|
||||
private FlowLayoutPanel flowLayoutPanel1;
|
||||
private Button button_Stop;
|
||||
private Button button_Start;
|
||||
@@ -219,5 +283,9 @@
|
||||
private Button button_ForwardStep;
|
||||
private Button button_ForwardFast;
|
||||
private Button button_Restart;
|
||||
private Button button_FullScreen;
|
||||
private Panel panel_ViewContainer;
|
||||
private Forms.SpinePreviewFullScreenForm spinePreviewFullScreenForm;
|
||||
private WallpaperForm wallpaperForm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Security.Policy;
|
||||
using System.Diagnostics;
|
||||
using NLog;
|
||||
using SpineViewer.Utils;
|
||||
using System.Drawing.Design;
|
||||
|
||||
namespace SpineViewer.Controls
|
||||
{
|
||||
@@ -19,14 +20,6 @@ namespace SpineViewer.Controls
|
||||
public SpinePreviewPanel()
|
||||
{
|
||||
InitializeComponent();
|
||||
renderWindow = new(panel_Render.Handle);
|
||||
renderWindow.SetActive(false);
|
||||
|
||||
// 设置默认参数
|
||||
Resolution = new(2048, 2048);
|
||||
Center = new(0, 0);
|
||||
FlipY = true;
|
||||
MaxFps = 30;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,42 +61,37 @@ namespace SpineViewer.Controls
|
||||
get => resolution;
|
||||
set
|
||||
{
|
||||
if (renderWindow is null) return;
|
||||
|
||||
if (value == resolution) return;
|
||||
if (value.Width <= 0) value.Width = 100;
|
||||
if (value.Height <= 0) value.Height = 100;
|
||||
|
||||
float parentX = panel_Render.Parent.Width;
|
||||
float parentY = panel_Render.Parent.Height;
|
||||
float sizeX = value.Width;
|
||||
float sizeY = value.Height;
|
||||
var previousZoom = Zoom;
|
||||
|
||||
if ((sizeY / sizeX) < (parentY / parentX))
|
||||
{
|
||||
// 相同的 X, 子窗口 Y 更小
|
||||
sizeY = parentX * sizeY / sizeX;
|
||||
sizeX = parentX;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 相同的 Y, 子窗口 X 更小
|
||||
sizeX = parentY * sizeX / sizeY;
|
||||
sizeY = parentY;
|
||||
}
|
||||
|
||||
// 必须通过 SFML 的方法调整窗口
|
||||
renderWindow.Position = new((int)(parentX - sizeX) / 2, (int)(parentY - sizeY) / 2);
|
||||
renderWindow.Size = new((uint)sizeX, (uint)sizeY);
|
||||
|
||||
// 将 view 的大小设置成于 resolution 相同的大小, 其余属性都不变
|
||||
using var view = renderWindow.GetView();
|
||||
var signX = Math.Sign(view.Size.X);
|
||||
var signY = Math.Sign(view.Size.Y);
|
||||
view.Size = new(value.Width * signX, value.Height * signY);
|
||||
renderWindow.SetView(view);
|
||||
float parentW = panel_Render.Parent.Width;
|
||||
float parentH = panel_Render.Parent.Height;
|
||||
float renderW = value.Width;
|
||||
float renderH = value.Height;
|
||||
float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render
|
||||
renderW *= scale;
|
||||
renderH *= scale;
|
||||
|
||||
panel_Render.Location = new((int)((parentW - renderW) / 2 + 0.5), (int)((parentH - renderH) / 2 + 0.5));
|
||||
panel_Render.Size = new((int)(renderW + 0.5), (int)(renderH + 0.5));
|
||||
resolution = value;
|
||||
|
||||
// 设置完 resolution 后还原缩放比例
|
||||
Zoom = previousZoom;
|
||||
|
||||
// 设置壁纸窗口分辨率
|
||||
using var view = renderWindow.GetView();
|
||||
wallpaperWindow.SetView(view);
|
||||
wallpaperForm.Size = value; // 必须两个 Size 都设置
|
||||
wallpaperWindow.Size = new((uint)value.Width, (uint)value.Height);
|
||||
}
|
||||
}
|
||||
private Size resolution = new(0, 0);
|
||||
private Size resolution = new(100, 100);
|
||||
|
||||
/// <summary>
|
||||
/// 画面中心点
|
||||
@@ -114,15 +102,20 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
get
|
||||
{
|
||||
if (renderWindow is null) return new(-1, -1);
|
||||
|
||||
using var view = renderWindow.GetView();
|
||||
var center = view.Center;
|
||||
return new(center.X, center.Y);
|
||||
}
|
||||
set
|
||||
{
|
||||
if (renderWindow is null) return;
|
||||
|
||||
using var view = renderWindow.GetView();
|
||||
view.Center = new(value.X, value.Y);
|
||||
renderWindow.SetView(view);
|
||||
wallpaperWindow.SetView(view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,17 +128,22 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
get
|
||||
{
|
||||
if (renderWindow is null) return -1;
|
||||
|
||||
using var view = renderWindow.GetView();
|
||||
return resolution.Width / Math.Abs(view.Size.X);
|
||||
}
|
||||
set
|
||||
{
|
||||
if (renderWindow is null) return;
|
||||
|
||||
value = Math.Clamp(value, 0.001f, 1000f);
|
||||
using var view = renderWindow.GetView();
|
||||
var signX = Math.Sign(view.Size.X);
|
||||
var signY = Math.Sign(view.Size.Y);
|
||||
view.Size = new(resolution.Width / value * signX, resolution.Height / value * signY);
|
||||
renderWindow.SetView(view);
|
||||
wallpaperWindow.SetView(view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,14 +156,19 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
get
|
||||
{
|
||||
if (renderWindow is null) return -1;
|
||||
|
||||
using var view = renderWindow.GetView();
|
||||
return view.Rotation;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (renderWindow is null) return;
|
||||
|
||||
using var view = renderWindow.GetView();
|
||||
view.Rotation = value;
|
||||
renderWindow.SetView(view);
|
||||
wallpaperWindow.SetView(view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,17 +181,22 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
get
|
||||
{
|
||||
if (renderWindow is null) return false;
|
||||
|
||||
using var view = renderWindow.GetView();
|
||||
return view.Size.X < 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (renderWindow is null) return;
|
||||
|
||||
using var view = renderWindow.GetView();
|
||||
var size = view.Size;
|
||||
if (size.X > 0 && value || size.X < 0 && !value)
|
||||
size.X *= -1;
|
||||
view.Size = size;
|
||||
renderWindow.SetView(view);
|
||||
wallpaperWindow.SetView(view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,17 +209,22 @@ namespace SpineViewer.Controls
|
||||
{
|
||||
get
|
||||
{
|
||||
if (renderWindow is null) return false;
|
||||
|
||||
using var view = renderWindow.GetView();
|
||||
return view.Size.Y < 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (renderWindow is null) return;
|
||||
|
||||
using var view = renderWindow.GetView();
|
||||
var size = view.Size;
|
||||
if (size.Y > 0 && value || size.Y < 0 && !value)
|
||||
size.Y *= -1;
|
||||
view.Size = size;
|
||||
renderWindow.SetView(view);
|
||||
wallpaperWindow.SetView(view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +247,17 @@ namespace SpineViewer.Controls
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
public uint MaxFps { get => maxFps; set { renderWindow.SetFramerateLimit(value); maxFps = value; } }
|
||||
public uint MaxFps
|
||||
{
|
||||
get => maxFps;
|
||||
set
|
||||
{
|
||||
if (renderWindow is null) return;
|
||||
|
||||
renderWindow.SetFramerateLimit(value);
|
||||
maxFps = value;
|
||||
}
|
||||
}
|
||||
private uint maxFps = 60;
|
||||
|
||||
/// <summary>
|
||||
@@ -242,14 +265,45 @@ namespace SpineViewer.Controls
|
||||
/// </summary>
|
||||
public SFML.Graphics.View GetView() => renderWindow.GetView();
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// 是否开启桌面投影
|
||||
/// </summary>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
public bool EnableDesktopProjection
|
||||
{
|
||||
get => enableDesktopProjection;
|
||||
set
|
||||
{
|
||||
if (renderWindow is null) return;
|
||||
|
||||
#region 渲染管理
|
||||
if (enableDesktopProjection == value) return;
|
||||
if (value)
|
||||
{
|
||||
var screenBounds = Screen.FromControl(this).Bounds;
|
||||
Resolution = screenBounds.Size;
|
||||
wallpaperWindow.Position = new(screenBounds.X, screenBounds.Y);
|
||||
wallpaperForm.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
wallpaperForm.Hide();
|
||||
}
|
||||
enableDesktopProjection = value;
|
||||
}
|
||||
}
|
||||
private bool enableDesktopProjection = false;
|
||||
|
||||
/// <summary>
|
||||
/// 预览画面背景色
|
||||
/// </summary>
|
||||
private static readonly SFML.Graphics.Color BackgroundColor = new(105, 105, 105);
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
public SFML.Graphics.Color BackgroundColor { get; set; } = new(105, 105, 105);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 渲染管理
|
||||
|
||||
/// <summary>
|
||||
/// 预览画面坐标轴颜色
|
||||
@@ -264,7 +318,12 @@ namespace SpineViewer.Controls
|
||||
/// <summary>
|
||||
/// 渲染窗口
|
||||
/// </summary>
|
||||
private readonly SFML.Graphics.RenderWindow renderWindow;
|
||||
private SFML.Graphics.RenderWindow renderWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 壁纸窗口
|
||||
/// </summary>
|
||||
private SFML.Graphics.RenderWindow wallpaperWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 帧间隔计时器
|
||||
@@ -312,11 +371,27 @@ namespace SpineViewer.Controls
|
||||
/// </summary>
|
||||
public void StartRender()
|
||||
{
|
||||
if (task is not null)
|
||||
return;
|
||||
// 延迟到第一次开启渲染时进行初始化
|
||||
if (renderWindow is null)
|
||||
{
|
||||
renderWindow = new(panel_Render.Handle);
|
||||
renderWindow.SetActive(false);
|
||||
wallpaperWindow = new(wallpaperForm.Handle);
|
||||
wallpaperWindow.SetActive(false);
|
||||
|
||||
// 设置默认参数
|
||||
Resolution = new(2048, 2048);
|
||||
Zoom = 1;
|
||||
Center = new(0, 0);
|
||||
FlipY = true;
|
||||
MaxFps = 30;
|
||||
}
|
||||
|
||||
if (task is not null) return;
|
||||
cancelToken = new();
|
||||
task = Task.Run(RenderTask, cancelToken.Token);
|
||||
IsUpdating = true;
|
||||
if (enableDesktopProjection) wallpaperForm.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -324,6 +399,8 @@ namespace SpineViewer.Controls
|
||||
/// </summary>
|
||||
public void StopRender()
|
||||
{
|
||||
if (wallpaperForm.InvokeRequired) wallpaperForm.Invoke(wallpaperForm.Hide);
|
||||
else wallpaperForm.Hide();
|
||||
IsUpdating = false;
|
||||
if (task is null || cancelToken is null)
|
||||
return;
|
||||
@@ -341,10 +418,14 @@ namespace SpineViewer.Controls
|
||||
try
|
||||
{
|
||||
renderWindow.SetActive(true);
|
||||
wallpaperWindow.SetActive(true);
|
||||
|
||||
float delta;
|
||||
while (cancelToken is not null && !cancelToken.IsCancellationRequested)
|
||||
{
|
||||
// 必须让 SFML 有机会处理窗口消息, 例如位置和大小变化
|
||||
renderWindow.DispatchEvents();
|
||||
|
||||
delta = clock.ElapsedTime.AsSeconds();
|
||||
clock.Restart();
|
||||
|
||||
@@ -359,6 +440,7 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
|
||||
renderWindow.Clear(BackgroundColor);
|
||||
if (enableDesktopProjection) wallpaperWindow.Clear(BackgroundColor);
|
||||
|
||||
if (ShowAxis)
|
||||
{
|
||||
@@ -392,11 +474,15 @@ namespace SpineViewer.Controls
|
||||
spine.EnableDebug = true;
|
||||
renderWindow.Draw(spine);
|
||||
spine.EnableDebug = false;
|
||||
|
||||
if (enableDesktopProjection) wallpaperWindow.Draw(spine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderWindow.Display();
|
||||
|
||||
if (enableDesktopProjection) wallpaperWindow.Display();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -408,6 +494,7 @@ namespace SpineViewer.Controls
|
||||
finally
|
||||
{
|
||||
renderWindow.SetActive(false);
|
||||
wallpaperWindow.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,36 +505,26 @@ namespace SpineViewer.Controls
|
||||
/// </summary>
|
||||
private SFML.System.Vector2f? draggingSrc = null;
|
||||
|
||||
private void SpinePreviewPanel_SizeChanged(object sender, EventArgs e)
|
||||
private void panel_RenderContainer_SizeChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (renderWindow is null)
|
||||
return;
|
||||
if (renderWindow is null) return;
|
||||
|
||||
float parentX = panel_Render.Parent.Width;
|
||||
float parentY = panel_Render.Parent.Height;
|
||||
float sizeX = panel_Render.Width;
|
||||
float sizeY = panel_Render.Height;
|
||||
float parentW = panel_Render.Parent.Width;
|
||||
float parentH = panel_Render.Parent.Height;
|
||||
float renderW = panel_Render.Width;
|
||||
float renderH = panel_Render.Height;
|
||||
float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render
|
||||
renderW *= scale;
|
||||
renderH *= scale;
|
||||
|
||||
if ((sizeY / sizeX) < (parentY / parentX))
|
||||
{
|
||||
// 相同的 X, 子窗口 Y 更小
|
||||
sizeY = parentX * sizeY / sizeX;
|
||||
sizeX = parentX;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 相同的 Y, 子窗口 X 更小
|
||||
sizeX = parentY * sizeX / sizeY;
|
||||
sizeY = parentY;
|
||||
}
|
||||
|
||||
// 必须通过 SFML 的方法调整窗口
|
||||
renderWindow.Position = new((int)(parentX - sizeX) / 2, (int)(parentY - sizeY) / 2);
|
||||
renderWindow.Size = new((uint)sizeX, (uint)sizeY);
|
||||
panel_Render.Location = new((int)((parentW - renderW) / 2 + 0.5), (int)((parentH - renderH) / 2 + 0.5));
|
||||
panel_Render.Size = new((int)(renderW + 0.5), (int)(renderH + 0.5));
|
||||
}
|
||||
|
||||
private void panel_Render_MouseDown(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (renderWindow is null) return;
|
||||
|
||||
// 右键优先级高, 进入画面拖动模式, 需要重新记录源点
|
||||
if ((e.Button & MouseButtons.Right) != 0)
|
||||
{
|
||||
@@ -475,7 +552,7 @@ namespace SpineViewer.Controls
|
||||
foreach (int i in SpineListView.SelectedIndices)
|
||||
{
|
||||
if (spines[i].IsHidden) continue;
|
||||
if (!spines[i].Bounds.Contains(src)) continue;
|
||||
if (!spines[i].GetCurrentBounds().Contains(src)) continue;
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
@@ -492,7 +569,7 @@ namespace SpineViewer.Controls
|
||||
for (int i = 0; i < spines.Count; i++)
|
||||
{
|
||||
if (spines[i].IsHidden) continue;
|
||||
if (!spines[i].Bounds.Contains(src)) continue;
|
||||
if (!spines[i].GetCurrentBounds().Contains(src)) continue;
|
||||
|
||||
hit = true;
|
||||
|
||||
@@ -514,7 +591,7 @@ namespace SpineViewer.Controls
|
||||
for (int i = 0; i < spines.Count; i++)
|
||||
{
|
||||
if (spines[i].IsHidden) continue;
|
||||
if (!spines[i].Bounds.Contains(src)) continue;
|
||||
if (!spines[i].GetCurrentBounds().Contains(src)) continue;
|
||||
|
||||
SpineListView.SelectedIndices.Add(i);
|
||||
break;
|
||||
@@ -527,8 +604,9 @@ namespace SpineViewer.Controls
|
||||
|
||||
private void panel_Render_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (draggingSrc is null)
|
||||
return;
|
||||
if (renderWindow is null) return;
|
||||
|
||||
if (draggingSrc is null) return;
|
||||
|
||||
var src = (SFML.System.Vector2f)draggingSrc;
|
||||
var dst = renderWindow.MapPixelToCoords(new(e.X, e.Y));
|
||||
@@ -655,6 +733,41 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private void button_FullScreen_Click(object sender, EventArgs e)
|
||||
{
|
||||
var screenBounds = Screen.FromControl(this).Bounds;
|
||||
Resolution = screenBounds.Size;
|
||||
PropertyGrid?.Refresh();
|
||||
|
||||
// PerfMonitorV2 模式下, 位置和大小需要分开设置
|
||||
// 因为目标位置的 DPI 可能发生变化, 因此在 WM_POSITIONCHANGED 之后会收到 WM_DPICHANGED
|
||||
// 进而导致一次额外的 WM_SIZE 消息, 其大小是 DPI 修改前的大小, 这个消息在此次设置之后发生
|
||||
// 因此如果同时设置位置和大小则大小可能设置失败
|
||||
spinePreviewFullScreenForm.Location = screenBounds.Location;
|
||||
spinePreviewFullScreenForm.Size = screenBounds.Size;
|
||||
spinePreviewFullScreenForm.Controls.Add(panel_RenderContainer);
|
||||
spinePreviewFullScreenForm.Show();
|
||||
}
|
||||
|
||||
private void spinePreviewFullScreenForm_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Escape)
|
||||
{
|
||||
spinePreviewFullScreenForm.Hide();
|
||||
panel_ViewContainer.Controls.Add(panel_RenderContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private void spinePreviewFullScreenForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
e.Cancel = e.CloseReason == CloseReason.UserClosing;
|
||||
}
|
||||
|
||||
private void wallpaperForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
e.Cancel = e.CloseReason == CloseReason.UserClosing;
|
||||
}
|
||||
|
||||
//public void ClickStopButton() => button_Stop_Click(button_Stop, EventArgs.Empty);
|
||||
//public void ClickRestartButton() => button_Restart_Click(button_Restart, EventArgs.Empty);
|
||||
//public void ClickStartButton() => button_Start_Click(button_Start, EventArgs.Empty);
|
||||
@@ -670,7 +783,8 @@ namespace SpineViewer.Controls
|
||||
[Browsable(false)]
|
||||
public SpinePreviewPanel PreviewPanel { get; } = previewPanel;
|
||||
|
||||
[TypeConverter(typeof(SizeConverter))]
|
||||
[RefreshProperties(RefreshProperties.All)]
|
||||
[TypeConverter(typeof(ResolutionConverter))]
|
||||
[Category("[0] 导出"), DisplayName("分辨率")]
|
||||
public Size Resolution { get => PreviewPanel.Resolution; set => PreviewPanel.Resolution = value; }
|
||||
|
||||
@@ -698,5 +812,10 @@ namespace SpineViewer.Controls
|
||||
|
||||
[Category("[1] 预览"), DisplayName("最大帧率")]
|
||||
public uint MaxFps { get => PreviewPanel.MaxFps; set => PreviewPanel.MaxFps = value; }
|
||||
|
||||
[Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))]
|
||||
[TypeConverter(typeof(SFMLColorConverter))]
|
||||
[Category("[1] 预览"), DisplayName("背景颜色")]
|
||||
public SFML.Graphics.Color BackgroundColor { get => PreviewPanel.BackgroundColor; set => PreviewPanel.BackgroundColor = value; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,203 +124,170 @@
|
||||
<value>
|
||||
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
|
||||
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
|
||||
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAHi0AAAJNU0Z0AUkBTAIBAQYB
|
||||
AAF4AQABeAEAAR8BAAEYAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABfAMAATADAAEBAQABIAYAAV0q
|
||||
AAQCAy0BRQNbAc0DXwHoA1kBxgMyAU8DDwEUBAIYAAMOARIDQwF3A10BzwNbAc0DLQFFBAIYAANWAbID
|
||||
XwHoA1wB1gNDAXcDFgEeBAIYAAMKAQ0DSQGFA18B4wNfAeUDUQGeAyQBNAMJAQsEARgAAwsBDgM7AWQD
|
||||
XgHSA1YBsv8AEQAEAgMxAUwDYgHhAwAB/wMxAfkDYAHbA0QBewMeASoDBgEIFAADGAEhA1cBwgMhAfsD
|
||||
YgHhAzEBTAQCGAADWQHDAwAB/wMjAfwDXgHrA0gBhAMWAR4YAAMLAQ4DTQGSAy4B+QMQAf4DUwHyA1gB
|
||||
ugM3AVoDEQEWAwIBAxQAAxcBHwNJAYYDXgHrA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wMAAf8DIAH9A2AB
|
||||
4ANQAZoDLgFGAxEBFgMGAQcEAQgAAxoBJANbAc0DAAH/A2IB4QMxAUwEAhgAA1kBwwMAAf8DAAH/AwAB
|
||||
/wNbAdADPgFrAw8BEwMCAQMQAAMLAQ4DTQGSAy4B+QMAAf8DAAH/AzwB9gNcAcsDRAF5Ax4BKgMGAQcM
|
||||
AAQBAxgBIQNKAYsDXAHsA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wMxAfkDXAHnA0QB9QNXAe4DWQG7A0MB
|
||||
dwMoATsDDwEUBAEEAAMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMDIgH8A1EB8ANEAfUDMQH5A10B
|
||||
zgNDAXcDGgEjAwIBAwwAAwsBDgNNAZIDLgH5AwEB/wMlAfoDOwH4AzEB+QNeAeMDTgGYAyQBNQMGAQgE
|
||||
AQQABAEDGAEhA0oBiwNcAewDWQHD/wARAAQCAzEBTANiAeEDAAH/A1wB2QM7AWMDWQG7Az8B9wM6AfgD
|
||||
XAHnA1sBxQNBAXMDEwEZAwIBAwMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMDXgHrA1ABmgNVAbED
|
||||
TgHzAyEB+wNbAeQDSwGPAxsBJQMDAQQIAAMLAQ4DTQGSAy4B+QMhAf0DXAHZA1oBxwNEAfUDEAH+A14B
|
||||
6wNSAaMDJQE3AwMBBAQABAEDGAEhA0oBiwNcAewDWQHD/wARAAQCAzEBTANiAeEDAAH/A1sBzQMaASQD
|
||||
FgEdA1UBrwMAAf8DAAH/AwAB/wMAAf8DVQGvAxYBHQMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMD
|
||||
WwHkAzsBZQMHAQkDSQGGAyEB+wMAAf8DAAH/A1kBwQMdASkIAAMLAQ4DTQGSAy4B+QMuAfkDTQGSAzkE
|
||||
XgHiAwAB/wMAAf8DXgHjAzYBWAMFAQYEAAQBAxgBIQNKAYsDXAHsA1kBw/8AEQAEAgMxAUwDYgHhAwAB
|
||||
/wNbAc0DGgEkAwIBAwMTARoDRgF/A1sB3gMhAfsDEAH+AzoB+ANZAbsDOwFjA1wB2QMAAf8DYgHhAzEB
|
||||
TAQCGAADWQHDA1sB5AM7AWUDBwQJAQwDOgFhA18B1QNDAfUDMQH5A1sBygMyAU8DDwEUAw0BEQNNAZID
|
||||
LgH5Ay4B+QNNAZIDEQEWAy0BRANaAb8DVAHvAyEB+wNdAdwDPwFuAxYBHgMEAQUDGAEhA0oBiwNcAewD
|
||||
WQHD/wARAAQCAzEBTANiAeEDAAH/A1sBzQMaASQEAAQCAxMBGgM9AWcDWQHAA1QB7wMQAf4DPwH3A1wB
|
||||
5wMxAfkDAAH/A2IB4QMxAUwEAhgAA1kBwwNbAeQDOwFlAwcBCQQAAwsBDgMxAU0DWAG9AzwB9gMxAfkD
|
||||
YAHbA0QBeAMhAS8DTgGVAy4B+QMuAfkDTQGSAwsBDgMGAQgDIAEuA00BkgNdAdwDRAH1A1oB6QNOAZYD
|
||||
KAE8AyABLQNLAY0DXAHsA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wNbAc0DGgEkCAAEAgMMARADLwFJA1IB
|
||||
owNbAeQDPwH3AxAB/gMAAf8DAAH/A2IB4QMxAUwEAhgAA1kBwwNbAeQDOwFlAwcBCQgAAxABFQM/AW0D
|
||||
XAHWAyUB+gMgAf0DXAHWA0QBeQNUAasDIQH7Ay4B+QNNAZIDCwEOBAADAwEEAxsBJgNBAXMDWgHEA0cB
|
||||
9ANXAe4DVQG0A0QBegNRAaIDVwHuA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wNbAc0DGgEkEAADCAEKAyMB
|
||||
MwNEAXkDVwG8A18B5QMlAfoDAAH/A2IB4QMxAUwEAhgAA1kBwwNbAeQDOwFlAwcBCQgAAwMBBAMaASQD
|
||||
QgF0A14B0gMhAfsDPAH2A10B0QNgAeADIAH9Ay4B+QNNAZIDCwEOCAADAgEDAxIBFwM1AVUDWAG6A1YB
|
||||
8QM8AfYDWwHeA2AB2wM6AfgDWQHD/wARAAQCAzEBTANiAeEDAAH/A1sBzQMaASQUAAMCAQMDDgESAyMB
|
||||
MgM9AWgDXgHdAwAB/wNiAeEDMQFMBAIYAANZAcMDWwHkAzsBZQMHAQkMAAMCAQMDDwETAzQBUwNdAdED
|
||||
OgH4AyEB/AMhAf0DAAH/Ay4B+QNNAZIDCwEODAAEAQMJAQwDIwEzA1UBrgMiAf0DIAH9AyEB/AMQAf4D
|
||||
WQHD/wARAAQCAzEBTANiAeEDAAH/A1sBzQMaASQgAAMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMD
|
||||
WwHkAzsBZQMHAQkYAAMPARQDVwG5AwAB/wMAAf8DAAH/Ay4B+QNNAZIDCwEOGAADRwGAA14B7QMAAf8D
|
||||
AAH/AwAB/wNZAcP/ABEABAIDMQFMA2IB4QMAAf8DWwHNAxoBJBQABAIDBwEJAxYBHQM1AVUDXAHZAwAB
|
||||
/wNiAeEDMQFMBAIYAANZAcMDWwHkAzsBZQMHAQkMAAQBAw0BEQM0AVMDXQHRAzEB+QMRAf4DEAH+AwAB
|
||||
/wMuAfkDTQGSAwsBDgwABAEDCQELAx4BKwNTAakDIgH9AyAB/QMhAfwDEAH+A1kBw/8AEQAEAgMxAUwD
|
||||
YgHhAwAB/wNbAc0DGgEkEAADBwEJAxsBJgM0AVMDTQGSA14B3QMxAfkDAAH/A2IB4QMxAUwEAhgAA1kB
|
||||
wwNbAeQDOwFlAwcBCQgABAIDEAEVAzkBXgNbAc0DIQH7AyEB+wNcAecDVwHuAxAB/gMuAfkDTQGSAwsB
|
||||
DggAAwIBAwMSARcDNQFVA1gBuANWAfEDPAH2A1sB3gNgAdsDOgH4A1kBw/8AEQAEAgMxAUwDYgHhAwAB
|
||||
/wNbAc0DGgEkCAAEAgMMARADLgFGA00BkgNcAcgDXgHrAyAB/QMAAf8DAAH/A2IB4QMxAUwEAhgAA1kB
|
||||
wwNbAeQDOwFlAwcBCQgAAwkBDAMxAU4DWAG3A04B8wMgAf0DXgHdA04BlgNZAb4DIQH8Ay4B+QNNAZID
|
||||
CwEOBAADAwEEAxsBJgNBAXMDWgHEA0cB9ANXAe4DVQG0A0QBegNRAaIDVwHuA1kBw/8AEQAEAgMxAUwD
|
||||
YgHhAwAB/wNbAc0DGgEkBAAEAgMTARoDPQFnA1oBvwNeAesDJQH6Az0B9gNcAecDMQH5AwAB/wNiAeED
|
||||
MQFMBAIYAANZAcMDWwHkAzsBZQMHAQkEAAMLAQ4DLgFHA1UBrQNUAe8DOwH4A2AB2wNEAXoDJAE1A04B
|
||||
mAMkAfoDLgH5A00BkgMLAQ4DBgEIAyABLgNNAZIDXQHcA0QB9QNaAekDTgGWAygBPAMgAS0DSwGNA1wB
|
||||
7ANZAcP/ABEABAIDMQFMA2IB4QMAAf8DWwHNAxoBJAMCAQMDEwEaA0YBfwNbAd4DIQH7AxAB/gM6AfgD
|
||||
WQG7AzsBYwNcAdkDAAH/A2IB4QMxAUwEAhgAA1kBwwNbAeQDOwFlAwcECQEMAzoBYQNdAdQDRwH0AzEB
|
||||
+QNbAcoDMgFPAw8BFAMNAREDTQGSAy4B+QMuAfkDTQGSAxEBFgMtAUQDWgG/A1QB7wMiAfwDYAHgA0AB
|
||||
cQMXAR8DBAEFAxgBIQNKAYsDXAHsA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wNbAc0DGgEkAxYBHQNVAa8D
|
||||
AAH/AwAB/wMAAf8DAAH/A1UBrwMWAR0DGgEkA1sBzQMAAf8DYgHhAzEBTAQCGAADWQHDA1sB5AM7AWUD
|
||||
BwEJA0kBhgMhAfsDAAH/AwAB/wNZAcEDHQEpCAADCwEOA00BkgMuAfkDLgH5A00BkgM5BF4B4gMAAf8D
|
||||
AAH/A1oB7QNKAYsDGAEgCAEDGAEhA0oBiwNcAewDWQHD/wARAAQCAzEBTANiAeEDAAH/A1wB2QM7AWMD
|
||||
WQG7Az8B9wMiAfwDRwH0A1wB2QNGAX4DEwEaAwIBAwMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMD
|
||||
XgHrA1ABmgNVAbEDTgHzAyEB+wNfAeUDSwGPAxsBJQMDAQQIAAMLAQ4DTQGSAy4B+QMhAfsDVgGzA0wB
|
||||
jgNeAesDEAH+A1EB8ANXAbkDLgFHAwYBCAQABAEDGAEhA0oBiwNcAewDWQHD/wARAAQCAzEBTANiAeED
|
||||
AAH/AzEB+QNcAecDRAH1A1UB8QNbAdMDUQGkAzgBWwMTARkEAgQAAxoBJANbAc0DAAH/A2IB4QMxAUwE
|
||||
AhgAA1kBwwMiAfwDUQHwA0QB9QMhAf0DXgHXA0YBfgMaASQDAgEDDAADCwEOA00BkgMuAfkDEAH+A04B
|
||||
8wNXAe4DPwH3A14B4wNOAZgDJwE5AwgBCgQBBAAEAQMYASEDSgGLA1wB7ANZAcP/ABEABAIDMQFMA2IB
|
||||
4QMAAf8DAAH/AyAB/QNiAeEDUgGjAzsBYwMhAS8DCgENBAIIAAMaASQDWwHNAwAB/wNiAeEDMQFMBAIY
|
||||
AANZAcMDAAH/AwAB/wMAAf8DXwHoA0oBiwMWAR0DBAEFEAADCwEOA00BkgMuAfkDAAH/AwAB/wM8AfYD
|
||||
XAHLA0QBeQMeASoDBgEHDAAEAQMYASEDSgGLA1wB7ANZAcP/ABEABAIDMQFMA2IB4QMAAf8DMQH5A2AB
|
||||
2wNFAXwDIQEwAwwBEAMDAQQQAAMaASMDXAHIAyEB/QNiAeEDMQFMBAIYAANZAcMDAAH/AyMB/ANeAesD
|
||||
TgGXAyMBMwQCFAADCwEOA00BkgMuAfkDEAH+A1MB8gNYAboDNwFaAxEBFgMCAQMUAAMXAR8DSQGGA14B
|
||||
6wNZAcP/ABEABAIDLQFFA14B0gNTAfIDXQHJAzIBTwMQARUDAgEDGAADEwEaA1ABnwNbAeQDWwHQAy0B
|
||||
RQQCGAADVQG0A1gB7gNfAdoDRAF4AxgBIAMDAQQYAAMKAQ0DSQGGA18B6ANaAekDUAGfAyQBNAMJAQsE
|
||||
ARgAAwsBDgM7AWUDWwHTA1YBsv8AFQADAwEEAycBOgM/AW0DFAEbKAADDwETAzEBTgMdASkDAgEDHAAD
|
||||
DgESAzEBTQMjATMDBAEFJAADBgEHAygBPAMoATwDBgEHJAAEAQMHAQkDCwEOAwIBA/8ABQADAgEDAw8B
|
||||
FANJAYgDXwHlA18B6ANfAegDXwHoA18B6ANfAegDXwHoA18B6ANfAegDXwHoA18B6ANfAegDXwHoA18B
|
||||
6ANfAegDXwHoA18B6ANfAegDXwHoA1sB5ANGAX4DBQEGBAEoAAMCAQMDDQERAx8BLAMtAUYDVwG5A18B
|
||||
6ANfAegDXwHoA18B6ANfAegDXwHoA2AB4wNZAb4DMgFPAw8BEwMCAQMwAAMTARkDRgF+A18B6ANfAegD
|
||||
WQHBAzkBXgMmATgDDwEUBAJcAAM5AV8DXgHiA1wB5wNfAegDXwHoA18B6ANfAegDXQHcAzwBZgMGAQgD
|
||||
BgEIAzwBZgNdAdwDXwHoA18B6ANfAegDXwHoA10B3ANQAZ0DJQE2IAADDAEQAzwBZgNdAc8DIAH9AyUB
|
||||
+gNUAe8DWgHtA1oB7QNaAe0DWgHtA1oB7QNaAe0DWgHtA1oB7QNaAe0DWgHtA1oB7QNaAe0DWgHtA1oB
|
||||
7QNUAe8DJQH6AyIB/ANWAbUDKwFCAwgBCiQABAIDGAEhA0ABcANaAb8DXQHfA1wB7QNUAe8DXAHsA2AB
|
||||
5gNfAeUDWgHqA1oB7QNRAfADPQH2A10B0QNDAXcDHgErAwYBBywAAz0BaQNbAd4DAQH/AzEB+QNdAewD
|
||||
WwHkA14B1wNEAXsDHgEqAwYBCFgAAz8BbAMhAf0DOgH4A1QB7wNaAe0DVwHuAyUB+gM8AfYDTwGbAx4B
|
||||
KwMgAS0DUAGdAzwB9gMlAfoDVwHuA1oB7QNUAe8DPwH3A1MB8gM7AWUgAAMTARkDUAGcAz8B9wM9AfYD
|
||||
XQHMA0sBjQNGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYAD
|
||||
RgGAA0sBjwNcAdkDIAH9A1UB8QNOAZQDEgEYJAADBQEGAzEBTgNbAdADUwHyA14B4gNVAa4DSgGKA0YB
|
||||
fwNDAXcDQwF2A0UBfANGAYADTwGbA14B3QNfAegDXwHaA04BlgMoATwDCQEMKAADRwGDAzoB+AMiAfwD
|
||||
WwHTA1MBpgNcAdkDMQH5A2AB4ANQAZoDLQFEAwsBDlQAAz8BbAMiAfwDWwHTA0wBjgNGAYADSgGKA10B
|
||||
3AMhAf0DXgHdAzoBYAM6AWIDXQHfAyAB/QNdAdwDSgGKA0YBgANMAY4DWwHTAyIB/AM/AWwgAAMUARsD
|
||||
UgGlAx8B/QNbAdADPwFsAxgBIQMOARIDDgESAw4BEgMOARIDDgESAw4BEgMOARIDDgESAw4BEgMOARID
|
||||
DgESAw4BEgMOARIDDgESAyABLgNXAbkDIAH9Ax8B/QNSAaUDFAEbJAADBQEGAzEBTQNaAccDXAHIA0AB
|
||||
bwMlATcDEwEaAw4BEgMNAREDDAEQAw4BEgMOARIDHQEoAzoBYANLAY8DWwHYA2AB5gNWAbYDMQFNAwYB
|
||||
CCQAA0kBhgMhAfsDMQH5A1UBrgMqAUADPwFtA10B0QNEAfUDVwHuA1gBtwM2AVcDFgEdAwwBEAQCSAAD
|
||||
PwFsAzEB+QNVAa4DIAEtAw4BEgMbASUDWQG+AwAB/wNaAe0DPwFtAz8BbQNaAe0DAAH/A1kBvgMbASUD
|
||||
DgESAyABLQNVAa4DMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1YBtgMoATwDCAEKOAADFgEeA1YBswMgAf0D
|
||||
HwH9A1IBpQMUARskAAQBAw8BFAMjATMDIQEwAwsBDgMDAQQEARQABAIDBwEJAx4BKgNLAYwDXQHfAz8B
|
||||
9wNTAakDKAE7JAADSQGGAyEB+wMxAfkDUwGnAxYBHgMMARADKgFAA1gBugNPAfMDSQH0A2AB2wNXAbwD
|
||||
QgF0AxMBGQMCAQNEAAM/AWwDMQH5A1MBpwMVARwEAAMPARQDVwG5AwAB/wNaAe0DPwFtAz8BbQNaAe0D
|
||||
AAH/A1cBuQMPARQEAAMVARwDUwGnAzEB+QM/AWwgAAMUARsDUgGlAx8B/QNVAbQDJgE4AwcBCTgAAxYB
|
||||
HgNWAbMDIAH9Ax8B/QNSAaUDFAEbYAADFQEcA1MBpwMxAfkDIQH7A0kBhiQAA0kBhgMhAfsDMQH5A1MB
|
||||
pwMVARwIAAMPARMDQwF2A1wB2QMhAf0DAAH/AwAB/wNVAa8DFgEdRAADPwFsAzEB+QNTAacDFQEcBAAD
|
||||
DwEUA1cBuQMAAf8DWgHtAz8BbQM/AW0DWgHtAwAB/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAAD
|
||||
FAEbA1IBpQMfAf0DVQG0AyYBOAMHAQk4AAMWAR4DVgGzAyAB/QMfAf0DUgGlAxQBG2AAAwIBAwMeASoD
|
||||
VQG0Az8B9wNSAaADGwElAwUBBhwAA0kBhgMhAfsDMQH5A1MBpwMVARwIAAQCAwkBCwMYASADRAF6A2AB
|
||||
2wM9AfcDPwH3A1kBuwMqAUADDgESAwUBBgQCNAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8D
|
||||
WgHtAz8BbQM/AW0DWgHtAwAB/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0D
|
||||
VQG0AyYBOAMHAQk4AAMWAR4DVgGzAyAB/QMfAf0DUgGlAxQBG2QAAw8BEwNAAW8DXQHUA1UB7wNMAZED
|
||||
EgEYHAADSQGGAyEB+wMxAfkDUwGnAxUBHBAABAIDDQERAzYBVwNYAbcDXAHsA1YB8QNdAdEDRAF5AzMB
|
||||
UAMbASYDBgEHMAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8DWgHtAz8BbQM/AW0DWgHtAwAB
|
||||
/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0DVQG0AyYBOAMHAQk4AAMWAR4D
|
||||
VgGzAyAB/QMfAf0DUgGlAxQBG2QAAwIBAwMPARMDUQGeAx8B/QNSAaUDFAEbHAADSQGGAyEB+wMxAfkD
|
||||
UwGnAxUBHBgAAwsBDgMtAUQDSwGPA1sBxQNiAeEDYgHhA1sBzQNMAZADKAE7AwkBDCwAAz8BbAMxAfkD
|
||||
UwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUBHANTAacD
|
||||
MQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IBpQMUARts
|
||||
AANNAZMDHwH9A1IBpQMUARscAANJAYYDIQH7AzEB+QNTAacDFQEcHAADBgEIAxkBIgMvAUkDQAFxA10B
|
||||
zANOAfMDWgHpA1YBtgMtAUQsAAM/AWwDMQH5A1MBpwMVARwEAAMPARQDVwG5AwAB/wNaAe0DPwFtAz8B
|
||||
bQNaAe0DAAH/A1cBuQMPARQEAAMVARwDUwGnAzEB+QM/AWwgAAMUARsDUgGlAx8B/QNVAbQDJgE4AwcB
|
||||
CTgAAxYBHgNWAbMDIAH9Ax8B/QNSAaUDFAEbbAADTQGTAx8B/QNSAaUDFAEbHAADSQGGAyEB+wMxAfkD
|
||||
UwGnAxUBHCAABAIDBAEFAwsBDgMwAUwDWQHBAz0B9gMvAfkDQQFyAwYBCCgAAz8BbAMxAfkDUwGnAxUB
|
||||
HAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUBHANTAacDMQH5Az8B
|
||||
bCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IBpQMUARtsAANNAZMD
|
||||
HwH9A1IBpQMUARscAANJAYYDIQH7AzEB+QNTAacDFQEcMAADFQEcA1MBpwMxAfkDWwHTAzoBYSgAAz8B
|
||||
bAMxAfkDUwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUB
|
||||
HANTAacDMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IB
|
||||
pQMUARsQAAQBAwQBBQMLAQ4DDwETAw8BEwMPARMDDwETAw8BEwMNAREDCQEMAwMBBAQBLAADTQGTAx8B
|
||||
/QNSAaUDFAEbHAADSQGGAyEB+wMxAfkDUwGnAxUBHCAABAEDAgEDAwsBDgMwAUwDWQHBAz0B9gMvAfkD
|
||||
QQFyAwYBCCgAAz8BbAMxAfkDUwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8D
|
||||
VwG5Aw8BFAQAAxUBHANTAacDMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YB
|
||||
swMgAf0DHwH9A1IBpQMUARsQAAMGAQgDJAE1Az4BawNGAX0DRgF+A0YBfgNGAX4DRgF+A0QBewM9AWkD
|
||||
JAE0AwkBDCwAA00BkwMfAf0DUgGlAxQBGxwAA0kBhgMhAfsDMQH5A1MBpwMVARwcAAMGAQgDEgEXAyMB
|
||||
MwM/AW4DWwHNAz0B9gNaAeoDVgG2Ay0BRCwAAz8BbAMxAfkDUwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB
|
||||
7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUBHANTAacDMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UB
|
||||
tAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IBpQMUARsQAAMQARUDRgF/A1wB2QNaAeoDYAHmA2IB
|
||||
4QNgAeADXgHiA1wB5wNiAeEDUAGdAyEBMCQAAwIBAwMPARMDUQGeAx8B/QNSAaUDFAEbHAADSQGGAyEB
|
||||
+wMxAfkDUwGnAxUBHBgAAwsBDgMtAUQDRgGBA1MBqQNdAd8DXwHoA18B2gNOAZYDKAE8AwkBDCwAAz8B
|
||||
bAMxAfkDUwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUB
|
||||
HANTAacDMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IB
|
||||
pQMUARsQAAMTARoDTgGYA0MB9QMAAf8DYAHgA1YBtgNZAb4DXgHXAz8B9wMiAfwDWgHHAysBQiQAAw8B
|
||||
EwNAAW8DXQHUA1UB7wNNAZIDEgEYHAADSQGGAyEB+wMxAfkDUwGnAxUBHBAABAIDDQERAzYBVwNYAbcD
|
||||
WgHqA10B7ANfAdUDSwGPAzoBYAMeASsDBgEHMAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8D
|
||||
WgHtAz8BbQM/AW0DWgHtAwAB/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0D
|
||||
VQG0AyYBOAMHAQk4AAMWAR4DVgGzAyAB/QMfAf0DUgGlAxQBGxAAAxQBGwNQAZoDPAH2AwAB/wNRAaQD
|
||||
JAE0A0QBeANeAd0DPwH3A1sB3gM7AWMDDgESIAADAgEDAx4BKgNVAbQDPwH3A1EBoQMbASYDBQEGHAAD
|
||||
SQGGAyEB+wMxAfkDUwGnAxUBHAwABAEDEwEZA0QBegNgAdsDPQH3Az8B9wNZAbsDKwFCAxMBGQMIAQoD
|
||||
AgEDNAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8DWgHtAz8BbQM/AW0DWgHtAwAB/wNXAbkD
|
||||
DwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0DVQG0AyYBOAMHAQk4AAMWAR4DVgGzAyAB
|
||||
/QMfAf0DUgGlAxQBGxAAAxQBGwNQAZoDPAH2AwAB/wNOAZUDMQFMA2IB4QMAAf8DWwHNAxoBJCgAAxUB
|
||||
HANTAacDMQH5AyEB+wNJAYYkAANJAYYDIQH7AzEB+QNTAacDFQEcDAADCwEOA00BkgMuAfkDAAH/AwAB
|
||||
/wNVAa8DFgEdRAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8DWgHtAz8BbQM/AW0DWgHtAwAB
|
||||
/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0DVgG2AygBPAMIAQo4AAMWAR4D
|
||||
VgGzAyAB/QMfAf0DUgGlAxQBGxAAAxQBGwNQAZoDPAH2AwAB/wNEAfUDVgHvAyMB/AMSAf4DXQHMAx0B
|
||||
KQQCGAAEAgMGAQcDDwETAzIBTwNZAcADQwH1A10B0QM6AWAkAANJAYYDIQH7AzEB+QNTAacDFgEeAwwB
|
||||
EAMqAUADVwG5A1oB6QNVAe8DXQHoA10BzwNGAX0DEwEaAwIBA0QAAz8BbAMxAfkDUwGnAxUBHAQAAw8B
|
||||
FANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUBHANTAacDMQH5Az8BbCAAAxQB
|
||||
GwNSAaUDHwH9A1sB0AM/AWwDGAEhAw4BEgMOARIDDgESAw4BEgMOARIDDgESAw4BEgMOARIDDgESAw4B
|
||||
EgMOARIDDgESAw4BEgMOARIDIAEuA1cBuQMgAf0DHwH9A1IBpQMUARsQAAMUARsDUAGaAzwB9gMAAf8D
|
||||
AAH/AyQB+gNDAfUDIAH9A2AB4ANAAXEDHQEoAw4BEgMNAREDCQEMAwgBCgMLBA4BEgMcAScDOAFbA0QB
|
||||
eQNaAccDYgHhA1YBtgM0AVQDDAEPJAADSQGGAyEB+wMxAfkDVQGuAyoBQAM/AW0DXQHRA0QB9QNXAe4D
|
||||
WQG7A0QBeQMqAUADEQEWBAJIAAM/AWwDMQH5A1UBrgMgAS0DDgESAxsBJQNZAb4DAAH/A1oB7QM/AW0D
|
||||
PwFtA1oB7QMAAf8DWQG+AxsBJQMOARIDIAEtA1UBrgMxAfkDPwFsIAADEwEaA1EBpAMhAfwDPAH2A10B
|
||||
zANLAY0DRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YB
|
||||
gANLAY8DXAHZAyAB/QNHAfQDTgGYAxMBGRAAAxIBGANLAYwDWgHtAwAB/wMiAf0DXAHZA1UBsQNdAd8D
|
||||
RAH1A2AB4ANPAZsDRgGAA0QBegM5AV4DMwFQAz0BaANGAX4DUAGaA18B2gNbAeQDXwHaA08BmwMpAT4D
|
||||
CgENKAADSQGGAyEB+wMiAfwDWwHTA1MBpgNcAdkDMQH5A2AB4ANQAZoDLQFFAxABFQMGAQcEAUwAAz8B
|
||||
bAMiAfwDWwHTA0wBjgNGAYADSgGKA10B3AMgAf0DXQHfAzoBYgM6AWIDXQHfAyAB/QNdAdwDSgGKA0YB
|
||||
gANMAY4DWwHTAyIB/AM/AWwgAAMSARcDSgGLA1wB6gMQAf4DJQH6A1QB7wNaAe0DWgHtA1oB7QNaAe0D
|
||||
WgHtA1oB7QNaAe0DWgHtA1oB7QNaAe0DWgHtA1oB7QNaAe0DWgHtA1QB7wMlAfoDIAH9A1cBwgM0AVQD
|
||||
CwEOEAADCAEKAy4BSANVAbEDXgHiA2AB2wNPAZsDLQFEA0MBdwNdAcwDYgHhA1oB6gNaAe0DWgHpA1sB
|
||||
0wNcAcgDXwHaA14B6wNQAfIDMQH5A14B0gNEAXgDIQEvAwcBCSwAA0IBdQNaAeoDAQH/AzEB+QNRAfAD
|
||||
XQHsA1wB2QNEAXsDHgEqAwYBCFgAAz8BbAMBAf8DMQH5A1QB7wNaAe0DVwHuAyUB+gM8AfYDUAGdAyAB
|
||||
LQMgAS0DUAGdAzwB9gMlAfoDVwHuA1oB7QNUAe8DMQH5AxAB/gM/AWwgAAMFAQYDGgEjA00BkwNhAeYD
|
||||
XAHtAz8B9gM/AfcDPwH3Az8B9wM/AfcDPwH3Az8B9wM/AfcDPwH3Az8B9wM/AfcDPwH3Az8B9wM/AfcD
|
||||
PwH3Az8B9gNcAe0DXwHlA0cBgwMKAQ0EAhAABAEDCAEKAx8BLAMuAUgDLQFEAxsBJQMHAQkDDwETAyMB
|
||||
MgMuAUcDVwG5A18B6ANaAekDWAHuA1YB8QNdAewDWgHpA18B6ANbAdADNAFTAw8BEwMCAQMwAAMWAR4D
|
||||
RwGDA1wB7QNWAfEDXwHVA0wBjgMrAUIDDwEUBAJcAAM6AWIDWgHpA1wB7QM/AfYDPwH3Az8B9gNdAewD
|
||||
XQHcAzwBZgMGAQgDBgEIAzwBZgNdAdwDXAHtAz8B9gM/AfcDPwH2A1wB7QNhAeYDOgFiLAAEAQMjATMD
|
||||
TgGXA1QBqwNUAasDVAGrA1QBqwNUAasDVAGrA1QBqwNUAasDVAGrA1QBqwNUAasDVAGrA1QBqwNUAasD
|
||||
TgGXAyMBMwQBTAADCQELAzkBXQNHAYIDJwE6AwQBBUgABAIDIgExAzoBYQMPARRwAAMDAQQDKAE7A04B
|
||||
mANUAasDUQGeAyEBLxQAAwMBBAMlATcDUAGfA1QBqwNOAZgDKAE7AwMBBBgAAUIBTQE+BwABPgMAASgD
|
||||
AAF8AwABMAMAAQEBAAEBBgABAxYAA/8BAAH8AQMB8AE/AQMB8AEPAcAIAAH8AQEB8AE/AQMB8AEHAcAI
|
||||
AAH8AQABMAE/AQAB8AEDAYAIAAH8AQABEAE/AQABcAEAAYAIAAH8AgABPwEAATABAAGACAAB/AIAAT8B
|
||||
AAEwAQABgAgAAfwCAAE/DAAB/AEIAQABPwEICwAB/AEMAQABPwEMAQABIAkAAfwBDwEAAT8BDAEAATAJ
|
||||
AAH8AQ8BgAE/AQ4BAAE4CQAB/AEPAfABPwEPAcABPwkAAfwBDwGAAT8BDgEAATgJAAH8AQ8BAAE/AQwB
|
||||
AAEwCQAB/AEMAQABPwEMAQABIAkAAfwBCAEAAT8BCAsAAfwCAAE/DAAB/AIAAT8BAAEwCgAB/AIAAT8B
|
||||
AAEwAQABgAgAAfwBAAEQAT8BAAFwAQABgAgAAfwBAAEwAT8BAAHwAQMBgAgAAfwBAAHwAT8BAQHwAQcB
|
||||
wAgAAfwBAwHwAT8BAwHwAQ8BwAgAAf4BHwH4AX8BDwH4AX8BwAgAAeACAAEHAf4BAAEBAf8B4AEPAv8B
|
||||
4AEAAQEB8AHgAgABBwH8AgAB/wHgAQcC/wHgAQABAQHwAeACAAEHAfwCAAF/AeABAwL/AeABAAEBAfAB
|
||||
4AIAAQcB/AIAAT8B4AEAAX8B/wHgAQABAQHwAeABfwH+AQcB/AEHAcABPwHgAQABPwH/AeEBAAEhAfAB
|
||||
4AF/Af4BBwL/AfgBPwHgAcABPwH/AeEBAAEhAfAB4AF/Af4BBwL/AfgBDwHgAcABAwH/AeEBAAEhAfAB
|
||||
4AF/Af4BBwL/AfwBDwHgAfABAQH/AeEBAAEhAfAB4AF/Af4BBwL/AfwBDwHgAfwBAAH/AeEBAAEhAfAB
|
||||
4AF/Af4BBwP/AQ8B4AH+AQAB/wHhAQABIQHwAeABfwH+AQcD/wEPAeAB/wEAAX8B4QEAASEB8AHgAX8B
|
||||
/gEHA/8BDwHgAf8B8AF/AeEBAAEhAfAB4AF/Af4BBwGAAQcB/wEPAeAB/wEAAX8B4QEAASEB8AHgAX8B
|
||||
/gEHAYABBwH/AQ8B4AH+AQAB/wHhAQABIQHwAeABfwH+AQcBgAEHAfwBDwHgAfwBAAH/AeEBAAEhAfAB
|
||||
4AF/Af4BBwGAAQcB/AEPAeAB8AEBAf8B4QEAASEB8AHgAX8B/gEHAYABBwH4AQ8C4AEDAf8B4QEAASEB
|
||||
8AHgAX8B/gEHAYABHwH4AT8C4AE/Af8B4QEAASEB8AHgAX8B/gEHAYABDwHAAT8B4AEAAT8B/wHhAQAB
|
||||
IQHwAeACAAEHAYACAAE/AeABAAF/Af8B4AEAAQEB8AHgAgABBwGAAgABfwHgAQAC/wHgAQABAQHwAeAC
|
||||
AAEHAYACAAH/AeABBwL/AeABAAEBAfAB4AIAAQcBgAEAAQEB/wHgAQ8C/wHgAQABAQHwAfwCAAE/Af8B
|
||||
+AE/Af8B8AP/AfABPgEDAfAL
|
||||
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA8iMAAAJNU0Z0AUkBTAIBAQcB
|
||||
AAGQAQABkAEAAR8BAAEYAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABfAMAATADAAEBAQABIAYAAV0+
|
||||
AAMEAQUDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf9DAAH/AwAB/wMAAf8DAAH/A1UB
|
||||
sWQAA1gB7wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/Ay0BRbcAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf87AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf9cAANEAXgDAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8DAAH/swAB/wMAAf8DAAH/AwAB/wMAAf8XAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/zcAAf8DAAH/BwAB/wMAAf8DAAH/AwAB/wMAAf9XAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf+vAAH/AwAB/wNRAaQnAAH/AwAB/wMAAf8DAAH/MwAB/wMAAf8IAANOAZcDAAH/AwAB/wMAAf8D
|
||||
AAH/Ay4BSE8AAf8DAAH/AwAB/0sAAf8DAAH/AwAB/+AAAxUBHQMAAf8DAAH/AwAB/y8AAf8DAAH/EwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf/kAAMmATgDAAH/AwAB/wMAAf8r
|
||||
AAH/AwAB/xsAAf8DAAH/AwAB/wMAAf8DAAH/QwAB/wMAAf8DAAH/SwAB/wMAAf8DAAH/6wAB/wMAAf8D
|
||||
AAH/KwAB/wMAAf8cAANCAfYDAAH/AwAB/wMAAf8DAAH/AwQBBTsAAf8DAAH/AwAB/0sAAf8DAAH/AwAB
|
||||
/+8AAf8DAAH/AwAB/ycAAf8DAAH/JwAB/wMAAf8DAAH/AwAB/wMAAf83AAH/AwAB/wMAAf9LAAH/AwAB
|
||||
/wMAAf/vAAH/AwAB/wMAAf8nAAH/AwAB/ygAAwcBCQMAAf8DAAH/AwAB/wMAAf8DYAHjLwAB/wMAAf8D
|
||||
AAH/SwAB/wMAAf8DAAH/7AADIAEtAwAB/wMAAf8nAAH/AwAB/zMAAf8DAAH/AwAB/wMAAf8DAAH/KwAB
|
||||
/wMAAf8DAAH/SwAB/wMAAf8DAAH/8wAB/wMAAf8nAAH/AwAB/zsAAf8DAAH/AwAB/wMAAf8nAAH/AwAB
|
||||
/wMAAf9LAAH/AwAB/wMAAf/zAAH/AwAB/ycAAf8DAAH/PAADPwFsAwAB/wMAAf8nAAH/AwAB/wMAAf9L
|
||||
AAH/AwAB/wMAAf/zAAH/AwAB/ycAAf8DAAH/OwAB/wMAAf8DAAH/AwAB/ycAAf8DAAH/AwAB/0sAAf8D
|
||||
AAH/AwAB/5sAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/NAADCAEKAwAB/wMAAf8nAAH/AwAB
|
||||
/zAAA10BzgMAAf8DAAH/AwAB/wMAAf8EAScAAf8DAAH/AwAB/0sAAf8DAAH/AwAB/5QAAwUBBgMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/zMAAf8DAAH/AwAB/ycAAf8DAAH/LwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8vAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf+UAAMFAQYDAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8zAAH/AwAB/wMAAf8nAAH/AwAB/ycAAf8DAAH/AwAB/wMAAf8DAAH/NwAB
|
||||
/wMAAf8DAAH/SwAB/wMAAf8DAAH/lAADBQEGAwAB/wMAAf8PAAH/AwAB/wMAAf8DFQEcLwAB/wMAAf8D
|
||||
AAH/KwAB/wMAAf8cAAM9AWkDAAH/AwAB/wMAAf8DAAH/A0MBdjsAAf8DAAH/AwAB/0sAAf8DAAH/AwAB
|
||||
/5QAAwUBBgMAAf8DAAH/CwAB/wMAAf8DAAH/AxMBGjMAAf8DAAH/AwAB/ysAAf8DAAH/GwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf9DAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf+UAAMFAQYDAAH/AwAB/wcAAf8DAAH/AwAB
|
||||
/wMbASYzAAH/AwAB/wMAAf8vAAH/AwAB/xMAAf8DAAH/AwAB/wMAAf8DAAH/SwAB/wMAAf8DAAH/SwAB
|
||||
/wMAAf8DAAH/lAADBQEGAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMdASgkAANZAe4DAAH/AwAB
|
||||
/wMAAf8EAS8AAf8DAAH/CAADGAEhAwAB/wMAAf8DAAH/AwAB/wNcActPAAH/AwAB/wMAAf9LAAH/AwAB
|
||||
/wMAAf+UAAMFAQYDAAH/AwAB/wMAAf8DAAH/A2AB4wMAAf8DAAH/AwAB/wMAAf8DUAGfFwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf83AAH/AwAB/wcAAf8DAAH/AwAB/wMAAf8DAAH/VwAB/wMAAf8DAAH/AwAB/wMqAUAD
|
||||
KgFAAyoBQAMqAUADKgFAAyoBQAMqAUADKgFAAyoBQAMqAUADKgFAAyoBQAMqAUADKgFAAyoBQAMqAUAD
|
||||
AAH/AwAB/wMAAf8DAAH/mwAB/wMAAf8DAAH/Ax0BKQQAAwIBAwMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf87AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DBwEJWAAD
|
||||
WQHDAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/5wAAxIBFwMAAf8UAANKAYkDAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf9DAAH/AwAB/wMAAf8DAAH/AyEB+2cAAf8DAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wNbAcXIAANGAYADWgG/Ay4BSFQAA1oBv3QAA0YBgANaAb8DWgG/A1oBvwNaAb8DWgG/A1oB
|
||||
vwNaAb8DWgG/A1oBvwNaAb8DWgG/A1oBvwNaAb8DWgG/A1oBvwNaAb8DLgFIpwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8DAAH/FAADPQFpAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
|
||||
AAH/EwAB/wMAAf8DAAH/LwAB/wMAAf8DAAH/KAADGQEiAwAB/wMAAf8jAAH/AwAB/wMAAf8oAAM/AW0D
|
||||
AAH/AwAB/zsAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8TAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
|
||||
AAH/KwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/FAADAwEEAwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8DAAH/AwAB/yQAA1YBtgMAAf8DAAH/AwAB
|
||||
/wMAAf8kAAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AwAB/wMAAf8DPAH4IwAB/wMAAf8DAAH/NwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/JwAB/wMAAf8DAAH/AwAB/wMAAf87AAH/AwAB/wMAAf8DAAH/AwAB/wwAA1QBqwMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/IAADVgG1AwAB/wMAAf8DAAH/AwAB/wMAAf8gAAM1AfkDAAH/AwAB/xwAA1cB
|
||||
8QMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/x8AAf8DAAH/AwAB/zcAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wsAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/ycAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AxIB/jMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wwAA1QBqwMAAf8DAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wNZAbsYAANWAbUDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DUQGeGAADNQH5AwAB/wMAAf8c
|
||||
AANXAfEDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A1ABnRcAAf8DAAH/AwAB/zcAAf8DAAH/AwAB
|
||||
/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/ycAAf8DAAH/A1YBswMAAf8DAAH/AwAB
|
||||
/wMAAf8rAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8DPQFoAwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8UAANWAbUDAAH/AwAB/wMzAVADAAH/AwAB/wMAAf8DAAH/AwAB/xQAAzUB+QMAAf8D
|
||||
AAH/HAADVwHxAwAB/wMAAf8DEQEWBAEDAAH/AwAB/wMAAf8DAAH/AwAB/xMAAf8DAAH/AwAB/zcAAf8D
|
||||
AAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/ycAAf8DAAH/A1YBswcAAf8D
|
||||
AAH/AwAB/wMAAf8jAAH/AwAB/wMAAf8DAAH/BwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8DPQFoBwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8QAANWAbUDAAH/AwAB/wMzAVAHAAH/AwAB/wMAAf8DAAH/AwAB/xAAAzUB
|
||||
+QMAAf8DAAH/HAADVwHxAwAB/wMAAf8DEQEWCwAB/wMAAf8DAAH/AwAB/wMAAf8PAAH/AwAB/wMAAf83
|
||||
AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8nAAH/AwAB/wNWAbML
|
||||
AAH/AwAB/wMAAf8DEgH+GwAB/wMAAf8DAAH/AwAB/wsAAf8DAAH/AwAB/wwAA1QBqwMAAf8DAAH/Az0B
|
||||
aAgAA2AB2wMAAf8DAAH/AwAB/wMAAf8MAANWAbUDAAH/AwAB/wMzAVAIAANgAeMDAAH/AwAB/wMAAf8D
|
||||
AAH/DAADNQH5AwAB/wMAAf8cAANXAfEDAAH/AwAB/wMRARYMAAMzAVIDAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wcAAf8DAAH/AwAB/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB
|
||||
/ycAAf8DAAH/A1YBsw8AAf8DAAH/AwAB/wMAAf8TAAH/AwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/DAAD
|
||||
VAGrAwAB/wMAAf8DPQFoDAADJQE3AwAB/wMAAf8DAAH/AwAB/wgAA1YBtQMAAf8DAAH/AzMBUAwAAzIB
|
||||
TwMAAf8DAAH/AwAB/wMAAf8IAAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFhcAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AxIB/gMAAf8DAAH/NwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/DwAB
|
||||
/wMAAf8DAAH/JwAB/wMAAf8XAAH/AwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/AwAB/xAAAwwBDwMAAf8D
|
||||
QgH2DAADVAGrAwAB/wMAAf8DPQFoFwAB/wMAAf8DAAH/AwAB/wQAA1YBtQMAAf8DAAH/AzMBUBcAAf8D
|
||||
AAH/AwAB/wMAAf8EAAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFhgAA1oB6QMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB
|
||||
/0cAAf8DAAH/AwAB/wMSAf4DAAH/AwAB/wMAAf8DAAH/LAADVAGrAwAB/wMAAf8DPQFoGwAB/wMAAf8D
|
||||
AAH/AwAB/wNdAc4DAAH/AwAB/wMzAVAbAAH/AwAB/wMAAf8DAAH/AyYB+gMAAf8DAAH/HAADVwHxAwAB
|
||||
/wMAAf8DEQEWIwAB/wMAAf8DAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB
|
||||
/wMAAf8PAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8wAANUAasDAAH/AwAB/wM9AWgf
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DMwFQHwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/HAADVwHxAwAB
|
||||
/wMAAf8DEQEWJwAB/wMAAf8DAAH/AwAB/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB
|
||||
/w8AAf8DAAH/AwAB/08AAf8DAAH/AwAB/wMAAf80AANUAasDAAH/AwAB/wM9AWgjAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wMzAVAjAAH/AwAB/wMAAf8DAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFisAAf8DAAH/AwAB
|
||||
/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/0sAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AxIB/jAAA1QBqwMAAf8DAAH/Az0BaB8AAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMzAVAf
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8cAANXAfEDAAH/AwAB/wMRARYkAANcAdkDAAH/AwAB/wMAAf83
|
||||
AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8PAAH/AwAB/wMAAf9HAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/ywAA1QBqwMAAf8DAAH/Az0BaBsAAf8DAAH/AwAB/wMAAf8DVwHxAwAB
|
||||
/wMAAf8DMwFQGwAB/wMAAf8DAAH/AwAB/wMSAf4DAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFiMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/NwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/DwAB/wMAAf8D
|
||||
AAH/JwAB/wMAAf8XAAH/AwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/AwAB/xcAAf8DWAG4DAADVAGrAwAB
|
||||
/wMAAf8DPQFoFwAB/wMAAf8DAAH/AwAB/wQAA1YBtQMAAf8DAAH/AzMBUBcAAf8DAAH/AwAB/wMAAf8E
|
||||
AAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFhgAAzABSgMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/ycAAf8DAAH/A1YB
|
||||
sw8AAf8DAAH/AwAB/wMAAf8TAAH/AwAB/wMAAf8DEgH+DwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8D
|
||||
PQFoEwAB/wMAAf8DAAH/AwAB/wgAA1YBtQMAAf8DAAH/AzMBUAwAAwkBDAMAAf8DAAH/AwAB/wMAAf8I
|
||||
AAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFhcAAf8DAAH/AwAB/wMAAf8DAAH/AyQB/QMAAf8D
|
||||
AAH/NwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/JwAB/wMAAf8D
|
||||
VgGzCwAB/wMAAf8DAAH/AwAB/xsAAf8DAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8MAANUAasDAAH/AwAB
|
||||
/wM9AWgIAAM6AWADAAH/AwAB/wMAAf8DAAH/DAADVgG1AwAB/wMAAf8DMwFQCAADSwGMAwAB/wMAAf8D
|
||||
AAH/AwAB/wwAAzUB+QMAAf8DAAH/HAADVwHxAwAB/wMAAf8DEQEWDAADBwEJAwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8HAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8PAAH/AwAB
|
||||
/wMAAf8nAAH/AwAB/wNWAbMHAAH/AwAB/wMAAf8DAAH/IwAB/wMAAf8DAAH/AwAB/wcAAf8DAAH/AwAB
|
||||
/wwAA1QBqwMAAf8DAAH/Az0BaAcAAf8DAAH/AwAB/wMAAf8DAAH/EAADVgG1AwAB/wMAAf8DMwFQBwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8QAAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFgsAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/A08BmQsAAf8DAAH/AwAB/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB
|
||||
/w8AAf8DAAH/AwAB/ycAAf8DAAH/A1YBswMAAf8DAAH/AwAB/wMAAf8rAAH/AwAB/wMAAf8DEgH+AwAB
|
||||
/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8DPQFoAwAB/wMAAf8DAAH/AwAB/wMAAf8UAANWAbUDAAH/AwAB
|
||||
/wMzAVADAAH/AwAB/wMAAf8DAAH/AwAB/xQAAzUB+QMAAf8DAAH/HAADVwHxAwAB/wMAAf8DEQEWBwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8TAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB
|
||||
/wMAAf8PAAH/AwAB/wMAAf8nAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8zAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8MAANUAasDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DTQH0GAADVgG1AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/A10B7BgAAzUB+QMAAf8DAAH/HAADVwHxAwAB/wMAAf8DYQHrAwAB/wMAAf8DAAH/AwAB
|
||||
/wNNAfQXAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8PAAH/AwAB
|
||||
/wMAAf8nAAH/AwAB/wMAAf8DAAH/AwAB/zsAAf8DAAH/AwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8DQwF3HAADVgG1AwAB/wMAAf8DAAH/AwAB/wMAAf8DMAFMHAADNQH5AwAB/wMAAf8c
|
||||
AANXAfEDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8fAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8DKgFAAyoB
|
||||
QAMqAUADAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8DKgFAAyoBQAMqAUADAAH/AwAB/wMAAf8nAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8bAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/wMAAf8MAANUAasDAAH/AwAB/wMAAf8DAAH/AwYBCCAAA1YBtgMAAf8DAAH/AwAB/wMAAf8DAgEDIAAD
|
||||
NQH5AwAB/wMAAf8cAANXAfEDAAH/AwAB/wMAAf8DAAH/AwAB/yMAAf8DAAH/AwAB/zcAAf8DAAH/AwAB
|
||||
/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wsAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
|
||||
/ycAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/xQAA0wBkAMAAf8DAAH/AwAB/wMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/xMAAf8DAAH/AwAB/y8AAf8DAAH/AwAB/ygAAzYBWAMAAf8DAAH/IwAB
|
||||
/wMAAf8DAAH/Aw0BESQAA1UBrQMAAf8DAAH/OwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/xMAAf8D
|
||||
AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8sAANaAb8DWgG/A1oBvwNaAb8DWgG/A1oBvwNaAb8DFQEcGAAD
|
||||
TwGZA1oBvwNaAb8DWgG/A1oBvwNaAb8DWgG/A1oBvxgAA04BlzQAA0sBjTAAAwwBDygAA1ABmjAAAygB
|
||||
PEAAAzgBWwNaAb8DWgG/A1oBvwMwAUwYAANEAXoDWgG/A1oBvwNaAb8DIAEtHAABQgFNAT4HAAE+AwAB
|
||||
KAMAAXwDAAEwAwABAQEAAQEGAAEDFgAD/wEAAf8B4AEHAf8B+AE/Av8B4AIAAXgEAAH/AcABAQH/AfgB
|
||||
HwL/AcACAAF4BAAB/wGDAeAB/wH5AQcC/wHAAgABOAQAAf8BjwH4AX8B+QGBAv8BxwH/Af4BOAQAAv8B
|
||||
/AE/AfkB4AL/AccB/wH+ATgEAAL/Af4BHwH5AfgBPwH/AccB/wH+ATgEAAP/AR8B+QH8AQ8B/wHHAf8B
|
||||
/gE4BAAD/wGPAfkB/wEHAf8BxwH/Af4BOAQAA/8BjwH5Af8BgQH/AccB/wH+ATgEAAP/AY8B+QH/AeAB
|
||||
/wHHAf8B/gE4BAAD/wHPAfkB/wH4AX8BxwH/Af4BOAQAA/8BzwH5Af8B/AF/AccB/wH+ATgEAAP/Ac8B
|
||||
+QH/AfgBfwHHAf8B/gE4BAAB8AEPAf8BjwH5Af8B4AF/AccB/wH+ATgEAAHgAQcB/wGPAfkB/wHBAf8B
|
||||
xwH/Af4BOAQAAeABBwH/AY8B+QH/AQcB/wHHAf8B/gE4BAAB4wGHAf8BHwH5AfwBDwH/AccB/wH+ATgE
|
||||
AAHjAQ8B/wEfAfkB+AE/Af8BxwH/Af4BOAQAAeIBHwH+AT8B+QHgAv8BxwH/Af4BOAQAAeABDwH4AT8B
|
||||
+QGBAv8BxwH/Af4BOAQAAeABAwHgAf8B+QEHAv8BwAIAATgEAAHwAYABAQH/AfgBDwL/AcACAAF4BAAB
|
||||
8wHgAQcB/wH4AT8C/wHgAgABeAQAAf8B/gE/Af8B/gP/AfgBAAEBAfgEAAHwAQcBwAEPAR8B/AF/AeMB
|
||||
/AF/AeMB/wHwAR4BAwLwAQcBwAEOAQ8B+AE/AeMB+AEfAeMB/wHgAQwBAQLwAX8B/gEOAQcB+AEfAeMB
|
||||
+AEPAeMB/wHgAQwBAQLwAT8B/AEOAQEB+AEHAeMB+AEDAeMB/wHjAYwBcQLwAR8B+AEOAQAB+AEDAeMB
|
||||
+AEBAeMB/wHjAYwBcQHwAfEBDwHwAY4BEAF4AUEB4wH4AWAB4wH/AeMBjAFxAfAB8QGHAeEBjgEYATgB
|
||||
YAHjAfgBcAEjAf8B4wGMAXEB8AHxAsMBjgEcARgBcAFjAfgBfAEDAf8B4wGMAXEB8AHzAeEBhwGOAR8B
|
||||
CAF8ASMB+AF+AQMB/wHjAYwBcQHwAf8B8AEPAf4BHwGAAX4BAwH4AX8BgwH/AeMBjAFxAfAB/wH4AR8B
|
||||
/gEfAcABfwEDAfgBfwHDAf8B4wGMAXEB8AH/AfwBPwH+AR8B4AF/AYMB+AF/AeMB/wHjAYwBcQHwAf8B
|
||||
+AEfAf4BHwHAAX8BAwH4AX8BwwH/AeMBjAFxAfAB/wHwAQ8B/gEfAYABfgEDAfgBfwGDAf8B4wGMAXEB
|
||||
8AHzAeEBhwHOAR8BCAF8ASMB+AF+AQMB/wHjAYwBcQHwAfECwwGOAR4BGAFwAWMB+AF8AQMB/wHjAYwB
|
||||
cQHwAfEBhwHhAY4BGAE4AWAB4wH4AXABIwH/AeMBjAFxAfAB8QEPAfABjgEQAXgBQQHjAfgBYAFjAf8B
|
||||
4wGMAXEC8AEfAfgBDgEAAfgBAwHjAfgBQQHjAf8B4wGMAXEC8AE/AfwBDgEBAfgBBwHjAfgBAwHjAf8B
|
||||
4wGMAXEC8AF/Af4BDgEDAfgBDwHjAfgBDwHjAf8B4AEMAQEC8AEHAeABDgEHAfgBHwHjAfgBHwHjAf8B
|
||||
4AEMAQEC8AEHAcABDwEfAfwBfwHjAfwBPwHjAf8B8AEeAQMB8AH4AQcB4AEfAb8B/gH/AfcB/gH/AfcB
|
||||
/wH4AT8BBwHwCw==
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>165, 17</value>
|
||||
</metadata>
|
||||
<metadata name="spinePreviewFullScreenForm.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>307, 18</value>
|
||||
</metadata>
|
||||
<metadata name="wallpaperForm.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>618, 18</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -39,8 +39,9 @@
|
||||
tabPage_Skin = new TabPage();
|
||||
propertyGrid_Skin = new PropertyGrid();
|
||||
contextMenuStrip_Skin = new ContextMenuStrip(components);
|
||||
toolStripMenuItem_AddSkin = new ToolStripMenuItem();
|
||||
toolStripMenuItem_RemoveSkin = new ToolStripMenuItem();
|
||||
toolStripMenuItem_ReloadSkins = new ToolStripMenuItem();
|
||||
tabPage_Slot = new TabPage();
|
||||
propertyGrid_Slot = new PropertyGrid();
|
||||
tabPage_Animation = new TabPage();
|
||||
propertyGrid_Animation = new PropertyGrid();
|
||||
contextMenuStrip_Animation = new ContextMenuStrip(components);
|
||||
@@ -54,6 +55,7 @@
|
||||
tabPage_Transform.SuspendLayout();
|
||||
tabPage_Skin.SuspendLayout();
|
||||
contextMenuStrip_Skin.SuspendLayout();
|
||||
tabPage_Slot.SuspendLayout();
|
||||
tabPage_Animation.SuspendLayout();
|
||||
contextMenuStrip_Animation.SuspendLayout();
|
||||
tabPage_Debug.SuspendLayout();
|
||||
@@ -66,6 +68,7 @@
|
||||
tabControl.Controls.Add(tabPage_Render);
|
||||
tabControl.Controls.Add(tabPage_Transform);
|
||||
tabControl.Controls.Add(tabPage_Skin);
|
||||
tabControl.Controls.Add(tabPage_Slot);
|
||||
tabControl.Controls.Add(tabPage_Animation);
|
||||
tabControl.Controls.Add(tabPage_Debug);
|
||||
tabControl.Dock = DockStyle.Fill;
|
||||
@@ -108,7 +111,7 @@
|
||||
tabPage_Render.Location = new Point(4, 4);
|
||||
tabPage_Render.Margin = new Padding(0);
|
||||
tabPage_Render.Name = "tabPage_Render";
|
||||
tabPage_Render.Size = new Size(364, 380);
|
||||
tabPage_Render.Size = new Size(364, 370);
|
||||
tabPage_Render.TabIndex = 1;
|
||||
tabPage_Render.Text = "渲染";
|
||||
//
|
||||
@@ -119,7 +122,7 @@
|
||||
propertyGrid_Render.Location = new Point(0, 0);
|
||||
propertyGrid_Render.Name = "propertyGrid_Render";
|
||||
propertyGrid_Render.PropertySort = PropertySort.Alphabetical;
|
||||
propertyGrid_Render.Size = new Size(364, 380);
|
||||
propertyGrid_Render.Size = new Size(364, 370);
|
||||
propertyGrid_Render.TabIndex = 1;
|
||||
propertyGrid_Render.ToolbarVisible = false;
|
||||
//
|
||||
@@ -130,7 +133,7 @@
|
||||
tabPage_Transform.Location = new Point(4, 4);
|
||||
tabPage_Transform.Margin = new Padding(0);
|
||||
tabPage_Transform.Name = "tabPage_Transform";
|
||||
tabPage_Transform.Size = new Size(364, 380);
|
||||
tabPage_Transform.Size = new Size(364, 370);
|
||||
tabPage_Transform.TabIndex = 2;
|
||||
tabPage_Transform.Text = "变换";
|
||||
//
|
||||
@@ -141,7 +144,7 @@
|
||||
propertyGrid_Transform.Location = new Point(0, 0);
|
||||
propertyGrid_Transform.Name = "propertyGrid_Transform";
|
||||
propertyGrid_Transform.PropertySort = PropertySort.Alphabetical;
|
||||
propertyGrid_Transform.Size = new Size(364, 380);
|
||||
propertyGrid_Transform.Size = new Size(364, 370);
|
||||
propertyGrid_Transform.TabIndex = 1;
|
||||
propertyGrid_Transform.ToolbarVisible = false;
|
||||
//
|
||||
@@ -152,7 +155,7 @@
|
||||
tabPage_Skin.Location = new Point(4, 4);
|
||||
tabPage_Skin.Margin = new Padding(0);
|
||||
tabPage_Skin.Name = "tabPage_Skin";
|
||||
tabPage_Skin.Size = new Size(364, 380);
|
||||
tabPage_Skin.Size = new Size(364, 370);
|
||||
tabPage_Skin.TabIndex = 3;
|
||||
tabPage_Skin.Text = "皮肤";
|
||||
//
|
||||
@@ -164,31 +167,45 @@
|
||||
propertyGrid_Skin.Location = new Point(0, 0);
|
||||
propertyGrid_Skin.Name = "propertyGrid_Skin";
|
||||
propertyGrid_Skin.PropertySort = PropertySort.NoSort;
|
||||
propertyGrid_Skin.Size = new Size(364, 380);
|
||||
propertyGrid_Skin.Size = new Size(364, 370);
|
||||
propertyGrid_Skin.TabIndex = 1;
|
||||
propertyGrid_Skin.ToolbarVisible = false;
|
||||
//
|
||||
// contextMenuStrip_Skin
|
||||
//
|
||||
contextMenuStrip_Skin.ImageScalingSize = new Size(24, 24);
|
||||
contextMenuStrip_Skin.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_AddSkin, toolStripMenuItem_RemoveSkin });
|
||||
contextMenuStrip_Skin.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_ReloadSkins });
|
||||
contextMenuStrip_Skin.Name = "contextMenuStrip1";
|
||||
contextMenuStrip_Skin.Size = new Size(117, 64);
|
||||
contextMenuStrip_Skin.Opening += contextMenuStrip_Skin_Opening;
|
||||
contextMenuStrip_Skin.Size = new Size(241, 67);
|
||||
//
|
||||
// toolStripMenuItem_AddSkin
|
||||
// toolStripMenuItem_ReloadSkins
|
||||
//
|
||||
toolStripMenuItem_AddSkin.Name = "toolStripMenuItem_AddSkin";
|
||||
toolStripMenuItem_AddSkin.Size = new Size(116, 30);
|
||||
toolStripMenuItem_AddSkin.Text = "添加";
|
||||
toolStripMenuItem_AddSkin.Click += toolStripMenuItem_AddSkin_Click;
|
||||
toolStripMenuItem_ReloadSkins.Name = "toolStripMenuItem_ReloadSkins";
|
||||
toolStripMenuItem_ReloadSkins.Size = new Size(240, 30);
|
||||
toolStripMenuItem_ReloadSkins.Text = "重新加载皮肤";
|
||||
toolStripMenuItem_ReloadSkins.Click += toolStripMenuItem_ReloadSkins_Click;
|
||||
//
|
||||
// toolStripMenuItem_RemoveSkin
|
||||
// tabPage_Slot
|
||||
//
|
||||
toolStripMenuItem_RemoveSkin.Name = "toolStripMenuItem_RemoveSkin";
|
||||
toolStripMenuItem_RemoveSkin.Size = new Size(116, 30);
|
||||
toolStripMenuItem_RemoveSkin.Text = "移除";
|
||||
toolStripMenuItem_RemoveSkin.Click += toolStripMenuItem_RemoveSkin_Click;
|
||||
tabPage_Slot.BackColor = SystemColors.Control;
|
||||
tabPage_Slot.Controls.Add(propertyGrid_Slot);
|
||||
tabPage_Slot.Location = new Point(4, 4);
|
||||
tabPage_Slot.Margin = new Padding(0);
|
||||
tabPage_Slot.Name = "tabPage_Slot";
|
||||
tabPage_Slot.Size = new Size(364, 370);
|
||||
tabPage_Slot.TabIndex = 6;
|
||||
tabPage_Slot.Text = "插槽";
|
||||
//
|
||||
// propertyGrid_Slot
|
||||
//
|
||||
propertyGrid_Slot.Dock = DockStyle.Fill;
|
||||
propertyGrid_Slot.HelpVisible = false;
|
||||
propertyGrid_Slot.Location = new Point(0, 0);
|
||||
propertyGrid_Slot.Name = "propertyGrid_Slot";
|
||||
propertyGrid_Slot.PropertySort = PropertySort.Alphabetical;
|
||||
propertyGrid_Slot.Size = new Size(364, 370);
|
||||
propertyGrid_Slot.TabIndex = 2;
|
||||
propertyGrid_Slot.ToolbarVisible = false;
|
||||
//
|
||||
// tabPage_Animation
|
||||
//
|
||||
@@ -197,7 +214,7 @@
|
||||
tabPage_Animation.Location = new Point(4, 4);
|
||||
tabPage_Animation.Margin = new Padding(0);
|
||||
tabPage_Animation.Name = "tabPage_Animation";
|
||||
tabPage_Animation.Size = new Size(364, 380);
|
||||
tabPage_Animation.Size = new Size(364, 370);
|
||||
tabPage_Animation.TabIndex = 4;
|
||||
tabPage_Animation.Text = "动画";
|
||||
//
|
||||
@@ -209,7 +226,7 @@
|
||||
propertyGrid_Animation.Location = new Point(0, 0);
|
||||
propertyGrid_Animation.Name = "propertyGrid_Animation";
|
||||
propertyGrid_Animation.PropertySort = PropertySort.NoSort;
|
||||
propertyGrid_Animation.Size = new Size(364, 380);
|
||||
propertyGrid_Animation.Size = new Size(364, 370);
|
||||
propertyGrid_Animation.TabIndex = 1;
|
||||
propertyGrid_Animation.ToolbarVisible = false;
|
||||
//
|
||||
@@ -241,7 +258,7 @@
|
||||
tabPage_Debug.Controls.Add(propertyGrid_Debug);
|
||||
tabPage_Debug.Location = new Point(4, 4);
|
||||
tabPage_Debug.Name = "tabPage_Debug";
|
||||
tabPage_Debug.Size = new Size(364, 380);
|
||||
tabPage_Debug.Size = new Size(364, 370);
|
||||
tabPage_Debug.TabIndex = 5;
|
||||
tabPage_Debug.Text = "调试";
|
||||
//
|
||||
@@ -252,16 +269,16 @@
|
||||
propertyGrid_Debug.Location = new Point(0, 0);
|
||||
propertyGrid_Debug.Name = "propertyGrid_Debug";
|
||||
propertyGrid_Debug.PropertySort = PropertySort.NoSort;
|
||||
propertyGrid_Debug.Size = new Size(364, 380);
|
||||
propertyGrid_Debug.Size = new Size(364, 370);
|
||||
propertyGrid_Debug.TabIndex = 2;
|
||||
propertyGrid_Debug.ToolbarVisible = false;
|
||||
//
|
||||
// SpinePropertyGrid
|
||||
// SpineViewPropertyGrid
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
Controls.Add(tabControl);
|
||||
Name = "SpinePropertyGrid";
|
||||
Name = "SpineViewPropertyGrid";
|
||||
Size = new Size(372, 448);
|
||||
tabControl.ResumeLayout(false);
|
||||
tabPage_BaseInfo.ResumeLayout(false);
|
||||
@@ -269,6 +286,7 @@
|
||||
tabPage_Transform.ResumeLayout(false);
|
||||
tabPage_Skin.ResumeLayout(false);
|
||||
contextMenuStrip_Skin.ResumeLayout(false);
|
||||
tabPage_Slot.ResumeLayout(false);
|
||||
tabPage_Animation.ResumeLayout(false);
|
||||
contextMenuStrip_Animation.ResumeLayout(false);
|
||||
tabPage_Debug.ResumeLayout(false);
|
||||
@@ -290,11 +308,12 @@
|
||||
private PropertyGrid propertyGrid_Animation;
|
||||
private ContextMenuStrip contextMenuStrip_Skin;
|
||||
private ContextMenuStrip contextMenuStrip_Animation;
|
||||
private ToolStripMenuItem toolStripMenuItem_AddSkin;
|
||||
private ToolStripMenuItem toolStripMenuItem_RemoveSkin;
|
||||
private ToolStripMenuItem toolStripMenuItem_ReloadSkins;
|
||||
private ToolStripMenuItem toolStripMenuItem_AddAnimation;
|
||||
private ToolStripMenuItem toolStripMenuItem_RemoveAnimation;
|
||||
private TabPage tabPage_Debug;
|
||||
private PropertyGrid propertyGrid_Debug;
|
||||
private TabPage tabPage_Slot;
|
||||
private PropertyGrid propertyGrid_Slot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace SpineViewer.Controls
|
||||
propertyGrid_Render.SelectedObject = null;
|
||||
propertyGrid_Transform.SelectedObject = null;
|
||||
propertyGrid_Skin.SelectedObject = null;
|
||||
propertyGrid_Slot.SelectedObject = null;
|
||||
propertyGrid_Animation.SelectedObject = null;
|
||||
propertyGrid_Debug.SelectedObject = null;
|
||||
}
|
||||
@@ -44,6 +45,7 @@ namespace SpineViewer.Controls
|
||||
propertyGrid_Render.SelectedObjects = value.Select(e => e.Render).ToArray();
|
||||
propertyGrid_Transform.SelectedObjects = value.Select(e => e.Transform).ToArray();
|
||||
propertyGrid_Skin.SelectedObjects = value.Select(e => e.Skin).ToArray();
|
||||
propertyGrid_Slot.SelectedObjects = value.Select(e => e.Slot).ToArray();
|
||||
propertyGrid_Animation.SelectedObjects = value.Select(e => e.Animation).ToArray();
|
||||
propertyGrid_Debug.SelectedObjects = value.Select(e => e.Debug).ToArray();
|
||||
}
|
||||
@@ -51,20 +53,6 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
private SpineObjectProperty[]? selectedSpines = null;
|
||||
|
||||
private void contextMenuStrip_Skin_Opening(object sender, CancelEventArgs e)
|
||||
{
|
||||
if (selectedSpines?.Length == 1)
|
||||
{
|
||||
toolStripMenuItem_AddSkin.Enabled = true;
|
||||
toolStripMenuItem_RemoveSkin.Enabled = propertyGrid_Skin.SelectedGridItem.Value is SkinNameProperty;
|
||||
}
|
||||
else
|
||||
{
|
||||
toolStripMenuItem_AddSkin.Enabled = false;
|
||||
toolStripMenuItem_RemoveSkin.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void contextMenuStrip_Animation_Opening(object sender, CancelEventArgs e)
|
||||
{
|
||||
if (selectedSpines?.Length == 1)
|
||||
@@ -79,31 +67,10 @@ namespace SpineViewer.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_AddSkin_Click(object sender, EventArgs e)
|
||||
private void toolStripMenuItem_ReloadSkins_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (selectedSpines?.Length != 1) return;
|
||||
|
||||
var spine = selectedSpines[0].Skin.Spine;
|
||||
|
||||
if (spine.SkinNames.Count <= 0)
|
||||
{
|
||||
MessagePopup.Info("没有可用的皮肤");
|
||||
return;
|
||||
}
|
||||
|
||||
spine.LoadSkin(spine.SkinNames[0]);
|
||||
propertyGrid_Skin.Refresh();
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_RemoveSkin_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (selectedSpines?.Length != 1) return;
|
||||
|
||||
if (propertyGrid_Skin.SelectedGridItem.Value is SkinNameProperty wrapper)
|
||||
{
|
||||
selectedSpines[0].Skin.Spine.UnloadSkin(wrapper.Index);
|
||||
propertyGrid_Skin.Refresh();
|
||||
}
|
||||
foreach (var sp in selectedSpines)
|
||||
sp.Spine.ReloadSkins();
|
||||
}
|
||||
|
||||
private void toolStripMenuItem_AddAnimation_Click(object sender, EventArgs e)
|
||||
|
||||
110
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
110
SpineViewer/Dialogs/ConvertFileFormatDialog.Designer.cs
generated
@@ -31,6 +31,7 @@
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConvertFileFormatDialog));
|
||||
panel = new Panel();
|
||||
tableLayoutPanel1 = new TableLayoutPanel();
|
||||
label5 = new Label();
|
||||
comboBox_TargetVersion = new ComboBox();
|
||||
flowLayoutPanel_TargetFormat = new FlowLayoutPanel();
|
||||
radioButton_BinaryTarget = new RadioButton();
|
||||
@@ -44,10 +45,15 @@
|
||||
button_Cancel = new Button();
|
||||
label2 = new Label();
|
||||
skelFileListBox = new SpineViewer.Controls.SkelFileListBox();
|
||||
tableLayoutPanel3 = new TableLayoutPanel();
|
||||
textBox_OutputDir = new TextBox();
|
||||
button_SelectOutputDir = new Button();
|
||||
folderBrowserDialog_Output = new FolderBrowserDialog();
|
||||
panel.SuspendLayout();
|
||||
tableLayoutPanel1.SuspendLayout();
|
||||
flowLayoutPanel_TargetFormat.SuspendLayout();
|
||||
tableLayoutPanel2.SuspendLayout();
|
||||
tableLayoutPanel3.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// panel
|
||||
@@ -57,7 +63,7 @@
|
||||
panel.Location = new Point(0, 0);
|
||||
panel.Name = "panel";
|
||||
panel.Padding = new Padding(50, 15, 50, 10);
|
||||
panel.Size = new Size(1051, 538);
|
||||
panel.Size = new Size(1051, 702);
|
||||
panel.TabIndex = 2;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
@@ -65,34 +71,48 @@
|
||||
tableLayoutPanel1.ColumnCount = 2;
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel1.Controls.Add(comboBox_TargetVersion, 1, 3);
|
||||
tableLayoutPanel1.Controls.Add(flowLayoutPanel_TargetFormat, 1, 4);
|
||||
tableLayoutPanel1.Controls.Add(label1, 0, 3);
|
||||
tableLayoutPanel1.Controls.Add(label5, 0, 2);
|
||||
tableLayoutPanel1.Controls.Add(comboBox_TargetVersion, 1, 4);
|
||||
tableLayoutPanel1.Controls.Add(flowLayoutPanel_TargetFormat, 1, 5);
|
||||
tableLayoutPanel1.Controls.Add(label1, 0, 4);
|
||||
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
||||
tableLayoutPanel1.Controls.Add(label3, 0, 2);
|
||||
tableLayoutPanel1.Controls.Add(comboBox_SourceVersion, 1, 2);
|
||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 5);
|
||||
tableLayoutPanel1.Controls.Add(label2, 0, 4);
|
||||
tableLayoutPanel1.Controls.Add(label3, 0, 3);
|
||||
tableLayoutPanel1.Controls.Add(comboBox_SourceVersion, 1, 3);
|
||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 6);
|
||||
tableLayoutPanel1.Controls.Add(label2, 0, 5);
|
||||
tableLayoutPanel1.Controls.Add(skelFileListBox, 0, 1);
|
||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel3, 1, 2);
|
||||
tableLayoutPanel1.Dock = DockStyle.Fill;
|
||||
tableLayoutPanel1.Location = new Point(50, 15);
|
||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
tableLayoutPanel1.RowCount = 6;
|
||||
tableLayoutPanel1.RowCount = 7;
|
||||
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.Size = new Size(951, 513);
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 20F));
|
||||
tableLayoutPanel1.Size = new Size(951, 677);
|
||||
tableLayoutPanel1.TabIndex = 1;
|
||||
//
|
||||
// label5
|
||||
//
|
||||
label5.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
label5.AutoSize = true;
|
||||
label5.Location = new Point(3, 462);
|
||||
label5.Name = "label5";
|
||||
label5.Size = new Size(104, 24);
|
||||
label5.TabIndex = 23;
|
||||
label5.Text = "输出文件夹:";
|
||||
//
|
||||
// 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.Location = new Point(113, 535);
|
||||
comboBox_TargetVersion.Name = "comboBox_TargetVersion";
|
||||
comboBox_TargetVersion.Size = new Size(182, 32);
|
||||
comboBox_TargetVersion.Sorted = true;
|
||||
@@ -104,9 +124,10 @@
|
||||
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_BinaryTarget);
|
||||
flowLayoutPanel_TargetFormat.Controls.Add(radioButton_JsonTarget);
|
||||
flowLayoutPanel_TargetFormat.Dock = DockStyle.Fill;
|
||||
flowLayoutPanel_TargetFormat.Location = new Point(95, 403);
|
||||
flowLayoutPanel_TargetFormat.Location = new Point(110, 570);
|
||||
flowLayoutPanel_TargetFormat.Margin = new Padding(0);
|
||||
flowLayoutPanel_TargetFormat.Name = "flowLayoutPanel_TargetFormat";
|
||||
flowLayoutPanel_TargetFormat.Size = new Size(853, 34);
|
||||
flowLayoutPanel_TargetFormat.Size = new Size(841, 34);
|
||||
flowLayoutPanel_TargetFormat.TabIndex = 19;
|
||||
//
|
||||
// radioButton_BinaryTarget
|
||||
@@ -135,7 +156,7 @@
|
||||
//
|
||||
label1.Anchor = AnchorStyles.Right;
|
||||
label1.AutoSize = true;
|
||||
label1.Location = new Point(3, 369);
|
||||
label1.Location = new Point(21, 539);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new Size(86, 24);
|
||||
label1.TabIndex = 15;
|
||||
@@ -151,14 +172,14 @@
|
||||
label4.Name = "label4";
|
||||
label4.Size = new Size(921, 24);
|
||||
label4.TabIndex = 14;
|
||||
label4.Text = "说明:将在每个文件同级目录下生成目标格式后缀的文件,会覆盖已存在文件";
|
||||
label4.Text = "说明:输出文件夹留空则在每个文件同级目录下生成目标格式后缀的文件,视情况会覆盖已存在文件";
|
||||
label4.TextAlign = ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// label3
|
||||
//
|
||||
label3.Anchor = AnchorStyles.Right;
|
||||
label3.AutoSize = true;
|
||||
label3.Location = new Point(21, 331);
|
||||
label3.Location = new Point(39, 501);
|
||||
label3.Name = "label3";
|
||||
label3.Size = new Size(68, 24);
|
||||
label3.TabIndex = 12;
|
||||
@@ -169,7 +190,7 @@
|
||||
comboBox_SourceVersion.Anchor = AnchorStyles.Left;
|
||||
comboBox_SourceVersion.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
comboBox_SourceVersion.FormattingEnabled = true;
|
||||
comboBox_SourceVersion.Location = new Point(95, 327);
|
||||
comboBox_SourceVersion.Location = new Point(113, 497);
|
||||
comboBox_SourceVersion.Name = "comboBox_SourceVersion";
|
||||
comboBox_SourceVersion.Size = new Size(182, 32);
|
||||
comboBox_SourceVersion.Sorted = true;
|
||||
@@ -186,7 +207,7 @@
|
||||
tableLayoutPanel2.Controls.Add(button_Ok, 0, 0);
|
||||
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
||||
tableLayoutPanel2.Dock = DockStyle.Fill;
|
||||
tableLayoutPanel2.Location = new Point(3, 470);
|
||||
tableLayoutPanel2.Location = new Point(3, 634);
|
||||
tableLayoutPanel2.Margin = new Padding(3, 30, 3, 3);
|
||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
tableLayoutPanel2.RowCount = 1;
|
||||
@@ -222,7 +243,7 @@
|
||||
//
|
||||
label2.Anchor = AnchorStyles.Right;
|
||||
label2.AutoSize = true;
|
||||
label2.Location = new Point(3, 408);
|
||||
label2.Location = new Point(21, 575);
|
||||
label2.Name = "label2";
|
||||
label2.Size = new Size(86, 24);
|
||||
label2.TabIndex = 16;
|
||||
@@ -234,16 +255,56 @@
|
||||
skelFileListBox.Dock = DockStyle.Fill;
|
||||
skelFileListBox.Location = new Point(3, 57);
|
||||
skelFileListBox.Name = "skelFileListBox";
|
||||
skelFileListBox.Size = new Size(945, 264);
|
||||
skelFileListBox.Size = new Size(945, 394);
|
||||
skelFileListBox.TabIndex = 20;
|
||||
//
|
||||
// tableLayoutPanel3
|
||||
//
|
||||
tableLayoutPanel3.AutoSize = true;
|
||||
tableLayoutPanel3.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
tableLayoutPanel3.ColumnCount = 3;
|
||||
tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle());
|
||||
tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel3.ColumnStyles.Add(new ColumnStyle());
|
||||
tableLayoutPanel3.Controls.Add(textBox_OutputDir, 1, 0);
|
||||
tableLayoutPanel3.Controls.Add(button_SelectOutputDir, 2, 0);
|
||||
tableLayoutPanel3.Dock = DockStyle.Fill;
|
||||
tableLayoutPanel3.Location = new Point(110, 454);
|
||||
tableLayoutPanel3.Margin = new Padding(0);
|
||||
tableLayoutPanel3.Name = "tableLayoutPanel3";
|
||||
tableLayoutPanel3.RowCount = 1;
|
||||
tableLayoutPanel3.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
tableLayoutPanel3.Size = new Size(841, 40);
|
||||
tableLayoutPanel3.TabIndex = 22;
|
||||
//
|
||||
// textBox_OutputDir
|
||||
//
|
||||
textBox_OutputDir.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
textBox_OutputDir.Location = new Point(3, 5);
|
||||
textBox_OutputDir.Name = "textBox_OutputDir";
|
||||
textBox_OutputDir.Size = new Size(797, 30);
|
||||
textBox_OutputDir.TabIndex = 1;
|
||||
//
|
||||
// button_SelectOutputDir
|
||||
//
|
||||
button_SelectOutputDir.Anchor = AnchorStyles.Left | AnchorStyles.Right;
|
||||
button_SelectOutputDir.AutoSize = true;
|
||||
button_SelectOutputDir.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
button_SelectOutputDir.Location = new Point(806, 3);
|
||||
button_SelectOutputDir.Name = "button_SelectOutputDir";
|
||||
button_SelectOutputDir.Size = new Size(32, 34);
|
||||
button_SelectOutputDir.TabIndex = 2;
|
||||
button_SelectOutputDir.Text = "...";
|
||||
button_SelectOutputDir.UseVisualStyleBackColor = true;
|
||||
button_SelectOutputDir.Click += button_SelectOutputDir_Click;
|
||||
//
|
||||
// ConvertFileFormatDialog
|
||||
//
|
||||
AcceptButton = button_Ok;
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
CancelButton = button_Cancel;
|
||||
ClientSize = new Size(1051, 538);
|
||||
ClientSize = new Size(1051, 702);
|
||||
Controls.Add(panel);
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
@@ -259,6 +320,8 @@
|
||||
flowLayoutPanel_TargetFormat.ResumeLayout(false);
|
||||
flowLayoutPanel_TargetFormat.PerformLayout();
|
||||
tableLayoutPanel2.ResumeLayout(false);
|
||||
tableLayoutPanel3.ResumeLayout(false);
|
||||
tableLayoutPanel3.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
@@ -279,5 +342,10 @@
|
||||
private RadioButton radioButton_JsonTarget;
|
||||
private Controls.SkelFileListBox skelFileListBox;
|
||||
private ComboBox comboBox_TargetVersion;
|
||||
private FolderBrowserDialog folderBrowserDialog_Output;
|
||||
private TableLayoutPanel tableLayoutPanel3;
|
||||
private TextBox textBox_OutputDir;
|
||||
private Button button_SelectOutputDir;
|
||||
private Label label5;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using SpineViewer.Spine;
|
||||
using NLog;
|
||||
using SpineViewer.Spine;
|
||||
using SpineViewer.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -14,6 +15,8 @@ namespace SpineViewer.Dialogs
|
||||
{
|
||||
public partial class ConvertFileFormatDialog : Form
|
||||
{
|
||||
private readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// 对话框结果, 取消时为 null
|
||||
/// </summary>
|
||||
@@ -37,8 +40,17 @@ namespace SpineViewer.Dialogs
|
||||
comboBox_TargetVersion.SelectedValue = SpineVersion.V38;
|
||||
}
|
||||
|
||||
private void button_SelectOutputDir_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (folderBrowserDialog_Output.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
textBox_OutputDir.Text = Path.GetFullPath(folderBrowserDialog_Output.SelectedPath);
|
||||
}
|
||||
|
||||
private void button_Ok_Click(object sender, EventArgs e)
|
||||
{
|
||||
var outputDir = textBox_OutputDir.Text;
|
||||
var sourceVersion = (SpineVersion)comboBox_SourceVersion.SelectedValue;
|
||||
var targetVersion = (SpineVersion)comboBox_TargetVersion.SelectedValue;
|
||||
var jsonTarget = radioButton_JsonTarget.Checked;
|
||||
@@ -51,6 +63,36 @@ namespace SpineViewer.Dialogs
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(outputDir))
|
||||
{
|
||||
outputDir = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
outputDir = Path.GetFullPath(outputDir);
|
||||
if (!Directory.Exists(outputDir))
|
||||
{
|
||||
if (MessagePopup.Quest("输出文件夹不存在,是否创建?") == DialogResult.OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(outputDir);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex.ToString());
|
||||
logger.Error("Failed to create output dir {}", outputDir);
|
||||
MessagePopup.Error(ex.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string p in items)
|
||||
{
|
||||
if (!File.Exists(p))
|
||||
@@ -72,7 +114,7 @@ namespace SpineViewer.Dialogs
|
||||
return;
|
||||
}
|
||||
|
||||
Result = new(items.Cast<string>().ToArray(), sourceVersion, targetVersion, jsonTarget);
|
||||
Result = new(outputDir, items.Cast<string>().ToArray(), sourceVersion, targetVersion, jsonTarget);
|
||||
DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
@@ -85,8 +127,13 @@ namespace SpineViewer.Dialogs
|
||||
/// <summary>
|
||||
/// 文件格式转换对话框结果包装类
|
||||
/// </summary>
|
||||
public class ConvertFileFormatDialogResult(string[] skelPaths, SpineVersion sourceVersion, SpineVersion targetVersion, bool jsonTarget)
|
||||
public class ConvertFileFormatDialogResult(string? outputDir, string[] skelPaths, SpineVersion sourceVersion, SpineVersion targetVersion, bool jsonTarget)
|
||||
{
|
||||
/// <summary>
|
||||
/// 输出文件夹, 如果为空, 则将转换后的文件转换到各自的文件夹下
|
||||
/// </summary>
|
||||
public string? OutputDir => outputDir;
|
||||
|
||||
/// <summary>
|
||||
/// 骨骼文件路径列表
|
||||
/// </summary>
|
||||
|
||||
@@ -117,6 +117,9 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="folderBrowserDialog_Output.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>36, 22</value>
|
||||
</metadata>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
|
||||
10
SpineViewer/Dialogs/ExportDialog.Designer.cs
generated
10
SpineViewer/Dialogs/ExportDialog.Designer.cs
generated
@@ -47,7 +47,7 @@
|
||||
panel1.Location = new Point(0, 0);
|
||||
panel1.Name = "panel1";
|
||||
panel1.Padding = new Padding(50, 15, 50, 10);
|
||||
panel1.Size = new Size(793, 754);
|
||||
panel1.Size = new Size(793, 841);
|
||||
panel1.TabIndex = 2;
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
@@ -65,7 +65,7 @@
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 20F));
|
||||
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Absolute, 20F));
|
||||
tableLayoutPanel1.Size = new Size(693, 729);
|
||||
tableLayoutPanel1.Size = new Size(693, 816);
|
||||
tableLayoutPanel1.TabIndex = 0;
|
||||
//
|
||||
// propertyGrid_ExportArgs
|
||||
@@ -74,7 +74,7 @@
|
||||
propertyGrid_ExportArgs.Location = new Point(3, 3);
|
||||
propertyGrid_ExportArgs.Name = "propertyGrid_ExportArgs";
|
||||
propertyGrid_ExportArgs.PropertySort = PropertySort.Categorized;
|
||||
propertyGrid_ExportArgs.Size = new Size(687, 650);
|
||||
propertyGrid_ExportArgs.Size = new Size(687, 737);
|
||||
propertyGrid_ExportArgs.TabIndex = 1;
|
||||
propertyGrid_ExportArgs.ToolbarVisible = false;
|
||||
//
|
||||
@@ -88,7 +88,7 @@
|
||||
tableLayoutPanel2.Controls.Add(button_Ok, 0, 0);
|
||||
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
||||
tableLayoutPanel2.Location = new Point(3, 686);
|
||||
tableLayoutPanel2.Location = new Point(3, 773);
|
||||
tableLayoutPanel2.Margin = new Padding(3, 30, 3, 3);
|
||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
tableLayoutPanel2.RowCount = 1;
|
||||
@@ -126,7 +126,7 @@
|
||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
CancelButton = button_Cancel;
|
||||
ClientSize = new Size(793, 754);
|
||||
ClientSize = new Size(793, 841);
|
||||
Controls.Add(panel1);
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
MaximizeBox = false;
|
||||
|
||||
4
SpineViewer/Dialogs/OpenSpineDialog.Designer.cs
generated
4
SpineViewer/Dialogs/OpenSpineDialog.Designer.cs
generated
@@ -232,14 +232,14 @@
|
||||
//
|
||||
openFileDialog_Skel.AddExtension = false;
|
||||
openFileDialog_Skel.AddToRecent = false;
|
||||
openFileDialog_Skel.Filter = "skel 文件 (*.skel; *.json)|*.skel;*.json|二进制文件 (*.skel)|*.skel|文本文件 (*.json)|*.json|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Skel.Filter = "所有文件 (*.*)|*.*|skel 文件 (*.skel; *.json)|*.skel;*.json";
|
||||
openFileDialog_Skel.Title = "选择skel文件";
|
||||
//
|
||||
// openFileDialog_Atlas
|
||||
//
|
||||
openFileDialog_Atlas.AddExtension = false;
|
||||
openFileDialog_Atlas.AddToRecent = false;
|
||||
openFileDialog_Atlas.Filter = "atlas 文件 (*.atlas)|*.atlas|所有文件 (*.*)|*.*";
|
||||
openFileDialog_Atlas.Filter = "所有文件 (*.*)|*.*|atlas 文件 (*.atlas)|*.atlas";
|
||||
openFileDialog_Atlas.Title = "选择atlas文件";
|
||||
//
|
||||
// OpenSpineDialog
|
||||
|
||||
@@ -8,6 +8,51 @@ namespace SpineViewer.Extensions
|
||||
{
|
||||
public static class SFMLExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取并集范围
|
||||
/// </summary>
|
||||
public static RectangleF Union(this RectangleF bounds, RectangleF other)
|
||||
{
|
||||
var x = Math.Min(bounds.X, other.X);
|
||||
var y = Math.Min(bounds.Y, other.Y);
|
||||
var w = Math.Max(bounds.Right, other.Right) - x;
|
||||
var h = Math.Max(bounds.Bottom, other.Bottom) - y;
|
||||
return new(x, y, w, h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取适合指定画布参数下能够覆盖包围盒的画布视区包围盒
|
||||
/// </summary>
|
||||
public static RectangleF GetCanvasBounds(this RectangleF bounds, Size resolution) => GetCanvasBounds(bounds, resolution, new(0), new(0));
|
||||
|
||||
/// <summary>
|
||||
/// 获取适合指定画布参数下能够覆盖包围盒的画布视区包围盒
|
||||
/// </summary>
|
||||
public static RectangleF GetCanvasBounds(this RectangleF bounds, Size resolution, Padding margin) => GetCanvasBounds(bounds, resolution, margin, new(0));
|
||||
|
||||
/// <summary>
|
||||
/// 获取适合指定画布参数下能够覆盖包围盒的画布视区包围盒
|
||||
/// </summary>
|
||||
public static RectangleF GetCanvasBounds(this RectangleF bounds, Size resolution, Padding margin, Padding padding)
|
||||
{
|
||||
float sizeW = bounds.Width;
|
||||
float sizeH = bounds.Height;
|
||||
float innerW = resolution.Width - padding.Horizontal;
|
||||
float innerH = resolution.Height - padding.Vertical;
|
||||
float scale = Math.Max(Math.Abs(sizeW / innerW), Math.Abs(sizeH / innerH)); // 取两方向上较大的缩放比, 以此让画布可以覆盖内容
|
||||
float scaleW = scale * Math.Sign(sizeW);
|
||||
float scaleH = scale * Math.Sign(sizeH);
|
||||
|
||||
innerW *= scaleW;
|
||||
innerH *= scaleH;
|
||||
|
||||
var x = bounds.X - (innerW - sizeW) / 2 - (margin.Left + padding.Left) * scaleW;
|
||||
var y = bounds.Y - (innerH - sizeH) / 2 - (margin.Top + padding.Top) * scaleH;
|
||||
var w = (resolution.Width + margin.Horizontal) * scaleW;
|
||||
var h = (resolution.Height + margin.Vertical) * scaleH;
|
||||
return new(x, y, w, h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Winforms Bitmap 对象, 需要使用 Dispose 释放对象
|
||||
/// </summary>
|
||||
@@ -29,45 +74,45 @@ namespace SpineViewer.Extensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取某个包围盒下合适的视图
|
||||
/// 获取视区的包围盒
|
||||
/// </summary>
|
||||
public static SFML.Graphics.View GetView(this RectangleF bounds, Size resolution, Padding padding)
|
||||
=> bounds.GetView((uint)resolution.Width, (uint)resolution.Height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
||||
|
||||
/// <summary>
|
||||
/// 获取某个包围盒下合适的视图
|
||||
/// </summary>
|
||||
public static SFML.Graphics.View GetView(this RectangleF bounds, uint width, uint height, Padding padding)
|
||||
=> bounds.GetView(width, height, (uint)padding.Left, (uint)padding.Right, (uint)padding.Top, (uint)padding.Bottom);
|
||||
|
||||
/// <summary>
|
||||
/// 获取某个包围盒下合适的视图
|
||||
/// </summary>
|
||||
public static SFML.Graphics.View GetView(this RectangleF bounds, Size resolution, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1)
|
||||
=> bounds.GetView((uint)resolution.Width, (uint)resolution.Height, paddingL, paddingR, paddingT, paddingB);
|
||||
|
||||
/// <summary>
|
||||
/// 获取某个包围盒下合适的视图
|
||||
/// </summary>
|
||||
public static SFML.Graphics.View GetView(this RectangleF bounds, uint width, uint height, uint paddingL = 1, uint paddingR = 1, uint paddingT = 1, uint paddingB = 1)
|
||||
public static RectangleF GetBounds(this SFML.Graphics.View view)
|
||||
{
|
||||
float sizeX = bounds.Width;
|
||||
float sizeY = bounds.Height;
|
||||
float innerW = width - paddingL - paddingR;
|
||||
float innerH = height - paddingT - paddingB;
|
||||
return new(
|
||||
view.Center.X - view.Size.X / 2,
|
||||
view.Center.Y - view.Size.Y / 2,
|
||||
view.Size.X,
|
||||
view.Size.Y
|
||||
);
|
||||
}
|
||||
|
||||
float scale = 1;
|
||||
if (sizeY / sizeX < innerH / innerW)
|
||||
scale = sizeX / innerW; // 相同的 X, 视窗 Y 更大
|
||||
else
|
||||
scale = sizeY / innerH; // 相同的 Y, 视窗 X 更大
|
||||
/// <summary>
|
||||
/// 按画布设置视区, 边缘和填充区域将不会出现内容
|
||||
/// </summary>
|
||||
public static void SetViewport(this SFML.Graphics.View view, Size resolution, Padding margin) => SetViewport(view, resolution, margin, new(0));
|
||||
|
||||
var x = bounds.X + bounds.Width / 2 + (paddingL - (float)paddingR) * scale;
|
||||
var y = bounds.Y + bounds.Height / 2 + (paddingT - (float)paddingB) * scale;
|
||||
var viewX = width * scale;
|
||||
var viewY = height * scale;
|
||||
/// <summary>
|
||||
/// 按画布设置视区, 边缘和填充区域将不会出现内容
|
||||
/// </summary>
|
||||
public static void SetViewport(this SFML.Graphics.View view, Size resolution, Padding margin, Padding padding)
|
||||
{
|
||||
var innerW = resolution.Width - padding.Horizontal;
|
||||
var innerH = resolution.Height - padding.Vertical;
|
||||
|
||||
return new(new(x, y), new(viewX, -viewY));
|
||||
float width = resolution.Width + margin.Horizontal;
|
||||
float height = resolution.Height + margin.Vertical;
|
||||
|
||||
view.Viewport = new(
|
||||
(margin.Left + padding.Left) / width,
|
||||
(margin.Top + padding.Top) / height,
|
||||
innerW / width,
|
||||
innerH / height
|
||||
);
|
||||
|
||||
var bounds = view.GetBounds().GetCanvasBounds(new(innerW, innerH));
|
||||
|
||||
view.Center = new(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2);
|
||||
view.Size = new(bounds.Width, bounds.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
using SpineViewer.Natives;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace SpineViewer
|
||||
{
|
||||
public partial class PetForm: Form
|
||||
{
|
||||
public PetForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override CreateParams CreateParams
|
||||
{
|
||||
get
|
||||
{
|
||||
//var style = Win32.GetWindowLong(hWnd, Win32.GWL_STYLE) | Win32.WS_POPUP;
|
||||
//var exStyle = Win32.GetWindowLong(hWnd, Win32.GWL_EXSTYLE) | Win32.WS_EX_LAYERED | Win32.WS_EX_TOOLWINDOW | Win32.WS_EX_TOPMOST;
|
||||
//Win32.SetWindowLong(hWnd, Win32.GWL_STYLE, style);
|
||||
//Win32.SetWindowLong(hWnd, Win32.GWL_EXSTYLE, exStyle);
|
||||
//Win32.SetLayeredWindowAttributes(hWnd, crKey, 255, Win32.LWA_COLORKEY | Win32.LWA_ALPHA);
|
||||
//Win32.SetWindowPos(hWnd, Win32.HWND_TOPMOST, 0, 0, 0, 0, Win32.SWP_NOMOVE | Win32.SWP_NOSIZE);
|
||||
var cp = base.CreateParams;
|
||||
cp.ExStyle = Win32.WS_EX_LAYERED | Win32.WS_EX_TOPMOST;
|
||||
cp.Style = Win32.WS_POPUP;
|
||||
//cp.ExStyle |= Win32.WS_EX_LAYERED | Win32.WS_EX_TOOLWINDOW | Win32.WS_EX_TOPMOST;
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs e)
|
||||
{
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
SpineViewer/Forms/SpinePreviewFullScreenForm.Designer.cs
generated
Normal file
51
SpineViewer/Forms/SpinePreviewFullScreenForm.Designer.cs
generated
Normal file
@@ -0,0 +1,51 @@
|
||||
namespace SpineViewer.Forms
|
||||
{
|
||||
partial class SpinePreviewFullScreenForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
SuspendLayout();
|
||||
//
|
||||
// SpinePreviewFullScreenForm
|
||||
//
|
||||
AutoScaleMode = AutoScaleMode.None;
|
||||
ClientSize = new Size(512, 512);
|
||||
ControlBox = false;
|
||||
FormBorderStyle = FormBorderStyle.None;
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "SpinePreviewFullScreenForm";
|
||||
ShowIcon = false;
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.Manual;
|
||||
TopMost = true;
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
35
SpineViewer/Forms/SpinePreviewFullScreenForm.cs
Normal file
35
SpineViewer/Forms/SpinePreviewFullScreenForm.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using SpineViewer.Natives;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace SpineViewer.Forms
|
||||
{
|
||||
[ToolboxItem(true)]
|
||||
[Designer(typeof(ComponentDesigner), typeof(IDesigner))]
|
||||
[DesignTimeVisible(true)]
|
||||
public partial class SpinePreviewFullScreenForm: Form
|
||||
{
|
||||
public SpinePreviewFullScreenForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override CreateParams CreateParams
|
||||
{
|
||||
get
|
||||
{
|
||||
var cp = base.CreateParams;
|
||||
cp.ExStyle |= Win32.WS_EX_TOOLWINDOW;
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
SpineViewer/Forms/SpineViewerForm.Designer.cs
generated
91
SpineViewer/Forms/SpineViewerForm.Designer.cs
generated
@@ -59,6 +59,9 @@
|
||||
toolStripMenuItem_Diagnostics = new ToolStripMenuItem();
|
||||
toolStripSeparator3 = new ToolStripSeparator();
|
||||
toolStripMenuItem_About = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Debug = new ToolStripMenuItem();
|
||||
toolStripMenuItem_Experiment = new ToolStripMenuItem();
|
||||
toolStripMenuItem_DesktopProjection = new ToolStripMenuItem();
|
||||
rtbLog = new RichTextBox();
|
||||
splitContainer_MainForm = new SplitContainer();
|
||||
splitContainer_Functional = new SplitContainer();
|
||||
@@ -102,10 +105,10 @@
|
||||
//
|
||||
menuStrip.BackColor = SystemColors.Control;
|
||||
menuStrip.ImageScalingSize = new Size(24, 24);
|
||||
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_File, toolStripMenuItem_Tool, toolStripMenuItem_Download, toolStripMenuItem_Help });
|
||||
menuStrip.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_File, toolStripMenuItem_Tool, toolStripMenuItem_Download, toolStripMenuItem_Help, toolStripMenuItem_Experiment });
|
||||
menuStrip.Location = new Point(0, 0);
|
||||
menuStrip.Name = "menuStrip";
|
||||
menuStrip.Size = new Size(1778, 32);
|
||||
menuStrip.Size = new Size(1778, 36);
|
||||
menuStrip.TabIndex = 0;
|
||||
menuStrip.Text = "菜单";
|
||||
//
|
||||
@@ -113,7 +116,7 @@
|
||||
//
|
||||
toolStripMenuItem_File.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Open, toolStripMenuItem_BatchOpen, toolStripSeparator1, toolStripMenuItem_Export, toolStripSeparator2, toolStripMenuItem_Exit });
|
||||
toolStripMenuItem_File.Name = "toolStripMenuItem_File";
|
||||
toolStripMenuItem_File.Size = new Size(84, 28);
|
||||
toolStripMenuItem_File.Size = new Size(84, 30);
|
||||
toolStripMenuItem_File.Text = "文件(&F)";
|
||||
//
|
||||
// toolStripMenuItem_Open
|
||||
@@ -245,7 +248,7 @@
|
||||
//
|
||||
toolStripMenuItem_Tool.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ConvertFileFormat });
|
||||
toolStripMenuItem_Tool.Name = "toolStripMenuItem_Tool";
|
||||
toolStripMenuItem_Tool.Size = new Size(84, 28);
|
||||
toolStripMenuItem_Tool.Size = new Size(84, 30);
|
||||
toolStripMenuItem_Tool.Text = "工具(&T)";
|
||||
//
|
||||
// toolStripMenuItem_ConvertFileFormat
|
||||
@@ -259,7 +262,7 @@
|
||||
//
|
||||
toolStripMenuItem_Download.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ManageResource });
|
||||
toolStripMenuItem_Download.Name = "toolStripMenuItem_Download";
|
||||
toolStripMenuItem_Download.Size = new Size(88, 28);
|
||||
toolStripMenuItem_Download.Size = new Size(88, 30);
|
||||
toolStripMenuItem_Download.Text = "下载(&D)";
|
||||
//
|
||||
// toolStripMenuItem_ManageResource
|
||||
@@ -271,30 +274,52 @@
|
||||
//
|
||||
// toolStripMenuItem_Help
|
||||
//
|
||||
toolStripMenuItem_Help.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Diagnostics, toolStripSeparator3, toolStripMenuItem_About });
|
||||
toolStripMenuItem_Help.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Diagnostics, toolStripSeparator3, toolStripMenuItem_About, toolStripMenuItem_Debug });
|
||||
toolStripMenuItem_Help.Name = "toolStripMenuItem_Help";
|
||||
toolStripMenuItem_Help.Size = new Size(88, 28);
|
||||
toolStripMenuItem_Help.Size = new Size(88, 30);
|
||||
toolStripMenuItem_Help.Text = "帮助(&H)";
|
||||
//
|
||||
// toolStripMenuItem_Diagnostics
|
||||
//
|
||||
toolStripMenuItem_Diagnostics.Name = "toolStripMenuItem_Diagnostics";
|
||||
toolStripMenuItem_Diagnostics.Size = new Size(208, 34);
|
||||
toolStripMenuItem_Diagnostics.Size = new Size(270, 34);
|
||||
toolStripMenuItem_Diagnostics.Text = "诊断信息(&D)";
|
||||
toolStripMenuItem_Diagnostics.Click += toolStripMenuItem_Diagnostics_Click;
|
||||
//
|
||||
// toolStripSeparator3
|
||||
//
|
||||
toolStripSeparator3.Name = "toolStripSeparator3";
|
||||
toolStripSeparator3.Size = new Size(205, 6);
|
||||
toolStripSeparator3.Size = new Size(267, 6);
|
||||
//
|
||||
// toolStripMenuItem_About
|
||||
//
|
||||
toolStripMenuItem_About.Name = "toolStripMenuItem_About";
|
||||
toolStripMenuItem_About.Size = new Size(208, 34);
|
||||
toolStripMenuItem_About.Size = new Size(270, 34);
|
||||
toolStripMenuItem_About.Text = "关于(&A)";
|
||||
toolStripMenuItem_About.Click += toolStripMenuItem_About_Click;
|
||||
//
|
||||
// toolStripMenuItem_Debug
|
||||
//
|
||||
toolStripMenuItem_Debug.Name = "toolStripMenuItem_Debug";
|
||||
toolStripMenuItem_Debug.Size = new Size(270, 34);
|
||||
toolStripMenuItem_Debug.Text = "调试";
|
||||
toolStripMenuItem_Debug.Visible = false;
|
||||
toolStripMenuItem_Debug.Click += toolStripMenuItem_Debug_Click;
|
||||
//
|
||||
// toolStripMenuItem_Experiment
|
||||
//
|
||||
toolStripMenuItem_Experiment.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_DesktopProjection });
|
||||
toolStripMenuItem_Experiment.Name = "toolStripMenuItem_Experiment";
|
||||
toolStripMenuItem_Experiment.Size = new Size(138, 30);
|
||||
toolStripMenuItem_Experiment.Text = "实验性功能(&E)";
|
||||
//
|
||||
// toolStripMenuItem_DesktopProjection
|
||||
//
|
||||
toolStripMenuItem_DesktopProjection.Name = "toolStripMenuItem_DesktopProjection";
|
||||
toolStripMenuItem_DesktopProjection.Size = new Size(182, 34);
|
||||
toolStripMenuItem_DesktopProjection.Text = "桌面投影";
|
||||
toolStripMenuItem_DesktopProjection.Click += toolStripMenuItem_DesktopProjection_Click;
|
||||
//
|
||||
// rtbLog
|
||||
//
|
||||
rtbLog.BackColor = SystemColors.Window;
|
||||
@@ -305,7 +330,7 @@
|
||||
rtbLog.Margin = new Padding(3, 2, 3, 2);
|
||||
rtbLog.Name = "rtbLog";
|
||||
rtbLog.ReadOnly = true;
|
||||
rtbLog.Size = new Size(1758, 142);
|
||||
rtbLog.Size = new Size(1758, 158);
|
||||
rtbLog.TabIndex = 0;
|
||||
rtbLog.Text = "";
|
||||
rtbLog.WordWrap = false;
|
||||
@@ -328,8 +353,8 @@
|
||||
//
|
||||
splitContainer_MainForm.Panel2.Controls.Add(rtbLog);
|
||||
splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_MainForm.Size = new Size(1758, 1097);
|
||||
splitContainer_MainForm.SplitterDistance = 947;
|
||||
splitContainer_MainForm.Size = new Size(1758, 1093);
|
||||
splitContainer_MainForm.SplitterDistance = 927;
|
||||
splitContainer_MainForm.SplitterWidth = 8;
|
||||
splitContainer_MainForm.TabIndex = 3;
|
||||
splitContainer_MainForm.TabStop = false;
|
||||
@@ -353,7 +378,7 @@
|
||||
//
|
||||
splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview);
|
||||
splitContainer_Functional.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_Functional.Size = new Size(1758, 947);
|
||||
splitContainer_Functional.Size = new Size(1758, 927);
|
||||
splitContainer_Functional.SplitterDistance = 788;
|
||||
splitContainer_Functional.SplitterWidth = 8;
|
||||
splitContainer_Functional.TabIndex = 2;
|
||||
@@ -377,7 +402,7 @@
|
||||
//
|
||||
splitContainer_Information.Panel2.Controls.Add(splitContainer_Config);
|
||||
splitContainer_Information.Panel2.Cursor = Cursors.Default;
|
||||
splitContainer_Information.Size = new Size(788, 947);
|
||||
splitContainer_Information.Size = new Size(788, 927);
|
||||
splitContainer_Information.SplitterDistance = 351;
|
||||
splitContainer_Information.SplitterWidth = 8;
|
||||
splitContainer_Information.TabIndex = 1;
|
||||
@@ -391,7 +416,7 @@
|
||||
groupBox_SkelList.Dock = DockStyle.Fill;
|
||||
groupBox_SkelList.Location = new Point(0, 0);
|
||||
groupBox_SkelList.Name = "groupBox_SkelList";
|
||||
groupBox_SkelList.Size = new Size(351, 947);
|
||||
groupBox_SkelList.Size = new Size(351, 927);
|
||||
groupBox_SkelList.TabIndex = 0;
|
||||
groupBox_SkelList.TabStop = false;
|
||||
groupBox_SkelList.Text = "模型列表";
|
||||
@@ -401,16 +426,16 @@
|
||||
spineListView.Dock = DockStyle.Fill;
|
||||
spineListView.Location = new Point(3, 26);
|
||||
spineListView.Name = "spineListView";
|
||||
spineListView.Size = new Size(345, 918);
|
||||
spineListView.Size = new Size(345, 898);
|
||||
spineListView.SpinePropertyGrid = spineViewPropertyGrid;
|
||||
spineListView.TabIndex = 0;
|
||||
//
|
||||
// spinePropertyGrid
|
||||
// spineViewPropertyGrid
|
||||
//
|
||||
spineViewPropertyGrid.Dock = DockStyle.Fill;
|
||||
spineViewPropertyGrid.Location = new Point(3, 26);
|
||||
spineViewPropertyGrid.Name = "spinePropertyGrid";
|
||||
spineViewPropertyGrid.Size = new Size(423, 586);
|
||||
spineViewPropertyGrid.Name = "spineViewPropertyGrid";
|
||||
spineViewPropertyGrid.Size = new Size(423, 575);
|
||||
spineViewPropertyGrid.TabIndex = 0;
|
||||
//
|
||||
// splitContainer_Config
|
||||
@@ -427,8 +452,8 @@
|
||||
// splitContainer_Config.Panel2
|
||||
//
|
||||
splitContainer_Config.Panel2.Controls.Add(groupBox_SkelConfig);
|
||||
splitContainer_Config.Size = new Size(429, 947);
|
||||
splitContainer_Config.SplitterDistance = 324;
|
||||
splitContainer_Config.Size = new Size(429, 927);
|
||||
splitContainer_Config.SplitterDistance = 315;
|
||||
splitContainer_Config.SplitterWidth = 8;
|
||||
splitContainer_Config.TabIndex = 0;
|
||||
//
|
||||
@@ -439,7 +464,7 @@
|
||||
groupBox_PreviewConfig.Location = new Point(0, 0);
|
||||
groupBox_PreviewConfig.Margin = new Padding(0);
|
||||
groupBox_PreviewConfig.Name = "groupBox_PreviewConfig";
|
||||
groupBox_PreviewConfig.Size = new Size(429, 324);
|
||||
groupBox_PreviewConfig.Size = new Size(429, 315);
|
||||
groupBox_PreviewConfig.TabIndex = 1;
|
||||
groupBox_PreviewConfig.TabStop = false;
|
||||
groupBox_PreviewConfig.Text = "画面参数";
|
||||
@@ -450,10 +475,9 @@
|
||||
propertyGrid_Previewer.HelpVisible = false;
|
||||
propertyGrid_Previewer.Location = new Point(3, 26);
|
||||
propertyGrid_Previewer.Name = "propertyGrid_Previewer";
|
||||
propertyGrid_Previewer.Size = new Size(423, 295);
|
||||
propertyGrid_Previewer.Size = new Size(423, 286);
|
||||
propertyGrid_Previewer.TabIndex = 1;
|
||||
propertyGrid_Previewer.ToolbarVisible = false;
|
||||
propertyGrid_Previewer.PropertyValueChanged += propertyGrid_PropertyValueChanged;
|
||||
//
|
||||
// groupBox_SkelConfig
|
||||
//
|
||||
@@ -462,7 +486,7 @@
|
||||
groupBox_SkelConfig.Location = new Point(0, 0);
|
||||
groupBox_SkelConfig.Margin = new Padding(0);
|
||||
groupBox_SkelConfig.Name = "groupBox_SkelConfig";
|
||||
groupBox_SkelConfig.Size = new Size(429, 615);
|
||||
groupBox_SkelConfig.Size = new Size(429, 604);
|
||||
groupBox_SkelConfig.TabIndex = 0;
|
||||
groupBox_SkelConfig.TabStop = false;
|
||||
groupBox_SkelConfig.Text = "模型参数";
|
||||
@@ -473,18 +497,18 @@
|
||||
groupBox_Preview.Dock = DockStyle.Fill;
|
||||
groupBox_Preview.Location = new Point(0, 0);
|
||||
groupBox_Preview.Name = "groupBox_Preview";
|
||||
groupBox_Preview.Size = new Size(962, 947);
|
||||
groupBox_Preview.Size = new Size(962, 927);
|
||||
groupBox_Preview.TabIndex = 1;
|
||||
groupBox_Preview.TabStop = false;
|
||||
groupBox_Preview.Text = "预览画面";
|
||||
//
|
||||
// spinePreviewer
|
||||
// spinePreviewPanel
|
||||
//
|
||||
spinePreviewPanel.Dock = DockStyle.Fill;
|
||||
spinePreviewPanel.Location = new Point(3, 26);
|
||||
spinePreviewPanel.Name = "spinePreviewer";
|
||||
spinePreviewPanel.Name = "spinePreviewPanel";
|
||||
spinePreviewPanel.PropertyGrid = propertyGrid_Previewer;
|
||||
spinePreviewPanel.Size = new Size(956, 918);
|
||||
spinePreviewPanel.Size = new Size(956, 898);
|
||||
spinePreviewPanel.SpineListView = spineListView;
|
||||
spinePreviewPanel.TabIndex = 0;
|
||||
//
|
||||
@@ -492,10 +516,10 @@
|
||||
//
|
||||
panel_MainForm.Controls.Add(splitContainer_MainForm);
|
||||
panel_MainForm.Dock = DockStyle.Fill;
|
||||
panel_MainForm.Location = new Point(0, 32);
|
||||
panel_MainForm.Location = new Point(0, 36);
|
||||
panel_MainForm.Name = "panel_MainForm";
|
||||
panel_MainForm.Padding = new Padding(10, 5, 10, 10);
|
||||
panel_MainForm.Size = new Size(1778, 1112);
|
||||
panel_MainForm.Size = new Size(1778, 1108);
|
||||
panel_MainForm.TabIndex = 4;
|
||||
//
|
||||
// toolTip
|
||||
@@ -590,5 +614,8 @@
|
||||
private ToolStripSeparator toolStripSeparator5;
|
||||
private ToolStripSeparator toolStripSeparator6;
|
||||
private SplitContainer splitContainer_Config;
|
||||
private ToolStripMenuItem toolStripMenuItem_Experiment;
|
||||
private ToolStripMenuItem toolStripMenuItem_DesktopProjection;
|
||||
private ToolStripMenuItem toolStripMenuItem_Debug;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace SpineViewer
|
||||
MessagePopup.Warn("Fragment shader 加载失败,预乘Alpha通道属性失效");
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
toolStripMenuItem_Debug.Visible = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -92,8 +95,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new FrameExporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new FrameExporterProperty((FrameExporter)exporter));
|
||||
@@ -112,8 +116,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new FrameSequenceExporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new FrameSequenceExporterProperty((FrameSequenceExporter)exporter));
|
||||
@@ -132,8 +137,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new GifExporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new GifExporterProperty((GifExporter)exporter));
|
||||
@@ -152,8 +158,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new WebpExporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new WebpExporterProperty((WebpExporter)exporter));
|
||||
@@ -172,8 +179,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new AvifExporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new AvifExporterProperty((AvifExporter)exporter));
|
||||
@@ -192,8 +200,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new Mp4Exporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new Mp4ExporterProperty((Mp4Exporter)exporter));
|
||||
@@ -212,8 +221,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new WebmExporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new WebmExporterProperty((WebmExporter)exporter));
|
||||
@@ -232,8 +242,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new MkvExporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new MkvExporterProperty((MkvExporter)exporter));
|
||||
@@ -252,8 +263,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new MovExporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new MovExporterProperty((MovExporter)exporter));
|
||||
@@ -272,8 +284,9 @@ namespace SpineViewer
|
||||
if (!exporterCache.ContainsKey(k)) exporterCache[k] = new CustomExporter();
|
||||
|
||||
var exporter = exporterCache[k];
|
||||
using var view = spinePreviewPanel.GetView();
|
||||
exporter.Resolution = spinePreviewPanel.Resolution;
|
||||
exporter.View = spinePreviewPanel.GetView();
|
||||
exporter.PreviewerView = view;
|
||||
exporter.RenderSelectedOnly = spinePreviewPanel.RenderSelectedOnly;
|
||||
|
||||
var exportDialog = new Dialogs.ExportDialog(new CustomExporterProperty((CustomExporter)exporter));
|
||||
@@ -326,12 +339,6 @@ namespace SpineViewer
|
||||
|
||||
private void splitContainer_MouseUp(object sender, MouseEventArgs e) => ActiveControl = null;
|
||||
|
||||
private void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
|
||||
{
|
||||
// 用来解决对面板某些值修改之后, 其他被联动修改的值不会实时刷新的问题
|
||||
(sender as PropertyGrid)?.Refresh();
|
||||
}
|
||||
|
||||
private void Export_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = (BackgroundWorker)sender;
|
||||
@@ -347,19 +354,15 @@ namespace SpineViewer
|
||||
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
|
||||
{
|
||||
var worker = sender as BackgroundWorker;
|
||||
var arguments = e.Argument as Dialogs.ConvertFileFormatDialogResult;
|
||||
var skelPaths = arguments.SkelPaths;
|
||||
var srcVersion = arguments.SourceVersion;
|
||||
var tgtVersion = arguments.TargetVersion;
|
||||
var jsonTarget = arguments.JsonTarget;
|
||||
var newSuffix = jsonTarget ? ".json" : ".skel";
|
||||
var args = e.Argument as Dialogs.ConvertFileFormatDialogResult;
|
||||
var newSuffix = args.JsonTarget ? ".json" : ".skel";
|
||||
|
||||
int totalCount = skelPaths.Length;
|
||||
int totalCount = args.SkelPaths.Length;
|
||||
int success = 0;
|
||||
int error = 0;
|
||||
|
||||
SkeletonConverter srcCvter = srcVersion != SpineVersion.Auto ? SkeletonConverter.New(srcVersion) : null;
|
||||
SkeletonConverter tgtCvter = SkeletonConverter.New(tgtVersion);
|
||||
SkeletonConverter srcCvter = args.SourceVersion != SpineVersion.Auto ? SkeletonConverter.New(args.SourceVersion) : null;
|
||||
SkeletonConverter tgtCvter = SkeletonConverter.New(args.TargetVersion);
|
||||
|
||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
||||
for (int i = 0; i < totalCount; i++)
|
||||
@@ -370,12 +373,13 @@ namespace SpineViewer
|
||||
break;
|
||||
}
|
||||
|
||||
var skelPath = skelPaths[i];
|
||||
var skelPath = args.SkelPaths[i];
|
||||
var newPath = Path.ChangeExtension(skelPath, newSuffix);
|
||||
if (args.OutputDir is string outputDir) newPath = Path.Combine(outputDir, Path.GetFileName(newPath));
|
||||
|
||||
try
|
||||
{
|
||||
if (srcVersion == SpineVersion.Auto)
|
||||
if (args.SourceVersion == SpineVersion.Auto)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -387,8 +391,9 @@ namespace SpineViewer
|
||||
}
|
||||
}
|
||||
var root = srcCvter.Read(skelPath);
|
||||
root = srcCvter.ToVersion(root, tgtVersion);
|
||||
if (jsonTarget) tgtCvter.WriteJson(root, newPath); else tgtCvter.WriteBinary(root, newPath);
|
||||
root = srcCvter.ToVersion(root, args.TargetVersion);
|
||||
if (args.JsonTarget) tgtCvter.WriteJson(root, newPath);
|
||||
else tgtCvter.WriteBinary(root, newPath);
|
||||
success++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -411,48 +416,30 @@ namespace SpineViewer
|
||||
}
|
||||
}
|
||||
|
||||
//private System.Windows.Forms.Timer timer = new();
|
||||
//private PetForm pet = new PetForm();
|
||||
//private IntPtr screenDC;
|
||||
//private IntPtr memDC;
|
||||
//private void _Test()
|
||||
//{
|
||||
// screenDC = Win32.GetDC(IntPtr.Zero);
|
||||
// memDC = Win32.CreateCompatibleDC(screenDC);
|
||||
// pet.Show();
|
||||
// timer.Tick += Timer_Tick;
|
||||
// timer.Enabled = true;
|
||||
// timer.Interval = 50;
|
||||
// timer.Start();
|
||||
//}
|
||||
private void toolStripMenuItem_DesktopProjection_Click(object sender, EventArgs e)
|
||||
{
|
||||
toolStripMenuItem_DesktopProjection.Checked = !toolStripMenuItem_DesktopProjection.Checked;
|
||||
spinePreviewPanel.EnableDesktopProjection = toolStripMenuItem_DesktopProjection.Checked;
|
||||
}
|
||||
|
||||
//private void Timer_Tick(object? sender, EventArgs e)
|
||||
//{
|
||||
// using var tex = new SFML.Graphics.RenderTexture((uint)pet.Width, (uint)pet.Height);
|
||||
// var v = spinePreviewer.GetView();
|
||||
// tex.SetView(v);
|
||||
// tex.Clear(new SFML.Graphics.Color(0, 0, 0, 0));
|
||||
// lock (spineListView.Spines)
|
||||
// {
|
||||
// foreach (var sp in spineListView.Spines)
|
||||
// tex.Draw(sp);
|
||||
// }
|
||||
// tex.Display();
|
||||
// using var frame = new SFMLImageVideoFrame(tex.Texture.CopyToImage());
|
||||
// using var bitmap = frame.CopyToBitmap();
|
||||
private void toolStripMenuItem_Debug_Click(object sender, EventArgs e)
|
||||
{
|
||||
#if DEBUG
|
||||
//var cvt = SkeletonConverter.New(SpineVersion.V38);
|
||||
//var root = cvt.ReadBinary(@"D:\ACGN\AzurLane_Export\AzurLane_Dynamic\docs\aerhangeersike\aerhangeersike_3\aerhangeersike_3 - 副本.skel");
|
||||
//cvt.WriteJson(root, @"D:\ACGN\AzurLane_Export\AzurLane_Dynamic\docs\aerhangeersike\aerhangeersike_3\aerhangeersike_3.json");
|
||||
|
||||
// var newBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
|
||||
// var oldBitmap = Win32.SelectObject(memDC, newBitmap);
|
||||
//root = cvt.ReadJson(@"D:\ACGN\AzurLane_Export\AzurLane_Dynamic\docs\aerhangeersike\aerhangeersike_3\aerhangeersike_3.json");
|
||||
//cvt.WriteBinary(root, @"D:\ACGN\AzurLane_Export\AzurLane_Dynamic\docs\aerhangeersike\aerhangeersike_3\aerhangeersike_3.skel");
|
||||
//var sp = SpineObject.New(SpineVersion.V38, @"D:\ACGN\AzurLane_Export\AzurLane_Dynamic\docs\aerhangeersike\aerhangeersike_3\aerhangeersike_3.skel");
|
||||
|
||||
// Win32.SIZE size = new Win32.SIZE { cx = pet.Width, cy = pet.Height };
|
||||
// Win32.POINT srcPos = new Win32.POINT { x = 0, y = 0 };
|
||||
// Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION { BlendOp = 0, BlendFlags = 0, SourceConstantAlpha = 255, AlphaFormat = Win32.AC_SRC_ALPHA };
|
||||
|
||||
// Win32.UpdateLayeredWindow(pet.Handle, screenDC, IntPtr.Zero, ref size, memDC, ref srcPos, 0, ref blend, Win32.ULW_ALPHA);
|
||||
|
||||
// Win32.SelectObject(memDC, oldBitmap);
|
||||
// Win32.DeleteObject(newBitmap);
|
||||
//}
|
||||
//var cvt = SkeletonConverter.New(SpineVersion.V38);
|
||||
//var root = cvt.ReadJson(@"D:\ACGN\G\GirlsCreation\standing_spine\st4020069\st4020069.json");
|
||||
//cvt.WriteBinary(root, @"D:\ACGN\G\GirlsCreation\standing_spine\st4020069\st4020069.skel");
|
||||
//var sp = SpineObject.New(SpineVersion.V38, @"D:\ACGN\G\GirlsCreation\standing_spine\st4020069\st4020069.skel");
|
||||
//_Test();
|
||||
#endif
|
||||
}
|
||||
|
||||
//private void spinePreviewer_KeyDown(object sender, KeyEventArgs e)
|
||||
//{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace SpineViewer
|
||||
{
|
||||
partial class PetForm
|
||||
partial class WallpaperForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
@@ -30,18 +30,19 @@
|
||||
{
|
||||
SuspendLayout();
|
||||
//
|
||||
// PetForm
|
||||
// WallpaperForm
|
||||
//
|
||||
AutoScaleMode = AutoScaleMode.None;
|
||||
ClientSize = new Size(490, 456);
|
||||
ClientSize = new Size(512, 512);
|
||||
ControlBox = false;
|
||||
FormBorderStyle = FormBorderStyle.None;
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "PetForm";
|
||||
Name = "WallpaperForm";
|
||||
ShowIcon = false;
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.Manual;
|
||||
Text = "PetForm";
|
||||
WindowState = FormWindowState.Minimized;
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
73
SpineViewer/Forms/WallpaperForm.cs
Normal file
73
SpineViewer/Forms/WallpaperForm.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using SpineViewer.Natives;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace SpineViewer
|
||||
{
|
||||
[ToolboxItem(true)]
|
||||
[Designer(typeof(ComponentDesigner), typeof(IDesigner))]
|
||||
[DesignTimeVisible(true)]
|
||||
public partial class WallpaperForm: Form
|
||||
{
|
||||
public WallpaperForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override CreateParams CreateParams
|
||||
{
|
||||
get
|
||||
{
|
||||
var cp = base.CreateParams;
|
||||
cp.ExStyle |= Win32.WS_EX_TOOLWINDOW | Win32.WS_EX_LAYERED;
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLoad(EventArgs e)
|
||||
{
|
||||
base.OnLoad(e);
|
||||
|
||||
// 设置成嵌入桌面
|
||||
var progman = Win32.FindWindow("Progman", null);
|
||||
if (progman != IntPtr.Zero)
|
||||
{
|
||||
// 确保 WorkerW 被创建
|
||||
Win32.SendMessageTimeout(progman, Win32.WM_SPAWN_WORKER, IntPtr.Zero, IntPtr.Zero, Win32.SMTO_NORMAL, 1000, out _);
|
||||
var workerW = Win32.GetWorkerW();
|
||||
if (workerW != IntPtr.Zero)
|
||||
{
|
||||
Win32.SetLayeredWindowAttributes(Handle, 0, 255, Win32.LWA_ALPHA);
|
||||
Win32.SetParent(Handle, workerW); // 嵌入之前必须保证有 WS_EX_LAYERED 标志
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
[Browsable(false)]
|
||||
public byte LayeredWindowAlpha
|
||||
{
|
||||
get
|
||||
{
|
||||
uint crKey = 0;
|
||||
byte bAlpha = 255;
|
||||
uint dwFlags = Win32.LWA_ALPHA;
|
||||
Win32.GetLayeredWindowAttributes(Handle, ref crKey, ref bAlpha, ref dwFlags);
|
||||
return bAlpha;
|
||||
}
|
||||
set
|
||||
{
|
||||
Win32.SetLayeredWindowAttributes(Handle, 0, value, Win32.LWA_ALPHA);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
120
SpineViewer/Forms/WallpaperForm.resx
Normal file
120
SpineViewer/Forms/WallpaperForm.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?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>
|
||||
</root>
|
||||
@@ -17,6 +17,8 @@ namespace SpineViewer.Natives
|
||||
public const int GWL_STYLE = -16;
|
||||
public const int WS_SIZEBOX = 0x40000;
|
||||
public const int WS_BORDER = 0x800000;
|
||||
public const int WS_VISIBLE = 0x10000000;
|
||||
public const int WS_CHILD = 0x40000000;
|
||||
public const int WS_POPUP = unchecked((int)0x80000000);
|
||||
|
||||
public const int GWL_EXSTYLE = -20;
|
||||
@@ -25,8 +27,10 @@ namespace SpineViewer.Natives
|
||||
public const int WS_EX_TOOLWINDOW = 0x80;
|
||||
public const int WS_EX_WINDOWEDGE = 0x100;
|
||||
public const int WS_EX_CLIENTEDGE = 0x200;
|
||||
public const int WS_EX_APPWINDOW = 0x40000;
|
||||
public const int WS_EX_LAYERED = 0x80000;
|
||||
public const int WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE;
|
||||
public const int WS_EX_NOACTIVATE = 0x8000000;
|
||||
|
||||
public const uint LWA_COLORKEY = 0x1;
|
||||
public const uint LWA_ALPHA = 0x2;
|
||||
@@ -44,7 +48,6 @@ namespace SpineViewer.Natives
|
||||
public const uint SWP_NOMOVE = 0x0002;
|
||||
public const uint SWP_NOZORDER = 0x0004;
|
||||
public const uint SWP_FRAMECHANGED = 0x0020;
|
||||
public const uint SWP_REFRESHLONG = SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED;
|
||||
|
||||
public const int WM_SPAWN_WORKER = 0x052C; // 一个未公开的神秘消息
|
||||
|
||||
@@ -170,7 +173,7 @@ namespace SpineViewer.Natives
|
||||
if (progman == nint.Zero)
|
||||
return nint.Zero;
|
||||
nint hWnd = FindWindowEx(progman, 0, "WorkerW", null);
|
||||
Debug.WriteLine($"{hWnd:x8}");
|
||||
Debug.WriteLine($"HWND(Progman.WorkerW): {hWnd:x8}");
|
||||
return hWnd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,45 @@ using System.Globalization;
|
||||
namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
{
|
||||
[SpineImplementation(SpineVersion.V38)]
|
||||
class SkeletonConverter38 : SpineViewer.Spine.SkeletonConverter
|
||||
public class SkeletonConverter38 : Spine.SkeletonConverter
|
||||
{
|
||||
private static readonly Dictionary<TransformMode, string> TransformModeJsonValue = new()
|
||||
{
|
||||
[TransformMode.Normal] = "normal",
|
||||
[TransformMode.OnlyTranslation] = "onlyTranslation",
|
||||
[TransformMode.NoRotationOrReflection] = "noRotationOrReflection",
|
||||
[TransformMode.NoScale] = "noScale",
|
||||
[TransformMode.NoScaleOrReflection] = "noScaleOrReflection",
|
||||
};
|
||||
|
||||
private static readonly Dictionary<BlendMode, string> BlendModeJsonValue = new()
|
||||
{
|
||||
[BlendMode.Normal] = "normal",
|
||||
[BlendMode.Additive] = "additive",
|
||||
[BlendMode.Multiply] = "multiply",
|
||||
[BlendMode.Screen] = "screen",
|
||||
};
|
||||
|
||||
private static readonly Dictionary<PositionMode, string> PositionModeJsonValue = new()
|
||||
{
|
||||
[PositionMode.Fixed] = "fixed",
|
||||
[PositionMode.Percent] = "percent",
|
||||
};
|
||||
|
||||
private static readonly Dictionary<SpacingMode, string> SpacingModeJsonValue = new()
|
||||
{
|
||||
[SpacingMode.Length] = "length",
|
||||
[SpacingMode.Fixed] = "fixed",
|
||||
[SpacingMode.Percent] = "percent",
|
||||
};
|
||||
|
||||
private static readonly Dictionary<RotateMode, string> RotateModeJsonValue = new()
|
||||
{
|
||||
[RotateMode.Tangent] = "tangent",
|
||||
[RotateMode.Chain] = "chain",
|
||||
[RotateMode.ChainScale] = "chainScale",
|
||||
};
|
||||
|
||||
private BinaryReader reader = null;
|
||||
private JsonObject root = null;
|
||||
private bool nonessential = false;
|
||||
@@ -44,6 +81,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
|
||||
idx2event.Clear();
|
||||
|
||||
// 清理临时属性
|
||||
foreach (var (_, data) in root["events"].AsObject()) data.AsObject().Remove("__name__");
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -90,7 +130,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
data["shearX"] = reader.ReadFloat();
|
||||
data["shearY"] = reader.ReadFloat();
|
||||
data["length"] = reader.ReadFloat();
|
||||
data["transform"] = SkeletonBinary.TransformModeValues[reader.ReadVarInt()].ToString();
|
||||
data["transform"] = TransformModeJsonValue[SkeletonBinary.TransformModeValues[reader.ReadVarInt()]];
|
||||
data["skin"] = reader.ReadBoolean();
|
||||
if (nonessential) reader.ReadInt();
|
||||
bones.Add(data);
|
||||
@@ -111,7 +151,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
int dark = reader.ReadInt();
|
||||
if (dark != -1) data["dark"] = dark.ToString("x6"); // 0x00rrggbb -> rrggbb
|
||||
data["attachment"] = reader.ReadStringRef();
|
||||
data["blend"] = ((BlendMode)reader.ReadVarInt()).ToString();
|
||||
data["blend"] = BlendModeJsonValue[((BlendMode)reader.ReadVarInt())];
|
||||
slots.Add(data);
|
||||
}
|
||||
root["slots"] = slots;
|
||||
@@ -181,9 +221,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
data["skin"] = reader.ReadBoolean();
|
||||
data["bones"] = ReadNames(bones);
|
||||
data["target"] = (string)bones[reader.ReadVarInt()]["name"];
|
||||
data["positionMode"] = ((PositionMode)reader.ReadVarInt()).ToString();
|
||||
data["spacingMode"] = ((SpacingMode)reader.ReadVarInt()).ToString();
|
||||
data["rotateMode"] = ((RotateMode)reader.ReadVarInt()).ToString();
|
||||
data["positionMode"] = PositionModeJsonValue[((PositionMode)reader.ReadVarInt())];
|
||||
data["spacingMode"] = SpacingModeJsonValue[((SpacingMode)reader.ReadVarInt())];
|
||||
data["rotateMode"] = RotateModeJsonValue[((RotateMode)reader.ReadVarInt())];
|
||||
data["rotation"] = reader.ReadFloat();
|
||||
data["position"] = reader.ReadFloat();
|
||||
data["spacing"] = reader.ReadFloat();
|
||||
@@ -283,7 +323,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
if (path is not null) attachment["path"] = path;
|
||||
attachment["color"] = reader.ReadInt().ToString("x8");
|
||||
vertexCount = reader.ReadVarInt();
|
||||
attachment["uvs"] = ReadFloatArray(vertexCount << 1); // vertexCount = uvs.Length
|
||||
attachment["uvs"] = ReadFloatArray(vertexCount << 1); // vertexCount = uvs.Length >> 1
|
||||
attachment["triangles"] = ReadShortArray();
|
||||
attachment["vertices"] = ReadVertices(vertexCount);
|
||||
attachment["hull"] = reader.ReadVarInt();
|
||||
@@ -348,7 +388,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
JsonObject data = [];
|
||||
var name = reader.ReadStringRef();
|
||||
events[name] = data;
|
||||
data["name"] = name; // 额外增加的, 方便后面查找
|
||||
data["__name__"] = name; // 数据里是不应该有这个字段的, 但是为了 ReadEventTimelines 里能够提供 name 字段, 临时增加了一下
|
||||
data["int"] = reader.ReadVarInt(false);
|
||||
data["float"] = reader.ReadFloat();
|
||||
data["string"] = reader.ReadString();
|
||||
@@ -376,7 +416,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
if (ReadTransformTimelines() is JsonObject transform) data["transform"] = transform;
|
||||
if (ReadPathTimelines() is JsonObject path) data["path"] = path;
|
||||
if (ReadDeformTimelines() is JsonObject deform) data["deform"] = deform;
|
||||
if (ReadDrawOrderTimelines() is JsonArray draworder) data["drawOrder"] = draworder;
|
||||
if (ReadDrawOrderTimelines() is JsonArray draworder) data["draworder"] = draworder;
|
||||
if (ReadEventTimelines() is JsonArray events) data["events"] = events;
|
||||
}
|
||||
root["animations"] = animations;
|
||||
@@ -592,7 +632,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
for (int timelineCount = reader.ReadVarInt(); timelineCount > 0; timelineCount--)
|
||||
{
|
||||
JsonArray frames = [];
|
||||
var type = reader.ReadByte();
|
||||
var type = reader.ReadSByte();
|
||||
var frameCount = reader.ReadVarInt();
|
||||
switch (type)
|
||||
{
|
||||
@@ -600,11 +640,13 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
timeline["position"] = frames;
|
||||
while (frameCount-- > 0)
|
||||
{
|
||||
frames.Add(new JsonObject()
|
||||
var o = new JsonObject()
|
||||
{
|
||||
["time"] = reader.ReadFloat(),
|
||||
["position"] = reader.ReadFloat(),
|
||||
});
|
||||
};
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
frames.Add(o);
|
||||
}
|
||||
break;
|
||||
case SkeletonBinary.PATH_SPACING:
|
||||
@@ -671,8 +713,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
var end = reader.ReadVarInt();
|
||||
if (end > 0)
|
||||
{
|
||||
var start = reader.ReadVarInt();
|
||||
o["offset"] = start;
|
||||
o["offset"] = reader.ReadVarInt();
|
||||
o["vertices"] = ReadFloatArray(end);
|
||||
}
|
||||
if (frameCount > 0) ReadCurve(o);
|
||||
@@ -719,14 +760,14 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
JsonObject data = [];
|
||||
data["time"] = reader.ReadFloat();
|
||||
JsonObject eventData = idx2event[reader.ReadVarInt()].AsObject();
|
||||
data["name"] = (string)eventData["name"];
|
||||
data["name"] = (string)eventData["__name__"];
|
||||
data["int"] = reader.ReadVarInt();
|
||||
data["float"] = reader.ReadFloat();
|
||||
data["string"] = reader.ReadBoolean() ? reader.ReadString() : (string)eventData["string"];
|
||||
if (reader.ReadBoolean()) data["string"] = reader.ReadString();
|
||||
if (eventData.ContainsKey("audio"))
|
||||
{
|
||||
data["volume"] = (string)eventData["volume"];
|
||||
data["balance"] = (string)eventData["balance"];
|
||||
data["volume"] = reader.ReadFloat();
|
||||
data["balance"] = reader.ReadFloat();
|
||||
}
|
||||
eventTimelines.Add(data);
|
||||
}
|
||||
@@ -785,10 +826,6 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
switch (type)
|
||||
{
|
||||
case SkeletonBinary.CURVE_LINEAR:
|
||||
obj["curve"] = 1 / 3f;
|
||||
obj["c2"] = 1 / 3f;
|
||||
obj["c3"] = 2 / 3f;
|
||||
obj["c4"] = 2 / 3f;
|
||||
break;
|
||||
case SkeletonBinary.CURVE_STEPPED:
|
||||
obj["curve"] = "stepped";
|
||||
@@ -810,6 +847,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
private readonly Dictionary<string, int> ik2idx = [];
|
||||
private readonly Dictionary<string, int> transform2idx = [];
|
||||
private readonly Dictionary<string, int> path2idx = [];
|
||||
private readonly Dictionary<string, int> skin2idx = [];
|
||||
private readonly Dictionary<string, int> event2idx = [];
|
||||
|
||||
public override void WriteBinary(JsonObject root, string binPath, bool nonessential = false)
|
||||
@@ -818,7 +856,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
this.root = root;
|
||||
|
||||
using var outputBody = new MemoryStream(); // 先把主体写入内存缓冲区
|
||||
writer = new(outputBody);
|
||||
BinaryWriter tmpWriter = writer = new (outputBody);
|
||||
|
||||
WriteBones();
|
||||
WriteSlots();
|
||||
@@ -829,15 +867,27 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
WriteEvents();
|
||||
WriteAnimations();
|
||||
|
||||
//using var output = File.Create(binPath); // 将数据写入文件
|
||||
//writer = new(output);
|
||||
using var output = File.Create(binPath); // 将数据写入文件
|
||||
writer = new(output);
|
||||
|
||||
// 把字符串表保留过来
|
||||
writer.StringTable.AddRange(tmpWriter.StringTable);
|
||||
|
||||
WriteSkeleton();
|
||||
WriteStrings();
|
||||
//output.Write(outputBody.GetBuffer());
|
||||
outputBody.Seek(0, SeekOrigin.Begin);
|
||||
outputBody.CopyTo(output);
|
||||
|
||||
writer = null;
|
||||
this.root = null;
|
||||
|
||||
bone2idx.Clear();
|
||||
slot2idx.Clear();
|
||||
ik2idx.Clear();
|
||||
transform2idx.Clear();
|
||||
path2idx.Clear();
|
||||
skin2idx.Clear();
|
||||
event2idx.Clear();
|
||||
}
|
||||
|
||||
private void WriteSkeleton()
|
||||
@@ -847,16 +897,16 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
var version = (string)skeleton["spine"];
|
||||
if (version == "3.8.75") version = "3.8.76"; // replace 3.8.75 to another version to avoid detection in official runtime
|
||||
writer.WriteString(version);
|
||||
if (skeleton.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||
if (skeleton.TryGetPropertyValue("y", out var y)) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||
if (skeleton.TryGetPropertyValue("width", out var width)) writer.WriteFloat((float)width); else writer.WriteFloat(0);
|
||||
if (skeleton.TryGetPropertyValue("height", out var height)) writer.WriteFloat((float)height); else writer.WriteFloat(0);
|
||||
if (skeleton["x"] is JsonValue x) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||
if (skeleton["y"] is JsonValue y) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||
if (skeleton["width"] is JsonValue width) writer.WriteFloat((float)width); else writer.WriteFloat(0);
|
||||
if (skeleton["height"] is JsonValue height) writer.WriteFloat((float)height); else writer.WriteFloat(0);
|
||||
writer.WriteBoolean(nonessential);
|
||||
if (nonessential)
|
||||
{
|
||||
if (skeleton.TryGetPropertyValue("fps", out var fps)) writer.WriteFloat((float)fps); else writer.WriteFloat(30);
|
||||
if (skeleton.TryGetPropertyValue("images", out var images)) writer.WriteString((string)images); else writer.WriteString(null);
|
||||
if (skeleton.TryGetPropertyValue("audio", out var audio)) writer.WriteString((string)audio); else writer.WriteString(null);
|
||||
if (skeleton["fps"] is JsonValue fps) writer.WriteFloat((float)fps); else writer.WriteFloat(30);
|
||||
if (skeleton["images"] is JsonValue images) writer.WriteString((string)images); else writer.WriteString(null);
|
||||
if (skeleton["audio"] is JsonValue audio) writer.WriteString((string)audio); else writer.WriteString(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,16 +932,16 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
var name = (string)data["name"];
|
||||
writer.WriteString(name);
|
||||
if (i > 0) writer.WriteVarInt(bone2idx[(string)data["parent"]]);
|
||||
if (data.TryGetPropertyValue("rotation", out var rotation)) writer.WriteFloat((float)rotation); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("y", out var y)) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("scaleX", out var scaleX)) writer.WriteFloat((float)scaleX); else writer.WriteFloat(1);
|
||||
if (data.TryGetPropertyValue("scaleY", out var scaleY)) writer.WriteFloat((float)scaleY); else writer.WriteFloat(1);
|
||||
if (data.TryGetPropertyValue("shearX", out var shearX)) writer.WriteFloat((float)shearX); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("shearY", out var shearY)) writer.WriteFloat((float)shearY); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("length", out var length)) writer.WriteFloat((float)length); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("transform", out var transform)) writer.WriteVarInt((int)Enum.Parse<TransformMode>((string)transform, true)); else writer.WriteVarInt((int)TransformMode.Normal);
|
||||
if (data.TryGetPropertyValue("skin", out var skin)) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false);
|
||||
if (data["rotation"] is JsonValue rotation) writer.WriteFloat((float)rotation); else writer.WriteFloat(0);
|
||||
if (data["x"] is JsonValue x) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||
if (data["y"] is JsonValue y) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||
if (data["scaleX"] is JsonValue scaleX) writer.WriteFloat((float)scaleX); else writer.WriteFloat(1);
|
||||
if (data["scaleY"] is JsonValue scaleY) writer.WriteFloat((float)scaleY); else writer.WriteFloat(1);
|
||||
if (data["shearX"] is JsonValue shearX) writer.WriteFloat((float)shearX); else writer.WriteFloat(0);
|
||||
if (data["shearY"] is JsonValue shearY) writer.WriteFloat((float)shearY); else writer.WriteFloat(0);
|
||||
if (data["length"] is JsonValue length) writer.WriteFloat((float)length); else writer.WriteFloat(0);
|
||||
if (data["transform"] is JsonValue transform) writer.WriteVarInt(Array.IndexOf(SkeletonBinary.TransformModeValues, Enum.Parse<TransformMode>((string)transform, true))); else writer.WriteVarInt(0);
|
||||
if (data["skin"] is JsonValue skin) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false);
|
||||
if (nonessential) writer.WriteInt(0);
|
||||
bone2idx[name] = i;
|
||||
}
|
||||
@@ -912,10 +962,10 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
var name = (string)data["name"];
|
||||
writer.WriteString(name);
|
||||
writer.WriteVarInt(bone2idx[(string)data["bone"]]);
|
||||
if (data.TryGetPropertyValue("color", out var color)) writer.WriteInt(int.Parse((string)color, NumberStyles.HexNumber)); else writer.WriteInt(0);
|
||||
if (data.TryGetPropertyValue("dark", out var dark)) writer.WriteInt(int.Parse((string)dark, NumberStyles.HexNumber)); else writer.WriteInt(-1);
|
||||
if (data.TryGetPropertyValue("attachment", out var attachment)) writer.WriteStringRef((string)attachment); else writer.WriteStringRef(null);
|
||||
if (data.TryGetPropertyValue("blend", out var blend)) writer.WriteVarInt((int)Enum.Parse<BlendMode>((string)blend, true)); else writer.WriteVarInt((int)BlendMode.Normal);
|
||||
if (data["color"]is JsonValue color) writer.WriteInt(int.Parse((string)color, NumberStyles.HexNumber)); else writer.WriteInt(-1); // 默认值是全 255
|
||||
if (data["dark"] is JsonValue dark) writer.WriteInt(int.Parse((string)dark, NumberStyles.HexNumber)); else writer.WriteInt(-1);
|
||||
if (data["attachment"] is JsonValue attachment) writer.WriteStringRef((string)attachment); else writer.WriteStringRef(null);
|
||||
if (data["blend"] is JsonValue blend) writer.WriteVarInt((int)Enum.Parse<BlendMode>((string)blend, true)); else writer.WriteVarInt((int)BlendMode.Normal);
|
||||
slot2idx[name] = i;
|
||||
}
|
||||
}
|
||||
@@ -934,16 +984,16 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
JsonObject data = ik[i].AsObject();
|
||||
var name = (string)data["name"];
|
||||
writer.WriteString(name);
|
||||
if (data.TryGetPropertyValue("order", out var order)) writer.WriteVarInt((int)order); else writer.WriteVarInt(0);
|
||||
if (data.TryGetPropertyValue("skin", out var skin)) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false);
|
||||
if (data.TryGetPropertyValue("bones", out var bones)) WriteNames(bone2idx, bones.AsArray()); else writer.WriteVarInt(0);
|
||||
if (data["order"] is JsonValue order) writer.WriteVarInt((int)order); else writer.WriteVarInt(0);
|
||||
if (data["skin"] is JsonValue skin) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false);
|
||||
if (data["bones"] is JsonArray bones) WriteNames(bone2idx, bones); else writer.WriteVarInt(0);
|
||||
writer.WriteVarInt(bone2idx[(string)data["target"]]);
|
||||
if (data.TryGetPropertyValue("mix", out var mix)) writer.WriteFloat((float)mix); else writer.WriteFloat(1);
|
||||
if (data.TryGetPropertyValue("softness", out var softness)) writer.WriteFloat((float)softness); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("bendPositive", out var bendPositive)) writer.WriteSByte((sbyte)((bool)bendPositive ? 1 : -1)); else writer.WriteSByte(1);
|
||||
if (data.TryGetPropertyValue("compress", out var compress)) writer.WriteBoolean((bool)compress); else writer.WriteBoolean(false);
|
||||
if (data.TryGetPropertyValue("stretch", out var stretch)) writer.WriteBoolean((bool)stretch); else writer.WriteBoolean(false);
|
||||
if (data.TryGetPropertyValue("uniform", out var uniform)) writer.WriteBoolean((bool)uniform); else writer.WriteBoolean(false);
|
||||
if (data["mix"] is JsonValue mix) writer.WriteFloat((float)mix); else writer.WriteFloat(1);
|
||||
if (data["softness"] is JsonValue softness) writer.WriteFloat((float)softness); else writer.WriteFloat(0);
|
||||
if (data["bendPositive"] is JsonValue bendPositive) writer.WriteSByte((sbyte)((bool)bendPositive ? 1 : -1)); else writer.WriteSByte(1);
|
||||
if (data["compress"] is JsonValue compress) writer.WriteBoolean((bool)compress); else writer.WriteBoolean(false);
|
||||
if (data["stretch"] is JsonValue stretch) writer.WriteBoolean((bool)stretch); else writer.WriteBoolean(false);
|
||||
if (data["uniform"] is JsonValue uniform) writer.WriteBoolean((bool)uniform); else writer.WriteBoolean(false);
|
||||
ik2idx[name] = i;
|
||||
}
|
||||
}
|
||||
@@ -962,22 +1012,22 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
JsonObject data = transform[i].AsObject();
|
||||
var name = (string)data["name"];
|
||||
writer.WriteString(name);
|
||||
if (data.TryGetPropertyValue("order", out var order)) writer.WriteVarInt((int)order); else writer.WriteVarInt(0);
|
||||
if (data.TryGetPropertyValue("skin", out var skin)) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false);
|
||||
if (data.TryGetPropertyValue("bones", out var bones)) WriteNames(bone2idx, bones.AsArray()); else writer.WriteVarInt(0);
|
||||
if (data["order"] is JsonValue order) writer.WriteVarInt((int)order); else writer.WriteVarInt(0);
|
||||
if (data["skin"] is JsonValue skin) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false);
|
||||
if (data["bones"] is JsonArray bones) WriteNames(bone2idx, bones); else writer.WriteVarInt(0);
|
||||
writer.WriteVarInt(bone2idx[(string)data["target"]]);
|
||||
if (data.TryGetPropertyValue("local", out var local)) writer.WriteBoolean((bool)local); else writer.WriteBoolean(false);
|
||||
if (data.TryGetPropertyValue("relative", out var relative)) writer.WriteBoolean((bool)relative); else writer.WriteBoolean(false);
|
||||
if (data.TryGetPropertyValue("rotation", out var rotation)) writer.WriteFloat((float)rotation); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("y", out var y)) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("scaleX", out var scaleX)) writer.WriteFloat((float)scaleX); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("scaleY", out var scaleY)) writer.WriteFloat((float)scaleY); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("shearY", out var shearY)) writer.WriteFloat((float)shearY); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("rotateMix", out var rotateMix)) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1);
|
||||
if (data.TryGetPropertyValue("translateMix", out var translateMix)) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1);
|
||||
if (data.TryGetPropertyValue("scaleMix", out var scaleMix)) writer.WriteFloat((float)scaleMix); else writer.WriteFloat(1);
|
||||
if (data.TryGetPropertyValue("shearMix", out var shearMix)) writer.WriteFloat((float)shearMix); else writer.WriteFloat(1);
|
||||
if (data["local"] is JsonValue local) writer.WriteBoolean((bool)local); else writer.WriteBoolean(false);
|
||||
if (data["relative"] is JsonValue relative) writer.WriteBoolean((bool)relative); else writer.WriteBoolean(false);
|
||||
if (data["rotation"] is JsonValue rotation) writer.WriteFloat((float)rotation); else writer.WriteFloat(0);
|
||||
if (data["x"] is JsonValue x) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||
if (data["y"] is JsonValue y) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||
if (data["scaleX"] is JsonValue scaleX) writer.WriteFloat((float)scaleX); else writer.WriteFloat(0);
|
||||
if (data["scaleY"] is JsonValue scaleY) writer.WriteFloat((float)scaleY); else writer.WriteFloat(0);
|
||||
if (data["shearY"] is JsonValue shearY) writer.WriteFloat((float)shearY); else writer.WriteFloat(0);
|
||||
if (data["rotateMix"] is JsonValue rotateMix) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1);
|
||||
if (data["translateMix"] is JsonValue translateMix) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1);
|
||||
if (data["scaleMix"] is JsonValue scaleMix) writer.WriteFloat((float)scaleMix); else writer.WriteFloat(1);
|
||||
if (data["shearMix"] is JsonValue shearMix) writer.WriteFloat((float)shearMix); else writer.WriteFloat(1);
|
||||
transform2idx[name] = i;
|
||||
}
|
||||
}
|
||||
@@ -996,18 +1046,18 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
JsonObject data = path[i].AsObject();
|
||||
var name = (string)data["name"];
|
||||
writer.WriteString(name);
|
||||
if (data.TryGetPropertyValue("order", out var order)) writer.WriteVarInt((int)order); else writer.WriteVarInt(0);
|
||||
if (data.TryGetPropertyValue("skin", out var skin)) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false);
|
||||
if (data.TryGetPropertyValue("bones", out var bones)) WriteNames(bone2idx, bones.AsArray()); else writer.WriteVarInt(0);
|
||||
if (data["order"] is JsonValue order) writer.WriteVarInt((int)order); else writer.WriteVarInt(0);
|
||||
if (data["skin"] is JsonValue skin) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false);
|
||||
if (data["bones"] is JsonArray bones) WriteNames(bone2idx, bones); else writer.WriteVarInt(0);
|
||||
writer.WriteVarInt(bone2idx[(string)data["target"]]);
|
||||
if (data.TryGetPropertyValue("positionMode", out var positionMode)) writer.WriteVarInt((int)Enum.Parse<PositionMode>((string)positionMode, true)); else writer.WriteVarInt((int)PositionMode.Percent);
|
||||
if (data.TryGetPropertyValue("spacingMode", out var spacingMode)) writer.WriteVarInt((int)Enum.Parse<SpacingMode>((string)spacingMode, true)); else writer.WriteVarInt((int)SpacingMode.Length);
|
||||
if (data.TryGetPropertyValue("rotateMode", out var rotateMode)) writer.WriteVarInt((int)Enum.Parse<RotateMode>((string)rotateMode, true)); else writer.WriteVarInt((int)RotateMode.Tangent);
|
||||
if (data.TryGetPropertyValue("rotation", out var rotation)) writer.WriteFloat((float)rotation); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("position", out var position)) writer.WriteFloat((float)position); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("spacing", out var spacing)) writer.WriteFloat((float)spacing); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("rotateMix", out var rotateMix)) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1);
|
||||
if (data.TryGetPropertyValue("translateMix", out var translateMix)) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1);
|
||||
if (data["positionMode"] is JsonValue positionMode) writer.WriteVarInt((int)Enum.Parse<PositionMode>((string)positionMode, true)); else writer.WriteVarInt((int)PositionMode.Percent);
|
||||
if (data["spacingMode"] is JsonValue spacingMode) writer.WriteVarInt((int)Enum.Parse<SpacingMode>((string)spacingMode, true)); else writer.WriteVarInt((int)SpacingMode.Length);
|
||||
if (data["rotateMode"] is JsonValue rotateMode) writer.WriteVarInt((int)Enum.Parse<RotateMode>((string)rotateMode, true)); else writer.WriteVarInt((int)RotateMode.Tangent);
|
||||
if (data["rotation"] is JsonValue rotation) writer.WriteFloat((float)rotation); else writer.WriteFloat(0);
|
||||
if (data["position"] is JsonValue position) writer.WriteFloat((float)position); else writer.WriteFloat(0);
|
||||
if (data["spacing"] is JsonValue spacing) writer.WriteFloat((float)spacing); else writer.WriteFloat(0);
|
||||
if (data["rotateMix"] is JsonValue rotateMix) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1);
|
||||
if (data["translateMix"] is JsonValue translateMix) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1);
|
||||
path2idx[name] = i;
|
||||
}
|
||||
}
|
||||
@@ -1018,6 +1068,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
{
|
||||
writer.WriteVarInt(0); // default 的 slotCount
|
||||
writer.WriteVarInt(0); // 其他皮肤数量
|
||||
skin2idx["default"] = skin2idx.Count;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1029,6 +1080,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
{
|
||||
hasDefault = true;
|
||||
WriteSkin(skin, true);
|
||||
skin2idx["default"] = skin2idx.Count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1045,8 +1097,12 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
writer.WriteVarInt(skinCount);
|
||||
foreach (JsonObject skin in skins)
|
||||
{
|
||||
if ((string)skin["name"] != "default")
|
||||
var name = (string)skin["name"];
|
||||
if (name != "default")
|
||||
{
|
||||
WriteSkin(skin);
|
||||
skin2idx[name] = skin2idx.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1056,17 +1112,17 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
if (isDefault)
|
||||
{
|
||||
// 这里固定有一个给 default 的 count 值, 算是占位符, 如果是 0 则表示没有 default 的 skin
|
||||
if (skin.TryGetPropertyValue("attachments", out var attachments)) skinAttachments = attachments.AsObject();
|
||||
if (skin["attachments"] is JsonObject attachments) skinAttachments = attachments;
|
||||
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();
|
||||
if (skin["bones"] is JsonArray bones) WriteNames(bone2idx, bones); else writer.WriteVarInt(0);
|
||||
if (skin["ik"] is JsonArray ik) WriteNames(ik2idx, ik); else writer.WriteVarInt(0);
|
||||
if (skin["transform"] is JsonArray transform) WriteNames(transform2idx, transform); else writer.WriteVarInt(0);
|
||||
if (skin["path"] is JsonArray path) WriteNames(path2idx, path); else writer.WriteVarInt(0);
|
||||
if (skin["attachments"] is JsonObject attachments) skinAttachments = attachments;
|
||||
writer.WriteVarInt(skinAttachments?.Count ?? 0);
|
||||
}
|
||||
|
||||
@@ -1093,76 +1149,76 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
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);
|
||||
if (attachment["name"] is JsonValue _name) name = (string)_name;
|
||||
if (attachment["type"] is JsonValue _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);
|
||||
if (attachment["path"] is JsonValue path1) writer.WriteStringRef((string)path1); else writer.WriteStringRef(null);
|
||||
if (attachment["rotation"] is JsonValue rotation1) writer.WriteFloat((float)rotation1); else writer.WriteFloat(0);
|
||||
if (attachment["x"] is JsonValue x1) writer.WriteFloat((float)x1); else writer.WriteFloat(0);
|
||||
if (attachment["y"] is JsonValue y1) writer.WriteFloat((float)y1); else writer.WriteFloat(0);
|
||||
if (attachment["scaleX"] is JsonValue scaleX) writer.WriteFloat((float)scaleX); else writer.WriteFloat(1);
|
||||
if (attachment["scaleY"] is JsonValue scaleY) writer.WriteFloat((float)scaleY); else writer.WriteFloat(1);
|
||||
if (attachment["width"] is JsonValue width) writer.WriteFloat((float)width); else writer.WriteFloat(32);
|
||||
if (attachment["height"] is JsonValue height) writer.WriteFloat((float)height); else writer.WriteFloat(32);
|
||||
if (attachment["color"] is JsonValue color1) writer.WriteInt(int.Parse((string)color1, NumberStyles.HexNumber)); else writer.WriteInt(-1);
|
||||
break;
|
||||
case AttachmentType.Boundingbox:
|
||||
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount1)) vertexCount = (int)_vertexCount1; else vertexCount = 0;
|
||||
if (attachment["vertexCount"] is JsonValue _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;
|
||||
if (attachment["path"] is JsonValue path2) writer.WriteStringRef((string)path2); else writer.WriteStringRef(null);
|
||||
if (attachment["color"] is JsonValue color2) writer.WriteInt(int.Parse((string)color2, NumberStyles.HexNumber)); else writer.WriteInt(-1);
|
||||
vertexCount = attachment["uvs"].AsArray().Count >> 1;
|
||||
writer.WriteVarInt(vertexCount);
|
||||
WriteFloatArray(attachment["uvs"].AsArray(), vertexCount << 1); // vertexCount = uvs.Length
|
||||
WriteFloatArray(attachment["uvs"].AsArray(), vertexCount << 1); // vertexCount = uvs.Length >> 1
|
||||
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 (attachment["hull"] is JsonValue 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);
|
||||
if (attachment["edges"] is JsonArray edges) WriteShortArray(edges); else writer.WriteVarInt(0);
|
||||
if (attachment["width"] is JsonValue _width) writer.WriteFloat((float)_width); else writer.WriteFloat(0);
|
||||
if (attachment["height"] is JsonValue _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 (attachment["path"] is JsonValue path3) writer.WriteStringRef((string)path3); else writer.WriteStringRef(null);
|
||||
if (attachment["color"] is JsonValue color3) writer.WriteInt(int.Parse((string)color3, NumberStyles.HexNumber)); else writer.WriteInt(-1);
|
||||
if (attachment["skin"] is JsonValue skin) writer.WriteStringRef((string)skin); else writer.WriteStringRef(null);
|
||||
if (attachment["parent"] is JsonValue parent) writer.WriteStringRef((string)parent); else writer.WriteStringRef(null);
|
||||
if (attachment["deform"] is JsonValue 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);
|
||||
if (attachment["width"] is JsonValue _width) writer.WriteFloat((float)_width); else writer.WriteFloat(0);
|
||||
if (attachment["height"] is JsonValue _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;
|
||||
if (attachment["closed"] is JsonValue closed) writer.WriteBoolean((bool)closed); else writer.WriteBoolean(false);
|
||||
if (attachment["constantSpeed"] is JsonValue constantSpeed) writer.WriteBoolean((bool)constantSpeed); else writer.WriteBoolean(true);
|
||||
if (attachment["vertexCount"] is JsonValue _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 (attachment["rotation"] is JsonValue rotation2) writer.WriteFloat((float)rotation2); else writer.WriteFloat(0);
|
||||
if (attachment["x"] is JsonValue x2) writer.WriteFloat((float)x2); else writer.WriteFloat(0);
|
||||
if (attachment["y"] is JsonValue 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;
|
||||
if (attachment["vertexCount"] is JsonValue _vertexCount4) vertexCount = (int)_vertexCount4; else vertexCount = 0;
|
||||
writer.WriteVarInt(vertexCount);
|
||||
WriteVertices(attachment["vertices"].AsArray(), vertexCount);
|
||||
if (nonessential) writer.WriteInt(0);
|
||||
@@ -1186,19 +1242,23 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
{
|
||||
JsonObject data = _data.AsObject();
|
||||
writer.WriteStringRef(name);
|
||||
if (data.TryGetPropertyValue("int", out var @int)) writer.WriteVarInt((int)@int); else writer.WriteVarInt(0);
|
||||
if (data.TryGetPropertyValue("float", out var @float)) writer.WriteFloat((float)@float); else writer.WriteFloat(0);
|
||||
if (data.TryGetPropertyValue("string", out var @string)) writer.WriteString((string)@string); else writer.WriteString("");
|
||||
if (data.TryGetPropertyValue("audio", out var _audio))
|
||||
if (data["int"] is JsonValue @int) writer.WriteVarInt((int)@int); else writer.WriteVarInt(0);
|
||||
if (data["float"] is JsonValue @float) writer.WriteFloat((float)@float); else writer.WriteFloat(0);
|
||||
if (data["string"] is JsonValue @string) writer.WriteString((string)@string); else writer.WriteString("");
|
||||
if (data["audio"] is JsonValue _audio)
|
||||
{
|
||||
var audio = (string)_audio;
|
||||
writer.WriteString(audio);
|
||||
if (audio is not null)
|
||||
{
|
||||
if (data.TryGetPropertyValue("volume", out var volume)) writer.WriteFloat((float)volume); else writer.WriteFloat(1);
|
||||
if (data.TryGetPropertyValue("balance", out var balance)) writer.WriteFloat((float)balance); else writer.WriteFloat(0);
|
||||
if (data["volume"] is JsonValue volume) writer.WriteFloat((float)volume); else writer.WriteFloat(1);
|
||||
if (data["balance"] is JsonValue balance) writer.WriteFloat((float)balance); else writer.WriteFloat(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteString(null);
|
||||
}
|
||||
event2idx[name] = i++;
|
||||
}
|
||||
}
|
||||
@@ -1210,11 +1270,331 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
writer.WriteVarInt(0);
|
||||
return;
|
||||
}
|
||||
JsonArray animations = root["animations"].AsArray();
|
||||
|
||||
JsonObject animations = root["animations"].AsObject();
|
||||
writer.WriteVarInt(animations.Count);
|
||||
for (int i = 0, n = animations.Count; i < n; i++)
|
||||
foreach (var (name, _data) in animations)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
JsonObject data = _data.AsObject();
|
||||
writer.WriteString(name);
|
||||
if (data["slots"] is JsonObject slots) WriteSlotTimelines(slots); else writer.WriteVarInt(0);
|
||||
if (data["bones"] is JsonObject bones) WriteBoneTimelines(bones); else writer.WriteVarInt(0);
|
||||
if (data["ik"] is JsonObject ik) WriteIKTimelines(ik); else writer.WriteVarInt(0);
|
||||
if (data["transform"] is JsonObject transform) WriteTransformTimelines(transform); else writer.WriteVarInt(0);
|
||||
if (data["path"] is JsonObject path) WritePathTimelines(path); else writer.WriteVarInt(0);
|
||||
if (data["deform"] is JsonObject deform) WriteDeformTimelines(deform); else writer.WriteVarInt(0);
|
||||
if (data["drawOrder"] is JsonArray drawOrder) WriteDrawOrderTimelines(drawOrder); else
|
||||
if (data["draworder"] is JsonArray draworder) WriteDrawOrderTimelines(draworder); else writer.WriteVarInt(0);
|
||||
if (data["events"] is JsonArray events) WriteEventTimelines(events); else writer.WriteVarInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteSlotTimelines(JsonObject slotTimelines)
|
||||
{
|
||||
writer.WriteVarInt(slotTimelines.Count);
|
||||
foreach (var (name, _timeline) in slotTimelines)
|
||||
{
|
||||
JsonObject timeline = _timeline.AsObject();
|
||||
writer.WriteVarInt(slot2idx[name]);
|
||||
writer.WriteVarInt(timeline.Count);
|
||||
foreach (var (type, _frames) in timeline)
|
||||
{
|
||||
JsonArray frames = _frames.AsArray();
|
||||
if (type == "attachment")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.SLOT_ATTACHMENT);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
foreach (JsonObject o in frames)
|
||||
{
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
writer.WriteStringRef((string)o["name"]);
|
||||
}
|
||||
}
|
||||
else if (type == "color")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.SLOT_COLOR);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
writer.WriteInt(int.Parse((string)o["color"], NumberStyles.HexNumber));
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
else if (type == "twoColor")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.SLOT_TWO_COLOR);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
writer.WriteInt(int.Parse((string)o["light"], NumberStyles.HexNumber));
|
||||
writer.WriteInt(int.Parse((string)o["dark"], NumberStyles.HexNumber));
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteBoneTimelines(JsonObject boneTimelines)
|
||||
{
|
||||
writer.WriteVarInt(boneTimelines.Count);
|
||||
foreach (var (name, _timeline) in boneTimelines)
|
||||
{
|
||||
JsonObject timeline = _timeline.AsObject();
|
||||
writer.WriteVarInt(bone2idx[name]);
|
||||
writer.WriteVarInt(timeline.Count);
|
||||
foreach (var (type, _frames) in timeline)
|
||||
{
|
||||
JsonArray frames = _frames.AsArray();
|
||||
if (type == "rotate")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.BONE_ROTATE);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["angle"] is JsonValue angle) writer.WriteFloat((float)angle); else writer.WriteFloat(0);
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
else if (type == "translate")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.BONE_TRANSLATE);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["x"] is JsonValue x) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||
if (o["y"] is JsonValue y) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
else if (type == "scale")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.BONE_SCALE);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["x"] is JsonValue x) writer.WriteFloat((float)x); else writer.WriteFloat(1);
|
||||
if (o["y"] is JsonValue y) writer.WriteFloat((float)y); else writer.WriteFloat(1);
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
else if (type == "shear")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.BONE_SHEAR);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["x"] is JsonValue x) writer.WriteFloat((float)x); else writer.WriteFloat(0);
|
||||
if (o["y"] is JsonValue y) writer.WriteFloat((float)y); else writer.WriteFloat(0);
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteIKTimelines(JsonObject ikTimelines)
|
||||
{
|
||||
writer.WriteVarInt(ikTimelines.Count);
|
||||
foreach (var (name, _frames) in ikTimelines)
|
||||
{
|
||||
JsonArray frames = _frames.AsArray();
|
||||
writer.WriteVarInt(ik2idx[name]);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["mix"] is JsonValue mix) writer.WriteFloat((float)mix); else writer.WriteFloat(1);
|
||||
if (o["softness"] is JsonValue softness) writer.WriteFloat((float)softness); else writer.WriteFloat(0);
|
||||
if (o["bendPositive"] is JsonValue bendPositive) writer.WriteSByte((sbyte)((bool)bendPositive ? 1 : -1)); else writer.WriteSByte(1);
|
||||
if (o["compress"] is JsonValue compress) writer.WriteBoolean((bool)compress); else writer.WriteBoolean(false);
|
||||
if (o["stretch"] is JsonValue stretch) writer.WriteBoolean((bool)stretch); else writer.WriteBoolean(false);
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteTransformTimelines(JsonObject transformTimelines)
|
||||
{
|
||||
writer.WriteVarInt(transformTimelines.Count);
|
||||
foreach (var (name, _frames) in transformTimelines)
|
||||
{
|
||||
JsonArray frames = _frames.AsArray();
|
||||
writer.WriteVarInt(transform2idx[name]);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["rotateMix"] is JsonValue rotateMix) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1);
|
||||
if (o["translateMix"] is JsonValue translateMix) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1);
|
||||
if (o["scaleMix"] is JsonValue scaleMix) writer.WriteFloat((float)scaleMix); else writer.WriteFloat(1);
|
||||
if (o["shearMix"] is JsonValue shearMix) writer.WriteFloat((float)shearMix); else writer.WriteFloat(1);
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WritePathTimelines(JsonObject pathTimelines)
|
||||
{
|
||||
writer.WriteVarInt(pathTimelines.Count);
|
||||
foreach (var (name, _timeline) in pathTimelines)
|
||||
{
|
||||
JsonObject timeline = _timeline.AsObject();
|
||||
writer.WriteVarInt(path2idx[name]);
|
||||
writer.WriteVarInt(timeline.Count);
|
||||
foreach (var (type, _frame) in timeline)
|
||||
{
|
||||
JsonArray frames = _frame.AsArray();
|
||||
if (type == "position")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.PATH_POSITION);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["position"] is JsonValue position) writer.WriteFloat((float)position); else writer.WriteFloat(0);
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
else if (type == "spacing")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.PATH_SPACING);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["spacing"] is JsonValue spacing) writer.WriteFloat((float)spacing); else writer.WriteFloat(0);
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
else if (type == "mix")
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.PATH_MIX);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["rotateMix"] is JsonValue rotateMix) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1);
|
||||
if (o["translateMix"] is JsonValue translateMix) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1);
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteDeformTimelines(JsonObject deformTimelines)
|
||||
{
|
||||
writer.WriteVarInt(deformTimelines.Count);
|
||||
foreach (var (skinName, _skinValue) in deformTimelines)
|
||||
{
|
||||
JsonObject skinValue = _skinValue.AsObject();
|
||||
writer.WriteVarInt(skin2idx[skinName]);
|
||||
writer.WriteVarInt(skinValue.Count);
|
||||
foreach (var (slotName, _slotValue) in skinValue)
|
||||
{
|
||||
JsonObject slotValue = _slotValue.AsObject();
|
||||
writer.WriteVarInt(slot2idx[slotName]);
|
||||
writer.WriteVarInt(slotValue.Count);
|
||||
foreach (var (attachmentName, _frames) in slotValue)
|
||||
{
|
||||
JsonArray frames = _frames.AsArray();
|
||||
writer.WriteStringRef(attachmentName);
|
||||
writer.WriteVarInt(frames.Count);
|
||||
for (int i = 0, n = frames.Count; i < n; i++)
|
||||
{
|
||||
JsonObject o = frames[i].AsObject();
|
||||
if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (o["vertices"] is JsonArray vertices)
|
||||
{
|
||||
writer.WriteVarInt(vertices.Count);
|
||||
if (vertices.Count > 0)
|
||||
{
|
||||
if (o["offset"] is JsonValue offset) writer.WriteVarInt((int)offset); else writer.WriteVarInt(0);
|
||||
WriteFloatArray(vertices, vertices.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteVarInt(0);
|
||||
}
|
||||
if (i < n - 1) WriteCurve(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteDrawOrderTimelines(JsonArray drawOrderTimelines)
|
||||
{
|
||||
writer.WriteVarInt(drawOrderTimelines.Count);
|
||||
foreach (JsonObject data in drawOrderTimelines)
|
||||
{
|
||||
if (data["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
if (data["offsets"] is JsonArray offsets)
|
||||
{
|
||||
writer.WriteVarInt(offsets.Count);
|
||||
foreach (JsonObject o in offsets)
|
||||
{
|
||||
writer.WriteVarInt(slot2idx[(string)o["slot"]]);
|
||||
writer.WriteVarInt((int)o["offset"]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteVarInt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteEventTimelines(JsonArray eventTimelines)
|
||||
{
|
||||
JsonObject events = root["events"].AsObject();
|
||||
|
||||
writer.WriteVarInt(eventTimelines.Count);
|
||||
foreach(JsonObject data in eventTimelines)
|
||||
{
|
||||
JsonObject eventData = events[(string)data["name"]].AsObject();
|
||||
if (data["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
|
||||
writer.WriteVarInt(event2idx[(string)data["name"]]);
|
||||
if (data["int"] is JsonValue @int) writer.WriteVarInt((int)@int); else
|
||||
if (eventData["int"] is JsonValue @int2) writer.WriteVarInt((int)@int2); else writer.WriteVarInt(0);
|
||||
if (data["float"] is JsonValue @float) writer.WriteFloat((float)@float); else
|
||||
if (eventData["float"] is JsonValue @float2) writer.WriteFloat((float)@float2); else writer.WriteFloat(0);
|
||||
if (data["string"] is JsonValue @string)
|
||||
{
|
||||
writer.WriteBoolean(true);
|
||||
writer.WriteString((string)@string);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteBoolean(false);
|
||||
}
|
||||
|
||||
if (eventData.ContainsKey("audio"))
|
||||
{
|
||||
if (data["volume"] is JsonValue volume) writer.WriteFloat((float)volume); else
|
||||
if (eventData["volume"] is JsonValue volume2) writer.WriteFloat((float)volume2); else writer.WriteFloat(1);
|
||||
if (data["balance"] is JsonValue balance) writer.WriteFloat((float)balance); else
|
||||
if (eventData["balance"] is JsonValue balance2) writer.WriteFloat((float)balance2); else writer.WriteFloat(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1234,7 +1614,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
public void WriteShortArray(JsonArray array)
|
||||
{
|
||||
writer.WriteVarInt(array.Count);
|
||||
foreach (uint i in array)
|
||||
foreach (int i in array)
|
||||
{
|
||||
writer.WriteByte((byte)(i >> 8));
|
||||
writer.WriteByte((byte)i);
|
||||
@@ -1267,6 +1647,29 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCurve(JsonObject obj)
|
||||
{
|
||||
if (obj["curve"] is JsonValue curve)
|
||||
{
|
||||
if (curve.GetValueKind() == JsonValueKind.String)
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.CURVE_STEPPED);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.CURVE_BEZIER);
|
||||
writer.WriteFloat((float)curve);
|
||||
if (obj["c2"] is JsonValue c2) writer.WriteFloat((float)c2); else writer.WriteFloat(0);
|
||||
if (obj["c3"] is JsonValue c3) writer.WriteFloat((float)c3); else writer.WriteFloat(1);
|
||||
if (obj["c4"] is JsonValue c4) writer.WriteFloat((float)c4); else writer.WriteFloat(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteByte(SkeletonBinary.CURVE_LINEAR);
|
||||
}
|
||||
}
|
||||
|
||||
public override JsonObject ReadJson(string jsonPath)
|
||||
{
|
||||
// replace 3.8.75 to another version to avoid detection in official runtime
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime21;
|
||||
using SpineViewer.Extensions;
|
||||
using SpineViewer.Utils;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
@@ -14,7 +17,17 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
[SpineImplementation(SpineVersion.V21)]
|
||||
internal class SpineObject21 : Spine.SpineObject
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
//private static SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
//{
|
||||
// return spineBlendMode switch
|
||||
// {
|
||||
// BlendMode.Normal => BlendMode.Normal,
|
||||
// BlendMode.Additive => BlendMode.Additive,
|
||||
// BlendMode.Multiply => BlendMode.Multiply,
|
||||
// BlendMode.Screen => BlendMode.Screen,
|
||||
// _ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
// };
|
||||
//}
|
||||
|
||||
private class TextureLoader : SpineRuntime21.TextureLoader
|
||||
{
|
||||
@@ -34,11 +47,13 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
((SFML.Graphics.Texture)texture).Dispose();
|
||||
}
|
||||
}
|
||||
private static TextureLoader textureLoader = new();
|
||||
|
||||
private Atlas atlas;
|
||||
private SkeletonBinary? skeletonBinary;
|
||||
private SkeletonJson? skeletonJson;
|
||||
private readonly static TextureLoader textureLoader = new();
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private readonly Atlas atlas;
|
||||
private readonly SkeletonBinary? skeletonBinary;
|
||||
private readonly SkeletonJson? skeletonJson;
|
||||
private SkeletonData skeletonData;
|
||||
private AnimationStateData animationStateData;
|
||||
|
||||
@@ -48,6 +63,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
// 2.1.x 不支持剪裁
|
||||
//private SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject21(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
atlas = new Atlas(AtlasPath, textureLoader);
|
||||
@@ -74,13 +94,21 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var (k, att) in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots[k.Key].Name;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
|
||||
skeleton = new Skeleton(skeletonData);
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
animationState = new AnimationState(animationStateData);
|
||||
}
|
||||
@@ -95,45 +123,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
protected override float scale
|
||||
{
|
||||
get
|
||||
{
|
||||
if (skeletonBinary is not null)
|
||||
return skeletonBinary.Scale;
|
||||
else if (skeletonJson is not null)
|
||||
return skeletonJson.Scale;
|
||||
else
|
||||
return 1f;
|
||||
}
|
||||
get => Math.Abs(skeleton.ScaleX);
|
||||
set
|
||||
{
|
||||
// 保存状态
|
||||
var pos = position;
|
||||
var fX = flipX;
|
||||
var fY = flipY;
|
||||
var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
|
||||
|
||||
if (skeletonBinary is not null)
|
||||
{
|
||||
skeletonBinary.Scale = value;
|
||||
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
|
||||
}
|
||||
else if (skeletonJson is not null)
|
||||
{
|
||||
skeletonJson.Scale = value;
|
||||
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
|
||||
}
|
||||
|
||||
// reload skel-dependent data
|
||||
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
|
||||
skeleton = new Skeleton(skeletonData);
|
||||
animationState = new AnimationState(animationStateData);
|
||||
|
||||
// 恢复状态
|
||||
position = pos;
|
||||
flipX = fX;
|
||||
flipY = fY;
|
||||
foreach (var s in loadedSkins) addSkin(s);
|
||||
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
||||
skeleton.ScaleX = Math.Sign(skeleton.ScaleX) * value;
|
||||
skeleton.ScaleY = Math.Sign(skeleton.ScaleY) * value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,26 +143,49 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
protected override bool flipX
|
||||
{
|
||||
get => skeleton.FlipX;
|
||||
set => skeleton.FlipX = value;
|
||||
get => skeleton.ScaleX < 0;
|
||||
set
|
||||
{
|
||||
if (skeleton.ScaleX > 0 && value || skeleton.ScaleX < 0 && !value)
|
||||
skeleton.ScaleX *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool flipY
|
||||
{
|
||||
get => skeleton.FlipY;
|
||||
set => skeleton.FlipY = value;
|
||||
get => skeleton.ScaleY < 0;
|
||||
set
|
||||
{
|
||||
if (skeleton.ScaleY > 0 && value || skeleton.ScaleY < 0 && !value)
|
||||
skeleton.ScaleY *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (!skinNames.Contains(name)) return;
|
||||
skeleton.SetSkin(name); // XXX: 3.7 及以下不支持 AddSkin
|
||||
// default 不需要加载
|
||||
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
|
||||
{
|
||||
// XXX: 3.7 及以下不支持 AddSkin
|
||||
foreach (var (k, v) in sk.Attachments)
|
||||
skeleton.Skin.AddAttachment(k.Key, k.Value, v);
|
||||
}
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.SetSkin(skeletonData.DefaultSkin);
|
||||
skeleton.Skin.Attachments.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
@@ -179,8 +196,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
protected override void setAnimation(int track, string name)
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
animationState.SetAnimation(track, EmptyAnimation, true);
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -188,54 +205,53 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||
|
||||
protected override RectangleF bounds
|
||||
protected override RectangleF getCurrentBounds()
|
||||
{
|
||||
get
|
||||
{
|
||||
float[] temp = new float[8];
|
||||
var drawOrderItems = skeleton.DrawOrder;
|
||||
float minX = int.MaxValue, minY = int.MaxValue, maxX = int.MinValue, maxY = int.MinValue;
|
||||
for (int i = 0, n = skeleton.DrawOrder.Count; i < n; i++)
|
||||
{
|
||||
Slot slot = drawOrderItems[i];
|
||||
int verticesLength = 0;
|
||||
float[] vertices = null;
|
||||
Attachment attachment = slot.Attachment;
|
||||
var regionAttachment = attachment as RegionAttachment;
|
||||
if (regionAttachment != null)
|
||||
{
|
||||
verticesLength = 8;
|
||||
vertices = temp;
|
||||
if (vertices.Length < 8) vertices = temp = new float[8];
|
||||
regionAttachment.ComputeWorldVertices(slot.Bone, temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
var meshAttachment = attachment as MeshAttachment;
|
||||
if (meshAttachment != null)
|
||||
{
|
||||
MeshAttachment mesh = meshAttachment;
|
||||
verticesLength = mesh.Vertices.Length;
|
||||
vertices = temp;
|
||||
if (vertices.Length < verticesLength) vertices = temp = new float[verticesLength];
|
||||
mesh.ComputeWorldVertices(slot, temp);
|
||||
}
|
||||
}
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h);
|
||||
return new RectangleF(x, y, w, h);
|
||||
}
|
||||
|
||||
if (vertices != null)
|
||||
{
|
||||
for (int ii = 0; ii < verticesLength; ii += 2)
|
||||
{
|
||||
float vx = vertices[ii], vy = vertices[ii + 1];
|
||||
minX = Math.Min(minX, vx);
|
||||
minY = Math.Min(minY, vy);
|
||||
maxX = Math.Max(maxX, vx);
|
||||
maxY = Math.Max(maxY, vy);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new RectangleF(minX, minY, maxX - minX, maxY - minY);
|
||||
protected override RectangleF getBounds()
|
||||
{
|
||||
// 初始化临时对象
|
||||
var maxDuration = 0f;
|
||||
var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
|
||||
var tmpAnimationState = new AnimationState(animationStateData);
|
||||
tmpSkeleton.ScaleX = skeleton.ScaleX;
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value))
|
||||
{
|
||||
foreach (var (k, v) in skeletonData.FindSkin(name).Attachments)
|
||||
tmpSkeleton.Skin.AddAttachment(k.Key, k.Value, v);
|
||||
}
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks[i] is not null))
|
||||
{
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
tmpAnimationState.SetAnimation(tr, ani, true);
|
||||
if (ani.Duration > maxDuration) maxDuration = ani.Duration;
|
||||
}
|
||||
tmpSkeleton.SetSlotsToSetupPose();
|
||||
tmpAnimationState.Update(0);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
tmpAnimationState.Update(delta);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(delta);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
protected override void update(float delta)
|
||||
@@ -246,18 +262,6 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
//private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
//{
|
||||
// return spineBlendMode switch
|
||||
// {
|
||||
// BlendMode.Normal => BlendMode.Normal,
|
||||
// BlendMode.Additive => BlendMode.Additive,
|
||||
// BlendMode.Multiply => BlendMode.Multiply,
|
||||
// BlendMode.Screen => BlendMode.Screen,
|
||||
// _ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
// };
|
||||
//}
|
||||
|
||||
protected override void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
|
||||
{
|
||||
triangleVertices.Clear();
|
||||
@@ -503,7 +507,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
if (debugBounds)
|
||||
{
|
||||
var vt = new SFML.Graphics.Vertex() { Color = BoundsColor };
|
||||
var b = bounds;
|
||||
var b = getCurrentBounds();
|
||||
|
||||
vt.Position.X = b.Left;
|
||||
vt.Position.Y = b.Top;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime36;
|
||||
using SpineViewer.Extensions;
|
||||
using SpineViewer.Utils;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
@@ -14,7 +17,17 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
[SpineImplementation(SpineVersion.V36)]
|
||||
internal class SpineObject36 : Spine.SpineObject
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
private static SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
private class TextureLoader : SpineRuntime36.TextureLoader
|
||||
{
|
||||
@@ -34,18 +47,25 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
((SFML.Graphics.Texture)texture).Dispose();
|
||||
}
|
||||
}
|
||||
private static TextureLoader textureLoader = new();
|
||||
|
||||
private Atlas atlas;
|
||||
private SkeletonBinary? skeletonBinary;
|
||||
private SkeletonJson? skeletonJson;
|
||||
private static readonly TextureLoader textureLoader = new();
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private readonly Atlas atlas;
|
||||
private readonly SkeletonBinary? skeletonBinary;
|
||||
private readonly SkeletonJson? skeletonJson;
|
||||
private SkeletonData skeletonData;
|
||||
private AnimationStateData animationStateData;
|
||||
|
||||
private Skeleton skeleton;
|
||||
private AnimationState animationState;
|
||||
|
||||
private SkeletonClipping clipping = new();
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject36(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
@@ -73,13 +93,21 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var (k, att) in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[k.slotIndex].Name;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
|
||||
skeleton = new Skeleton(skeletonData);
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
animationState = new AnimationState(animationStateData);
|
||||
}
|
||||
@@ -94,45 +122,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
protected override float scale
|
||||
{
|
||||
get
|
||||
{
|
||||
if (skeletonBinary is not null)
|
||||
return skeletonBinary.Scale;
|
||||
else if (skeletonJson is not null)
|
||||
return skeletonJson.Scale;
|
||||
else
|
||||
return 1f;
|
||||
}
|
||||
get => Math.Abs(skeleton.ScaleX);
|
||||
set
|
||||
{
|
||||
// 保存状态
|
||||
var pos = position;
|
||||
var fX = flipX;
|
||||
var fY = flipY;
|
||||
var animations = animationState.Tracks.Where(te => te is not null).Select(te => te.Animation.Name).ToArray();
|
||||
|
||||
if (skeletonBinary is not null)
|
||||
{
|
||||
skeletonBinary.Scale = value;
|
||||
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
|
||||
}
|
||||
else if (skeletonJson is not null)
|
||||
{
|
||||
skeletonJson.Scale = value;
|
||||
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
|
||||
}
|
||||
|
||||
// reload skel-dependent data
|
||||
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
|
||||
skeleton = new Skeleton(skeletonData);
|
||||
animationState = new AnimationState(animationStateData);
|
||||
|
||||
// 恢复状态
|
||||
position = pos;
|
||||
flipX = fX;
|
||||
flipY = fY;
|
||||
foreach (var s in loadedSkins) addSkin(s);
|
||||
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
|
||||
skeleton.ScaleX = Math.Sign(skeleton.ScaleX) * value;
|
||||
skeleton.ScaleY = Math.Sign(skeleton.ScaleY) * value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,26 +142,49 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
protected override bool flipX
|
||||
{
|
||||
get => skeleton.FlipX;
|
||||
set => skeleton.FlipX = value;
|
||||
get => skeleton.ScaleX < 0;
|
||||
set
|
||||
{
|
||||
if (skeleton.ScaleX > 0 && value || skeleton.ScaleX < 0 && !value)
|
||||
skeleton.ScaleX *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool flipY
|
||||
{
|
||||
get => skeleton.FlipY;
|
||||
set => skeleton.FlipY = value;
|
||||
get => skeleton.ScaleY < 0;
|
||||
set
|
||||
{
|
||||
if (skeleton.ScaleY > 0 && value || skeleton.ScaleY < 0 && !value)
|
||||
skeleton.ScaleY *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (!skinNames.Contains(name)) return;
|
||||
skeleton.SetSkin(name); // XXX: 3.7 及以下不支持 AddSkin
|
||||
// default 不需要加载
|
||||
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
|
||||
{
|
||||
// XXX: 3.7 及以下不支持 AddSkin
|
||||
foreach (var (k, v) in sk.Attachments)
|
||||
skeleton.Skin.AddAttachment(k.slotIndex, k.name, v);
|
||||
}
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.SetSkin(skeletonData.DefaultSkin);
|
||||
skeleton.Skin.Attachments.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
@@ -178,8 +195,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
protected override void setAnimation(int track, string name)
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
animationState.SetAnimation(track, EmptyAnimation, true);
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -187,14 +204,54 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||
|
||||
protected override RectangleF bounds
|
||||
protected override RectangleF getCurrentBounds()
|
||||
{
|
||||
get
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
}
|
||||
|
||||
protected override RectangleF getBounds()
|
||||
{
|
||||
// 初始化临时对象
|
||||
var maxDuration = 0f;
|
||||
var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
|
||||
var tmpAnimationState = new AnimationState(animationStateData);
|
||||
tmpSkeleton.ScaleX = skeleton.ScaleX;
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value))
|
||||
{
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
foreach (var (k, v) in skeletonData.FindSkin(name).Attachments)
|
||||
tmpSkeleton.Skin.AddAttachment(k.slotIndex, k.name, v);
|
||||
}
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
tmpAnimationState.SetAnimation(tr, ani, true);
|
||||
if (ani.Duration > maxDuration) maxDuration = ani.Duration;
|
||||
}
|
||||
tmpSkeleton.SetSlotsToSetupPose();
|
||||
tmpAnimationState.Update(0);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
tmpAnimationState.Update(delta);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(delta);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
protected override void update(float delta)
|
||||
@@ -205,18 +262,6 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
|
||||
{
|
||||
triangleVertices.Clear();
|
||||
@@ -489,7 +534,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
if (debugBounds)
|
||||
{
|
||||
var vt = new SFML.Graphics.Vertex() { Color = BoundsColor };
|
||||
var b = bounds;
|
||||
var b = getCurrentBounds();
|
||||
|
||||
vt.Position.X = b.Left;
|
||||
vt.Position.Y = b.Top;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime37;
|
||||
using SpineViewer.Extensions;
|
||||
using SpineViewer.Utils;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
@@ -11,7 +14,17 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
[SpineImplementation(SpineVersion.V37)]
|
||||
internal class SpineObject37 : Spine.SpineObject
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
private static SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
private class TextureLoader : SpineRuntime37.TextureLoader
|
||||
{
|
||||
@@ -32,18 +45,24 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
private static TextureLoader textureLoader = new();
|
||||
private static readonly TextureLoader textureLoader = new();
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private Atlas atlas;
|
||||
private SkeletonBinary? skeletonBinary;
|
||||
private SkeletonJson? skeletonJson;
|
||||
private SkeletonData skeletonData;
|
||||
private AnimationStateData animationStateData;
|
||||
private readonly Atlas atlas;
|
||||
private readonly SkeletonBinary? skeletonBinary;
|
||||
private readonly SkeletonJson? skeletonJson;
|
||||
private readonly SkeletonData skeletonData;
|
||||
private readonly AnimationStateData animationStateData;
|
||||
|
||||
private Skeleton skeleton;
|
||||
private AnimationState animationState;
|
||||
private readonly Skeleton skeleton;
|
||||
private readonly AnimationState animationState;
|
||||
|
||||
private SkeletonClipping clipping = new();
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject37(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
@@ -71,13 +90,21 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var (k, att) in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[k.slotIndex].Name;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
|
||||
skeleton = new Skeleton(skeletonData);
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
animationState = new AnimationState(animationStateData);
|
||||
}
|
||||
@@ -130,16 +157,31 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (!skinNames.Contains(name)) return;
|
||||
skeleton.SetSkin(name); // XXX: 3.7 及以下不支持 AddSkin
|
||||
// default 不需要加载
|
||||
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
|
||||
{
|
||||
// XXX: 3.7 及以下不支持 AddSkin
|
||||
foreach (var (k, v) in sk.Attachments)
|
||||
skeleton.Skin.AddAttachment(k.slotIndex, k.name, v);
|
||||
}
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.SetSkin(skeletonData.DefaultSkin);
|
||||
skeleton.Skin.Attachments.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
|
||||
@@ -150,8 +192,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
protected override void setAnimation(int track, string name)
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
animationState.SetAnimation(track, EmptyAnimation, true);
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -159,14 +201,54 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||
|
||||
protected override RectangleF bounds
|
||||
protected override RectangleF getCurrentBounds()
|
||||
{
|
||||
get
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
}
|
||||
|
||||
protected override RectangleF getBounds()
|
||||
{
|
||||
// 初始化临时对象
|
||||
var maxDuration = 0f;
|
||||
var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
|
||||
var tmpAnimationState = new AnimationState(animationStateData);
|
||||
tmpSkeleton.ScaleX = skeleton.ScaleX;
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value))
|
||||
{
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
foreach (var (k, v) in skeletonData.FindSkin(name).Attachments)
|
||||
tmpSkeleton.Skin.AddAttachment(k.slotIndex, k.name, v);
|
||||
}
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
tmpAnimationState.SetAnimation(tr, ani, true);
|
||||
if (ani.Duration > maxDuration) maxDuration = ani.Duration;
|
||||
}
|
||||
tmpSkeleton.SetSlotsToSetupPose();
|
||||
tmpAnimationState.Update(0);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
tmpAnimationState.Update(delta);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(delta);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
protected override void update(float delta)
|
||||
@@ -177,18 +259,6 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
|
||||
{
|
||||
triangleVertices.Clear();
|
||||
@@ -461,7 +531,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
if (debugBounds)
|
||||
{
|
||||
var vt = new SFML.Graphics.Vertex() { Color = BoundsColor };
|
||||
var b = bounds;
|
||||
var b = getCurrentBounds();
|
||||
|
||||
vt.Position.X = b.Left;
|
||||
vt.Position.Y = b.Top;
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using SpineRuntime38;
|
||||
using SpineRuntime38.Attachments;
|
||||
using SpineViewer.Extensions;
|
||||
using SpineViewer.Utils;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
@@ -14,7 +18,17 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
[SpineImplementation(SpineVersion.V38)]
|
||||
internal class SpineObject38 : Spine.SpineObject
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
private static SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
private class TextureLoader : SpineRuntime38.TextureLoader
|
||||
{
|
||||
@@ -38,18 +52,24 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
private static TextureLoader textureLoader = new();
|
||||
private static readonly TextureLoader textureLoader = new();
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private Atlas atlas;
|
||||
private SkeletonBinary? skeletonBinary;
|
||||
private SkeletonJson? skeletonJson;
|
||||
private SkeletonData skeletonData;
|
||||
private AnimationStateData animationStateData;
|
||||
private readonly Atlas atlas;
|
||||
private readonly SkeletonBinary? skeletonBinary;
|
||||
private readonly SkeletonJson? skeletonJson;
|
||||
private readonly SkeletonData skeletonData;
|
||||
private readonly AnimationStateData animationStateData;
|
||||
|
||||
private Skeleton skeleton;
|
||||
private AnimationState animationState;
|
||||
private readonly Skeleton skeleton;
|
||||
private readonly AnimationState animationState;
|
||||
|
||||
private SkeletonClipping clipping = new();
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject38(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
@@ -70,18 +90,26 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeletonJson = new SkeletonJson(atlas);
|
||||
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 都不行就报错
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}");
|
||||
throw new InvalidDataException($"Unknown skeleton file format {SkelPath}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var (k, att) in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[k.SlotIndex].Name;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -136,16 +164,27 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
// default 不需要加载
|
||||
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
|
||||
{
|
||||
skeleton.Skin.AddSkin(sk);
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -158,8 +197,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
protected override void setAnimation(int track, string name)
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
animationState.SetAnimation(track, EmptyAnimation, true);
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -167,14 +206,50 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||
|
||||
protected override RectangleF bounds
|
||||
protected override RectangleF getCurrentBounds()
|
||||
{
|
||||
get
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
}
|
||||
|
||||
protected override RectangleF getBounds()
|
||||
{
|
||||
// 初始化临时对象
|
||||
var maxDuration = 0f;
|
||||
var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
|
||||
var tmpAnimationState = new AnimationState(animationStateData);
|
||||
tmpSkeleton.ScaleX = skeleton.ScaleX;
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var (sk, _) in skinLoadStatus.Where(e => e.Value)) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
tmpAnimationState.SetAnimation(tr, ani, true);
|
||||
if (ani.Duration > maxDuration) maxDuration = ani.Duration;
|
||||
}
|
||||
tmpSkeleton.SetSlotsToSetupPose();
|
||||
tmpAnimationState.Update(0);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
tmpAnimationState.Update(delta);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(delta);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
protected override void update(float delta)
|
||||
@@ -185,18 +260,6 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
|
||||
{
|
||||
triangleVertices.Clear();
|
||||
@@ -469,7 +532,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
if (debugBounds)
|
||||
{
|
||||
var vt = new SFML.Graphics.Vertex() { Color = BoundsColor };
|
||||
var b = bounds;
|
||||
var b = getCurrentBounds();
|
||||
|
||||
vt.Position.X = b.Left;
|
||||
vt.Position.Y = b.Top;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime40;
|
||||
using SpineViewer.Extensions;
|
||||
using SpineViewer.Utils;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
@@ -13,7 +16,17 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
[SpineImplementation(SpineVersion.V40)]
|
||||
internal class SpineObject40 : Spine.SpineObject
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
private static SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
private class TextureLoader : SpineRuntime40.TextureLoader
|
||||
{
|
||||
@@ -34,18 +47,24 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
private static TextureLoader textureLoader = new();
|
||||
private static readonly TextureLoader textureLoader = new();
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private Atlas atlas;
|
||||
private SkeletonBinary? skeletonBinary;
|
||||
private SkeletonJson? skeletonJson;
|
||||
private SkeletonData skeletonData;
|
||||
private AnimationStateData animationStateData;
|
||||
private readonly Atlas atlas;
|
||||
private readonly SkeletonBinary? skeletonBinary;
|
||||
private readonly SkeletonJson? skeletonJson;
|
||||
private readonly SkeletonData skeletonData;
|
||||
private readonly AnimationStateData animationStateData;
|
||||
|
||||
private Skeleton skeleton;
|
||||
private AnimationState animationState;
|
||||
private readonly Skeleton skeleton;
|
||||
private readonly AnimationState animationState;
|
||||
|
||||
private SkeletonClipping clipping = new();
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject40(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
@@ -73,11 +92,20 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var e in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[e.SlotIndex].Name;
|
||||
var att = e.Attachment;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -132,16 +160,27 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
// default 不需要加载
|
||||
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
|
||||
{
|
||||
skeleton.Skin.AddSkin(sk);
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -154,8 +193,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
protected override void setAnimation(int track, string name)
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
animationState.SetAnimation(track, EmptyAnimation, true);
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -163,14 +202,50 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||
|
||||
protected override RectangleF bounds
|
||||
protected override RectangleF getCurrentBounds()
|
||||
{
|
||||
get
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
}
|
||||
|
||||
protected override RectangleF getBounds()
|
||||
{
|
||||
// 初始化临时对象
|
||||
var maxDuration = 0f;
|
||||
var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
|
||||
var tmpAnimationState = new AnimationState(animationStateData);
|
||||
tmpSkeleton.ScaleX = skeleton.ScaleX;
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var (sk, _) in skinLoadStatus.Where(e => e.Value)) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
tmpAnimationState.SetAnimation(tr, ani, true);
|
||||
if (ani.Duration > maxDuration) maxDuration = ani.Duration;
|
||||
}
|
||||
tmpSkeleton.SetSlotsToSetupPose();
|
||||
tmpAnimationState.Update(0);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
tmpAnimationState.Update(delta);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(delta);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
protected override void update(float delta)
|
||||
@@ -181,18 +256,6 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
|
||||
{
|
||||
triangleVertices.Clear();
|
||||
@@ -465,7 +528,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
if (debugBounds)
|
||||
{
|
||||
var vt = new SFML.Graphics.Vertex() { Color = BoundsColor };
|
||||
var b = bounds;
|
||||
var b = getCurrentBounds();
|
||||
|
||||
vt.Position.X = b.Left;
|
||||
vt.Position.Y = b.Top;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime41;
|
||||
using SpineViewer.Extensions;
|
||||
using SpineViewer.Utils;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
@@ -13,7 +16,17 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
[SpineImplementation(SpineVersion.V41)]
|
||||
internal class SpineObject41 : Spine.SpineObject
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
private static SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
private class TextureLoader : SpineRuntime41.TextureLoader
|
||||
{
|
||||
@@ -35,17 +48,23 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
|
||||
private static TextureLoader textureLoader = new();
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private Atlas atlas;
|
||||
private SkeletonBinary? skeletonBinary;
|
||||
private SkeletonJson? skeletonJson;
|
||||
private SkeletonData skeletonData;
|
||||
private AnimationStateData animationStateData;
|
||||
private readonly Atlas atlas;
|
||||
private readonly SkeletonBinary? skeletonBinary;
|
||||
private readonly SkeletonJson? skeletonJson;
|
||||
private readonly SkeletonData skeletonData;
|
||||
private readonly AnimationStateData animationStateData;
|
||||
|
||||
private Skeleton skeleton;
|
||||
private AnimationState animationState;
|
||||
private readonly Skeleton skeleton;
|
||||
private readonly AnimationState animationState;
|
||||
|
||||
private SkeletonClipping clipping = new();
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject41(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
@@ -73,11 +92,20 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var e in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[e.SlotIndex].Name;
|
||||
var att = e.Attachment;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -132,16 +160,27 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
// default 不需要加载
|
||||
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
|
||||
{
|
||||
skeleton.Skin.AddSkin(sk);
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -154,8 +193,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
protected override void setAnimation(int track, string name)
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
animationState.SetAnimation(track, EmptyAnimation, true);
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -163,14 +202,49 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||
|
||||
protected override RectangleF bounds
|
||||
protected override RectangleF getCurrentBounds()
|
||||
{
|
||||
get
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
}
|
||||
|
||||
protected override RectangleF getBounds()
|
||||
{
|
||||
// 初始化临时对象
|
||||
var maxDuration = 0f;
|
||||
var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
|
||||
var tmpAnimationState = new AnimationState(animationStateData);
|
||||
tmpSkeleton.ScaleX = skeleton.ScaleX;
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var (sk, _) in skinLoadStatus.Where(e => e.Value)) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
tmpAnimationState.SetAnimation(tr, ani, true);
|
||||
if (ani.Duration > maxDuration) maxDuration = ani.Duration;
|
||||
}
|
||||
tmpSkeleton.SetSlotsToSetupPose();
|
||||
tmpAnimationState.Update(0);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
//tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
tmpAnimationState.Update(delta);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
protected override void update(float delta)
|
||||
@@ -181,18 +255,6 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.UpdateWorldTransform();
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
|
||||
{
|
||||
triangleVertices.Clear();
|
||||
@@ -465,7 +527,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
if (debugBounds)
|
||||
{
|
||||
var vt = new SFML.Graphics.Vertex() { Color = BoundsColor };
|
||||
var b = bounds;
|
||||
var b = getCurrentBounds();
|
||||
|
||||
vt.Position.X = b.Left;
|
||||
vt.Position.Y = b.Top;
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SpineRuntime42;
|
||||
using SpineViewer.Extensions;
|
||||
using SpineViewer.Utils;
|
||||
|
||||
namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
{
|
||||
[SpineImplementation(SpineVersion.V42)]
|
||||
internal class Spineobject42 : Spine.SpineObject
|
||||
internal class SpineObject42 : Spine.SpineObject
|
||||
{
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
private class TextureLoader : SpineRuntime42.TextureLoader
|
||||
{
|
||||
@@ -34,20 +47,26 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
private static TextureLoader textureLoader = new();
|
||||
private static readonly TextureLoader textureLoader = new();
|
||||
private static readonly Animation EmptyAnimation = new(EMPTY_ANIMATION, [], 0);
|
||||
|
||||
private Atlas atlas;
|
||||
private SkeletonBinary? skeletonBinary;
|
||||
private SkeletonJson? skeletonJson;
|
||||
private SkeletonData skeletonData;
|
||||
private AnimationStateData animationStateData;
|
||||
private readonly Atlas atlas;
|
||||
private readonly SkeletonBinary? skeletonBinary;
|
||||
private readonly SkeletonJson? skeletonJson;
|
||||
private readonly SkeletonData skeletonData;
|
||||
private readonly AnimationStateData animationStateData;
|
||||
|
||||
private Skeleton skeleton;
|
||||
private AnimationState animationState;
|
||||
private readonly Skeleton skeleton;
|
||||
private readonly AnimationState animationState;
|
||||
|
||||
private SkeletonClipping clipping = new();
|
||||
private readonly SkeletonClipping clipping = new();
|
||||
|
||||
public Spineobject42(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
/// <summary>
|
||||
/// 所有插槽在所有皮肤中可用的附件集合
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Dictionary<string, Attachment>> slotAttachments = [];
|
||||
|
||||
public SpineObject42(string skelPath, string atlasPath) : base(skelPath, atlasPath)
|
||||
{
|
||||
atlas = new Atlas(AtlasPath, textureLoader);
|
||||
try
|
||||
@@ -73,11 +92,20 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var skin in skeletonData.Skins)
|
||||
skinNames.Add(skin.Name);
|
||||
|
||||
foreach (var anime in skeletonData.Animations)
|
||||
animationNames.Add(anime.Name);
|
||||
foreach (var sk in skeletonData.Skins)
|
||||
{
|
||||
foreach (var e in sk.Attachments)
|
||||
{
|
||||
var slotName = skeletonData.Slots.Items[e.SlotIndex].Name;
|
||||
var att = e.Attachment;
|
||||
if (!slotAttachments.TryGetValue(slotName, out var attachments))
|
||||
slotAttachments[slotName] = attachments = new() { [EMPTY_ATTACHMENT] = null };
|
||||
attachments[att.Name] = att;
|
||||
}
|
||||
}
|
||||
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
|
||||
SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
|
||||
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
|
||||
|
||||
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
|
||||
animationStateData = new AnimationStateData(skeletonData);
|
||||
@@ -132,16 +160,27 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
}
|
||||
}
|
||||
|
||||
protected override string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
|
||||
|
||||
protected override void setSlotAttachment(string slot, string name)
|
||||
{
|
||||
if (slotAttachments.TryGetValue(slot, out var attachments)
|
||||
&& attachments.TryGetValue(name, out var att)
|
||||
&& skeleton.FindSlot(slot) is Slot s)
|
||||
s.Attachment = att;
|
||||
}
|
||||
|
||||
protected override void addSkin(string name)
|
||||
{
|
||||
if (skeletonData.FindSkin(name) is Skin sk)
|
||||
// default 不需要加载
|
||||
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
|
||||
{
|
||||
skeleton.Skin.AddSkin(sk);
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void clearSkin()
|
||||
protected override void clearSkins()
|
||||
{
|
||||
skeleton.Skin.Clear();
|
||||
skeleton.SetSlotsToSetupPose();
|
||||
@@ -154,8 +193,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
protected override void setAnimation(int track, string name)
|
||||
{
|
||||
if (name == EMPTY_ANIMATION)
|
||||
animationState.SetAnimation(track, EmptyAnimation, false);
|
||||
else if (animationNames.Contains(name))
|
||||
animationState.SetAnimation(track, EmptyAnimation, true);
|
||||
else if (AnimationNames.Contains(name))
|
||||
animationState.SetAnimation(track, name, true);
|
||||
}
|
||||
|
||||
@@ -163,14 +202,50 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
|
||||
public override float GetAnimationDuration(string name) { return skeletonData.FindAnimation(name)?.Duration ?? 0f; }
|
||||
|
||||
protected override RectangleF bounds
|
||||
protected override RectangleF getCurrentBounds()
|
||||
{
|
||||
get
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
}
|
||||
|
||||
protected override RectangleF getBounds()
|
||||
{
|
||||
// 初始化临时对象
|
||||
var maxDuration = 0f;
|
||||
var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
|
||||
var tmpAnimationState = new AnimationState(animationStateData);
|
||||
tmpSkeleton.ScaleX = skeleton.ScaleX;
|
||||
tmpSkeleton.ScaleY = skeleton.ScaleY;
|
||||
tmpSkeleton.X = skeleton.X;
|
||||
tmpSkeleton.Y = skeleton.Y;
|
||||
foreach (var (sk, _) in skinLoadStatus.Where(e => e.Value)) tmpSkeleton.Skin.AddSkin(skeletonData.FindSkin(sk));
|
||||
foreach (var tr in animationState.Tracks.Select((_, i) => i).Where(i => animationState.Tracks.Items[i] is not null))
|
||||
{
|
||||
float[] _ = [];
|
||||
skeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
return new RectangleF(x, y, w, h);
|
||||
var ani = animationState.GetCurrent(tr).Animation;
|
||||
tmpAnimationState.SetAnimation(tr, ani, true);
|
||||
if (ani.Duration > maxDuration) maxDuration = ani.Duration;
|
||||
}
|
||||
tmpSkeleton.SetSlotsToSetupPose();
|
||||
tmpAnimationState.Update(0);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(0);
|
||||
tmpSkeleton.UpdateWorldTransform(Skeleton.Physics.Update);
|
||||
|
||||
// 按 10 帧每秒计算边框
|
||||
var bounds = getCurrentBounds();
|
||||
float[] _ = [];
|
||||
for (float tick = 0, delta = 0.1f; tick < maxDuration; tick += delta)
|
||||
{
|
||||
tmpSkeleton.GetBounds(out var x, out var y, out var w, out var h, ref _);
|
||||
bounds = bounds.Union(new(x, y, w, h));
|
||||
tmpAnimationState.Update(delta);
|
||||
tmpAnimationState.Apply(tmpSkeleton);
|
||||
tmpSkeleton.Update(delta);
|
||||
tmpSkeleton.UpdateWorldTransform(Skeleton.Physics.Update);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
protected override void update(float delta)
|
||||
@@ -181,18 +256,6 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
skeleton.UpdateWorldTransform(Skeleton.Physics.Update);
|
||||
}
|
||||
|
||||
private SFML.Graphics.BlendMode GetSFMLBlendMode(BlendMode spineBlendMode)
|
||||
{
|
||||
return spineBlendMode switch
|
||||
{
|
||||
BlendMode.Normal => SFMLBlendMode.NormalPma,
|
||||
BlendMode.Additive => SFMLBlendMode.AdditivePma,
|
||||
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
|
||||
BlendMode.Screen => SFMLBlendMode.ScreenPma,
|
||||
_ => throw new NotImplementedException($"{spineBlendMode}"),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states)
|
||||
{
|
||||
triangleVertices.Clear();
|
||||
@@ -465,7 +528,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
|
||||
if (debugBounds)
|
||||
{
|
||||
var vt = new SFML.Graphics.Vertex() { Color = BoundsColor };
|
||||
var b = bounds;
|
||||
var b = getCurrentBounds();
|
||||
|
||||
vt.Position.X = b.Left;
|
||||
vt.Position.Y = b.Top;
|
||||
@@ -25,11 +25,26 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
/// <summary>
|
||||
/// 可用于文件名的时间戳字符串
|
||||
/// </summary>
|
||||
protected readonly string timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
||||
protected string timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
||||
|
||||
/// <summary>
|
||||
/// 非自动分辨率下导出视区缓存
|
||||
/// </summary>
|
||||
private SFML.Graphics.View? exportViewCache = null;
|
||||
|
||||
/// <summary>
|
||||
/// 模型分辨率缓存
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, Size> spineResolutionCache = [];
|
||||
|
||||
/// <summary>
|
||||
/// 自动分辨率下每个模型的导出视区缓存
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, SFML.Graphics.View> spineViewCache = [];
|
||||
|
||||
~Exporter() { Dispose(false); }
|
||||
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
|
||||
protected virtual void Dispose(bool disposing) { View.Dispose(); }
|
||||
protected virtual void Dispose(bool disposing) { PreviewerView.Dispose(); }
|
||||
|
||||
/// <summary>
|
||||
/// 输出文件夹
|
||||
@@ -44,13 +59,29 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
/// <summary>
|
||||
/// 画面分辨率
|
||||
/// </summary>
|
||||
public Size Resolution { get; set; } = new(100, 100);
|
||||
public Size Resolution
|
||||
{
|
||||
get => resolution;
|
||||
set
|
||||
{
|
||||
if (value.Width <= 0) value.Width = 100;
|
||||
if (value.Height <= 0) value.Height = 100;
|
||||
resolution = value;
|
||||
exportResolution = new(value.Width + Margin.Horizontal, value.Height + Margin.Vertical);
|
||||
}
|
||||
}
|
||||
private Size resolution = new(100, 100);
|
||||
|
||||
/// <summary>
|
||||
/// 渲染视窗, 接管对象生命周期
|
||||
/// 包含边缘的分辨率
|
||||
/// </summary>
|
||||
public SFML.Graphics.View View { get => view; set { view.Dispose(); view = value; } }
|
||||
private SFML.Graphics.View view = new();
|
||||
private Size exportResolution = new(100, 100);
|
||||
|
||||
/// <summary>
|
||||
/// 预览画面的视区
|
||||
/// </summary>
|
||||
public SFML.Graphics.View PreviewerView { get => previewerView; set { previewerView.Dispose(); previewerView = new(value); } }
|
||||
private SFML.Graphics.View previewerView = new();
|
||||
|
||||
/// <summary>
|
||||
/// 是否仅渲染选中
|
||||
@@ -71,7 +102,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
bcPma.R = (byte)(bcPma.R * a);
|
||||
bcPma.G = (byte)(bcPma.G * a);
|
||||
bcPma.B = (byte)(bcPma.B * a);
|
||||
BackgroundColorPma = bcPma;
|
||||
backgroundColorPma = bcPma;
|
||||
}
|
||||
}
|
||||
private SFML.Graphics.Color backgroundColor = SFML.Graphics.Color.Transparent;
|
||||
@@ -79,16 +110,112 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
/// <summary>
|
||||
/// 预乘后的背景颜色
|
||||
/// </summary>
|
||||
public SFML.Graphics.Color BackgroundColorPma { get; private set; } = SFML.Graphics.Color.Transparent;
|
||||
private SFML.Graphics.Color backgroundColorPma = SFML.Graphics.Color.Transparent;
|
||||
|
||||
/// <summary>
|
||||
/// 获取供渲染的 SFML.Graphics.RenderTexture
|
||||
/// 四周边缘距离, 单位为像素
|
||||
/// </summary>
|
||||
private SFML.Graphics.RenderTexture GetRenderTexture()
|
||||
public Padding Margin
|
||||
{
|
||||
var tex = new SFML.Graphics.RenderTexture((uint)Resolution.Width, (uint)Resolution.Height);
|
||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||
tex.SetView(View);
|
||||
get => margin;
|
||||
set
|
||||
{
|
||||
if (value.Left < 0) value.Left = 0;
|
||||
if (value.Right < 0) value.Right = 0;
|
||||
if (value.Top < 0) value.Top = 0;
|
||||
if (value.Bottom < 0) value.Bottom = 0;
|
||||
margin = value;
|
||||
exportResolution = new(Resolution.Width + value.Horizontal, Resolution.Height + value.Vertical);
|
||||
}
|
||||
}
|
||||
private Padding margin = new(0);
|
||||
|
||||
/// <summary>
|
||||
/// 四周填充距离, 单位为像素, 自动分辨率下忽略该值
|
||||
/// </summary>
|
||||
public Padding Padding
|
||||
{
|
||||
get => padding;
|
||||
set
|
||||
{
|
||||
if (value.Left < 0) value.Left = 0;
|
||||
if (value.Right < 0) value.Right = 0;
|
||||
if (value.Top < 0) value.Top = 0;
|
||||
if (value.Bottom < 0) value.Bottom = 0;
|
||||
padding = value;
|
||||
}
|
||||
}
|
||||
private Padding padding = new(0);
|
||||
|
||||
/// <summary>
|
||||
/// 在使用预览画面分辨率的情况下, 允许内容溢出到边缘和填充区域, 自动分辨率下忽略该值
|
||||
/// </summary>
|
||||
public bool AllowContentOverflow { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 自动分辨率, 将会忽略预览画面的分辨率和预览画面视区, 使用模型自身的包围盒, 四周填充和内容溢出会被忽略
|
||||
/// </summary>
|
||||
public bool AutoResolution { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 获取导出渲染对象, 如果提供了模型列表则分辨率为模型大小, 否则是预览画面大小
|
||||
/// </summary>
|
||||
private SFML.Graphics.RenderTexture GetRenderTexture(SpineObject[]? spinesToRender = null)
|
||||
{
|
||||
uint width;
|
||||
uint height;
|
||||
SFML.Graphics.View view;
|
||||
|
||||
if (spinesToRender is null)
|
||||
{
|
||||
if (exportViewCache is null)
|
||||
{
|
||||
// 记录缓存
|
||||
exportViewCache = new SFML.Graphics.View(PreviewerView);
|
||||
if (AllowContentOverflow)
|
||||
{
|
||||
var canvasBounds = exportViewCache.GetBounds().GetCanvasBounds(Resolution, Margin, Padding);
|
||||
exportViewCache.Center = new(canvasBounds.X + canvasBounds.Width / 2, canvasBounds.Y + canvasBounds.Height / 2);
|
||||
exportViewCache.Size = new(canvasBounds.Width, canvasBounds.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
exportViewCache.SetViewport(Resolution, Margin, Padding);
|
||||
}
|
||||
}
|
||||
width = (uint)exportResolution.Width;
|
||||
height = (uint)exportResolution.Height;
|
||||
view = exportViewCache;
|
||||
}
|
||||
else
|
||||
{
|
||||
var cacheKey = string.Join("|", spinesToRender.Select(v => v.ID));
|
||||
|
||||
// 记录缓存
|
||||
if (!spineViewCache.TryGetValue(cacheKey, out var spineView))
|
||||
{
|
||||
var spineBounds = spinesToRender[0].GetBounds();
|
||||
foreach (var sp in spinesToRender.Skip(1))
|
||||
spineBounds = spineBounds.Union(sp.GetBounds());
|
||||
|
||||
var spineResolution = new Size((int)Math.Ceiling(spineBounds.Width), (int)Math.Ceiling(spineBounds.Height));
|
||||
var canvasBounds = spineBounds.GetCanvasBounds(spineResolution, Margin); // 忽略填充
|
||||
|
||||
spineResolutionCache[cacheKey] = new(spineResolution.Width + Margin.Horizontal, spineResolution.Height + Margin.Vertical);
|
||||
spineViewCache[cacheKey] = spineView = new SFML.Graphics.View(
|
||||
new(canvasBounds.X + canvasBounds.Width / 2, canvasBounds.Y + canvasBounds.Height / 2),
|
||||
new(canvasBounds.Width, -canvasBounds.Height)
|
||||
);
|
||||
|
||||
logger.Info("Auto resolusion: ({}, {})", spineResolution.Width, spineResolution.Height);
|
||||
}
|
||||
width = (uint)spineResolutionCache[cacheKey].Width;
|
||||
height = (uint)spineResolutionCache[cacheKey].Height;
|
||||
view = spineViewCache[cacheKey];
|
||||
}
|
||||
|
||||
var tex = new SFML.Graphics.RenderTexture(width, height);
|
||||
tex.SetView(view);
|
||||
return tex;
|
||||
}
|
||||
|
||||
@@ -103,10 +230,10 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
protected SFMLImageVideoFrame GetFrame(SpineObject[] spinesToRender)
|
||||
{
|
||||
// RenderTexture 必须临时创建, 随用随取, 防止出现跨线程的情况
|
||||
using var texPma = GetRenderTexture();
|
||||
using var texPma = GetRenderTexture(AutoResolution ? spinesToRender : null);
|
||||
|
||||
// 先将预乘结果准确绘制出来, 注意背景色也应当是预乘的
|
||||
texPma.Clear(BackgroundColorPma);
|
||||
texPma.Clear(backgroundColorPma);
|
||||
foreach (var spine in spinesToRender) texPma.Draw(spine);
|
||||
texPma.Display();
|
||||
|
||||
@@ -131,7 +258,7 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
st.Shader = SFMLShader.InversePma;
|
||||
|
||||
// 在最终结果上二次渲染非预乘画面
|
||||
using var tex = GetRenderTexture();
|
||||
using var tex = GetRenderTexture(AutoResolution ? spinesToRender : null);
|
||||
|
||||
// 将非预乘结果覆盖式绘制在目标对象上, 注意背景色应该用非预乘的
|
||||
tex.Clear(BackgroundColor);
|
||||
@@ -171,6 +298,15 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ClearCache()
|
||||
{
|
||||
exportViewCache?.Dispose();
|
||||
exportViewCache = null;
|
||||
spineResolutionCache.Clear();
|
||||
foreach (var v in spineViewCache.Values) v.Dispose();
|
||||
spineViewCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行导出
|
||||
/// </summary>
|
||||
@@ -179,13 +315,19 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public virtual void Export(SpineObject[] spines, BackgroundWorker? worker = null)
|
||||
{
|
||||
if (Validate() is string err)
|
||||
throw new ArgumentException(err);
|
||||
if (Validate() is string err) throw new ArgumentException(err);
|
||||
|
||||
var spinesToRender = spines.Where(sp => !RenderSelectedOnly || sp.IsSelected).Reverse().ToArray();
|
||||
if (spinesToRender.Length > 0)
|
||||
{
|
||||
ClearCache();
|
||||
|
||||
if (IsExportSingle) ExportSingle(spinesToRender, worker);
|
||||
else ExportIndividual(spinesToRender, worker);
|
||||
timestamp = DateTime.Now.ToString("yyMMddHHmmss"); // 刷新时间戳
|
||||
if (IsExportSingle) ExportSingle(spinesToRender, worker);
|
||||
else ExportIndividual(spinesToRender, worker);
|
||||
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
logger.LogCurrentProcessMemoryUsage();
|
||||
}
|
||||
@@ -220,10 +362,10 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
public Size Resolution { get => Exporter.Resolution; }
|
||||
|
||||
/// <summary>
|
||||
/// 渲染视窗
|
||||
/// 预览画面视区
|
||||
/// </summary>
|
||||
[Category("[0] 导出"), DisplayName("视图"), Description("画面的视图参数,请在预览画面参数面板进行调整")]
|
||||
public SFML.Graphics.View View { get => Exporter.View; }
|
||||
[Category("[0] 导出"), DisplayName("预览画面视区"), Description("预览画面的视区参数,请在预览画面参数面板进行调整")]
|
||||
public SFML.Graphics.View View { get => Exporter.PreviewerView; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否仅渲染选中
|
||||
@@ -238,5 +380,31 @@ namespace SpineViewer.Spine.SpineExporter
|
||||
[TypeConverter(typeof(SFMLColorConverter))]
|
||||
[Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")]
|
||||
public SFML.Graphics.Color BackgroundColor { get => Exporter.BackgroundColor; set => Exporter.BackgroundColor = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 四周边缘距离
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(PaddingConverter))]
|
||||
[Category("[0] 导出"), DisplayName("四周边缘距离"), Description("画布外部的边缘距离 (Margin), 最终导出的分辨率需要加上这个边距")]
|
||||
public Padding Margin { get => Exporter.Margin; set => Exporter.Margin = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 四周填充距离
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(PaddingConverter))]
|
||||
[Category("[0] 导出"), DisplayName("四周填充距离"), Description("画布内部的填充距离 (Padding), 导出的分辨率大小不会发生变化, 但是会留有四周空间")]
|
||||
public Padding Padding { get => Exporter.Padding; set => Exporter.Padding = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 允许内容溢出到边缘和填充区域
|
||||
/// </summary>
|
||||
[Category("[0] 导出"), DisplayName("允许内容溢出"), Description("使用预览画面分辨率的情况下, 允许内容溢出到边缘和填充区域")]
|
||||
public bool AllowContentOverflow { get => Exporter.AllowContentOverflow; set => Exporter.AllowContentOverflow = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 自动分辨率
|
||||
/// </summary>
|
||||
[Category("[0] 导出"), DisplayName("自动分辨率"), Description("根据导出内容自动设置分辨率, 四周填充距离和内容溢出参数将会被忽略")]
|
||||
public bool AutoResolution { get => Exporter.AutoResolution; set => Exporter.AutoResolution = value; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ using NLog;
|
||||
using System.Xml.Linq;
|
||||
using SpineViewer.Extensions;
|
||||
using SpineViewer.Utils;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Frozen;
|
||||
using System.Linq;
|
||||
|
||||
namespace SpineViewer.Spine
|
||||
{
|
||||
@@ -14,20 +17,20 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
public abstract class SpineObject : ImplementationResolver<SpineObject, SpineImplementationAttribute, SpineVersion>, SFML.Graphics.Drawable, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 空附件标记
|
||||
/// </summary>
|
||||
protected const string EMPTY_ATTACHMENT = "<Empty>";
|
||||
|
||||
/// <summary>
|
||||
/// 空动画标记
|
||||
/// </summary>
|
||||
protected const string EMPTY_ANIMATION = "<Empty>";
|
||||
|
||||
/// <summary>
|
||||
/// 预览图宽
|
||||
/// 预览图像素大小
|
||||
/// </summary>
|
||||
protected const uint PREVIEW_WIDTH = 256;
|
||||
|
||||
/// <summary>
|
||||
/// 预览图高
|
||||
/// </summary>
|
||||
protected const uint PREVIEW_HEIGHT = 256;
|
||||
protected static readonly Size PreviewResolution = new(256, 256);
|
||||
|
||||
/// <summary>
|
||||
/// 创建特定版本的 Spine
|
||||
@@ -52,7 +55,6 @@ namespace SpineViewer.Spine
|
||||
/// 日志器
|
||||
/// </summary>
|
||||
protected readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
private bool skinLoggerWarned = false;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
@@ -71,9 +73,6 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
private SpineObject PostInit()
|
||||
{
|
||||
SkinNames = skinNames.AsReadOnly();
|
||||
AnimationNames = animationNames.AsReadOnly();
|
||||
|
||||
// 必须 Update 一次否则包围盒还没有值
|
||||
update(0);
|
||||
|
||||
@@ -82,21 +81,23 @@ namespace SpineViewer.Spine
|
||||
// 虽然两边不会同时调用 Draw, 但是死锁似乎和 Draw 函数有关
|
||||
// 除此之外, 似乎还和 tex 的 Dispose 有关
|
||||
// 如果不对 tex 进行 Dispose, 那么不管是否 Draw 都正常不会死锁
|
||||
var tex = new SFML.Graphics.RenderTexture(PREVIEW_WIDTH, PREVIEW_HEIGHT);
|
||||
using var view = bounds.GetView(PREVIEW_WIDTH, PREVIEW_HEIGHT);
|
||||
var tex = new SFML.Graphics.RenderTexture((uint)PreviewResolution.Width, (uint)PreviewResolution.Height);
|
||||
var bounds = getCurrentBounds().GetCanvasBounds(PreviewResolution);
|
||||
using var view = new SFML.Graphics.View(
|
||||
new(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2),
|
||||
new(bounds.Width, -bounds.Height)
|
||||
);
|
||||
tex.SetView(view);
|
||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||
tex.Draw(this);
|
||||
tex.Display();
|
||||
Preview = tex.Texture.CopyToBitmap();
|
||||
|
||||
// 默认初始化10个空位
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
setAnimation(i, AnimationNames.First());
|
||||
loadedSkins.Add(SkinNames.First());
|
||||
}
|
||||
reloadSkins();
|
||||
// 初始化皮肤加载情况, 不需要记录 default
|
||||
foreach (var n in SkinNames.Where(v => v != "default")) skinLoadStatus[n] = false;
|
||||
|
||||
// 默认初始化10个动画空位
|
||||
for (int i = 0; i < 10; i++) setAnimation(i, AnimationNames.First());
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -163,12 +164,6 @@ namespace SpineViewer.Spine
|
||||
public bool UsePma { get { lock (_lock) return usePma; } set { lock (_lock) usePma = value; } }
|
||||
protected bool usePma = false;
|
||||
|
||||
/// <summary>
|
||||
/// 骨骼包围盒
|
||||
/// </summary>
|
||||
public RectangleF Bounds { get { lock (_lock) return bounds; } }
|
||||
protected abstract RectangleF bounds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 缩放比例
|
||||
/// </summary>
|
||||
@@ -209,18 +204,6 @@ namespace SpineViewer.Spine
|
||||
}
|
||||
protected abstract bool flipY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 包含的所有皮肤名称
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<string> SkinNames { get; private set; }
|
||||
protected readonly List<string> skinNames = [];
|
||||
|
||||
/// <summary>
|
||||
/// 包含的所有动画名称
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<string> AnimationNames { get; private set; }
|
||||
protected readonly List<string> animationNames = [EMPTY_ANIMATION];
|
||||
|
||||
/// <summary>
|
||||
/// 是否被选中
|
||||
/// </summary>
|
||||
@@ -321,6 +304,16 @@ namespace SpineViewer.Spine
|
||||
}
|
||||
protected bool debugPaths = false;
|
||||
|
||||
/// <summary>
|
||||
/// 显示点附件
|
||||
/// </summary>
|
||||
public bool DebugPoints
|
||||
{
|
||||
get { lock (_lock) return debugPoints; }
|
||||
set { lock (_lock) { debugPoints = value; update(0); } }
|
||||
}
|
||||
protected bool debugPoints = false;
|
||||
|
||||
/// <summary>
|
||||
/// 显示剪裁附件网格线
|
||||
/// </summary>
|
||||
@@ -332,76 +325,67 @@ namespace SpineViewer.Spine
|
||||
protected bool debugClippings = false;
|
||||
|
||||
/// <summary>
|
||||
/// 获取已加载的皮肤列表快照, 允许出现重复值
|
||||
/// 所有插槽下可用的附件名
|
||||
/// </summary>
|
||||
public string[] GetLoadedSkins() { lock (_lock) return loadedSkins.ToArray(); }
|
||||
protected readonly List<string> loadedSkins = [];
|
||||
public FrozenDictionary<string, ImmutableArray<string>> SlotAttachmentNames { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加载指定皮肤, 添加至列表末尾, 如果不存在则忽略, 允许加载重复的值
|
||||
/// 包含的所有皮肤名称 (不含 default 默认皮肤)
|
||||
/// </summary>
|
||||
public void LoadSkin(string name)
|
||||
{
|
||||
if (!skinNames.Contains(name)) return;
|
||||
lock (_lock)
|
||||
{
|
||||
loadedSkins.Add(name);
|
||||
reloadSkins();
|
||||
|
||||
if (!skinLoggerWarned && Version < SpineVersion.V38 && loadedSkins.Count > 1)
|
||||
{
|
||||
logger.Warn($"Multiplt skins not supported in SpineVersion {Version.GetName()}");
|
||||
skinLoggerWarned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
public ImmutableArray<string> SkinNames { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 卸载列表指定位置皮肤, 如果超出范围则忽略
|
||||
/// 包含的所有动画名称
|
||||
/// </summary>
|
||||
public void UnloadSkin(int idx)
|
||||
public ImmutableArray<string> AnimationNames { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取某个插槽当前加载的附件
|
||||
/// </summary>
|
||||
public string GetSlotAttachment(string slot) { lock (_lock) return getSlotAttachment(slot); }
|
||||
protected abstract string getSlotAttachment(string slot);
|
||||
|
||||
/// <summary>
|
||||
/// 设置某个插槽当前加载的附件
|
||||
/// </summary>
|
||||
public void SetSlotAttachment(string slot, string name) { lock (_lock) setSlotAttachment(slot, name); }
|
||||
protected abstract void setSlotAttachment(string slot, string name);
|
||||
|
||||
/// <summary>
|
||||
/// 皮肤的加载情况记录表
|
||||
/// </summary>
|
||||
protected readonly Dictionary<string, bool> skinLoadStatus = [];
|
||||
|
||||
/// <summary>
|
||||
/// 查询皮肤加载状态, 皮肤不存在时返回 false
|
||||
/// </summary>
|
||||
public bool GetSkinStatus(string name) { lock (_lock) return name == "default" || skinLoadStatus.TryGetValue(name, out var status) && status; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置皮肤加载状态, 忽略不存在的皮肤
|
||||
/// </summary>
|
||||
public void SetSkinStatus(string name, bool status)
|
||||
{
|
||||
if (!skinLoadStatus.ContainsKey(name)) return;
|
||||
lock (_lock)
|
||||
{
|
||||
if (idx < 0 || idx >= loadedSkins.Count) return;
|
||||
loadedSkins.RemoveAt(idx);
|
||||
skinLoadStatus[name] = status;
|
||||
reloadSkins();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 替换皮肤列表指定位置皮肤, 超出范围或者皮肤不存在则忽略
|
||||
/// </summary>
|
||||
public void ReplaceSkin(int idx, string name)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (idx < 0 || idx >= loadedSkins.Count || !skinNames.Contains(name)) return;
|
||||
loadedSkins[idx] = name;
|
||||
reloadSkins();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载现有皮肤列表, 用于刷新等操作
|
||||
/// 刷新已加载皮肤
|
||||
/// </summary>
|
||||
public void ReloadSkins() { lock (_lock) reloadSkins(); }
|
||||
private void reloadSkins()
|
||||
protected void reloadSkins()
|
||||
{
|
||||
clearSkin();
|
||||
foreach (var s in loadedSkins.Distinct()) addSkin(s);
|
||||
clearSkins();
|
||||
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value)) addSkin(name);
|
||||
update(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载皮肤, 如果不存在则忽略
|
||||
/// </summary>
|
||||
protected abstract void addSkin(string name);
|
||||
|
||||
/// <summary>
|
||||
/// 清空加载的所有皮肤
|
||||
/// </summary>
|
||||
protected abstract void clearSkin();
|
||||
protected abstract void clearSkins();
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有非 null 的轨道索引快照
|
||||
@@ -437,6 +421,18 @@ namespace SpineViewer.Spine
|
||||
/// </summary>
|
||||
public void ResetAnimationsTime() { lock (_lock) { foreach (var i in getTrackIndices()) setAnimation(i, getAnimation(i)); update(0); } }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前状态包围盒
|
||||
/// </summary>
|
||||
public RectangleF GetCurrentBounds() { lock (_lock) return getCurrentBounds(); }
|
||||
protected abstract RectangleF getCurrentBounds();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前参数下包围盒最大范围, 不是精确值
|
||||
/// </summary>
|
||||
public RectangleF GetBounds() { lock (_lock) return getBounds(); }
|
||||
protected abstract RectangleF getBounds();
|
||||
|
||||
/// <summary>
|
||||
/// 更新内部状态
|
||||
/// </summary>
|
||||
|
||||
@@ -85,9 +85,9 @@ namespace SpineViewer.Spine.SpineView
|
||||
var props = new PropertyDescriptorCollection(TypeDescriptor.GetProperties(this, attributes, true).Cast<PropertyDescriptor>().ToArray());
|
||||
foreach (var i in Spine.GetTrackIndices())
|
||||
{
|
||||
if (!pdCache.ContainsKey(i))
|
||||
pdCache[i] = new TrackWrapperPropertyDescriptor(i, [new DisplayNameAttribute($"轨道 {i}")]);
|
||||
props.Add(pdCache[i]);
|
||||
if (!pdCache.TryGetValue(i, out var pd))
|
||||
pdCache[i] = pd = new TrackWrapperPropertyDescriptor(i, [new DisplayNameAttribute($"轨道 {i}")]);
|
||||
props.Add(pd);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
@@ -186,17 +186,12 @@ namespace SpineViewer.Spine.SpineView
|
||||
{
|
||||
return new StandardValuesCollection(tracks.Spine.AnimationNames);
|
||||
}
|
||||
else if (context?.Instance is object[] instances && instances.All(x => x is SpineAnimationProperty))
|
||||
else if (context?.Instance is object[] instances)
|
||||
{
|
||||
// XXX: 这里不知道为啥总是会得到 object[] 类型而不是具体的类型
|
||||
var animTracks = instances.Cast<SpineAnimationProperty>().ToArray();
|
||||
if (animTracks.Length > 0)
|
||||
{
|
||||
IEnumerable<string> common = animTracks[0].Spine.AnimationNames;
|
||||
foreach (var t in animTracks.Skip(1))
|
||||
common = common.Union(t.Spine.AnimationNames);
|
||||
return new StandardValuesCollection(common.ToArray());
|
||||
}
|
||||
IEnumerable<string> common = [];
|
||||
foreach (SpineAnimationProperty prop in instances.Where(inst => inst is SpineAnimationProperty))
|
||||
common = common.Union(prop.Spine.AnimationNames);
|
||||
return new StandardValuesCollection(common.ToArray());
|
||||
}
|
||||
return base.GetStandardValues(context);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ namespace SpineViewer.Spine.SpineView
|
||||
//[DisplayName("Paths")]
|
||||
//public bool DebugPaths { get => Spine.DebugPaths; set => Spine.DebugPaths = value; }
|
||||
|
||||
///// <summary>
|
||||
///// 显示点附件
|
||||
///// </summary>
|
||||
//[DisplayName("Points")]
|
||||
//public bool DebugPoints { get => Spine.DebugPoints; set => Spine.DebugPoints = value; }
|
||||
|
||||
/// <summary>
|
||||
/// 显示剪裁附件网格线
|
||||
/// </summary>
|
||||
|
||||
@@ -27,6 +27,10 @@ namespace SpineViewer.Spine.SpineView
|
||||
[DisplayName("皮肤")]
|
||||
public SpineSkinProperty Skin { get; } = new(spine);
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
[DisplayName("插槽")]
|
||||
public SpineSlotProperty Slot { get; } = new(spine);
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
[DisplayName("动画")]
|
||||
public SpineAnimationProperty Animation { get; } = new(spine);
|
||||
|
||||
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
|
||||
namespace SpineViewer.Spine.SpineView
|
||||
{
|
||||
/// <summary>
|
||||
/// 皮肤列表动态类型包装类, 用于提供对 Spine 皮肤列表的管理能力
|
||||
/// 皮肤动态类型包装类, 用于提供对 Spine 皮肤的管理能力
|
||||
/// </summary>
|
||||
/// <param name="spine">关联的 Spine 对象</param>
|
||||
public class SpineSkinProperty(SpineObject spine) : ICustomTypeDescriptor
|
||||
@@ -18,34 +18,10 @@ namespace SpineViewer.Spine.SpineView
|
||||
[Browsable(false)]
|
||||
public SpineObject Spine { get; } = spine;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SpineSkinProperty"/> 属性缓存
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, SkinNameProperty> skinNameProperties = [];
|
||||
|
||||
/// <summary>
|
||||
/// <c>this.Skin{i}</c>
|
||||
/// </summary>
|
||||
public SkinNameProperty GetSkinName(int i)
|
||||
{
|
||||
if (!skinNameProperties.ContainsKey(i))
|
||||
skinNameProperties[i] = new SkinNameProperty(Spine, i);
|
||||
return skinNameProperties[i];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>this.Skin{i} = <paramref name="value"/></c>
|
||||
/// </summary>
|
||||
public void SetSkinName(int i, string value)
|
||||
{
|
||||
Spine.ReplaceSkin(i, value);
|
||||
TypeDescriptor.Refresh(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在属性面板悬停可以显示已加载的皮肤列表
|
||||
/// </summary>
|
||||
public override string ToString() => $"[{string.Join(", ", Spine.GetLoadedSkins())}]";
|
||||
public override string ToString() => $"[{string.Join(", ", Spine.SkinNames.Where(Spine.GetSkinStatus))}]";
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
@@ -59,7 +35,7 @@ namespace SpineViewer.Spine.SpineView
|
||||
|
||||
// XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确
|
||||
|
||||
private static readonly Dictionary<int, SkinNamePropertyDescriptor> pdCache = [];
|
||||
private static readonly Dictionary<string, SkinPropertyDescriptor> pdCache = [];
|
||||
|
||||
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
|
||||
public string? GetClassName() => TypeDescriptor.GetClassName(this, true);
|
||||
@@ -75,112 +51,44 @@ namespace SpineViewer.Spine.SpineView
|
||||
public PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
|
||||
{
|
||||
var props = new PropertyDescriptorCollection(TypeDescriptor.GetProperties(this, attributes, true).Cast<PropertyDescriptor>().ToArray());
|
||||
for (var i = 0; i < Spine.GetLoadedSkins().Length; i++)
|
||||
foreach (var name in Spine.SkinNames)
|
||||
{
|
||||
if (!pdCache.ContainsKey(i))
|
||||
pdCache[i] = new SkinNamePropertyDescriptor(i, [new DisplayNameAttribute($"皮肤 {i}")]);
|
||||
props.Add(pdCache[i]);
|
||||
if (!pdCache.TryGetValue(name, out var pd))
|
||||
pdCache[name] = pd = new SkinPropertyDescriptor(name, [new DisplayNameAttribute(name)]);
|
||||
props.Add(pd);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 皮肤属性描述符, 实现对属性的读取和赋值
|
||||
/// 皮肤属性描述符, 实现对皮肤的加载和卸载, <c><see cref="SpineSkinProperty"/>.{name}</c>
|
||||
/// </summary>
|
||||
private class SkinNamePropertyDescriptor(int i, Attribute[]? attributes) : PropertyDescriptor($"Skin{i}", attributes)
|
||||
private class SkinPropertyDescriptor(string name, Attribute[]? attributes) : PropertyDescriptor(name, attributes)
|
||||
{
|
||||
private readonly int idx = i;
|
||||
|
||||
public override Type ComponentType => typeof(SpineSkinProperty);
|
||||
public override bool IsReadOnly => false;
|
||||
public override Type PropertyType => typeof(SkinNameProperty);
|
||||
public override bool IsReadOnly => Name == "default";
|
||||
public override Type PropertyType => typeof(bool);
|
||||
public override bool CanResetValue(object component) => false;
|
||||
public override void ResetValue(object component) { }
|
||||
public override bool ShouldSerializeValue(object component) => false;
|
||||
|
||||
/// <summary>
|
||||
/// 得到一个 <see cref="SpineSkinProperty"/>, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
|
||||
/// </summary>
|
||||
public override object? GetValue(object? component)
|
||||
{
|
||||
if (component is SpineSkinProperty prop)
|
||||
return prop.GetSkinName(idx);
|
||||
return prop.Spine.GetSkinStatus(Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 允许通过字符串赋值修改该位置的皮肤
|
||||
/// </summary>
|
||||
public override void SetValue(object? component, object? value)
|
||||
{
|
||||
if (component is SpineSkinProperty prop)
|
||||
{
|
||||
if (value is string s)
|
||||
prop.SetSkinName(idx, s);
|
||||
if (value is bool s)
|
||||
prop.Spine.SetSkinStatus(Name, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对 <c><see cref="SpineSkinProperty"/>.Skin{i}</c> 属性的包装类
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(SkinNamePropertyConverter))]
|
||||
public class SkinNameProperty(SpineObject spine, int i)
|
||||
{
|
||||
private readonly SpineObject spine = spine;
|
||||
|
||||
[Browsable(false)]
|
||||
public int Index { get; } = i;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var loadedSkins = spine.GetLoadedSkins();
|
||||
if (Index >= 0 && Index < loadedSkins.Length)
|
||||
return loadedSkins[Index];
|
||||
return "!NULL"; // XXX: 预期应该不会发生
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is SkinNameProperty) return ToString() == obj.ToString();
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(SkinNameProperty).FullName.GetHashCode(), ToString().GetHashCode());
|
||||
}
|
||||
|
||||
public class SkinNamePropertyConverter : StringConverter
|
||||
{
|
||||
// NOTE: 可以不用实现 ConvertTo/ConvertFrom, 因为属性实现了与字符串之间的互转
|
||||
// ToString 实现了 ConvertTo
|
||||
// SetValue 实现了从字符串设置属性
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
||||
|
||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||
{
|
||||
if (context?.Instance is SpineSkinProperty manager)
|
||||
{
|
||||
return new StandardValuesCollection(manager.Spine.SkinNames);
|
||||
}
|
||||
else if (context?.Instance is object[] instances && instances.All(x => x is SpineSkinProperty))
|
||||
{
|
||||
// XXX: 这里不知道为啥总是会得到 object[] 类型而不是具体的 SpineSkinWrapper[] 类型
|
||||
var managers = instances.Cast<SpineSkinProperty>().ToArray();
|
||||
if (managers.Length > 0)
|
||||
{
|
||||
IEnumerable<string> common = managers[0].Spine.SkinNames;
|
||||
foreach (var t in managers.Skip(1))
|
||||
common = common.Union(t.Spine.SkinNames);
|
||||
return new StandardValuesCollection(common.ToArray());
|
||||
}
|
||||
}
|
||||
return base.GetStandardValues(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
170
SpineViewer/Spine/SpineView/SpineSlotProperty.cs
Normal file
170
SpineViewer/Spine/SpineView/SpineSlotProperty.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using SpineViewer.Spine;
|
||||
using SpineViewer.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SpineViewer.Spine.SpineView
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于在 PropertyGrid 上显示插槽附件加载情况包装类
|
||||
/// </summary>
|
||||
public class SpineSlotProperty(SpineObject spine) : ICustomTypeDescriptor
|
||||
{
|
||||
[Browsable(false)]
|
||||
public SpineObject Spine { get; } = spine;
|
||||
|
||||
/// <summary>
|
||||
/// 显示所有插槽集合
|
||||
/// </summary>
|
||||
public override string ToString() => $"[{string.Join(", ", Spine.SlotAttachmentNames.Keys)}]";
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is SpineAnimationProperty prop) return ToString() == prop.ToString();
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(SpineAnimationProperty).FullName.GetHashCode(), ToString().GetHashCode());
|
||||
|
||||
#region ICustomTypeDescriptor 接口实现
|
||||
|
||||
// XXX: 必须实现 ICustomTypeDescriptor 接口, 不能继承 CustomTypeDescriptor, 似乎继承下来的东西会有问题, 导致某些调用不正确
|
||||
|
||||
/// <summary>
|
||||
/// 属性描述符缓存
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, SlotPropertyDescriptor> pdCache = [];
|
||||
|
||||
public AttributeCollection GetAttributes() => TypeDescriptor.GetAttributes(this, true);
|
||||
public string? GetClassName() => TypeDescriptor.GetClassName(this, true);
|
||||
public string? GetComponentName() => TypeDescriptor.GetComponentName(this, true);
|
||||
public TypeConverter? GetConverter() => TypeDescriptor.GetConverter(this, true);
|
||||
public EventDescriptor? GetDefaultEvent() => TypeDescriptor.GetDefaultEvent(this, true);
|
||||
public PropertyDescriptor? GetDefaultProperty() => TypeDescriptor.GetDefaultProperty(this, true);
|
||||
public object? GetEditor(Type editorBaseType) => TypeDescriptor.GetEditor(this, editorBaseType, true);
|
||||
public EventDescriptorCollection GetEvents() => TypeDescriptor.GetEvents(this, true);
|
||||
public EventDescriptorCollection GetEvents(Attribute[]? attributes) => TypeDescriptor.GetEvents(this, attributes, true);
|
||||
public object? GetPropertyOwner(PropertyDescriptor? pd) => this;
|
||||
public PropertyDescriptorCollection GetProperties() => GetProperties(null);
|
||||
public PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
|
||||
{
|
||||
var props = new PropertyDescriptorCollection(TypeDescriptor.GetProperties(this, attributes, true).Cast<PropertyDescriptor>().ToArray());
|
||||
foreach (var slotName in Spine.SlotAttachmentNames.Keys)
|
||||
{
|
||||
if (!pdCache.TryGetValue(slotName, out var pd))
|
||||
pdCache[slotName] = pd = new SlotPropertyDescriptor(slotName, [new DisplayNameAttribute($"{slotName}")]);
|
||||
props.Add(pd);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插槽属性描述符, 实现对属性的读取和赋值
|
||||
/// </summary>
|
||||
internal class SlotPropertyDescriptor(string name, Attribute[]? attributes) : PropertyDescriptor(name, attributes)
|
||||
{
|
||||
public override Type ComponentType => typeof(SpineSlotProperty);
|
||||
public override bool IsReadOnly => false;
|
||||
public override Type PropertyType => typeof(SlotProperty);
|
||||
public override bool CanResetValue(object component) => false;
|
||||
public override void ResetValue(object component) { }
|
||||
public override bool ShouldSerializeValue(object component) => false;
|
||||
|
||||
/// <summary>
|
||||
/// 得到一个轨道包装类, 允许用户查看或者修改具体的属性值, 这个地方决定了在面板上看到的是一个对象及其属性
|
||||
/// </summary>
|
||||
public override object? GetValue(object? component)
|
||||
{
|
||||
if (component is SpineSlotProperty slots)
|
||||
return slots.Spine.GetSlotAttachment(Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 允许通过字符串赋值修改该轨道的动画, 这里决定了当其他地方的调用 (比如 Converter) 通过 value 来设置属性值的时候应该怎么处理
|
||||
/// </summary>
|
||||
public override void SetValue(object? component, object? value)
|
||||
{
|
||||
if (component is SpineSlotProperty slots)
|
||||
{
|
||||
if (value is string s)
|
||||
slots.Spine.SetSlotAttachment(Name, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对 <c><see cref="SpineSlotProperty"/>.{name}</c> 属性的包装类
|
||||
/// </summary>
|
||||
[TypeConverter(typeof(SlotPropertyConverter))]
|
||||
public class SlotProperty(SpineObject spine, string name)
|
||||
{
|
||||
private readonly SpineObject spine = spine;
|
||||
|
||||
[Browsable(false)]
|
||||
public string Name { get; } = name;
|
||||
|
||||
/// <summary>
|
||||
/// 实现了默认的转为字符串的方式
|
||||
/// </summary>
|
||||
public override string ToString() => spine.GetSlotAttachment(Name);
|
||||
|
||||
/// <summary>
|
||||
/// 影响了属性面板的判断, 当动画名称相同的时候认为两个对象是相同的, 这样属性面板可以在多选的时候正确显示相同取值的内容
|
||||
/// </summary>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is SlotProperty) return ToString() == obj.ToString();
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 哈希码需要和 Equals 行为类似
|
||||
/// </summary>
|
||||
public override int GetHashCode() => HashCode.Combine(typeof(SlotProperty).FullName.GetHashCode(), ToString().GetHashCode());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轨道索引包装类转换器, 实现字符串和包装类的互相转换, 并且提供标准值列表对属性进行设置, 同时还提供在面板上显示包装类属性的能力
|
||||
/// </summary>
|
||||
public class SlotPropertyConverter : StringConverter
|
||||
{
|
||||
// NOTE: 可以不用实现 ConvertTo/ConvertFrom, 因为属性实现了与字符串之间的互转
|
||||
// ToString 实现了 ConvertTo
|
||||
// SetValue 实现了从字符串设置属性
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => true;
|
||||
|
||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||
{
|
||||
if (context?.PropertyDescriptor is PropertyDescriptor pd)
|
||||
{
|
||||
if (context?.Instance is SpineSlotProperty slots)
|
||||
{
|
||||
if (slots.Spine.SlotAttachmentNames.TryGetValue(pd.Name, out var names))
|
||||
return new StandardValuesCollection(names);
|
||||
}
|
||||
else if (context?.Instance is object[] instances)
|
||||
{
|
||||
IEnumerable<string> common = [];
|
||||
foreach (SpineSlotProperty prop in instances.Where(inst => inst is SpineSlotProperty))
|
||||
{
|
||||
if (prop.Spine.SlotAttachmentNames.TryGetValue(pd.Name, out var names))
|
||||
common = common.Union(names);
|
||||
}
|
||||
return new StandardValuesCollection(common.ToArray());
|
||||
}
|
||||
}
|
||||
return base.GetStandardValues(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,11 @@
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<Version>0.12.3</Version>
|
||||
<Version>0.12.7</Version>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>appicon.ico</ApplicationIcon>
|
||||
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||
<GenerateResourceWarnOnBinaryFormatterUse>false</GenerateResourceWarnOnBinaryFormatterUse>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ namespace SpineViewer.Utils
|
||||
}
|
||||
}
|
||||
|
||||
private StandardValuesCollection standardValues;
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
|
||||
@@ -89,26 +91,76 @@ namespace SpineViewer.Utils
|
||||
|
||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||
{
|
||||
// 查找属性上的 StandardValuesAttribute
|
||||
var attribute = context?.PropertyDescriptor?.Attributes.OfType<StandardValuesAttribute>().FirstOrDefault();
|
||||
StandardValuesCollection result;
|
||||
if (attribute != null)
|
||||
result = new StandardValuesCollection(attribute.StandardValues);
|
||||
else
|
||||
result = new StandardValuesCollection(Array.Empty<string>());
|
||||
return result;
|
||||
if (standardValues is null)
|
||||
{
|
||||
// 查找属性上的 StandardValuesAttribute
|
||||
var attribute = context?.PropertyDescriptor?.Attributes.OfType<StandardValuesAttribute>().FirstOrDefault();
|
||||
if (attribute != null)
|
||||
standardValues = new StandardValuesCollection(attribute.StandardValues);
|
||||
else
|
||||
standardValues = new StandardValuesCollection(Array.Empty<string>());
|
||||
}
|
||||
return standardValues;
|
||||
}
|
||||
}
|
||||
|
||||
public class ResolutionConverter : SizeConverter
|
||||
{
|
||||
private static readonly StandardValuesCollection standardValues = new(new Size[] {
|
||||
new(4096, 4096),
|
||||
new(2048, 2048),
|
||||
new(1024, 1024),
|
||||
new(512, 512),
|
||||
new(3840, 2160),
|
||||
new(2560, 1440),
|
||||
new(1920, 1080),
|
||||
new(1280, 720),
|
||||
new(2160, 3840),
|
||||
new(1440, 2560),
|
||||
new(1080, 1920),
|
||||
new(720, 1280),
|
||||
});
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => false;
|
||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context) => standardValues;
|
||||
}
|
||||
|
||||
public class SFMLColorConverter : ExpandableObjectConverter
|
||||
{
|
||||
private class SFMLColorPropertyDescriptor : SimplePropertyDescriptor
|
||||
private static SFML.Graphics.Color ParseHexColor(string hex, bool includeAlpha)
|
||||
{
|
||||
public SFMLColorPropertyDescriptor(Type componentType, string name, Type propertyType) : base(componentType, name, propertyType) { }
|
||||
byte r = byte.Parse(hex.Substring(1, 2), NumberStyles.HexNumber);
|
||||
byte g = byte.Parse(hex.Substring(3, 2), NumberStyles.HexNumber);
|
||||
byte b = byte.Parse(hex.Substring(5, 2), NumberStyles.HexNumber);
|
||||
byte a = includeAlpha ? byte.Parse(hex.Substring(7, 2), NumberStyles.HexNumber) : (byte)255;
|
||||
return new SFML.Graphics.Color(r, g, b, a);
|
||||
}
|
||||
|
||||
public override object? GetValue(object? component) => component?.GetType().GetField(Name)?.GetValue(component) ?? default;
|
||||
private static SFML.Graphics.Color ParseShortHexColor(string hex, bool includeAlpha)
|
||||
{
|
||||
byte r = Convert.ToByte($"{hex[1]}{hex[1]}", 16);
|
||||
byte g = Convert.ToByte($"{hex[2]}{hex[2]}", 16);
|
||||
byte b = Convert.ToByte($"{hex[3]}{hex[3]}", 16);
|
||||
byte a = includeAlpha ? Convert.ToByte($"{hex[4]}{hex[4]}", 16) : (byte)255;
|
||||
return new SFML.Graphics.Color(r, g, b, a);
|
||||
}
|
||||
|
||||
public override void SetValue(object? component, object? value) => component?.GetType().GetField(Name)?.SetValue(component, value);
|
||||
private static readonly StandardValuesCollection standardValues;
|
||||
|
||||
static SFMLColorConverter()
|
||||
{
|
||||
// 初始化所有 KnownColor
|
||||
var knownColors = Enum.GetValues(typeof(KnownColor))
|
||||
.Cast<KnownColor>()
|
||||
.Select(knownColor =>
|
||||
{
|
||||
var color = Color.FromKnownColor(knownColor);
|
||||
return new SFML.Graphics.Color(color.R, color.G, color.B, color.A);
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
standardValues = new StandardValuesCollection(knownColors);
|
||||
}
|
||||
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
@@ -121,24 +173,48 @@ namespace SpineViewer.Utils
|
||||
if (value is string s)
|
||||
{
|
||||
s = s.Trim();
|
||||
if (s.StartsWith("#") && s.Length == 9)
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
// 处理 #RRGGBBAA 和 #RRGGBB 格式
|
||||
if (s.StartsWith("#"))
|
||||
{
|
||||
// 解析 R, G, B, A 分量,注意16进制解析
|
||||
byte r = byte.Parse(s.Substring(1, 2), NumberStyles.HexNumber);
|
||||
byte g = byte.Parse(s.Substring(3, 2), NumberStyles.HexNumber);
|
||||
byte b = byte.Parse(s.Substring(5, 2), NumberStyles.HexNumber);
|
||||
byte a = byte.Parse(s.Substring(7, 2), NumberStyles.HexNumber);
|
||||
if (s.Length == 9) // #RRGGBBAA
|
||||
return ParseHexColor(s, includeAlpha: true);
|
||||
if (s.Length == 7) // #RRGGBB
|
||||
return ParseHexColor(s, includeAlpha: false);
|
||||
if (s.Length == 5) // #RGBA
|
||||
return ParseShortHexColor(s, includeAlpha: true);
|
||||
if (s.Length == 4) // #RGB
|
||||
return ParseShortHexColor(s, includeAlpha: false);
|
||||
|
||||
throw new FormatException("无法解析颜色,请使用 #RRGGBBAA、#RRGGBB、#RGBA 或 #RGB 格式");
|
||||
}
|
||||
|
||||
// 处理 R,G,B,A 和 R,G,B 格式
|
||||
var parts = s.Split(',');
|
||||
if (parts.Length == 3 || parts.Length == 4)
|
||||
{
|
||||
byte r = byte.Parse(parts[0].Trim());
|
||||
byte g = byte.Parse(parts[1].Trim());
|
||||
byte b = byte.Parse(parts[2].Trim());
|
||||
byte a = parts.Length == 4 ? byte.Parse(parts[3].Trim()) : (byte)255;
|
||||
return new SFML.Graphics.Color(r, g, b, a);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new FormatException("无法解析颜色,确保格式为 #RRGGBBAA", ex);
|
||||
}
|
||||
|
||||
// 尝试解析为 KnownColor
|
||||
var color = Color.FromName(s);
|
||||
if (color.IsKnownColor || color.IsNamedColor)
|
||||
return new SFML.Graphics.Color(color.R, color.G, color.B, color.A);
|
||||
|
||||
throw new FormatException("无法解析颜色,请使用已知的颜色名称");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new FormatException("无法解析颜色,请检查格式", ex);
|
||||
}
|
||||
throw new FormatException("格式错误,正确格式为 #RRGGBBAA");
|
||||
}
|
||||
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
@@ -154,20 +230,33 @@ namespace SpineViewer.Utils
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
|
||||
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
|
||||
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) => false;
|
||||
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context) => standardValues;
|
||||
|
||||
private class SFMLColorPropertyDescriptor : SimplePropertyDescriptor
|
||||
{
|
||||
public SFMLColorPropertyDescriptor(Type componentType, string name, Type propertyType) : base(componentType, name, propertyType) { }
|
||||
|
||||
public override object? GetValue(object? component) => component?.GetType().GetField(Name)?.GetValue(component) ?? default;
|
||||
|
||||
public override void SetValue(object? component, object? value) => component?.GetType().GetField(Name)?.SetValue(component, value);
|
||||
}
|
||||
|
||||
private static PropertyDescriptorCollection pdCollection = null;
|
||||
|
||||
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext? context, object value, Attribute[]? attributes)
|
||||
{
|
||||
// 自定义属性集合
|
||||
var properties = new List<PropertyDescriptor>
|
||||
{
|
||||
// 定义 R, G, B, A 四个字段的描述器
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "R", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "G", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "B", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "A", typeof(byte))
|
||||
};
|
||||
|
||||
// 返回自定义属性集合
|
||||
return new PropertyDescriptorCollection(properties.ToArray());
|
||||
pdCollection ??= new(
|
||||
[
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "R", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "G", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "B", typeof(byte)),
|
||||
new SFMLColorPropertyDescriptor(typeof(SFML.Graphics.Color), "A", typeof(byte))
|
||||
],
|
||||
true
|
||||
);
|
||||
return pdCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
img/preview.webp
BIN
img/preview.webp
Binary file not shown.
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 85 KiB |
Reference in New Issue
Block a user