Compare commits

...

38 Commits

Author SHA1 Message Date
ww-rm
165be38cfe update changelog 2025-04-26 15:14:19 +08:00
ww-rm
f1fb5fe5e1 更新至v0.12.7 2025-04-26 15:13:32 +08:00
ww-rm
17554d3a8e 补充遗漏的方法 2025-04-26 15:13:17 +08:00
ww-rm
054ac3939b 修改函数调用 2025-04-26 15:04:09 +08:00
ww-rm
9566c0a6f5 修复跨线程调用问题 2025-04-26 13:27:08 +08:00
ww-rm
b8509ccb69 修正事件处理 2025-04-25 22:08:11 +08:00
ww-rm
1e043e4faa update readme 2025-04-23 15:19:09 +08:00
ww-rm
5b1c177f58 增加ScaleX和ScaleY 2025-04-22 23:54:20 +08:00
ww-rm
794de783db 设置空动画为循环避免空引用 2025-04-21 21:29:24 +08:00
ww-rm
7f6aa26986 补充注释 2025-04-21 00:54:04 +08:00
ww-rm
fcd809fda5 使用PerMonitorV2 2025-04-20 23:13:37 +08:00
ww-rm
71ff8007fb update preview 2025-04-20 21:04:42 +08:00
ww-rm
a8261f4f58 update readme 2025-04-20 21:01:24 +08:00
ww-rm
bb6a6f3664 small change 2025-04-20 17:26:26 +08:00
ww-rm
9c6af07d32 去除切换里的窗口显示 2025-04-20 17:24:38 +08:00
ww-rm
afc011adbb update readme 2025-04-20 17:10:06 +08:00
ww-rm
da42c14d35 更新至0.12.6 2025-04-20 17:09:46 +08:00
ww-rm
a7438e2026 update changelog 2025-04-20 17:09:25 +08:00
ww-rm
47e5314bb3 禁用用户关闭事件 2025-04-20 17:09:13 +08:00
ww-rm
1978c1da11 update changelog 2025-04-20 16:57:09 +08:00
ww-rm
914c9d0ea3 增加颜色预设 2025-04-20 16:55:14 +08:00
ww-rm
7134aebb7f 增加桌面投影 2025-04-20 16:14:40 +08:00
ww-rm
abb32e9ed2 修复一些奇怪的bug 2025-04-20 16:13:50 +08:00
ww-rm
2c77e385c5 small change 2025-04-20 16:13:02 +08:00
ww-rm
ba0f5ac124 修正窗口第一次会显示的问题 2025-04-20 14:23:02 +08:00
ww-rm
c4f17e3f06 延迟运行时属性在第一次调用时初始化 2025-04-20 14:05:04 +08:00
ww-rm
b802ec252a 改成属性暴露接口 2025-04-20 13:20:22 +08:00
ww-rm
68779caab0 缩短类限定名 2025-04-20 13:19:46 +08:00
ww-rm
5d819114d0 一个没问题的版本 2025-04-20 12:49:29 +08:00
ww-rm
46b3937236 增加背景颜色设置 2025-04-20 12:43:23 +08:00
ww-rm
6803b8cf4a 修改调试输出 2025-04-20 12:42:02 +08:00
ww-rm
bd9c5a176b 增加ResolutionConverter 2025-04-20 12:41:33 +08:00
ww-rm
76c1d96c87 增加实验性功能桌面投影 2025-04-19 18:58:17 +08:00
ww-rm
304af805cb 增加scaleX和scaleY 2025-04-19 14:32:56 +08:00
ww-rm
027d3af619 增加default皮肤显示 2025-04-19 12:05:04 +08:00
ww-rm
c612c01ac7 update changelog 2025-04-19 01:48:36 +08:00
ww-rm
cd7855a877 update readme 2025-04-19 01:39:34 +08:00
ww-rm
cdd81e0bfb 修改文本描述 2025-04-19 01:35:10 +08:00
36 changed files with 1354 additions and 894 deletions

View File

@@ -1,5 +1,22 @@
# CHANGELOG # CHANGELOG
## v0.12.7
- 修复一些问题
## v0.12.6
- 增加全屏预览
- 增加桌面投影 (实验性功能)
- 增加预览画面背景色设置
- 增加分辨率和颜色预设列表
- 皮肤面板显示 default
## v0.12.5
- 增加插槽属性面板
- 修改皮肤属性面板设置方式为True/False
## v0.12.4 ## v0.12.4
- 增加导出自动分辨率参数 - 增加导出自动分辨率参数

View File

@@ -1,106 +1,111 @@
# [SpineViewer](https://github.com/ww-rm/SpineViewer) # [SpineViewer](https://github.com/ww-rm/SpineViewer)
[![Build and Release](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml/badge.svg)](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml) [![Build and Release](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml/badge.svg)](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
[![GitHub Release](https://img.shields.io/github/v/release/ww-rm/SpineViewer?logo=github&logoColor=959da5&label=Release&labelColor=3f4850)](https://github.com/ww-rm/SpineViewer/releases) [![GitHub Release](https://img.shields.io/github/v/release/ww-rm/SpineViewer?logo=github&logoColor=959da5&label=Release&labelColor=3f4850)](https://github.com/ww-rm/SpineViewer/releases)
[![Downloads](https://img.shields.io/github/downloads/ww-rm/SpineViewer/total?logo=github&logoColor=959da5&label=Downloads&labelColor=3f4850)](https://github.com/ww-rm/SpineViewer/releases) [![Downloads](https://img.shields.io/github/downloads/ww-rm/SpineViewer/total?logo=github&logoColor=959da5&label=Downloads&labelColor=3f4850)](https://github.com/ww-rm/SpineViewer/releases)
[中文](README.md) | [English](README.en.md) [中文](README.md) | [English](README.en.md)
*A WYSIWYG Spine file viewer and exporter.* A *WYSIWYG* Spine file viewer & exporter.
![previewer](img/preview.webp) ![previewer](img/preview.webp)
--- ## 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 highresolution 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 | Browserfriendly 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 ## 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 systems 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 batchopen multiple skeleton files.
- Use **File > Open Single Model** to open one at a time.
| Export Format | Suitable for Scenario | ### Adjusting Content
| ------------ | ------------------------------------------------------------------------------------|
| 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. |
## Supported Spine Versions - Rightclick menu and keyboard shortcuts are available in the model list. You can multiselect to adjust parameters in batch.
- In the preview pane, you can also use mouse controls:
- **Leftclick & drag** to move a model; hold **Ctrl** to multiselect (synced with the list).
- **Rightclick & 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 | Below the preview, playback controls let you scrub through the timeline like a basic player.
| :------: | :-------------------: | :------------------: | :-----------------: |
| `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` | | | |
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 realtime 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 models 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 contents actual bounds; for animations, matches the full animation area.
- Drag and drop or paste the skeleton file/directory into the model list. ## More
- Batch open skeleton files from the File menu.
- Select a single model to open from the File menu.
### Adjusting the Preview 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).
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 models 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).
## Acknowledgements ## Acknowledgements
- [spine-runtimes](https://github.com/EsotericSoftware/spine-runtimes) - [spine-runtimes](https://github.com/EsotericSoftware/spine-runtimes)
- [SFML.Net](https://github.com/SFML/SFML.Net) - [SFML.Net](https://github.com/SFML/SFML.Net)
- [FFMpegCore](https://github.com/rosenbjerg/FFMpegCore) - [FFMpegCore](https://github.com/rosenbjerg/FFMpegCore)
--- ---
*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!
[![Stargazers over time](https://starchart.cc/ww-rm/SpineViewer.svg?variant=adaptive)](https://starchart.cc/ww-rm/SpineViewer) [![Stargazers over time](https://starchart.cc/ww-rm/SpineViewer.svg?variant=adaptive)](https://starchart.cc/ww-rm/SpineViewer)

View File

@@ -10,23 +10,37 @@
![previewer](img/preview.webp) ![previewer](img/preview.webp)
--- ## 功能
:sparkles: `v0.12.5` 新特性: 支持自定义槽位附件 :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 | 适合折腾. | | MKV/MOV | 适合折腾. |
| 自定义导出 | 除上述预设方案, 支持提供任意 FFmpeg 参数进行导出, 满足自定义复杂需求. | | 自定义导出 | 除上述预设方案, 支持提供任意 FFmpeg 参数进行导出, 满足自定义复杂需求. |
## Spine 版本支持 ## 安装
| 版本 | 查看&导出 | 格式转换 | 版本转换 | 前往 [Release](https://github.com/ww-rm/SpineViewer/releases) 界面下载压缩包.
| :---: | :---: | :---: | :---: |
| `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` | | | |
更多版本正在施工 :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` 键可以实现多选, 与左侧列表选择是联动的.
- 右键对整体画面进行拖动. - 右键对整体画面进行拖动.
- 滚轮进行画面缩放. - 滚轮进行画面缩放, 按住 `Ctrl` 可以对选中的模型进行批量缩放.
- 仅渲染选中模式, 在该模式下, 预览画面仅包含被选中的模型, 并且只能通过左侧列表改变选中状态. - 仅渲染选中模式, 在该模式下, 预览画面仅包含被选中的模型, 并且只能通过左侧列表改变选中状态.
预览画面下方按钮支持对画面时间进行调整, 可以当作一个简易的播放器. 预览画面下方按钮支持对画面时间进行调整, 可以当作一个简易的播放器.
### 预览内容导出 ### 内容导出
导出遵循 "所见即所得" 原则, 即实时预览的画面就是你导出的画面. 导出遵循 "所见即所得" 原则, 即实时预览的画面就是你导出的画面.
@@ -87,6 +94,7 @@
- 仅渲染选中. 这个参数不仅影响预览模式, 也影响导出, 如果仅渲染选中, 那么在导出时只有被选中的模型会被考虑, 忽略其他模型. - 仅渲染选中. 这个参数不仅影响预览模式, 也影响导出, 如果仅渲染选中, 那么在导出时只有被选中的模型会被考虑, 忽略其他模型.
- 输出文件夹. 这个参数某些时候可选, 当不提供时, 则将输出产物输出到每个模型各自的模型文件夹, 否则输出产物全部输出到提供的输出文件夹. - 输出文件夹. 这个参数某些时候可选, 当不提供时, 则将输出产物输出到每个模型各自的模型文件夹, 否则输出产物全部输出到提供的输出文件夹.
- 导出单个. 默认是每个模型独立导出, 即对模型列表进行批量操作, 如果选择仅导出单个, 那么被导出的所有模型将在同一个画面上被渲染, 输出产物只有一份. - 导出单个. 默认是每个模型独立导出, 即对模型列表进行批量操作, 如果选择仅导出单个, 那么被导出的所有模型将在同一个画面上被渲染, 输出产物只有一份.
- 自动分辨率. 该模式会忽略预览画面的分辨率和视区参数, 导出产物的分辨率与被导出内容的实际大小一致, 如果是动图或者视频则会与完整显示动画的必需大小一致.
### 更多 ### 更多

View File

@@ -33,7 +33,7 @@ using System.Collections.Generic;
namespace SpineRuntime21 { namespace SpineRuntime21 {
public class Bone{ public class Bone{
static public bool yDown; static readonly public bool yDown = false;
internal BoneData data; internal BoneData data;
internal Skeleton skeleton; internal Skeleton skeleton;
@@ -84,7 +84,8 @@ namespace SpineRuntime21 {
/// <summary>Computes the world SRT using the parent bone and the local SRT.</summary> /// <summary>Computes the world SRT using the parent bone and the local SRT.</summary>
public void UpdateWorldTransform () { 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; float x = this.x, y = this.y;
if (parent != null) { if (parent != null) {
worldX = x * parent.m00 + y * parent.m01 + parent.worldX; worldX = x * parent.m00 + y * parent.m01 + parent.worldX;
@@ -100,34 +101,22 @@ namespace SpineRuntime21 {
worldFlipX = parent.worldFlipX != flipX; worldFlipX = parent.worldFlipX != flipX;
worldFlipY = parent.worldFlipY != flipY; worldFlipY = parent.worldFlipY != flipY;
} else { } else {
Skeleton skeleton = this.skeleton; worldX = x * sx;
bool skeletonFlipX = skeleton.flipX, skeletonFlipY = skeleton.flipY; worldY = y * sy;
worldX = skeletonFlipX ? -x : x;
worldY = skeletonFlipY != yDown ? -y : y;
worldScaleX = scaleX; worldScaleX = scaleX;
worldScaleY = scaleY; worldScaleY = scaleY;
worldRotation = rotationIK; worldRotation = rotationIK;
worldFlipX = skeletonFlipX != flipX; worldFlipX = (sx < 0) != flipX;
worldFlipY = skeletonFlipY != flipY; worldFlipY = (sy < 0) != flipY;
} }
float radians = worldRotation * (float)Math.PI / 180; float radians = worldRotation * (float)Math.PI / 180;
float cos = (float)Math.Cos(radians); float cos = (float)Math.Cos(radians);
float sin = (float)Math.Sin(radians); float sin = (float)Math.Sin(radians);
if (worldFlipX) { m00 = cos * worldScaleX * sx;
m00 = -cos * worldScaleX; m01 = -sin * worldScaleY * sx;
m01 = sin * worldScaleY; m10 = sin * worldScaleX * sy;
} else { m11 = cos * worldScaleY * sy;
m00 = cos * worldScaleX; }
m01 = -sin * worldScaleY;
}
if (worldFlipY != yDown) {
m10 = -sin * worldScaleX;
m11 = -cos * worldScaleY;
} else {
m10 = sin * worldScaleX;
m11 = cos * worldScaleY;
}
}
public void SetToSetupPose () { public void SetToSetupPose () {
BoneData data = this.data; BoneData data = this.data;

View File

@@ -42,8 +42,8 @@ namespace SpineRuntime21 {
internal Skin skin; internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1; internal float r = 1, g = 1, b = 1, a = 1;
internal float time; internal float time;
internal bool flipX, flipY; internal float scaleX = 1, scaleY = 1;
internal float x, y; internal float x, y;
public SkeletonData Data { get { return data; } } public SkeletonData Data { get { return data; } }
public List<Bone> Bones { get { return bones; } } public List<Bone> Bones { get { return bones; } }
@@ -58,10 +58,16 @@ namespace SpineRuntime21 {
public float Time { get { return time; } set { time = value; } } public float Time { get { return time; } set { time = value; } }
public float X { get { return x; } set { x = value; } } public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } } public float Y { get { return y; } set { y = value; } }
public bool FlipX { get { return flipX; } set { flipX = value; } } public float ScaleX { get { return scaleX; } set { scaleX = value; } }
public bool FlipY { get { return flipY; } set { flipY = 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 { get {
return bones.Count == 0 ? null : bones[0]; return bones.Count == 0 ? null : bones[0];
} }

View File

@@ -152,27 +152,13 @@ namespace SpineRuntime36 {
Bone parent = this.parent; Bone parent = this.parent;
if (parent == null) { // Root bone. if (parent == null) { // Root bone.
float rotationY = rotation + 90 + shearY; float rotationY = rotation + 90 + shearY, sx = skeleton.scaleX, sy = skeleton.scaleY;
float la = MathUtils.CosDeg(rotation + shearX) * scaleX; a = MathUtils.CosDeg(rotation + shearX) * scaleX * sx;
float lb = MathUtils.CosDeg(rotationY) * scaleY; b = MathUtils.CosDeg(rotationY) * scaleY * sx;
float lc = MathUtils.SinDeg(rotation + shearX) * scaleX; c = MathUtils.SinDeg(rotation + shearX) * scaleX * sy;
float ld = MathUtils.SinDeg(rotationY) * scaleY; d = MathUtils.SinDeg(rotationY) * scaleY * sy;
if (skeleton.flipX) { worldX = x * sx + skeleton.x;
x = -x; worldY = y * sy + skeleton.y;
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;
return; return;
} }
@@ -228,13 +214,16 @@ namespace SpineRuntime36 {
case TransformMode.NoScale: case TransformMode.NoScale:
case TransformMode.NoScaleOrReflection: { case TransformMode.NoScaleOrReflection: {
float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation); float cos = MathUtils.CosDeg(rotation), sin = MathUtils.SinDeg(rotation);
float za = pa * cos + pb * sin; float za = (pa * cos + pb * sin) / skeleton.scaleX;
float zc = pc * cos + pd * sin; float zc = (pc * cos + pd * sin) / skeleton.scaleY;
float s = (float)Math.Sqrt(za * za + zc * zc); float s = (float)Math.Sqrt(za * za + zc * zc);
if (s > 0.00001f) s = 1 / s; if (s > 0.00001f) s = 1 / s;
za *= s; za *= s;
zc *= s; zc *= s;
s = (float)Math.Sqrt(za * za + zc * zc); 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 r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
float zb = MathUtils.Cos(r) * s; float zb = MathUtils.Cos(r) * s;
float zd = MathUtils.Sin(r) * s; float zd = MathUtils.Sin(r) * s;
@@ -242,26 +231,18 @@ namespace SpineRuntime36 {
float lb = MathUtils.CosDeg(90 + shearY) * scaleY; float lb = MathUtils.CosDeg(90 + shearY) * scaleY;
float lc = MathUtils.SinDeg(shearX) * scaleX; float lc = MathUtils.SinDeg(shearX) * scaleX;
float ld = MathUtils.SinDeg(90 + shearY) * scaleY; 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; a = za * la + zb * lc;
b = za * lb + zb * ld; b = za * lb + zb * ld;
c = zc * la + zd * lc; c = zc * la + zd * lc;
d = zc * lb + zd * ld; d = zc * lb + zd * ld;
return; break;
} }
} }
if (skeleton.flipX) { a *= skeleton.scaleX;
a = -a; b *= skeleton.scaleX;
b = -b; c *= skeleton.scaleY;
} d *= skeleton.scaleY;
if (skeleton.flipY != Bone.yDown) {
c = -c;
d = -d;
}
} }
public void SetToSetupPose () { public void SetToSetupPose () {

View File

@@ -45,8 +45,8 @@ namespace SpineRuntime36 {
internal Skin skin; internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1; internal float r = 1, g = 1, b = 1, a = 1;
internal float time; internal float time;
internal bool flipX, flipY; internal float scaleX = 1, scaleY = 1;
internal float x, y; internal float x, y;
public SkeletonData Data { get { return data; } } public SkeletonData Data { get { return data; } }
public ExposedList<Bone> Bones { get { return bones; } } public ExposedList<Bone> Bones { get { return bones; } }
@@ -64,10 +64,16 @@ namespace SpineRuntime36 {
public float Time { get { return time; } set { time = value; } } public float Time { get { return time; } set { time = value; } }
public float X { get { return x; } set { x = value; } } public float X { get { return x; } set { x = value; } }
public float Y { get { return y; } set { y = value; } } public float Y { get { return y; } set { y = value; } }
public bool FlipX { get { return flipX; } set { flipX = value; } } public float ScaleX { get { return scaleX; } set { scaleX = value; } }
public bool FlipY { get { return flipY; } set { flipY = 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]; } get { return bones.Count == 0 ? null : bones.Items[0]; }
} }

View File

@@ -17,13 +17,14 @@ namespace SpineViewer.Controls
public SkelFileListBox() public SkelFileListBox()
{ {
InitializeComponent(); InitializeComponent();
Items = listBox.Items;
} }
/// <summary> /// <summary>
/// ListBox.Items /// ListBox.Items
/// </summary> /// </summary>
public readonly ListBox.ObjectCollection Items; [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false)]
public ListBox.ObjectCollection Items { get => listBox.Items; }
/// <summary> /// <summary>
/// 从路径列表添加 /// 从路径列表添加

View File

@@ -29,12 +29,12 @@ namespace SpineViewer.Controls
/// <summary> /// <summary>
/// Spine 列表只读视图, 访问时必须使用 lock 语句锁定视图本身 /// Spine 列表只读视图, 访问时必须使用 lock 语句锁定视图本身
/// </summary> /// </summary>
public readonly ReadOnlyCollection<Spine.SpineObject> Spines; public readonly ReadOnlyCollection<SpineObject> Spines;
/// <summary> /// <summary>
/// Spine 列表, 访问时必须使用 lock 语句锁定只读视图 Spines /// Spine 列表, 访问时必须使用 lock 语句锁定只读视图 Spines
/// </summary> /// </summary>
private readonly List<Spine.SpineObject> spines = []; private readonly List<SpineObject> spines = [];
/// <summary> /// <summary>
/// 用于属性页显示模型参数的包装类 /// 用于属性页显示模型参数的包装类
@@ -80,7 +80,7 @@ namespace SpineViewer.Controls
{ {
try 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) if (index < 0 || index > listView.Items.Count)
@@ -155,7 +155,7 @@ namespace SpineViewer.Controls
try try
{ {
var spine = Spine.SpineObject.New(version, skelPath); var spine = SpineObject.New(version, skelPath);
var preview = spine.Preview; var preview = spine.Preview;
lock (Spines) { spines.Add(spine); } lock (Spines) { spines.Add(spine); }
spinePropertyWrappers[spine.ID] = new(spine); spinePropertyWrappers[spine.ID] = new(spine);

View File

@@ -32,7 +32,6 @@
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SpinePreviewPanel)); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SpinePreviewPanel));
panel_Render = new Panel(); panel_Render = new Panel();
tableLayoutPanel1 = new TableLayoutPanel(); tableLayoutPanel1 = new TableLayoutPanel();
panel_Container = new Panel();
flowLayoutPanel1 = new FlowLayoutPanel(); flowLayoutPanel1 = new FlowLayoutPanel();
button_Stop = new Button(); button_Stop = new Button();
imageList = new ImageList(components); imageList = new ImageList(components);
@@ -40,10 +39,16 @@
button_Start = new Button(); button_Start = new Button();
button_ForwardStep = new Button(); button_ForwardStep = new Button();
button_ForwardFast = new Button(); button_ForwardFast = new Button();
button_FullScreen = new Button();
panel_ViewContainer = new Panel();
panel_RenderContainer = new Panel();
toolTip = new ToolTip(components); toolTip = new ToolTip(components);
spinePreviewFullScreenForm = new SpineViewer.Forms.SpinePreviewFullScreenForm();
wallpaperForm = new WallpaperForm();
tableLayoutPanel1.SuspendLayout(); tableLayoutPanel1.SuspendLayout();
panel_Container.SuspendLayout();
flowLayoutPanel1.SuspendLayout(); flowLayoutPanel1.SuspendLayout();
panel_ViewContainer.SuspendLayout();
panel_RenderContainer.SuspendLayout();
SuspendLayout(); SuspendLayout();
// //
// panel_Render // panel_Render
@@ -63,8 +68,8 @@
// //
tableLayoutPanel1.ColumnCount = 1; tableLayoutPanel1.ColumnCount = 1;
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
tableLayoutPanel1.Controls.Add(panel_Container, 0, 0);
tableLayoutPanel1.Controls.Add(flowLayoutPanel1, 0, 1); tableLayoutPanel1.Controls.Add(flowLayoutPanel1, 0, 1);
tableLayoutPanel1.Controls.Add(panel_ViewContainer, 0, 0);
tableLayoutPanel1.Dock = DockStyle.Fill; tableLayoutPanel1.Dock = DockStyle.Fill;
tableLayoutPanel1.Location = new Point(0, 0); tableLayoutPanel1.Location = new Point(0, 0);
tableLayoutPanel1.Margin = new Padding(0); tableLayoutPanel1.Margin = new Padding(0);
@@ -75,17 +80,6 @@
tableLayoutPanel1.Size = new Size(641, 636); tableLayoutPanel1.Size = new Size(641, 636);
tableLayoutPanel1.TabIndex = 2; 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
// //
flowLayoutPanel1.Anchor = AnchorStyles.None; flowLayoutPanel1.Anchor = AnchorStyles.None;
@@ -96,10 +90,11 @@
flowLayoutPanel1.Controls.Add(button_Start); flowLayoutPanel1.Controls.Add(button_Start);
flowLayoutPanel1.Controls.Add(button_ForwardStep); flowLayoutPanel1.Controls.Add(button_ForwardStep);
flowLayoutPanel1.Controls.Add(button_ForwardFast); 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.Margin = new Padding(0);
flowLayoutPanel1.Name = "flowLayoutPanel1"; flowLayoutPanel1.Name = "flowLayoutPanel1";
flowLayoutPanel1.Size = new Size(365, 42); flowLayoutPanel1.Size = new Size(438, 42);
flowLayoutPanel1.TabIndex = 1; flowLayoutPanel1.TabIndex = 1;
// //
// button_Stop // button_Stop
@@ -122,18 +117,19 @@
imageList.ColorDepth = ColorDepth.Depth32Bit; imageList.ColorDepth = ColorDepth.Depth32Bit;
imageList.ImageStream = (ImageListStreamer)resources.GetObject("imageList.ImageStream"); imageList.ImageStream = (ImageListStreamer)resources.GetObject("imageList.ImageStream");
imageList.TransparentColor = Color.Transparent; imageList.TransparentColor = Color.Transparent;
imageList.Images.SetKeyName(0, "stop"); imageList.Images.SetKeyName(0, "arrows-maximize");
imageList.Images.SetKeyName(1, "restart"); imageList.Images.SetKeyName(1, "forward-fast");
imageList.Images.SetKeyName(2, "start"); imageList.Images.SetKeyName(2, "forward-step");
imageList.Images.SetKeyName(3, "pause"); imageList.Images.SetKeyName(3, "pause");
imageList.Images.SetKeyName(4, "forward-step"); imageList.Images.SetKeyName(4, "rotate-left");
imageList.Images.SetKeyName(5, "forward-fast"); imageList.Images.SetKeyName(5, "start");
imageList.Images.SetKeyName(6, "stop");
// //
// button_Restart // button_Restart
// //
button_Restart.AutoSize = true; button_Restart.AutoSize = true;
button_Restart.AutoSizeMode = AutoSizeMode.GrowAndShrink; button_Restart.AutoSizeMode = AutoSizeMode.GrowAndShrink;
button_Restart.ImageKey = "restart"; button_Restart.ImageKey = "rotate-left";
button_Restart.ImageList = imageList; button_Restart.ImageList = imageList;
button_Restart.Location = new Point(76, 3); button_Restart.Location = new Point(76, 3);
button_Restart.Name = "button_Restart"; button_Restart.Name = "button_Restart";
@@ -190,6 +186,74 @@
button_ForwardFast.UseVisualStyleBackColor = true; button_ForwardFast.UseVisualStyleBackColor = true;
button_ForwardFast.Click += button_ForwardFast_Click; 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 // SpinePreviewPanel
// //
AutoScaleDimensions = new SizeF(11F, 24F); AutoScaleDimensions = new SizeF(11F, 24F);
@@ -197,12 +261,12 @@
Controls.Add(tableLayoutPanel1); Controls.Add(tableLayoutPanel1);
Name = "SpinePreviewPanel"; Name = "SpinePreviewPanel";
Size = new Size(641, 636); Size = new Size(641, 636);
SizeChanged += SpinePreviewPanel_SizeChanged;
tableLayoutPanel1.ResumeLayout(false); tableLayoutPanel1.ResumeLayout(false);
tableLayoutPanel1.PerformLayout(); tableLayoutPanel1.PerformLayout();
panel_Container.ResumeLayout(false);
flowLayoutPanel1.ResumeLayout(false); flowLayoutPanel1.ResumeLayout(false);
flowLayoutPanel1.PerformLayout(); flowLayoutPanel1.PerformLayout();
panel_ViewContainer.ResumeLayout(false);
panel_RenderContainer.ResumeLayout(false);
ResumeLayout(false); ResumeLayout(false);
} }
@@ -210,7 +274,7 @@
private Panel panel_Render; private Panel panel_Render;
private TableLayoutPanel tableLayoutPanel1; private TableLayoutPanel tableLayoutPanel1;
private Panel panel_Container; private Panel panel_RenderContainer;
private FlowLayoutPanel flowLayoutPanel1; private FlowLayoutPanel flowLayoutPanel1;
private Button button_Stop; private Button button_Stop;
private Button button_Start; private Button button_Start;
@@ -219,5 +283,9 @@
private Button button_ForwardStep; private Button button_ForwardStep;
private Button button_ForwardFast; private Button button_ForwardFast;
private Button button_Restart; private Button button_Restart;
private Button button_FullScreen;
private Panel panel_ViewContainer;
private Forms.SpinePreviewFullScreenForm spinePreviewFullScreenForm;
private WallpaperForm wallpaperForm;
} }
} }

View File

@@ -11,6 +11,7 @@ using System.Security.Policy;
using System.Diagnostics; using System.Diagnostics;
using NLog; using NLog;
using SpineViewer.Utils; using SpineViewer.Utils;
using System.Drawing.Design;
namespace SpineViewer.Controls namespace SpineViewer.Controls
{ {
@@ -19,14 +20,6 @@ namespace SpineViewer.Controls
public SpinePreviewPanel() public SpinePreviewPanel()
{ {
InitializeComponent(); InitializeComponent();
renderWindow = new(panel_Render.Handle);
renderWindow.SetActive(false);
// 设置默认参数
Resolution = new(2048, 2048);
Center = new(0, 0);
FlipY = true;
MaxFps = 30;
} }
/// <summary> /// <summary>
@@ -68,42 +61,37 @@ namespace SpineViewer.Controls
get => resolution; get => resolution;
set set
{ {
if (renderWindow is null) return;
if (value == resolution) return;
if (value.Width <= 0) value.Width = 100; if (value.Width <= 0) value.Width = 100;
if (value.Height <= 0) value.Height = 100; if (value.Height <= 0) value.Height = 100;
float parentX = panel_Render.Parent.Width; var previousZoom = Zoom;
float parentY = panel_Render.Parent.Height;
float sizeX = value.Width;
float sizeY = value.Height;
if ((sizeY / sizeX) < (parentY / parentX)) float parentW = panel_Render.Parent.Width;
{ float parentH = panel_Render.Parent.Height;
// 相同的 X, 子窗口 Y 更小 float renderW = value.Width;
sizeY = parentX * sizeY / sizeX; float renderH = value.Height;
sizeX = parentX; float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render
} renderW *= scale;
else renderH *= scale;
{
// 相同的 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);
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 = 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> /// <summary>
/// 画面中心点 /// 画面中心点
@@ -114,15 +102,20 @@ namespace SpineViewer.Controls
{ {
get get
{ {
if (renderWindow is null) return new(-1, -1);
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
var center = view.Center; var center = view.Center;
return new(center.X, center.Y); return new(center.X, center.Y);
} }
set set
{ {
if (renderWindow is null) return;
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
view.Center = new(value.X, value.Y); view.Center = new(value.X, value.Y);
renderWindow.SetView(view); renderWindow.SetView(view);
wallpaperWindow.SetView(view);
} }
} }
@@ -135,17 +128,22 @@ namespace SpineViewer.Controls
{ {
get get
{ {
if (renderWindow is null) return -1;
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
return resolution.Width / Math.Abs(view.Size.X); return resolution.Width / Math.Abs(view.Size.X);
} }
set set
{ {
if (renderWindow is null) return;
value = Math.Clamp(value, 0.001f, 1000f); value = Math.Clamp(value, 0.001f, 1000f);
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
var signX = Math.Sign(view.Size.X); var signX = Math.Sign(view.Size.X);
var signY = Math.Sign(view.Size.Y); var signY = Math.Sign(view.Size.Y);
view.Size = new(resolution.Width / value * signX, resolution.Height / value * signY); view.Size = new(resolution.Width / value * signX, resolution.Height / value * signY);
renderWindow.SetView(view); renderWindow.SetView(view);
wallpaperWindow.SetView(view);
} }
} }
@@ -158,14 +156,19 @@ namespace SpineViewer.Controls
{ {
get get
{ {
if (renderWindow is null) return -1;
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
return view.Rotation; return view.Rotation;
} }
set set
{ {
if (renderWindow is null) return;
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
view.Rotation = value; view.Rotation = value;
renderWindow.SetView(view); renderWindow.SetView(view);
wallpaperWindow.SetView(view);
} }
} }
@@ -178,17 +181,22 @@ namespace SpineViewer.Controls
{ {
get get
{ {
if (renderWindow is null) return false;
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
return view.Size.X < 0; return view.Size.X < 0;
} }
set set
{ {
if (renderWindow is null) return;
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
var size = view.Size; var size = view.Size;
if (size.X > 0 && value || size.X < 0 && !value) if (size.X > 0 && value || size.X < 0 && !value)
size.X *= -1; size.X *= -1;
view.Size = size; view.Size = size;
renderWindow.SetView(view); renderWindow.SetView(view);
wallpaperWindow.SetView(view);
} }
} }
@@ -201,17 +209,22 @@ namespace SpineViewer.Controls
{ {
get get
{ {
if (renderWindow is null) return false;
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
return view.Size.Y < 0; return view.Size.Y < 0;
} }
set set
{ {
if (renderWindow is null) return;
using var view = renderWindow.GetView(); using var view = renderWindow.GetView();
var size = view.Size; var size = view.Size;
if (size.Y > 0 && value || size.Y < 0 && !value) if (size.Y > 0 && value || size.Y < 0 && !value)
size.Y *= -1; size.Y *= -1;
view.Size = size; view.Size = size;
renderWindow.SetView(view); renderWindow.SetView(view);
wallpaperWindow.SetView(view);
} }
} }
@@ -234,7 +247,17 @@ namespace SpineViewer.Controls
/// </summary> /// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false)] [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; private uint maxFps = 60;
/// <summary> /// <summary>
@@ -242,14 +265,45 @@ namespace SpineViewer.Controls
/// </summary> /// </summary>
public SFML.Graphics.View GetView() => renderWindow.GetView(); 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>
/// 预览画面背景色 /// 预览画面背景色
/// </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> /// <summary>
/// 预览画面坐标轴颜色 /// 预览画面坐标轴颜色
@@ -264,7 +318,12 @@ namespace SpineViewer.Controls
/// <summary> /// <summary>
/// 渲染窗口 /// 渲染窗口
/// </summary> /// </summary>
private readonly SFML.Graphics.RenderWindow renderWindow; private SFML.Graphics.RenderWindow renderWindow;
/// <summary>
/// 壁纸窗口
/// </summary>
private SFML.Graphics.RenderWindow wallpaperWindow;
/// <summary> /// <summary>
/// 帧间隔计时器 /// 帧间隔计时器
@@ -312,11 +371,27 @@ namespace SpineViewer.Controls
/// </summary> /// </summary>
public void StartRender() 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(); cancelToken = new();
task = Task.Run(RenderTask, cancelToken.Token); task = Task.Run(RenderTask, cancelToken.Token);
IsUpdating = true; IsUpdating = true;
if (enableDesktopProjection) wallpaperForm.Show();
} }
/// <summary> /// <summary>
@@ -324,6 +399,8 @@ namespace SpineViewer.Controls
/// </summary> /// </summary>
public void StopRender() public void StopRender()
{ {
if (wallpaperForm.InvokeRequired) wallpaperForm.Invoke(wallpaperForm.Hide);
else wallpaperForm.Hide();
IsUpdating = false; IsUpdating = false;
if (task is null || cancelToken is null) if (task is null || cancelToken is null)
return; return;
@@ -341,10 +418,14 @@ namespace SpineViewer.Controls
try try
{ {
renderWindow.SetActive(true); renderWindow.SetActive(true);
wallpaperWindow.SetActive(true);
float delta; float delta;
while (cancelToken is not null && !cancelToken.IsCancellationRequested) while (cancelToken is not null && !cancelToken.IsCancellationRequested)
{ {
// 必须让 SFML 有机会处理窗口消息, 例如位置和大小变化
renderWindow.DispatchEvents();
delta = clock.ElapsedTime.AsSeconds(); delta = clock.ElapsedTime.AsSeconds();
clock.Restart(); clock.Restart();
@@ -359,6 +440,7 @@ namespace SpineViewer.Controls
} }
renderWindow.Clear(BackgroundColor); renderWindow.Clear(BackgroundColor);
if (enableDesktopProjection) wallpaperWindow.Clear(BackgroundColor);
if (ShowAxis) if (ShowAxis)
{ {
@@ -392,11 +474,15 @@ namespace SpineViewer.Controls
spine.EnableDebug = true; spine.EnableDebug = true;
renderWindow.Draw(spine); renderWindow.Draw(spine);
spine.EnableDebug = false; spine.EnableDebug = false;
if (enableDesktopProjection) wallpaperWindow.Draw(spine);
} }
} }
} }
renderWindow.Display(); renderWindow.Display();
if (enableDesktopProjection) wallpaperWindow.Display();
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -408,6 +494,7 @@ namespace SpineViewer.Controls
finally finally
{ {
renderWindow.SetActive(false); renderWindow.SetActive(false);
wallpaperWindow.SetActive(false);
} }
} }
@@ -418,26 +505,26 @@ namespace SpineViewer.Controls
/// </summary> /// </summary>
private SFML.System.Vector2f? draggingSrc = null; 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) if (renderWindow is null) return;
return;
float parentW = panel_Render.Parent.Width; float parentW = panel_Render.Parent.Width;
float parentH = panel_Render.Parent.Height; float parentH = panel_Render.Parent.Height;
float renderW = panel_Render.Width; float renderW = panel_Render.Width;
float renderH = panel_Render.Height; float renderH = panel_Render.Height;
float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render
renderH *= scale;
renderW *= scale; renderW *= scale;
renderH *= scale;
// 必须通过 SFML 的方法调整窗口 panel_Render.Location = new((int)((parentW - renderW) / 2 + 0.5), (int)((parentH - renderH) / 2 + 0.5));
renderWindow.Position = new((int)(parentW - renderW) / 2, (int)(parentH - renderH) / 2); panel_Render.Size = new((int)(renderW + 0.5), (int)(renderH + 0.5));
renderWindow.Size = new((uint)renderW, (uint)renderH);
} }
private void panel_Render_MouseDown(object sender, MouseEventArgs e) private void panel_Render_MouseDown(object sender, MouseEventArgs e)
{ {
if (renderWindow is null) return;
// 右键优先级高, 进入画面拖动模式, 需要重新记录源点 // 右键优先级高, 进入画面拖动模式, 需要重新记录源点
if ((e.Button & MouseButtons.Right) != 0) if ((e.Button & MouseButtons.Right) != 0)
{ {
@@ -517,8 +604,9 @@ namespace SpineViewer.Controls
private void panel_Render_MouseMove(object sender, MouseEventArgs e) private void panel_Render_MouseMove(object sender, MouseEventArgs e)
{ {
if (draggingSrc is null) if (renderWindow is null) return;
return;
if (draggingSrc is null) return;
var src = (SFML.System.Vector2f)draggingSrc; var src = (SFML.System.Vector2f)draggingSrc;
var dst = renderWindow.MapPixelToCoords(new(e.X, e.Y)); var dst = renderWindow.MapPixelToCoords(new(e.X, e.Y));
@@ -645,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 ClickStopButton() => button_Stop_Click(button_Stop, EventArgs.Empty);
//public void ClickRestartButton() => button_Restart_Click(button_Restart, EventArgs.Empty); //public void ClickRestartButton() => button_Restart_Click(button_Restart, EventArgs.Empty);
//public void ClickStartButton() => button_Start_Click(button_Start, EventArgs.Empty); //public void ClickStartButton() => button_Start_Click(button_Start, EventArgs.Empty);
@@ -660,7 +783,8 @@ namespace SpineViewer.Controls
[Browsable(false)] [Browsable(false)]
public SpinePreviewPanel PreviewPanel { get; } = previewPanel; public SpinePreviewPanel PreviewPanel { get; } = previewPanel;
[TypeConverter(typeof(SizeConverter))] [RefreshProperties(RefreshProperties.All)]
[TypeConverter(typeof(ResolutionConverter))]
[Category("[0] "), DisplayName("")] [Category("[0] "), DisplayName("")]
public Size Resolution { get => PreviewPanel.Resolution; set => PreviewPanel.Resolution = value; } public Size Resolution { get => PreviewPanel.Resolution; set => PreviewPanel.Resolution = value; }
@@ -688,5 +812,10 @@ namespace SpineViewer.Controls
[Category("[1] "), DisplayName("")] [Category("[1] "), DisplayName("")]
public uint MaxFps { get => PreviewPanel.MaxFps; set => PreviewPanel.MaxFps = value; } 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; }
} }
} }

View File

@@ -124,203 +124,170 @@
<value> <value>
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAHi0AAAJNU0Z0AUkBTAIBAQYB SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAA8iMAAAJNU0Z0AUkBTAIBAQcB
AAF4AQABeAEAAR8BAAEYAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABfAMAATADAAEBAQABIAYAAV0q AAGQAQABkAEAAR8BAAEYAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABfAMAATADAAEBAQABIAYAAV0+
AAQCAy0BRQNbAc0DXwHoA1kBxgMyAU8DDwEUBAIYAAMOARIDQwF3A10BzwNbAc0DLQFFBAIYAANWAbID AAMEAQUDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf9DAAH/AwAB/wMAAf8DAAH/A1UB
XwHoA1wB1gNDAXcDFgEeBAIYAAMKAQ0DSQGFA18B4wNfAeUDUQGeAyQBNAMJAQsEARgAAwsBDgM7AWQD sWQAA1gB7wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
XgHSA1YBsv8AEQAEAgMxAUwDYgHhAwAB/wMxAfkDYAHbA0QBewMeASoDBgEIFAADGAEhA1cBwgMhAfsD AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/Ay0BRbcAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
YgHhAzEBTAQCGAADWQHDAwAB/wMjAfwDXgHrA0gBhAMWAR4YAAMLAQ4DTQGSAy4B+QMQAf4DUwHyA1gB AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf87AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf9cAANEAXgDAAH/AwAB
ugM3AVoDEQEWAwIBAxQAAxcBHwNJAYYDXgHrA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wMAAf8DIAH9A2AB /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
4ANQAZoDLgFGAxEBFgMGAQcEAQgAAxoBJANbAc0DAAH/A2IB4QMxAUwEAhgAA1kBwwMAAf8DAAH/AwAB /wMAAf8DAAH/AwAB/wMAAf8DAAH/swAB/wMAAf8DAAH/AwAB/wMAAf8XAAH/AwAB/wMAAf8DAAH/AwAB
/wNbAdADPgFrAw8BEwMCAQMQAAMLAQ4DTQGSAy4B+QMAAf8DAAH/AzwB9gNcAcsDRAF5Ax4BKgMGAQcM /zcAAf8DAAH/BwAB/wMAAf8DAAH/AwAB/wMAAf9XAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
AAQBAxgBIQNKAYsDXAHsA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wMxAfkDXAHnA0QB9QNXAe4DWQG7A0MB /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
dwMoATsDDwEUBAEEAAMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMDIgH8A1EB8ANEAfUDMQH5A10B /wMAAf+vAAH/AwAB/wNRAaQnAAH/AwAB/wMAAf8DAAH/MwAB/wMAAf8IAANOAZcDAAH/AwAB/wMAAf8D
zgNDAXcDGgEjAwIBAwwAAwsBDgNNAZIDLgH5AwEB/wMlAfoDOwH4AzEB+QNeAeMDTgGYAyQBNQMGAQgE AAH/Ay4BSE8AAf8DAAH/AwAB/0sAAf8DAAH/AwAB/+AAAxUBHQMAAf8DAAH/AwAB/y8AAf8DAAH/EwAB
AQQABAEDGAEhA0oBiwNcAewDWQHD/wARAAQCAzEBTANiAeEDAAH/A1wB2QM7AWMDWQG7Az8B9wM6AfgD /wMAAf8DAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf/kAAMmATgDAAH/AwAB/wMAAf8r
XAHnA1sBxQNBAXMDEwEZAwIBAwMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMDXgHrA1ABmgNVAbED AAH/AwAB/xsAAf8DAAH/AwAB/wMAAf8DAAH/QwAB/wMAAf8DAAH/SwAB/wMAAf8DAAH/6wAB/wMAAf8D
TgHzAyEB+wNbAeQDSwGPAxsBJQMDAQQIAAMLAQ4DTQGSAy4B+QMhAf0DXAHZA1oBxwNEAfUDEAH+A14B AAH/KwAB/wMAAf8cAANCAfYDAAH/AwAB/wMAAf8DAAH/AwQBBTsAAf8DAAH/AwAB/0sAAf8DAAH/AwAB
6wNSAaMDJQE3AwMBBAQABAEDGAEhA0oBiwNcAewDWQHD/wARAAQCAzEBTANiAeEDAAH/A1sBzQMaASQD /+8AAf8DAAH/AwAB/ycAAf8DAAH/JwAB/wMAAf8DAAH/AwAB/wMAAf83AAH/AwAB/wMAAf9LAAH/AwAB
FgEdA1UBrwMAAf8DAAH/AwAB/wMAAf8DVQGvAxYBHQMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMD /wMAAf/vAAH/AwAB/wMAAf8nAAH/AwAB/ygAAwcBCQMAAf8DAAH/AwAB/wMAAf8DYAHjLwAB/wMAAf8D
WwHkAzsBZQMHAQkDSQGGAyEB+wMAAf8DAAH/A1kBwQMdASkIAAMLAQ4DTQGSAy4B+QMuAfkDTQGSAzkE AAH/SwAB/wMAAf8DAAH/7AADIAEtAwAB/wMAAf8nAAH/AwAB/zMAAf8DAAH/AwAB/wMAAf8DAAH/KwAB
XgHiAwAB/wMAAf8DXgHjAzYBWAMFAQYEAAQBAxgBIQNKAYsDXAHsA1kBw/8AEQAEAgMxAUwDYgHhAwAB /wMAAf8DAAH/SwAB/wMAAf8DAAH/8wAB/wMAAf8nAAH/AwAB/zsAAf8DAAH/AwAB/wMAAf8nAAH/AwAB
/wNbAc0DGgEkAwIBAwMTARoDRgF/A1sB3gMhAfsDEAH+AzoB+ANZAbsDOwFjA1wB2QMAAf8DYgHhAzEB /wMAAf9LAAH/AwAB/wMAAf/zAAH/AwAB/ycAAf8DAAH/PAADPwFsAwAB/wMAAf8nAAH/AwAB/wMAAf9L
TAQCGAADWQHDA1sB5AM7AWUDBwQJAQwDOgFhA18B1QNDAfUDMQH5A1sBygMyAU8DDwEUAw0BEQNNAZID AAH/AwAB/wMAAf/zAAH/AwAB/ycAAf8DAAH/OwAB/wMAAf8DAAH/AwAB/ycAAf8DAAH/AwAB/0sAAf8D
LgH5Ay4B+QNNAZIDEQEWAy0BRANaAb8DVAHvAyEB+wNdAdwDPwFuAxYBHgMEAQUDGAEhA0oBiwNcAewD AAH/AwAB/5sAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/NAADCAEKAwAB/wMAAf8nAAH/AwAB
WQHD/wARAAQCAzEBTANiAeEDAAH/A1sBzQMaASQEAAQCAxMBGgM9AWcDWQHAA1QB7wMQAf4DPwH3A1wB /zAAA10BzgMAAf8DAAH/AwAB/wMAAf8EAScAAf8DAAH/AwAB/0sAAf8DAAH/AwAB/5QAAwUBBgMAAf8D
5wMxAfkDAAH/A2IB4QMxAUwEAhgAA1kBwwNbAeQDOwFlAwcBCQQAAwsBDgMxAU0DWAG9AzwB9gMxAfkD AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/zMAAf8DAAH/AwAB/ycAAf8DAAH/LwAB/wMAAf8D
YAHbA0QBeAMhAS8DTgGVAy4B+QMuAfkDTQGSAwsBDgMGAQgDIAEuA00BkgNdAdwDRAH1A1oB6QNOAZYD AAH/AwAB/wMAAf8vAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf+UAAMFAQYDAAH/AwAB/wMAAf8DAAH/AwAB
KAE8AyABLQNLAY0DXAHsA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wNbAc0DGgEkCAAEAgMMARADLwFJA1IB /wMAAf8DAAH/AwAB/wMAAf8zAAH/AwAB/wMAAf8nAAH/AwAB/ycAAf8DAAH/AwAB/wMAAf8DAAH/NwAB
owNbAeQDPwH3AxAB/gMAAf8DAAH/A2IB4QMxAUwEAhgAA1kBwwNbAeQDOwFlAwcBCQgAAxABFQM/AW0D /wMAAf8DAAH/SwAB/wMAAf8DAAH/lAADBQEGAwAB/wMAAf8PAAH/AwAB/wMAAf8DFQEcLwAB/wMAAf8D
XAHWAyUB+gMgAf0DXAHWA0QBeQNUAasDIQH7Ay4B+QNNAZIDCwEOBAADAwEEAxsBJgNBAXMDWgHEA0cB AAH/KwAB/wMAAf8cAAM9AWkDAAH/AwAB/wMAAf8DAAH/A0MBdjsAAf8DAAH/AwAB/0sAAf8DAAH/AwAB
9ANXAe4DVQG0A0QBegNRAaIDVwHuA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wNbAc0DGgEkEAADCAEKAyMB /5QAAwUBBgMAAf8DAAH/CwAB/wMAAf8DAAH/AxMBGjMAAf8DAAH/AwAB/ysAAf8DAAH/GwAB/wMAAf8D
MwNEAXkDVwG8A18B5QMlAfoDAAH/A2IB4QMxAUwEAhgAA1kBwwNbAeQDOwFlAwcBCQgAAwMBBAMaASQD AAH/AwAB/wMAAf9DAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf+UAAMFAQYDAAH/AwAB/wcAAf8DAAH/AwAB
QgF0A14B0gMhAfsDPAH2A10B0QNgAeADIAH9Ay4B+QNNAZIDCwEOCAADAgEDAxIBFwM1AVUDWAG6A1YB /wMbASYzAAH/AwAB/wMAAf8vAAH/AwAB/xMAAf8DAAH/AwAB/wMAAf8DAAH/SwAB/wMAAf8DAAH/SwAB
8QM8AfYDWwHeA2AB2wM6AfgDWQHD/wARAAQCAzEBTANiAeEDAAH/A1sBzQMaASQUAAMCAQMDDgESAyMB /wMAAf8DAAH/lAADBQEGAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMdASgkAANZAe4DAAH/AwAB
MgM9AWgDXgHdAwAB/wNiAeEDMQFMBAIYAANZAcMDWwHkAzsBZQMHAQkMAAMCAQMDDwETAzQBUwNdAdED /wMAAf8EAS8AAf8DAAH/CAADGAEhAwAB/wMAAf8DAAH/AwAB/wNcActPAAH/AwAB/wMAAf9LAAH/AwAB
OgH4AyEB/AMhAf0DAAH/Ay4B+QNNAZIDCwEODAAEAQMJAQwDIwEzA1UBrgMiAf0DIAH9AyEB/AMQAf4D /wMAAf+UAAMFAQYDAAH/AwAB/wMAAf8DAAH/A2AB4wMAAf8DAAH/AwAB/wMAAf8DUAGfFwAB/wMAAf8D
WQHD/wARAAQCAzEBTANiAeEDAAH/A1sBzQMaASQgAAMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMD AAH/AwAB/wMAAf83AAH/AwAB/wcAAf8DAAH/AwAB/wMAAf8DAAH/VwAB/wMAAf8DAAH/AwAB/wMqAUAD
WwHkAzsBZQMHAQkYAAMPARQDVwG5AwAB/wMAAf8DAAH/Ay4B+QNNAZIDCwEOGAADRwGAA14B7QMAAf8D KgFAAyoBQAMqAUADKgFAAyoBQAMqAUADKgFAAyoBQAMqAUADKgFAAyoBQAMqAUADKgFAAyoBQAMqAUAD
AAH/AwAB/wNZAcP/ABEABAIDMQFMA2IB4QMAAf8DWwHNAxoBJBQABAIDBwEJAxYBHQM1AVUDXAHZAwAB AAH/AwAB/wMAAf8DAAH/mwAB/wMAAf8DAAH/Ax0BKQQAAwIBAwMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
/wNiAeEDMQFMBAIYAANZAcMDWwHkAzsBZQMHAQkMAAQBAw0BEQM0AVMDXQHRAzEB+QMRAf4DEAH+AwAB /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf87AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DBwEJWAAD
/wMuAfkDTQGSAwsBDgwABAEDCQELAx4BKwNTAakDIgH9AyAB/QMhAfwDEAH+A1kBw/8AEQAEAgMxAUwD WQHDAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
YgHhAwAB/wNbAc0DGgEkEAADBwEJAxsBJgM0AVMDTQGSA14B3QMxAfkDAAH/A2IB4QMxAUwEAhgAA1kB AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/5wAAxIBFwMAAf8UAANKAYkDAAH/AwAB/wMAAf8D
wwNbAeQDOwFlAwcBCQgABAIDEAEVAzkBXgNbAc0DIQH7AyEB+wNcAecDVwHuAxAB/gMuAfkDTQGSAwsB AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf9DAAH/AwAB/wMAAf8DAAH/AyEB+2cAAf8DAAH/AwAB/wMAAf8D
DggAAwIBAwMSARcDNQFVA1gBuANWAfEDPAH2A1sB3gNgAdsDOgH4A1kBw/8AEQAEAgMxAUwDYgHhAwAB AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
/wNbAc0DGgEkCAAEAgMMARADLgFGA00BkgNcAcgDXgHrAyAB/QMAAf8DAAH/A2IB4QMxAUwEAhgAA1kB AAH/AwAB/wNbAcXIAANGAYADWgG/Ay4BSFQAA1oBv3QAA0YBgANaAb8DWgG/A1oBvwNaAb8DWgG/A1oB
wwNbAeQDOwFlAwcBCQgAAwkBDAMxAU4DWAG3A04B8wMgAf0DXgHdA04BlgNZAb4DIQH8Ay4B+QNNAZID vwNaAb8DWgG/A1oBvwNaAb8DWgG/A1oBvwNaAb8DWgG/A1oBvwNaAb8DLgFIpwAB/wMAAf8DAAH/AwAB
CwEOBAADAwEEAxsBJgNBAXMDWgHEA0cB9ANXAe4DVQG0A0QBegNRAaIDVwHuA1kBw/8AEQAEAgMxAUwD /wMAAf8DAAH/AwAB/wMAAf8DAAH/FAADPQFpAwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
YgHhAwAB/wNbAc0DGgEkBAAEAgMTARoDPQFnA1oBvwNeAesDJQH6Az0B9gNcAecDMQH5AwAB/wNiAeED AAH/EwAB/wMAAf8DAAH/LwAB/wMAAf8DAAH/KAADGQEiAwAB/wMAAf8jAAH/AwAB/wMAAf8oAAM/AW0D
MQFMBAIYAANZAcMDWwHkAzsBZQMHAQkEAAMLAQ4DLgFHA1UBrQNUAe8DOwH4A2AB2wNEAXoDJAE1A04B AAH/AwAB/zsAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8TAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8D
mAMkAfoDLgH5A00BkgMLAQ4DBgEIAyABLgNNAZIDXQHcA0QB9QNaAekDTgGWAygBPAMgAS0DSwGNA1wB AAH/KwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/FAADAwEEAwAB/wMAAf8DAAH/AwAB
7ANZAcP/ABEABAIDMQFMA2IB4QMAAf8DWwHNAxoBJAMCAQMDEwEaA0YBfwNbAd4DIQH7AxAB/gM6AfgD /wMAAf8DAAH/AwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8DAAH/AwAB/yQAA1YBtgMAAf8DAAH/AwAB
WQG7AzsBYwNcAdkDAAH/A2IB4QMxAUwEAhgAA1kBwwNbAeQDOwFlAwcECQEMAzoBYQNdAdQDRwH0AzEB /wMAAf8kAAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AwAB/wMAAf8DPAH4IwAB/wMAAf8DAAH/NwAB
+QNbAcoDMgFPAw8BFAMNAREDTQGSAy4B+QMuAfkDTQGSAxEBFgMtAUQDWgG/A1QB7wMiAfwDYAHgA0AB /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
cQMXAR8DBAEFAxgBIQNKAYsDXAHsA1kBw/8AEQAEAgMxAUwDYgHhAwAB/wNbAc0DGgEkAxYBHQNVAa8D /wMAAf8DAAH/JwAB/wMAAf8DAAH/AwAB/wMAAf87AAH/AwAB/wMAAf8DAAH/AwAB/wwAA1QBqwMAAf8D
AAH/AwAB/wMAAf8DAAH/A1UBrwMWAR0DGgEkA1sBzQMAAf8DYgHhAzEBTAQCGAADWQHDA1sB5AM7AWUD AAH/AwAB/wMAAf8DAAH/IAADVgG1AwAB/wMAAf8DAAH/AwAB/wMAAf8gAAM1AfkDAAH/AwAB/xwAA1cB
BwEJA0kBhgMhAfsDAAH/AwAB/wNZAcEDHQEpCAADCwEOA00BkgMuAfkDLgH5A00BkgM5BF4B4gMAAf8D 8QMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/x8AAf8DAAH/AwAB/zcAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
AAH/A1oB7QNKAYsDGAEgCAEDGAEhA0oBiwNcAewDWQHD/wARAAQCAzEBTANiAeEDAAH/A1wB2QM7AWMD /wMAAf8DAAH/AwAB/wsAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/ycAAf8DAAH/AwAB
WQG7Az8B9wMiAfwDRwH0A1wB2QNGAX4DEwEaAwIBAwMaASQDWwHNAwAB/wNiAeEDMQFMBAIYAANZAcMD /wMAAf8DAAH/AxIB/jMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wwAA1QBqwMAAf8DAAH/AwAB/wMAAf8D
XgHrA1ABmgNVAbEDTgHzAyEB+wNfAeUDSwGPAxsBJQMDAQQIAAMLAQ4DTQGSAy4B+QMhAfsDVgGzA0wB AAH/AwAB/wNZAbsYAANWAbUDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DUQGeGAADNQH5AwAB/wMAAf8c
jgNeAesDEAH+A1EB8ANXAbkDLgFHAwYBCAQABAEDGAEhA0oBiwNcAewDWQHD/wARAAQCAzEBTANiAeED AANXAfEDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/A1ABnRcAAf8DAAH/AwAB/zcAAf8DAAH/AwAB
AAH/AzEB+QNcAecDRAH1A1UB8QNbAdMDUQGkAzgBWwMTARkEAgQAAxoBJANbAc0DAAH/A2IB4QMxAUwE /w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/ycAAf8DAAH/A1YBswMAAf8DAAH/AwAB
AhgAA1kBwwMiAfwDUQHwA0QB9QMhAf0DXgHXA0YBfgMaASQDAgEDDAADCwEOA00BkgMuAfkDEAH+A04B /wMAAf8rAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8DPQFoAwAB/wMAAf8D
8wNXAe4DPwH3A14B4wNOAZgDJwE5AwgBCgQBBAAEAQMYASEDSgGLA1wB7ANZAcP/ABEABAIDMQFMA2IB AAH/AwAB/wMAAf8UAANWAbUDAAH/AwAB/wMzAVADAAH/AwAB/wMAAf8DAAH/AwAB/xQAAzUB+QMAAf8D
4QMAAf8DAAH/AyAB/QNiAeEDUgGjAzsBYwMhAS8DCgENBAIIAAMaASQDWwHNAwAB/wNiAeEDMQFMBAIY AAH/HAADVwHxAwAB/wMAAf8DEQEWBAEDAAH/AwAB/wMAAf8DAAH/AwAB/xMAAf8DAAH/AwAB/zcAAf8D
AANZAcMDAAH/AwAB/wMAAf8DXwHoA0oBiwMWAR0DBAEFEAADCwEOA00BkgMuAfkDAAH/AwAB/wM8AfYD AAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/ycAAf8DAAH/A1YBswcAAf8D
XAHLA0QBeQMeASoDBgEHDAAEAQMYASEDSgGLA1wB7ANZAcP/ABEABAIDMQFMA2IB4QMAAf8DMQH5A2AB AAH/AwAB/wMAAf8jAAH/AwAB/wMAAf8DAAH/BwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8DPQFoBwAB
2wNFAXwDIQEwAwwBEAMDAQQQAAMaASMDXAHIAyEB/QNiAeEDMQFMBAIYAANZAcMDAAH/AyMB/ANeAesD /wMAAf8DAAH/AwAB/wMAAf8QAANWAbUDAAH/AwAB/wMzAVAHAAH/AwAB/wMAAf8DAAH/AwAB/xAAAzUB
TgGXAyMBMwQCFAADCwEOA00BkgMuAfkDEAH+A1MB8gNYAboDNwFaAxEBFgMCAQMUAAMXAR8DSQGGA14B +QMAAf8DAAH/HAADVwHxAwAB/wMAAf8DEQEWCwAB/wMAAf8DAAH/AwAB/wMAAf8PAAH/AwAB/wMAAf83
6wNZAcP/ABEABAIDLQFFA14B0gNTAfIDXQHJAzIBTwMQARUDAgEDGAADEwEaA1ABnwNbAeQDWwHQAy0B AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8nAAH/AwAB/wNWAbML
RQQCGAADVQG0A1gB7gNfAdoDRAF4AxgBIAMDAQQYAAMKAQ0DSQGGA18B6ANaAekDUAGfAyQBNAMJAQsE AAH/AwAB/wMAAf8DEgH+GwAB/wMAAf8DAAH/AwAB/wsAAf8DAAH/AwAB/wwAA1QBqwMAAf8DAAH/Az0B
ARgAAwsBDgM7AWUDWwHTA1YBsv8AFQADAwEEAycBOgM/AW0DFAEbKAADDwETAzEBTgMdASkDAgEDHAAD aAgAA2AB2wMAAf8DAAH/AwAB/wMAAf8MAANWAbUDAAH/AwAB/wMzAVAIAANgAeMDAAH/AwAB/wMAAf8D
DgESAzEBTQMjATMDBAEFJAADBgEHAygBPAMoATwDBgEHJAAEAQMHAQkDCwEOAwIBA/8ABQADAgEDAw8B AAH/DAADNQH5AwAB/wMAAf8cAANXAfEDAAH/AwAB/wMRARYMAAMzAVIDAAH/AwAB/wMAAf8DAAH/AwAB
FANJAYgDXwHlA18B6ANfAegDXwHoA18B6ANfAegDXwHoA18B6ANfAegDXwHoA18B6ANfAegDXwHoA18B /wcAAf8DAAH/AwAB/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB
6ANfAegDXwHoA18B6ANfAegDXwHoA1sB5ANGAX4DBQEGBAEoAAMCAQMDDQERAx8BLAMtAUYDVwG5A18B /ycAAf8DAAH/A1YBsw8AAf8DAAH/AwAB/wMAAf8TAAH/AwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/DAAD
6ANfAegDXwHoA18B6ANfAegDXwHoA2AB4wNZAb4DMgFPAw8BEwMCAQMwAAMTARkDRgF+A18B6ANfAegD VAGrAwAB/wMAAf8DPQFoDAADJQE3AwAB/wMAAf8DAAH/AwAB/wgAA1YBtQMAAf8DAAH/AzMBUAwAAzIB
WQHBAzkBXgMmATgDDwEUBAJcAAM5AV8DXgHiA1wB5wNfAegDXwHoA18B6ANfAegDXQHcAzwBZgMGAQgD TwMAAf8DAAH/AwAB/wMAAf8IAAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFhcAAf8DAAH/AwAB
BgEIAzwBZgNdAdwDXwHoA18B6ANfAegDXwHoA10B3ANQAZ0DJQE2IAADDAEQAzwBZgNdAc8DIAH9AyUB /wMAAf8DAAH/AxIB/gMAAf8DAAH/NwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/DwAB
+gNUAe8DWgHtA1oB7QNaAe0DWgHtA1oB7QNaAe0DWgHtA1oB7QNaAe0DWgHtA1oB7QNaAe0DWgHtA1oB /wMAAf8DAAH/JwAB/wMAAf8XAAH/AwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/AwAB/xAAAwwBDwMAAf8D
7QNUAe8DJQH6AyIB/ANWAbUDKwFCAwgBCiQABAIDGAEhA0ABcANaAb8DXQHfA1wB7QNUAe8DXAHsA2AB QgH2DAADVAGrAwAB/wMAAf8DPQFoFwAB/wMAAf8DAAH/AwAB/wQAA1YBtQMAAf8DAAH/AzMBUBcAAf8D
5gNfAeUDWgHqA1oB7QNRAfADPQH2A10B0QNDAXcDHgErAwYBBywAAz0BaQNbAd4DAQH/AzEB+QNdAewD AAH/AwAB/wMAAf8EAAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFhgAA1oB6QMAAf8DAAH/AwAB
WwHkA14B1wNEAXsDHgEqAwYBCFgAAz8BbAMhAf0DOgH4A1QB7wNaAe0DVwHuAyUB+gM8AfYDTwGbAx4B /wMAAf8DAAH/AwAB/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB
KwMgAS0DUAGdAzwB9gMlAfoDVwHuA1oB7QNUAe8DPwH3A1MB8gM7AWUgAAMTARkDUAGcAz8B9wM9AfYD /0cAAf8DAAH/AwAB/wMSAf4DAAH/AwAB/wMAAf8DAAH/LAADVAGrAwAB/wMAAf8DPQFoGwAB/wMAAf8D
XQHMA0sBjQNGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYAD AAH/AwAB/wNdAc4DAAH/AwAB/wMzAVAbAAH/AwAB/wMAAf8DAAH/AyYB+gMAAf8DAAH/HAADVwHxAwAB
RgGAA0sBjwNcAdkDIAH9A1UB8QNOAZQDEgEYJAADBQEGAzEBTgNbAdADUwHyA14B4gNVAa4DSgGKA0YB /wMAAf8DEQEWIwAB/wMAAf8DAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB
fwNDAXcDQwF2A0UBfANGAYADTwGbA14B3QNfAegDXwHaA04BlgMoATwDCQEMKAADRwGDAzoB+AMiAfwD /wMAAf8PAAH/AwAB/wMAAf9LAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8wAANUAasDAAH/AwAB/wM9AWgf
WwHTA1MBpgNcAdkDMQH5A2AB4ANQAZoDLQFEAwsBDlQAAz8BbAMiAfwDWwHTA0wBjgNGAYADSgGKA10B AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DMwFQHwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/HAADVwHxAwAB
3AMhAf0DXgHdAzoBYAM6AWIDXQHfAyAB/QNdAdwDSgGKA0YBgANMAY4DWwHTAyIB/AM/AWwgAAMUARsD /wMAAf8DEQEWJwAB/wMAAf8DAAH/AwAB/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB
UgGlAx8B/QNbAdADPwFsAxgBIQMOARIDDgESAw4BEgMOARIDDgESAw4BEgMOARIDDgESAw4BEgMOARID /w8AAf8DAAH/AwAB/08AAf8DAAH/AwAB/wMAAf80AANUAasDAAH/AwAB/wM9AWgjAAH/AwAB/wMAAf8D
DgESAw4BEgMOARIDDgESAyABLgNXAbkDIAH9Ax8B/QNSAaUDFAEbJAADBQEGAzEBTQNaAccDXAHIA0AB AAH/AwAB/wMzAVAjAAH/AwAB/wMAAf8DAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFisAAf8DAAH/AwAB
bwMlATcDEwEaAw4BEgMNAREDDAEQAw4BEgMOARIDHQEoAzoBYANLAY8DWwHYA2AB5gNWAbYDMQFNAwYB /zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/0sAAf8DAAH/AwAB
CCQAA0kBhgMhAfsDMQH5A1UBrgMqAUADPwFtA10B0QNEAfUDVwHuA1gBtwM2AVcDFgEdAwwBEAQCSAAD /wMAAf8DAAH/AxIB/jAAA1QBqwMAAf8DAAH/Az0BaB8AAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMzAVAf
PwFsAzEB+QNVAa4DIAEtAw4BEgMbASUDWQG+AwAB/wNaAe0DPwFtAz8BbQNaAe0DAAH/A1kBvgMbASUD AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8cAANXAfEDAAH/AwAB/wMRARYkAANcAdkDAAH/AwAB/wMAAf83
DgESAyABLQNVAa4DMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1YBtgMoATwDCAEKOAADFgEeA1YBswMgAf0D AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8PAAH/AwAB/wMAAf9HAAH/AwAB/wMAAf8D
HwH9A1IBpQMUARskAAQBAw8BFAMjATMDIQEwAwsBDgMDAQQEARQABAIDBwEJAx4BKgNLAYwDXQHfAz8B AAH/AwAB/wMAAf8DAAH/AwAB/ywAA1QBqwMAAf8DAAH/Az0BaBsAAf8DAAH/AwAB/wMAAf8DVwHxAwAB
9wNTAakDKAE7JAADSQGGAyEB+wMxAfkDUwGnAxYBHgMMARADKgFAA1gBugNPAfMDSQH0A2AB2wNXAbwD /wMAAf8DMwFQGwAB/wMAAf8DAAH/AwAB/wMSAf4DAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFiMAAf8D
QgF0AxMBGQMCAQNEAAM/AWwDMQH5A1MBpwMVARwEAAMPARQDVwG5AwAB/wNaAe0DPwFtAz8BbQNaAe0D AAH/AwAB/wMAAf8DAAH/NwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/DwAB/wMAAf8D
AAH/A1cBuQMPARQEAAMVARwDUwGnAzEB+QM/AWwgAAMUARsDUgGlAx8B/QNVAbQDJgE4AwcBCTgAAxYB AAH/JwAB/wMAAf8XAAH/AwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/AwAB/xcAAf8DWAG4DAADVAGrAwAB
HgNWAbMDIAH9Ax8B/QNSAaUDFAEbYAADFQEcA1MBpwMxAfkDIQH7A0kBhiQAA0kBhgMhAfsDMQH5A1MB /wMAAf8DPQFoFwAB/wMAAf8DAAH/AwAB/wQAA1YBtQMAAf8DAAH/AzMBUBcAAf8DAAH/AwAB/wMAAf8E
pwMVARwIAAMPARMDQwF2A1wB2QMhAf0DAAH/AwAB/wNVAa8DFgEdRAADPwFsAzEB+QNTAacDFQEcBAAD AAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFhgAAzABSgMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
DwEUA1cBuQMAAf8DWgHtAz8BbQM/AW0DWgHtAwAB/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAAD /zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/ycAAf8DAAH/A1YB
FAEbA1IBpQMfAf0DVQG0AyYBOAMHAQk4AAMWAR4DVgGzAyAB/QMfAf0DUgGlAxQBG2AAAwIBAwMeASoD sw8AAf8DAAH/AwAB/wMAAf8TAAH/AwAB/wMAAf8DEgH+DwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8D
VQG0Az8B9wNSAaADGwElAwUBBhwAA0kBhgMhAfsDMQH5A1MBpwMVARwIAAQCAwkBCwMYASADRAF6A2AB PQFoEwAB/wMAAf8DAAH/AwAB/wgAA1YBtQMAAf8DAAH/AzMBUAwAAwkBDAMAAf8DAAH/AwAB/wMAAf8I
2wM9AfcDPwH3A1kBuwMqAUADDgESAwUBBgQCNAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8D AAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFhcAAf8DAAH/AwAB/wMAAf8DAAH/AyQB/QMAAf8D
WgHtAz8BbQM/AW0DWgHtAwAB/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0D AAH/NwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/CwAB/wMAAf8DAAH/DwAB/wMAAf8DAAH/JwAB/wMAAf8D
VQG0AyYBOAMHAQk4AAMWAR4DVgGzAyAB/QMfAf0DUgGlAxQBG2QAAw8BEwNAAW8DXQHUA1UB7wNMAZED VgGzCwAB/wMAAf8DAAH/AwAB/xsAAf8DAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8MAANUAasDAAH/AwAB
EgEYHAADSQGGAyEB+wMxAfkDUwGnAxUBHBAABAIDDQERAzYBVwNYAbcDXAHsA1YB8QNdAdEDRAF5AzMB /wM9AWgIAAM6AWADAAH/AwAB/wMAAf8DAAH/DAADVgG1AwAB/wMAAf8DMwFQCAADSwGMAwAB/wMAAf8D
UAMbASYDBgEHMAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8DWgHtAz8BbQM/AW0DWgHtAwAB AAH/AwAB/wwAAzUB+QMAAf8DAAH/HAADVwHxAwAB/wMAAf8DEQEWDAADBwEJAwAB/wMAAf8DAAH/AwAB
/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0DVQG0AyYBOAMHAQk4AAMWAR4D /wMAAf8HAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8PAAH/AwAB
VgGzAyAB/QMfAf0DUgGlAxQBG2QAAwIBAwMPARMDUQGeAx8B/QNSAaUDFAEbHAADSQGGAyEB+wMxAfkD /wMAAf8nAAH/AwAB/wNWAbMHAAH/AwAB/wMAAf8DAAH/IwAB/wMAAf8DAAH/AwAB/wcAAf8DAAH/AwAB
UwGnAxUBHBgAAwsBDgMtAUQDSwGPA1sBxQNiAeEDYgHhA1sBzQNMAZADKAE7AwkBDCwAAz8BbAMxAfkD /wwAA1QBqwMAAf8DAAH/Az0BaAcAAf8DAAH/AwAB/wMAAf8DAAH/EAADVgG1AwAB/wMAAf8DMwFQBwAB
UwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUBHANTAacD /wMAAf8DAAH/AwAB/wMAAf8QAAM1AfkDAAH/AwAB/xwAA1cB8QMAAf8DAAH/AxEBFgsAAf8DAAH/AwAB
MQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IBpQMUARts /wMAAf8DAAH/A08BmQsAAf8DAAH/AwAB/zcAAf8DAAH/AwAB/w8AAf8DAAH/AwAB/wsAAf8DAAH/AwAB
AANNAZMDHwH9A1IBpQMUARscAANJAYYDIQH7AzEB+QNTAacDFQEcHAADBgEIAxkBIgMvAUkDQAFxA10B /w8AAf8DAAH/AwAB/ycAAf8DAAH/A1YBswMAAf8DAAH/AwAB/wMAAf8rAAH/AwAB/wMAAf8DEgH+AwAB
zANOAfMDWgHpA1YBtgMtAUQsAAM/AWwDMQH5A1MBpwMVARwEAAMPARQDVwG5AwAB/wNaAe0DPwFtAz8B /wMAAf8DAAH/DAADVAGrAwAB/wMAAf8DPQFoAwAB/wMAAf8DAAH/AwAB/wMAAf8UAANWAbUDAAH/AwAB
bQNaAe0DAAH/A1cBuQMPARQEAAMVARwDUwGnAzEB+QM/AWwgAAMUARsDUgGlAx8B/QNVAbQDJgE4AwcB /wMzAVADAAH/AwAB/wMAAf8DAAH/AwAB/xQAAzUB+QMAAf8DAAH/HAADVwHxAwAB/wMAAf8DEQEWBwAB
CTgAAxYBHgNWAbMDIAH9Ax8B/QNSAaUDFAEbbAADTQGTAx8B/QNSAaUDFAEbHAADSQGGAyEB+wMxAfkD /wMAAf8DAAH/AwAB/wMAAf8TAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB
UwGnAxUBHCAABAIDBAEFAwsBDgMwAUwDWQHBAz0B9gMvAfkDQQFyAwYBCCgAAz8BbAMxAfkDUwGnAxUB /wMAAf8PAAH/AwAB/wMAAf8nAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8zAAH/AwAB/wMAAf8DAAH/AwAB
HAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUBHANTAacDMQH5Az8B /wMAAf8MAANUAasDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DTQH0GAADVgG1AwAB/wMAAf8DAAH/AwAB
bCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IBpQMUARtsAANNAZMD /wMAAf8DAAH/A10B7BgAAzUB+QMAAf8DAAH/HAADVwHxAwAB/wMAAf8DYQHrAwAB/wMAAf8DAAH/AwAB
HwH9A1IBpQMUARscAANJAYYDIQH7AzEB+QNTAacDFQEcMAADFQEcA1MBpwMxAfkDWwHTAzoBYSgAAz8B /wNNAfQXAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8PAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8PAAH/AwAB
bAMxAfkDUwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUB /wMAAf8nAAH/AwAB/wMAAf8DAAH/AwAB/zsAAf8DAAH/AwAB/wMAAf8DAAH/DAADVAGrAwAB/wMAAf8D
HANTAacDMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IB AAH/AwAB/wMAAf8DQwF3HAADVgG1AwAB/wMAAf8DAAH/AwAB/wMAAf8DMAFMHAADNQH5AwAB/wMAAf8c
pQMUARsQAAQBAwQBBQMLAQ4DDwETAw8BEwMPARMDDwETAw8BEwMNAREDCQEMAwMBBAQBLAADTQGTAx8B AANXAfEDAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8fAAH/AwAB/wMAAf83AAH/AwAB/wMAAf8DKgFAAyoB
/QNSAaUDFAEbHAADSQGGAyEB+wMxAfkDUwGnAxUBHCAABAEDAgEDAwsBDgMwAUwDWQHBAz0B9gMvAfkD QAMqAUADAAH/AwAB/wMAAf8LAAH/AwAB/wMAAf8DKgFAAyoBQAMqAUADAAH/AwAB/wMAAf8nAAH/AwAB
QQFyAwYBCCgAAz8BbAMxAfkDUwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8D /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8bAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
VwG5Aw8BFAQAAxUBHANTAacDMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YB /wMAAf8MAANUAasDAAH/AwAB/wMAAf8DAAH/AwYBCCAAA1YBtgMAAf8DAAH/AwAB/wMAAf8DAgEDIAAD
swMgAf0DHwH9A1IBpQMUARsQAAMGAQgDJAE1Az4BawNGAX0DRgF+A0YBfgNGAX4DRgF+A0QBewM9AWkD NQH5AwAB/wMAAf8cAANXAfEDAAH/AwAB/wMAAf8DAAH/AwAB/yMAAf8DAAH/AwAB/zcAAf8DAAH/AwAB
JAE0AwkBDCwAA00BkwMfAf0DUgGlAxQBGxwAA0kBhgMhAfsDMQH5A1MBpwMVARwcAAMGAQgDEgEXAyMB /wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wsAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB
MwM/AW4DWwHNAz0B9gNaAeoDVgG2Ay0BRCwAAz8BbAMxAfkDUwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB /ycAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/xQAA0wBkAMAAf8DAAH/AwAB/wMAAf8D
7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUBHANTAacDMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UB AAH/AwAB/wMAAf8DAAH/AwAB/xMAAf8DAAH/AwAB/y8AAf8DAAH/AwAB/ygAAzYBWAMAAf8DAAH/IwAB
tAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IBpQMUARsQAAMQARUDRgF/A1wB2QNaAeoDYAHmA2IB /wMAAf8DAAH/Aw0BESQAA1UBrQMAAf8DAAH/OwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/xMAAf8D
4QNgAeADXgHiA1wB5wNiAeEDUAGdAyEBMCQAAwIBAwMPARMDUQGeAx8B/QNSAaUDFAEbHAADSQGGAyEB AAH/AwAB/wMAAf8DAAH/AwAB/wMAAf8sAANaAb8DWgG/A1oBvwNaAb8DWgG/A1oBvwNaAb8DFQEcGAAD
+wMxAfkDUwGnAxUBHBgAAwsBDgMtAUQDRgGBA1MBqQNdAd8DXwHoA18B2gNOAZYDKAE8AwkBDCwAAz8B TwGZA1oBvwNaAb8DWgG/A1oBvwNaAb8DWgG/A1oBvxgAA04BlzQAA0sBjTAAAwwBDygAA1ABmjAAAygB
bAMxAfkDUwGnAxUBHAQAAw8BFANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUB PEAAAzgBWwNaAb8DWgG/A1oBvwMwAUwYAANEAXoDWgG/A1oBvwNaAb8DIAEtHAABQgFNAT4HAAE+AwAB
HANTAacDMQH5Az8BbCAAAxQBGwNSAaUDHwH9A1UBtAMmATgDBwEJOAADFgEeA1YBswMgAf0DHwH9A1IB KAMAAXwDAAEwAwABAQEAAQEGAAEDFgAD/wEAAf8B4AEHAf8B+AE/Av8B4AIAAXgEAAH/AcABAQH/AfgB
pQMUARsQAAMTARoDTgGYA0MB9QMAAf8DYAHgA1YBtgNZAb4DXgHXAz8B9wMiAfwDWgHHAysBQiQAAw8B HwL/AcACAAF4BAAB/wGDAeAB/wH5AQcC/wHAAgABOAQAAf8BjwH4AX8B+QGBAv8BxwH/Af4BOAQAAv8B
EwNAAW8DXQHUA1UB7wNNAZIDEgEYHAADSQGGAyEB+wMxAfkDUwGnAxUBHBAABAIDDQERAzYBVwNYAbcD /AE/AfkB4AL/AccB/wH+ATgEAAL/Af4BHwH5AfgBPwH/AccB/wH+ATgEAAP/AR8B+QH8AQ8B/wHHAf8B
WgHqA10B7ANfAdUDSwGPAzoBYAMeASsDBgEHMAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8D /gE4BAAD/wGPAfkB/wEHAf8BxwH/Af4BOAQAA/8BjwH5Af8BgQH/AccB/wH+ATgEAAP/AY8B+QH/AeAB
WgHtAz8BbQM/AW0DWgHtAwAB/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0D /wHHAf8B/gE4BAAD/wHPAfkB/wH4AX8BxwH/Af4BOAQAA/8BzwH5Af8B/AF/AccB/wH+ATgEAAP/Ac8B
VQG0AyYBOAMHAQk4AAMWAR4DVgGzAyAB/QMfAf0DUgGlAxQBGxAAAxQBGwNQAZoDPAH2AwAB/wNRAaQD +QH/AfgBfwHHAf8B/gE4BAAB8AEPAf8BjwH5Af8B4AF/AccB/wH+ATgEAAHgAQcB/wGPAfkB/wHBAf8B
JAE0A0QBeANeAd0DPwH3A1sB3gM7AWMDDgESIAADAgEDAx4BKgNVAbQDPwH3A1EBoQMbASYDBQEGHAAD xwH/Af4BOAQAAeABBwH/AY8B+QH/AQcB/wHHAf8B/gE4BAAB4wGHAf8BHwH5AfwBDwH/AccB/wH+ATgE
SQGGAyEB+wMxAfkDUwGnAxUBHAwABAEDEwEZA0QBegNgAdsDPQH3Az8B9wNZAbsDKwFCAxMBGQMIAQoD AAHjAQ8B/wEfAfkB+AE/Af8BxwH/Af4BOAQAAeIBHwH+AT8B+QHgAv8BxwH/Af4BOAQAAeABDwH4AT8B
AgEDNAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8DWgHtAz8BbQM/AW0DWgHtAwAB/wNXAbkD +QGBAv8BxwH/Af4BOAQAAeABAwHgAf8B+QEHAv8BwAIAATgEAAHwAYABAQH/AfgBDwL/AcACAAF4BAAB
DwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0DVQG0AyYBOAMHAQk4AAMWAR4DVgGzAyAB 8wHgAQcB/wH4AT8C/wHgAgABeAQAAf8B/gE/Af8B/gP/AfgBAAEBAfgEAAHwAQcBwAEPAR8B/AF/AeMB
/QMfAf0DUgGlAxQBGxAAAxQBGwNQAZoDPAH2AwAB/wNOAZUDMQFMA2IB4QMAAf8DWwHNAxoBJCgAAxUB /AF/AeMB/wHwAR4BAwLwAQcBwAEOAQ8B+AE/AeMB+AEfAeMB/wHgAQwBAQLwAX8B/gEOAQcB+AEfAeMB
HANTAacDMQH5AyEB+wNJAYYkAANJAYYDIQH7AzEB+QNTAacDFQEcDAADCwEOA00BkgMuAfkDAAH/AwAB +AEPAeMB/wHgAQwBAQLwAT8B/AEOAQEB+AEHAeMB+AEDAeMB/wHjAYwBcQLwAR8B+AEOAQAB+AEDAeMB
/wNVAa8DFgEdRAADPwFsAzEB+QNTAacDFQEcBAADDwEUA1cBuQMAAf8DWgHtAz8BbQM/AW0DWgHtAwAB +AEBAeMB/wHjAYwBcQHwAfEBDwHwAY4BEAF4AUEB4wH4AWAB4wH/AeMBjAFxAfAB8QGHAeEBjgEYATgB
/wNXAbkDDwEUBAADFQEcA1MBpwMxAfkDPwFsIAADFAEbA1IBpQMfAf0DVgG2AygBPAMIAQo4AAMWAR4D YAHjAfgBcAEjAf8B4wGMAXEB8AHxAsMBjgEcARgBcAFjAfgBfAEDAf8B4wGMAXEB8AHzAeEBhwGOAR8B
VgGzAyAB/QMfAf0DUgGlAxQBGxAAAxQBGwNQAZoDPAH2AwAB/wNEAfUDVgHvAyMB/AMSAf4DXQHMAx0B CAF8ASMB+AF+AQMB/wHjAYwBcQHwAf8B8AEPAf4BHwGAAX4BAwH4AX8BgwH/AeMBjAFxAfAB/wH4AR8B
KQQCGAAEAgMGAQcDDwETAzIBTwNZAcADQwH1A10B0QM6AWAkAANJAYYDIQH7AzEB+QNTAacDFgEeAwwB /gEfAcABfwEDAfgBfwHDAf8B4wGMAXEB8AH/AfwBPwH+AR8B4AF/AYMB+AF/AeMB/wHjAYwBcQHwAf8B
EAMqAUADVwG5A1oB6QNVAe8DXQHoA10BzwNGAX0DEwEaAwIBA0QAAz8BbAMxAfkDUwGnAxUBHAQAAw8B +AEfAf4BHwHAAX8BAwH4AX8BwwH/AeMBjAFxAfAB/wHwAQ8B/gEfAYABfgEDAfgBfwGDAf8B4wGMAXEB
FANXAbkDAAH/A1oB7QM/AW0DPwFtA1oB7QMAAf8DVwG5Aw8BFAQAAxUBHANTAacDMQH5Az8BbCAAAxQB 8AHzAeEBhwHOAR8BCAF8ASMB+AF+AQMB/wHjAYwBcQHwAfECwwGOAR4BGAFwAWMB+AF8AQMB/wHjAYwB
GwNSAaUDHwH9A1sB0AM/AWwDGAEhAw4BEgMOARIDDgESAw4BEgMOARIDDgESAw4BEgMOARIDDgESAw4B cQHwAfEBhwHhAY4BGAE4AWAB4wH4AXABIwH/AeMBjAFxAfAB8QEPAfABjgEQAXgBQQHjAfgBYAFjAf8B
EgMOARIDDgESAw4BEgMOARIDIAEuA1cBuQMgAf0DHwH9A1IBpQMUARsQAAMUARsDUAGaAzwB9gMAAf8D 4wGMAXEC8AEfAfgBDgEAAfgBAwHjAfgBQQHjAf8B4wGMAXEC8AE/AfwBDgEBAfgBBwHjAfgBAwHjAf8B
AAH/AyQB+gNDAfUDIAH9A2AB4ANAAXEDHQEoAw4BEgMNAREDCQEMAwgBCgMLBA4BEgMcAScDOAFbA0QB 4wGMAXEC8AF/Af4BDgEDAfgBDwHjAfgBDwHjAf8B4AEMAQEC8AEHAeABDgEHAfgBHwHjAfgBHwHjAf8B
eQNaAccDYgHhA1YBtgM0AVQDDAEPJAADSQGGAyEB+wMxAfkDVQGuAyoBQAM/AW0DXQHRA0QB9QNXAe4D 4AEMAQEC8AEHAcABDwEfAfwBfwHjAfwBPwHjAf8B8AEeAQMB8AH4AQcB4AEfAb8B/gH/AfcB/gH/AfcB
WQG7A0QBeQMqAUADEQEWBAJIAAM/AWwDMQH5A1UBrgMgAS0DDgESAxsBJQNZAb4DAAH/A1oB7QM/AW0D /wH4AT8BBwHwCw==
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
</value> </value>
</data> </data>
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>165, 17</value> <value>165, 17</value>
</metadata> </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> </root>

View File

@@ -176,13 +176,13 @@
contextMenuStrip_Skin.ImageScalingSize = new Size(24, 24); contextMenuStrip_Skin.ImageScalingSize = new Size(24, 24);
contextMenuStrip_Skin.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_ReloadSkins }); contextMenuStrip_Skin.Items.AddRange(new ToolStripItem[] { toolStripMenuItem_ReloadSkins });
contextMenuStrip_Skin.Name = "contextMenuStrip1"; contextMenuStrip_Skin.Name = "contextMenuStrip1";
contextMenuStrip_Skin.Size = new Size(225, 34); contextMenuStrip_Skin.Size = new Size(241, 67);
// //
// toolStripMenuItem_ReloadSkins // toolStripMenuItem_ReloadSkins
// //
toolStripMenuItem_ReloadSkins.Name = "toolStripMenuItem_ReloadSkins"; toolStripMenuItem_ReloadSkins.Name = "toolStripMenuItem_ReloadSkins";
toolStripMenuItem_ReloadSkins.Size = new Size(224, 30); toolStripMenuItem_ReloadSkins.Size = new Size(240, 30);
toolStripMenuItem_ReloadSkins.Text = "重新加载所选皮肤"; toolStripMenuItem_ReloadSkins.Text = "重新加载皮肤";
toolStripMenuItem_ReloadSkins.Click += toolStripMenuItem_ReloadSkins_Click; toolStripMenuItem_ReloadSkins.Click += toolStripMenuItem_ReloadSkins_Click;
// //
// tabPage_Slot // tabPage_Slot

View File

@@ -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)
{
;
}
}
}

View 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
}
}

View 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;
}
}
}
}

View File

@@ -59,6 +59,9 @@
toolStripMenuItem_Diagnostics = new ToolStripMenuItem(); toolStripMenuItem_Diagnostics = new ToolStripMenuItem();
toolStripSeparator3 = new ToolStripSeparator(); toolStripSeparator3 = new ToolStripSeparator();
toolStripMenuItem_About = new ToolStripMenuItem(); toolStripMenuItem_About = new ToolStripMenuItem();
toolStripMenuItem_Debug = new ToolStripMenuItem();
toolStripMenuItem_Experiment = new ToolStripMenuItem();
toolStripMenuItem_DesktopProjection = new ToolStripMenuItem();
rtbLog = new RichTextBox(); rtbLog = new RichTextBox();
splitContainer_MainForm = new SplitContainer(); splitContainer_MainForm = new SplitContainer();
splitContainer_Functional = new SplitContainer(); splitContainer_Functional = new SplitContainer();
@@ -102,10 +105,10 @@
// //
menuStrip.BackColor = SystemColors.Control; menuStrip.BackColor = SystemColors.Control;
menuStrip.ImageScalingSize = new Size(24, 24); 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.Location = new Point(0, 0);
menuStrip.Name = "menuStrip"; menuStrip.Name = "menuStrip";
menuStrip.Size = new Size(1778, 32); menuStrip.Size = new Size(1778, 36);
menuStrip.TabIndex = 0; menuStrip.TabIndex = 0;
menuStrip.Text = "菜单"; menuStrip.Text = "菜单";
// //
@@ -113,7 +116,7 @@
// //
toolStripMenuItem_File.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Open, toolStripMenuItem_BatchOpen, toolStripSeparator1, toolStripMenuItem_Export, toolStripSeparator2, toolStripMenuItem_Exit }); toolStripMenuItem_File.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_Open, toolStripMenuItem_BatchOpen, toolStripSeparator1, toolStripMenuItem_Export, toolStripSeparator2, toolStripMenuItem_Exit });
toolStripMenuItem_File.Name = "toolStripMenuItem_File"; toolStripMenuItem_File.Name = "toolStripMenuItem_File";
toolStripMenuItem_File.Size = new Size(84, 28); toolStripMenuItem_File.Size = new Size(84, 30);
toolStripMenuItem_File.Text = "文件(&F)"; toolStripMenuItem_File.Text = "文件(&F)";
// //
// toolStripMenuItem_Open // toolStripMenuItem_Open
@@ -245,7 +248,7 @@
// //
toolStripMenuItem_Tool.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ConvertFileFormat }); toolStripMenuItem_Tool.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ConvertFileFormat });
toolStripMenuItem_Tool.Name = "toolStripMenuItem_Tool"; toolStripMenuItem_Tool.Name = "toolStripMenuItem_Tool";
toolStripMenuItem_Tool.Size = new Size(84, 28); toolStripMenuItem_Tool.Size = new Size(84, 30);
toolStripMenuItem_Tool.Text = "工具(&T)"; toolStripMenuItem_Tool.Text = "工具(&T)";
// //
// toolStripMenuItem_ConvertFileFormat // toolStripMenuItem_ConvertFileFormat
@@ -259,7 +262,7 @@
// //
toolStripMenuItem_Download.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ManageResource }); toolStripMenuItem_Download.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ManageResource });
toolStripMenuItem_Download.Name = "toolStripMenuItem_Download"; toolStripMenuItem_Download.Name = "toolStripMenuItem_Download";
toolStripMenuItem_Download.Size = new Size(88, 28); toolStripMenuItem_Download.Size = new Size(88, 30);
toolStripMenuItem_Download.Text = "下载(&D)"; toolStripMenuItem_Download.Text = "下载(&D)";
// //
// toolStripMenuItem_ManageResource // toolStripMenuItem_ManageResource
@@ -271,30 +274,52 @@
// //
// toolStripMenuItem_Help // 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.Name = "toolStripMenuItem_Help";
toolStripMenuItem_Help.Size = new Size(88, 28); toolStripMenuItem_Help.Size = new Size(88, 30);
toolStripMenuItem_Help.Text = "帮助(&H)"; toolStripMenuItem_Help.Text = "帮助(&H)";
// //
// toolStripMenuItem_Diagnostics // toolStripMenuItem_Diagnostics
// //
toolStripMenuItem_Diagnostics.Name = "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.Text = "诊断信息(&D)";
toolStripMenuItem_Diagnostics.Click += toolStripMenuItem_Diagnostics_Click; toolStripMenuItem_Diagnostics.Click += toolStripMenuItem_Diagnostics_Click;
// //
// toolStripSeparator3 // toolStripSeparator3
// //
toolStripSeparator3.Name = "toolStripSeparator3"; toolStripSeparator3.Name = "toolStripSeparator3";
toolStripSeparator3.Size = new Size(205, 6); toolStripSeparator3.Size = new Size(267, 6);
// //
// toolStripMenuItem_About // toolStripMenuItem_About
// //
toolStripMenuItem_About.Name = "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.Text = "关于(&A)";
toolStripMenuItem_About.Click += toolStripMenuItem_About_Click; 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
// //
rtbLog.BackColor = SystemColors.Window; rtbLog.BackColor = SystemColors.Window;
@@ -305,7 +330,7 @@
rtbLog.Margin = new Padding(3, 2, 3, 2); rtbLog.Margin = new Padding(3, 2, 3, 2);
rtbLog.Name = "rtbLog"; rtbLog.Name = "rtbLog";
rtbLog.ReadOnly = true; rtbLog.ReadOnly = true;
rtbLog.Size = new Size(1758, 142); rtbLog.Size = new Size(1758, 158);
rtbLog.TabIndex = 0; rtbLog.TabIndex = 0;
rtbLog.Text = ""; rtbLog.Text = "";
rtbLog.WordWrap = false; rtbLog.WordWrap = false;
@@ -328,8 +353,8 @@
// //
splitContainer_MainForm.Panel2.Controls.Add(rtbLog); splitContainer_MainForm.Panel2.Controls.Add(rtbLog);
splitContainer_MainForm.Panel2.Cursor = Cursors.Default; splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
splitContainer_MainForm.Size = new Size(1758, 1097); splitContainer_MainForm.Size = new Size(1758, 1093);
splitContainer_MainForm.SplitterDistance = 947; splitContainer_MainForm.SplitterDistance = 927;
splitContainer_MainForm.SplitterWidth = 8; splitContainer_MainForm.SplitterWidth = 8;
splitContainer_MainForm.TabIndex = 3; splitContainer_MainForm.TabIndex = 3;
splitContainer_MainForm.TabStop = false; splitContainer_MainForm.TabStop = false;
@@ -353,7 +378,7 @@
// //
splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview); splitContainer_Functional.Panel2.Controls.Add(groupBox_Preview);
splitContainer_Functional.Panel2.Cursor = Cursors.Default; 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.SplitterDistance = 788;
splitContainer_Functional.SplitterWidth = 8; splitContainer_Functional.SplitterWidth = 8;
splitContainer_Functional.TabIndex = 2; splitContainer_Functional.TabIndex = 2;
@@ -377,7 +402,7 @@
// //
splitContainer_Information.Panel2.Controls.Add(splitContainer_Config); splitContainer_Information.Panel2.Controls.Add(splitContainer_Config);
splitContainer_Information.Panel2.Cursor = Cursors.Default; 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.SplitterDistance = 351;
splitContainer_Information.SplitterWidth = 8; splitContainer_Information.SplitterWidth = 8;
splitContainer_Information.TabIndex = 1; splitContainer_Information.TabIndex = 1;
@@ -391,7 +416,7 @@
groupBox_SkelList.Dock = DockStyle.Fill; groupBox_SkelList.Dock = DockStyle.Fill;
groupBox_SkelList.Location = new Point(0, 0); groupBox_SkelList.Location = new Point(0, 0);
groupBox_SkelList.Name = "groupBox_SkelList"; 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.TabIndex = 0;
groupBox_SkelList.TabStop = false; groupBox_SkelList.TabStop = false;
groupBox_SkelList.Text = "模型列表"; groupBox_SkelList.Text = "模型列表";
@@ -401,16 +426,16 @@
spineListView.Dock = DockStyle.Fill; spineListView.Dock = DockStyle.Fill;
spineListView.Location = new Point(3, 26); spineListView.Location = new Point(3, 26);
spineListView.Name = "spineListView"; spineListView.Name = "spineListView";
spineListView.Size = new Size(345, 918); spineListView.Size = new Size(345, 898);
spineListView.SpinePropertyGrid = spineViewPropertyGrid; spineListView.SpinePropertyGrid = spineViewPropertyGrid;
spineListView.TabIndex = 0; spineListView.TabIndex = 0;
// //
// spinePropertyGrid // spineViewPropertyGrid
// //
spineViewPropertyGrid.Dock = DockStyle.Fill; spineViewPropertyGrid.Dock = DockStyle.Fill;
spineViewPropertyGrid.Location = new Point(3, 26); spineViewPropertyGrid.Location = new Point(3, 26);
spineViewPropertyGrid.Name = "spinePropertyGrid"; spineViewPropertyGrid.Name = "spineViewPropertyGrid";
spineViewPropertyGrid.Size = new Size(423, 586); spineViewPropertyGrid.Size = new Size(423, 575);
spineViewPropertyGrid.TabIndex = 0; spineViewPropertyGrid.TabIndex = 0;
// //
// splitContainer_Config // splitContainer_Config
@@ -427,8 +452,8 @@
// splitContainer_Config.Panel2 // splitContainer_Config.Panel2
// //
splitContainer_Config.Panel2.Controls.Add(groupBox_SkelConfig); splitContainer_Config.Panel2.Controls.Add(groupBox_SkelConfig);
splitContainer_Config.Size = new Size(429, 947); splitContainer_Config.Size = new Size(429, 927);
splitContainer_Config.SplitterDistance = 324; splitContainer_Config.SplitterDistance = 315;
splitContainer_Config.SplitterWidth = 8; splitContainer_Config.SplitterWidth = 8;
splitContainer_Config.TabIndex = 0; splitContainer_Config.TabIndex = 0;
// //
@@ -439,7 +464,7 @@
groupBox_PreviewConfig.Location = new Point(0, 0); groupBox_PreviewConfig.Location = new Point(0, 0);
groupBox_PreviewConfig.Margin = new Padding(0); groupBox_PreviewConfig.Margin = new Padding(0);
groupBox_PreviewConfig.Name = "groupBox_PreviewConfig"; 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.TabIndex = 1;
groupBox_PreviewConfig.TabStop = false; groupBox_PreviewConfig.TabStop = false;
groupBox_PreviewConfig.Text = "画面参数"; groupBox_PreviewConfig.Text = "画面参数";
@@ -450,10 +475,9 @@
propertyGrid_Previewer.HelpVisible = false; propertyGrid_Previewer.HelpVisible = false;
propertyGrid_Previewer.Location = new Point(3, 26); propertyGrid_Previewer.Location = new Point(3, 26);
propertyGrid_Previewer.Name = "propertyGrid_Previewer"; 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.TabIndex = 1;
propertyGrid_Previewer.ToolbarVisible = false; propertyGrid_Previewer.ToolbarVisible = false;
propertyGrid_Previewer.PropertyValueChanged += propertyGrid_PropertyValueChanged;
// //
// groupBox_SkelConfig // groupBox_SkelConfig
// //
@@ -462,7 +486,7 @@
groupBox_SkelConfig.Location = new Point(0, 0); groupBox_SkelConfig.Location = new Point(0, 0);
groupBox_SkelConfig.Margin = new Padding(0); groupBox_SkelConfig.Margin = new Padding(0);
groupBox_SkelConfig.Name = "groupBox_SkelConfig"; 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.TabIndex = 0;
groupBox_SkelConfig.TabStop = false; groupBox_SkelConfig.TabStop = false;
groupBox_SkelConfig.Text = "模型参数"; groupBox_SkelConfig.Text = "模型参数";
@@ -473,18 +497,18 @@
groupBox_Preview.Dock = DockStyle.Fill; groupBox_Preview.Dock = DockStyle.Fill;
groupBox_Preview.Location = new Point(0, 0); groupBox_Preview.Location = new Point(0, 0);
groupBox_Preview.Name = "groupBox_Preview"; 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.TabIndex = 1;
groupBox_Preview.TabStop = false; groupBox_Preview.TabStop = false;
groupBox_Preview.Text = "预览画面"; groupBox_Preview.Text = "预览画面";
// //
// spinePreviewer // spinePreviewPanel
// //
spinePreviewPanel.Dock = DockStyle.Fill; spinePreviewPanel.Dock = DockStyle.Fill;
spinePreviewPanel.Location = new Point(3, 26); spinePreviewPanel.Location = new Point(3, 26);
spinePreviewPanel.Name = "spinePreviewer"; spinePreviewPanel.Name = "spinePreviewPanel";
spinePreviewPanel.PropertyGrid = propertyGrid_Previewer; spinePreviewPanel.PropertyGrid = propertyGrid_Previewer;
spinePreviewPanel.Size = new Size(956, 918); spinePreviewPanel.Size = new Size(956, 898);
spinePreviewPanel.SpineListView = spineListView; spinePreviewPanel.SpineListView = spineListView;
spinePreviewPanel.TabIndex = 0; spinePreviewPanel.TabIndex = 0;
// //
@@ -492,10 +516,10 @@
// //
panel_MainForm.Controls.Add(splitContainer_MainForm); panel_MainForm.Controls.Add(splitContainer_MainForm);
panel_MainForm.Dock = DockStyle.Fill; 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.Name = "panel_MainForm";
panel_MainForm.Padding = new Padding(10, 5, 10, 10); 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; panel_MainForm.TabIndex = 4;
// //
// toolTip // toolTip
@@ -590,5 +614,8 @@
private ToolStripSeparator toolStripSeparator5; private ToolStripSeparator toolStripSeparator5;
private ToolStripSeparator toolStripSeparator6; private ToolStripSeparator toolStripSeparator6;
private SplitContainer splitContainer_Config; private SplitContainer splitContainer_Config;
private ToolStripMenuItem toolStripMenuItem_Experiment;
private ToolStripMenuItem toolStripMenuItem_DesktopProjection;
private ToolStripMenuItem toolStripMenuItem_Debug;
} }
} }

View File

@@ -31,6 +31,9 @@ namespace SpineViewer
MessagePopup.Warn("Fragment shader 加载失败预乘Alpha通道属性失效"); MessagePopup.Warn("Fragment shader 加载失败预乘Alpha通道属性失效");
} }
#if DEBUG
toolStripMenuItem_Debug.Visible = true;
#endif
} }
/// <summary> /// <summary>
@@ -336,12 +339,6 @@ namespace SpineViewer
private void splitContainer_MouseUp(object sender, MouseEventArgs e) => ActiveControl = null; private void splitContainer_MouseUp(object sender, MouseEventArgs e) => ActiveControl = null;
private void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
{
// 用来解决对面板某些值修改之后, 其他被联动修改的值不会实时刷新的问题
(sender as PropertyGrid)?.Refresh();
}
private void Export_Work(object? sender, DoWorkEventArgs e) private void Export_Work(object? sender, DoWorkEventArgs e)
{ {
var worker = (BackgroundWorker)sender; var worker = (BackgroundWorker)sender;
@@ -395,7 +392,7 @@ namespace SpineViewer
} }
var root = srcCvter.Read(skelPath); var root = srcCvter.Read(skelPath);
root = srcCvter.ToVersion(root, args.TargetVersion); root = srcCvter.ToVersion(root, args.TargetVersion);
if (args.JsonTarget) tgtCvter.WriteJson(root, newPath); if (args.JsonTarget) tgtCvter.WriteJson(root, newPath);
else tgtCvter.WriteBinary(root, newPath); else tgtCvter.WriteBinary(root, newPath);
success++; success++;
} }
@@ -419,48 +416,30 @@ namespace SpineViewer
} }
} }
//private System.Windows.Forms.Timer timer = new(); private void toolStripMenuItem_DesktopProjection_Click(object sender, EventArgs e)
//private PetForm pet = new PetForm(); {
//private IntPtr screenDC; toolStripMenuItem_DesktopProjection.Checked = !toolStripMenuItem_DesktopProjection.Checked;
//private IntPtr memDC; spinePreviewPanel.EnableDesktopProjection = toolStripMenuItem_DesktopProjection.Checked;
//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 Timer_Tick(object? sender, EventArgs e) private void toolStripMenuItem_Debug_Click(object sender, EventArgs e)
//{ {
// using var tex = new SFML.Graphics.RenderTexture((uint)pet.Width, (uint)pet.Height); #if DEBUG
// var v = spinePreviewer.GetView(); //var cvt = SkeletonConverter.New(SpineVersion.V38);
// tex.SetView(v); //var root = cvt.ReadBinary(@"D:\ACGN\AzurLane_Export\AzurLane_Dynamic\docs\aerhangeersike\aerhangeersike_3\aerhangeersike_3 - 副本.skel");
// tex.Clear(new SFML.Graphics.Color(0, 0, 0, 0)); //cvt.WriteJson(root, @"D:\ACGN\AzurLane_Export\AzurLane_Dynamic\docs\aerhangeersike\aerhangeersike_3\aerhangeersike_3.json");
// 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();
// var newBitmap = bitmap.GetHbitmap(Color.FromArgb(0)); //root = cvt.ReadJson(@"D:\ACGN\AzurLane_Export\AzurLane_Dynamic\docs\aerhangeersike\aerhangeersike_3\aerhangeersike_3.json");
// var oldBitmap = Win32.SelectObject(memDC, newBitmap); //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 }; //var cvt = SkeletonConverter.New(SpineVersion.V38);
// Win32.POINT srcPos = new Win32.POINT { x = 0, y = 0 }; //var root = cvt.ReadJson(@"D:\ACGN\G\GirlsCreation\standing_spine\st4020069\st4020069.json");
// Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION { BlendOp = 0, BlendFlags = 0, SourceConstantAlpha = 255, AlphaFormat = Win32.AC_SRC_ALPHA }; //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");
// Win32.UpdateLayeredWindow(pet.Handle, screenDC, IntPtr.Zero, ref size, memDC, ref srcPos, 0, ref blend, Win32.ULW_ALPHA); //_Test();
#endif
// Win32.SelectObject(memDC, oldBitmap); }
// Win32.DeleteObject(newBitmap);
//}
//private void spinePreviewer_KeyDown(object sender, KeyEventArgs e) //private void spinePreviewer_KeyDown(object sender, KeyEventArgs e)
//{ //{

View File

@@ -1,6 +1,6 @@
namespace SpineViewer namespace SpineViewer
{ {
partial class PetForm partial class WallpaperForm
{ {
/// <summary> /// <summary>
/// Required designer variable. /// Required designer variable.
@@ -30,18 +30,19 @@
{ {
SuspendLayout(); SuspendLayout();
// //
// PetForm // WallpaperForm
// //
AutoScaleMode = AutoScaleMode.None; AutoScaleMode = AutoScaleMode.None;
ClientSize = new Size(490, 456); ClientSize = new Size(512, 512);
ControlBox = false; ControlBox = false;
FormBorderStyle = FormBorderStyle.None;
MaximizeBox = false; MaximizeBox = false;
MinimizeBox = false; MinimizeBox = false;
Name = "PetForm"; Name = "WallpaperForm";
ShowIcon = false; ShowIcon = false;
ShowInTaskbar = false; ShowInTaskbar = false;
StartPosition = FormStartPosition.Manual; StartPosition = FormStartPosition.Manual;
Text = "PetForm"; WindowState = FormWindowState.Minimized;
ResumeLayout(false); ResumeLayout(false);
} }

View 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);
}
}
}
}

View 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>

View File

@@ -17,6 +17,8 @@ namespace SpineViewer.Natives
public const int GWL_STYLE = -16; public const int GWL_STYLE = -16;
public const int WS_SIZEBOX = 0x40000; public const int WS_SIZEBOX = 0x40000;
public const int WS_BORDER = 0x800000; 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 WS_POPUP = unchecked((int)0x80000000);
public const int GWL_EXSTYLE = -20; 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_TOOLWINDOW = 0x80;
public const int WS_EX_WINDOWEDGE = 0x100; public const int WS_EX_WINDOWEDGE = 0x100;
public const int WS_EX_CLIENTEDGE = 0x200; 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_LAYERED = 0x80000;
public const int WS_EX_OVERLAPPEDWINDOW = WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE; 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_COLORKEY = 0x1;
public const uint LWA_ALPHA = 0x2; public const uint LWA_ALPHA = 0x2;
@@ -44,7 +48,6 @@ namespace SpineViewer.Natives
public const uint SWP_NOMOVE = 0x0002; public const uint SWP_NOMOVE = 0x0002;
public const uint SWP_NOZORDER = 0x0004; public const uint SWP_NOZORDER = 0x0004;
public const uint SWP_FRAMECHANGED = 0x0020; 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; // 一个未公开的神秘消息 public const int WM_SPAWN_WORKER = 0x052C; // 一个未公开的神秘消息
@@ -170,7 +173,7 @@ namespace SpineViewer.Natives
if (progman == nint.Zero) if (progman == nint.Zero)
return nint.Zero; return nint.Zero;
nint hWnd = FindWindowEx(progman, 0, "WorkerW", null); nint hWnd = FindWindowEx(progman, 0, "WorkerW", null);
Debug.WriteLine($"{hWnd:x8}"); Debug.WriteLine($"HWND(Progman.WorkerW): {hWnd:x8}");
return hWnd; return hWnd;
} }
} }

View File

@@ -897,16 +897,16 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
var version = (string)skeleton["spine"]; var version = (string)skeleton["spine"];
if (version == "3.8.75") version = "3.8.76"; // replace 3.8.75 to another version to avoid detection in official runtime 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); writer.WriteString(version);
if (skeleton.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(0); if (skeleton["x"] is JsonValue 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["y"] is JsonValue 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["width"] is JsonValue 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["height"] is JsonValue height) writer.WriteFloat((float)height); else writer.WriteFloat(0);
writer.WriteBoolean(nonessential); writer.WriteBoolean(nonessential);
if (nonessential) if (nonessential)
{ {
if (skeleton.TryGetPropertyValue("fps", out var fps)) writer.WriteFloat((float)fps); else writer.WriteFloat(30); if (skeleton["fps"] is JsonValue 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["images"] is JsonValue 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["audio"] is JsonValue audio) writer.WriteString((string)audio); else writer.WriteString(null);
} }
} }
@@ -932,16 +932,16 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
var name = (string)data["name"]; var name = (string)data["name"];
writer.WriteString(name); writer.WriteString(name);
if (i > 0) writer.WriteVarInt(bone2idx[(string)data["parent"]]); 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["rotation"] is JsonValue 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["x"] is JsonValue 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["y"] is JsonValue 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["scaleX"] is JsonValue 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["scaleY"] is JsonValue 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["shearX"] is JsonValue 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["shearY"] is JsonValue 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["length"] is JsonValue length) writer.WriteFloat((float)length); else writer.WriteFloat(0);
if (data.TryGetPropertyValue("transform", out var transform)) writer.WriteVarInt(Array.IndexOf(SkeletonBinary.TransformModeValues, Enum.Parse<TransformMode>((string)transform, true))); else writer.WriteVarInt(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.TryGetPropertyValue("skin", out var skin)) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false); if (data["skin"] is JsonValue skin) writer.WriteBoolean((bool)skin); else writer.WriteBoolean(false);
if (nonessential) writer.WriteInt(0); if (nonessential) writer.WriteInt(0);
bone2idx[name] = i; bone2idx[name] = i;
} }
@@ -962,10 +962,10 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
var name = (string)data["name"]; var name = (string)data["name"];
writer.WriteString(name); writer.WriteString(name);
writer.WriteVarInt(bone2idx[(string)data["bone"]]); writer.WriteVarInt(bone2idx[(string)data["bone"]]);
if (data.TryGetPropertyValue("color", out var color)) writer.WriteInt(int.Parse((string)color, NumberStyles.HexNumber)); else writer.WriteInt(-1); // 默认值是全 255 if (data["color"]is JsonValue color) writer.WriteInt(int.Parse((string)color, NumberStyles.HexNumber)); else writer.WriteInt(-1); // 默认值是全 255
if (data.TryGetPropertyValue("dark", out var dark)) writer.WriteInt(int.Parse((string)dark, NumberStyles.HexNumber)); else writer.WriteInt(-1); if (data["dark"] is JsonValue 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["attachment"] is JsonValue 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["blend"] is JsonValue blend) writer.WriteVarInt((int)Enum.Parse<BlendMode>((string)blend, true)); else writer.WriteVarInt((int)BlendMode.Normal);
slot2idx[name] = i; slot2idx[name] = i;
} }
} }
@@ -984,16 +984,16 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
JsonObject data = ik[i].AsObject(); JsonObject data = ik[i].AsObject();
var name = (string)data["name"]; var name = (string)data["name"];
writer.WriteString(name); writer.WriteString(name);
if (data.TryGetPropertyValue("order", out var order)) writer.WriteVarInt((int)order); else writer.WriteVarInt(0); if (data["order"] is JsonValue 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["skin"] is JsonValue 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["bones"] is JsonArray bones) WriteNames(bone2idx, bones); else writer.WriteVarInt(0);
writer.WriteVarInt(bone2idx[(string)data["target"]]); writer.WriteVarInt(bone2idx[(string)data["target"]]);
if (data.TryGetPropertyValue("mix", out var mix)) writer.WriteFloat((float)mix); else writer.WriteFloat(1); if (data["mix"] is JsonValue 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["softness"] is JsonValue 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["bendPositive"] is JsonValue 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["compress"] is JsonValue 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["stretch"] is JsonValue 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["uniform"] is JsonValue uniform) writer.WriteBoolean((bool)uniform); else writer.WriteBoolean(false);
ik2idx[name] = i; ik2idx[name] = i;
} }
} }
@@ -1012,22 +1012,22 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
JsonObject data = transform[i].AsObject(); JsonObject data = transform[i].AsObject();
var name = (string)data["name"]; var name = (string)data["name"];
writer.WriteString(name); writer.WriteString(name);
if (data.TryGetPropertyValue("order", out var order)) writer.WriteVarInt((int)order); else writer.WriteVarInt(0); if (data["order"] is JsonValue 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["skin"] is JsonValue 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["bones"] is JsonArray bones) WriteNames(bone2idx, bones); else writer.WriteVarInt(0);
writer.WriteVarInt(bone2idx[(string)data["target"]]); writer.WriteVarInt(bone2idx[(string)data["target"]]);
if (data.TryGetPropertyValue("local", out var local)) writer.WriteBoolean((bool)local); else writer.WriteBoolean(false); if (data["local"] is JsonValue 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["relative"] is JsonValue 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["rotation"] is JsonValue 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["x"] is JsonValue 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["y"] is JsonValue 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["scaleX"] is JsonValue 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["scaleY"] is JsonValue 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["shearY"] is JsonValue 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["rotateMix"] is JsonValue 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["translateMix"] is JsonValue 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["scaleMix"] is JsonValue 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["shearMix"] is JsonValue shearMix) writer.WriteFloat((float)shearMix); else writer.WriteFloat(1);
transform2idx[name] = i; transform2idx[name] = i;
} }
} }
@@ -1046,18 +1046,18 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
JsonObject data = path[i].AsObject(); JsonObject data = path[i].AsObject();
var name = (string)data["name"]; var name = (string)data["name"];
writer.WriteString(name); writer.WriteString(name);
if (data.TryGetPropertyValue("order", out var order)) writer.WriteVarInt((int)order); else writer.WriteVarInt(0); if (data["order"] is JsonValue 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["skin"] is JsonValue 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["bones"] is JsonArray bones) WriteNames(bone2idx, bones); else writer.WriteVarInt(0);
writer.WriteVarInt(bone2idx[(string)data["target"]]); 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["positionMode"] is JsonValue 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["spacingMode"] is JsonValue 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["rotateMode"] is JsonValue 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["rotation"] is JsonValue 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["position"] is JsonValue 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["spacing"] is JsonValue 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["rotateMix"] is JsonValue 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["translateMix"] is JsonValue translateMix) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1);
path2idx[name] = i; path2idx[name] = i;
} }
} }
@@ -1112,17 +1112,17 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
if (isDefault) if (isDefault)
{ {
// 这里固定有一个给 default 的 count 值, 算是占位符, 如果是 0 则表示没有 default 的 skin // 这里固定有一个给 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); writer.WriteVarInt(skinAttachments?.Count ?? 0);
} }
else else
{ {
writer.WriteStringRef((string)skin["name"]); writer.WriteStringRef((string)skin["name"]);
if (skin.TryGetPropertyValue("bones", out var bones)) WriteNames(bone2idx, bones.AsArray()); else writer.WriteVarInt(0); if (skin["bones"] is JsonArray bones) WriteNames(bone2idx, bones); else writer.WriteVarInt(0);
if (skin.TryGetPropertyValue("ik", out var ik)) WriteNames(ik2idx, ik.AsArray()); else writer.WriteVarInt(0); if (skin["ik"] is JsonArray ik) WriteNames(ik2idx, ik); else writer.WriteVarInt(0);
if (skin.TryGetPropertyValue("transform", out var transform)) WriteNames(transform2idx, transform.AsArray()); else writer.WriteVarInt(0); if (skin["transform"] is JsonArray transform) WriteNames(transform2idx, transform); else writer.WriteVarInt(0);
if (skin.TryGetPropertyValue("path", out var path)) WriteNames(path2idx, path.AsArray()); else writer.WriteVarInt(0); if (skin["path"] is JsonArray path) WriteNames(path2idx, path); else writer.WriteVarInt(0);
if (skin.TryGetPropertyValue("attachments", out var attachments)) skinAttachments = attachments.AsObject(); if (skin["attachments"] is JsonObject attachments) skinAttachments = attachments;
writer.WriteVarInt(skinAttachments?.Count ?? 0); writer.WriteVarInt(skinAttachments?.Count ?? 0);
} }
@@ -1149,76 +1149,76 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
string name = keyName; string name = keyName;
AttachmentType type = AttachmentType.Region; AttachmentType type = AttachmentType.Region;
if (attachment.TryGetPropertyValue("name", out var _name)) name = (string)_name; if (attachment["name"] is JsonValue _name) name = (string)_name;
if (attachment.TryGetPropertyValue("type", out var _type)) type = Enum.Parse<AttachmentType>((string)_type, true); if (attachment["type"] is JsonValue _type) type = Enum.Parse<AttachmentType>((string)_type, true);
writer.WriteStringRef(name); writer.WriteStringRef(name);
writer.WriteByte((byte)type); writer.WriteByte((byte)type);
switch (type) switch (type)
{ {
case AttachmentType.Region: case AttachmentType.Region:
if (attachment.TryGetPropertyValue("path", out var path1)) writer.WriteStringRef((string)path1); else writer.WriteStringRef(null); if (attachment["path"] is JsonValue 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["rotation"] is JsonValue 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["x"] is JsonValue 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["y"] is JsonValue 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["scaleX"] is JsonValue 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["scaleY"] is JsonValue 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["width"] is JsonValue 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["height"] is JsonValue 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(-1); if (attachment["color"] is JsonValue color1) writer.WriteInt(int.Parse((string)color1, NumberStyles.HexNumber)); else writer.WriteInt(-1);
break; break;
case AttachmentType.Boundingbox: 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); writer.WriteVarInt(vertexCount);
WriteVertices(attachment["vertices"].AsArray(), vertexCount); WriteVertices(attachment["vertices"].AsArray(), vertexCount);
if (nonessential) writer.WriteInt(0); if (nonessential) writer.WriteInt(0);
break; break;
case AttachmentType.Mesh: case AttachmentType.Mesh:
if (attachment.TryGetPropertyValue("path", out var path2)) writer.WriteStringRef((string)path2); else writer.WriteStringRef(null); if (attachment["path"] is JsonValue 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(-1); if (attachment["color"] is JsonValue color2) writer.WriteInt(int.Parse((string)color2, NumberStyles.HexNumber)); else writer.WriteInt(-1);
vertexCount = attachment["uvs"].AsArray().Count >> 1; vertexCount = attachment["uvs"].AsArray().Count >> 1;
writer.WriteVarInt(vertexCount); writer.WriteVarInt(vertexCount);
WriteFloatArray(attachment["uvs"].AsArray(), vertexCount << 1); // vertexCount = uvs.Length >> 1 WriteFloatArray(attachment["uvs"].AsArray(), vertexCount << 1); // vertexCount = uvs.Length >> 1
WriteShortArray(attachment["triangles"].AsArray()); WriteShortArray(attachment["triangles"].AsArray());
WriteVertices(attachment["vertices"].AsArray(), vertexCount); 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 (nonessential)
{ {
if (attachment.TryGetPropertyValue("edges", out var edges)) WriteShortArray(edges.AsArray()); else writer.WriteVarInt(0); if (attachment["edges"] is JsonArray edges) WriteShortArray(edges); else writer.WriteVarInt(0);
if (attachment.TryGetPropertyValue("width", out var _width)) writer.WriteFloat((float)_width); else writer.WriteFloat(0); if (attachment["width"] is JsonValue _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["height"] is JsonValue _height) writer.WriteFloat((float)_height); else writer.WriteFloat(0);
} }
break; break;
case AttachmentType.Linkedmesh: case AttachmentType.Linkedmesh:
if (attachment.TryGetPropertyValue("path", out var path3)) writer.WriteStringRef((string)path3); else writer.WriteStringRef(null); if (attachment["path"] is JsonValue 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(-1); if (attachment["color"] is JsonValue color3) writer.WriteInt(int.Parse((string)color3, NumberStyles.HexNumber)); else writer.WriteInt(-1);
if (attachment.TryGetPropertyValue("skin", out var skin)) writer.WriteStringRef((string)skin); else writer.WriteStringRef(null); if (attachment["skin"] is JsonValue 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["parent"] is JsonValue 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["deform"] is JsonValue deform) writer.WriteBoolean((bool)deform); else writer.WriteBoolean(true);
if (nonessential) if (nonessential)
{ {
if (attachment.TryGetPropertyValue("width", out var _width)) writer.WriteFloat((float)_width); else writer.WriteFloat(0); if (attachment["width"] is JsonValue _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["height"] is JsonValue _height) writer.WriteFloat((float)_height); else writer.WriteFloat(0);
} }
break; break;
case AttachmentType.Path: case AttachmentType.Path:
if (attachment.TryGetPropertyValue("closed", out var closed)) writer.WriteBoolean((bool)closed); else writer.WriteBoolean(false); if (attachment["closed"] is JsonValue 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["constantSpeed"] is JsonValue constantSpeed) writer.WriteBoolean((bool)constantSpeed); else writer.WriteBoolean(true);
if (attachment.TryGetPropertyValue("vertexCount", out var _vertexCount3)) vertexCount = (int)_vertexCount3; else vertexCount = 0; if (attachment["vertexCount"] is JsonValue _vertexCount3) vertexCount = (int)_vertexCount3; else vertexCount = 0;
writer.WriteVarInt(vertexCount); writer.WriteVarInt(vertexCount);
WriteVertices(attachment["vertices"].AsArray(), vertexCount); WriteVertices(attachment["vertices"].AsArray(), vertexCount);
WriteFloatArray(attachment["lengths"].AsArray(), vertexCount / 3); WriteFloatArray(attachment["lengths"].AsArray(), vertexCount / 3);
if (nonessential) writer.WriteInt(0); if (nonessential) writer.WriteInt(0);
break; break;
case AttachmentType.Point: case AttachmentType.Point:
if (attachment.TryGetPropertyValue("rotation", out var rotation2)) writer.WriteFloat((float)rotation2); else writer.WriteFloat(0); if (attachment["rotation"] is JsonValue 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["x"] is JsonValue 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["y"] is JsonValue y2) writer.WriteFloat((float)y2); else writer.WriteFloat(0);
if (nonessential) writer.WriteInt(0); if (nonessential) writer.WriteInt(0);
break; break;
case AttachmentType.Clipping: case AttachmentType.Clipping:
writer.WriteVarInt(slot2idx[(string)attachment["end"]]); 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); writer.WriteVarInt(vertexCount);
WriteVertices(attachment["vertices"].AsArray(), vertexCount); WriteVertices(attachment["vertices"].AsArray(), vertexCount);
if (nonessential) writer.WriteInt(0); if (nonessential) writer.WriteInt(0);
@@ -1242,17 +1242,17 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
{ {
JsonObject data = _data.AsObject(); JsonObject data = _data.AsObject();
writer.WriteStringRef(name); writer.WriteStringRef(name);
if (data.TryGetPropertyValue("int", out var @int)) writer.WriteVarInt((int)@int); else writer.WriteVarInt(0); if (data["int"] is JsonValue @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["float"] is JsonValue @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["string"] is JsonValue @string) writer.WriteString((string)@string); else writer.WriteString("");
if (data.TryGetPropertyValue("audio", out var _audio)) if (data["audio"] is JsonValue _audio)
{ {
var audio = (string)_audio; var audio = (string)_audio;
writer.WriteString(audio); writer.WriteString(audio);
if (audio is not null) if (audio is not null)
{ {
if (data.TryGetPropertyValue("volume", out var volume)) writer.WriteFloat((float)volume); else writer.WriteFloat(1); if (data["volume"] is JsonValue 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["balance"] is JsonValue balance) writer.WriteFloat((float)balance); else writer.WriteFloat(0);
} }
} }
else else
@@ -1277,15 +1277,15 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
{ {
JsonObject data = _data.AsObject(); JsonObject data = _data.AsObject();
writer.WriteString(name); writer.WriteString(name);
if (data.TryGetPropertyValue("slots", out var slots)) WriteSlotTimelines(slots.AsObject()); else writer.WriteVarInt(0); if (data["slots"] is JsonObject slots) WriteSlotTimelines(slots); else writer.WriteVarInt(0);
if (data.TryGetPropertyValue("bones", out var bones)) WriteBoneTimelines(bones.AsObject()); else writer.WriteVarInt(0); if (data["bones"] is JsonObject bones) WriteBoneTimelines(bones); else writer.WriteVarInt(0);
if (data.TryGetPropertyValue("ik", out var ik)) WriteIKTimelines(ik.AsObject()); else writer.WriteVarInt(0); if (data["ik"] is JsonObject ik) WriteIKTimelines(ik); else writer.WriteVarInt(0);
if (data.TryGetPropertyValue("transform", out var transform)) WriteTransformTimelines(transform.AsObject()); else writer.WriteVarInt(0); if (data["transform"] is JsonObject transform) WriteTransformTimelines(transform); else writer.WriteVarInt(0);
if (data.TryGetPropertyValue("path", out var path)) WritePathTimelines(path.AsObject()); else writer.WriteVarInt(0); if (data["path"] is JsonObject path) WritePathTimelines(path); else writer.WriteVarInt(0);
if (data.TryGetPropertyValue("deform", out var deform)) WriteDeformTimelines(deform.AsObject()); else writer.WriteVarInt(0); if (data["deform"] is JsonObject deform) WriteDeformTimelines(deform); else writer.WriteVarInt(0);
if (data.TryGetPropertyValue("drawOrder", out var drawOrder)) WriteDrawOrderTimelines(drawOrder.AsArray()); else if (data["drawOrder"] is JsonArray drawOrder) WriteDrawOrderTimelines(drawOrder); else
if (data.TryGetPropertyValue("draworder", out var draworder)) WriteDrawOrderTimelines(draworder.AsArray()); else writer.WriteVarInt(0); if (data["draworder"] is JsonArray draworder) WriteDrawOrderTimelines(draworder); else writer.WriteVarInt(0);
if (data.TryGetPropertyValue("events", out var events)) WriteEventTimelines(events.AsArray()); else writer.WriteVarInt(0); if (data["events"] is JsonArray events) WriteEventTimelines(events); else writer.WriteVarInt(0);
} }
} }
@@ -1306,7 +1306,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
writer.WriteVarInt(frames.Count); writer.WriteVarInt(frames.Count);
foreach (JsonObject o in frames) foreach (JsonObject o in frames)
{ {
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
writer.WriteStringRef((string)o["name"]); writer.WriteStringRef((string)o["name"]);
} }
} }
@@ -1317,7 +1317,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
writer.WriteInt(int.Parse((string)o["color"], NumberStyles.HexNumber)); writer.WriteInt(int.Parse((string)o["color"], NumberStyles.HexNumber));
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
@@ -1329,7 +1329,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); 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["light"], NumberStyles.HexNumber));
writer.WriteInt(int.Parse((string)o["dark"], NumberStyles.HexNumber)); writer.WriteInt(int.Parse((string)o["dark"], NumberStyles.HexNumber));
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
@@ -1357,8 +1357,8 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("angle", out var angle)) writer.WriteFloat((float)angle); else writer.WriteFloat(0); if (o["angle"] is JsonValue angle) writer.WriteFloat((float)angle); else writer.WriteFloat(0);
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
} }
@@ -1369,9 +1369,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(0); if (o["x"] is JsonValue x) writer.WriteFloat((float)x); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("y", out var y)) writer.WriteFloat((float)y); else writer.WriteFloat(0); if (o["y"] is JsonValue y) writer.WriteFloat((float)y); else writer.WriteFloat(0);
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
} }
@@ -1382,9 +1382,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(1); if (o["x"] is JsonValue x) writer.WriteFloat((float)x); else writer.WriteFloat(1);
if (o.TryGetPropertyValue("y", out var y)) writer.WriteFloat((float)y); else writer.WriteFloat(1); if (o["y"] is JsonValue y) writer.WriteFloat((float)y); else writer.WriteFloat(1);
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
} }
@@ -1395,9 +1395,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("x", out var x)) writer.WriteFloat((float)x); else writer.WriteFloat(0); if (o["x"] is JsonValue x) writer.WriteFloat((float)x); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("y", out var y)) writer.WriteFloat((float)y); else writer.WriteFloat(0); if (o["y"] is JsonValue y) writer.WriteFloat((float)y); else writer.WriteFloat(0);
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
} }
@@ -1416,12 +1416,12 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("mix", out var mix)) writer.WriteFloat((float)mix); else writer.WriteFloat(1); if (o["mix"] is JsonValue mix) writer.WriteFloat((float)mix); else writer.WriteFloat(1);
if (o.TryGetPropertyValue("softness", out var softness)) writer.WriteFloat((float)softness); else writer.WriteFloat(0); if (o["softness"] is JsonValue softness) writer.WriteFloat((float)softness); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("bendPositive", out var bendPositive)) writer.WriteSByte((sbyte)((bool)bendPositive ? 1 : -1)); else writer.WriteSByte(1); if (o["bendPositive"] is JsonValue bendPositive) writer.WriteSByte((sbyte)((bool)bendPositive ? 1 : -1)); else writer.WriteSByte(1);
if (o.TryGetPropertyValue("compress", out var compress)) writer.WriteBoolean((bool)compress); else writer.WriteBoolean(false); if (o["compress"] is JsonValue compress) writer.WriteBoolean((bool)compress); else writer.WriteBoolean(false);
if (o.TryGetPropertyValue("stretch", out var stretch)) writer.WriteBoolean((bool)stretch); else writer.WriteBoolean(false); if (o["stretch"] is JsonValue stretch) writer.WriteBoolean((bool)stretch); else writer.WriteBoolean(false);
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
} }
@@ -1438,11 +1438,11 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("rotateMix", out var rotateMix)) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1); if (o["rotateMix"] is JsonValue rotateMix) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1);
if (o.TryGetPropertyValue("translateMix", out var translateMix)) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1); if (o["translateMix"] is JsonValue translateMix) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1);
if (o.TryGetPropertyValue("scaleMix", out var scaleMix)) writer.WriteFloat((float)scaleMix); else writer.WriteFloat(1); if (o["scaleMix"] is JsonValue scaleMix) writer.WriteFloat((float)scaleMix); else writer.WriteFloat(1);
if (o.TryGetPropertyValue("shearMix", out var shearMix)) writer.WriteFloat((float)shearMix); else writer.WriteFloat(1); if (o["shearMix"] is JsonValue shearMix) writer.WriteFloat((float)shearMix); else writer.WriteFloat(1);
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
} }
@@ -1466,8 +1466,8 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("position", out var position)) writer.WriteFloat((float)position); else writer.WriteFloat(0); if (o["position"] is JsonValue position) writer.WriteFloat((float)position); else writer.WriteFloat(0);
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
} }
@@ -1478,8 +1478,8 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("spacing", out var spacing)) writer.WriteFloat((float)spacing); else writer.WriteFloat(0); if (o["spacing"] is JsonValue spacing) writer.WriteFloat((float)spacing); else writer.WriteFloat(0);
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
} }
@@ -1490,9 +1490,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("rotateMix", out var rotateMix)) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1); if (o["rotateMix"] is JsonValue rotateMix) writer.WriteFloat((float)rotateMix); else writer.WriteFloat(1);
if (o.TryGetPropertyValue("translateMix", out var translateMix)) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1); if (o["translateMix"] is JsonValue translateMix) writer.WriteFloat((float)translateMix); else writer.WriteFloat(1);
if (i < n - 1) WriteCurve(o); if (i < n - 1) WriteCurve(o);
} }
} }
@@ -1521,14 +1521,13 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
for (int i = 0, n = frames.Count; i < n; i++) for (int i = 0, n = frames.Count; i < n; i++)
{ {
JsonObject o = frames[i].AsObject(); JsonObject o = frames[i].AsObject();
if (o.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (o["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (o.TryGetPropertyValue("vertices", out var _vertices)) if (o["vertices"] is JsonArray vertices)
{ {
JsonArray vertices = _vertices.AsArray();
writer.WriteVarInt(vertices.Count); writer.WriteVarInt(vertices.Count);
if (vertices.Count > 0) if (vertices.Count > 0)
{ {
if (o.TryGetPropertyValue("offset", out var offset)) writer.WriteVarInt((int)offset); else writer.WriteVarInt(0); if (o["offset"] is JsonValue offset) writer.WriteVarInt((int)offset); else writer.WriteVarInt(0);
WriteFloatArray(vertices, vertices.Count); WriteFloatArray(vertices, vertices.Count);
} }
} }
@@ -1548,10 +1547,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
writer.WriteVarInt(drawOrderTimelines.Count); writer.WriteVarInt(drawOrderTimelines.Count);
foreach (JsonObject data in drawOrderTimelines) foreach (JsonObject data in drawOrderTimelines)
{ {
if (data.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (data["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
if (data.TryGetPropertyValue("offsets", out var _offsets)) if (data["offsets"] is JsonArray offsets)
{ {
JsonArray offsets = _offsets.AsArray();
writer.WriteVarInt(offsets.Count); writer.WriteVarInt(offsets.Count);
foreach (JsonObject o in offsets) foreach (JsonObject o in offsets)
{ {
@@ -1574,13 +1572,13 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
foreach(JsonObject data in eventTimelines) foreach(JsonObject data in eventTimelines)
{ {
JsonObject eventData = events[(string)data["name"]].AsObject(); JsonObject eventData = events[(string)data["name"]].AsObject();
if (data.TryGetPropertyValue("time", out var time)) writer.WriteFloat((float)time); else writer.WriteFloat(0); if (data["time"] is JsonValue time) writer.WriteFloat((float)time); else writer.WriteFloat(0);
writer.WriteVarInt(event2idx[(string)data["name"]]); writer.WriteVarInt(event2idx[(string)data["name"]]);
if (data.TryGetPropertyValue("int", out var @int)) writer.WriteVarInt((int)@int); else if (data["int"] is JsonValue @int) writer.WriteVarInt((int)@int); else
if (eventData.TryGetPropertyValue("int", out var @int2)) writer.WriteVarInt((int)@int2); else writer.WriteVarInt(0); if (eventData["int"] is JsonValue @int2) writer.WriteVarInt((int)@int2); else writer.WriteVarInt(0);
if (data.TryGetPropertyValue("float", out var @float)) writer.WriteFloat((float)@float); else if (data["float"] is JsonValue @float) writer.WriteFloat((float)@float); else
if (eventData.TryGetPropertyValue("float", out var @float2)) writer.WriteFloat((float)@float2); else writer.WriteFloat(0); if (eventData["float"] is JsonValue @float2) writer.WriteFloat((float)@float2); else writer.WriteFloat(0);
if (data.TryGetPropertyValue("string", out var @string)) if (data["string"] is JsonValue @string)
{ {
writer.WriteBoolean(true); writer.WriteBoolean(true);
writer.WriteString((string)@string); writer.WriteString((string)@string);
@@ -1592,10 +1590,10 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
if (eventData.ContainsKey("audio")) if (eventData.ContainsKey("audio"))
{ {
if (data.TryGetPropertyValue("volume", out var volume)) writer.WriteFloat((float)volume); else if (data["volume"] is JsonValue volume) writer.WriteFloat((float)volume); else
if (eventData.TryGetPropertyValue("volume", out var volume2)) writer.WriteFloat((float)volume2); else writer.WriteFloat(1); if (eventData["volume"] is JsonValue volume2) writer.WriteFloat((float)volume2); else writer.WriteFloat(1);
if (data.TryGetPropertyValue("balance", out var balance)) writer.WriteFloat((float)balance); else if (data["balance"] is JsonValue balance) writer.WriteFloat((float)balance); else
if (eventData.TryGetPropertyValue("balance", out var balance2)) writer.WriteFloat((float)balance2); else writer.WriteFloat(0); if (eventData["balance"] is JsonValue balance2) writer.WriteFloat((float)balance2); else writer.WriteFloat(0);
} }
} }
} }
@@ -1651,7 +1649,7 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
private void WriteCurve(JsonObject obj) private void WriteCurve(JsonObject obj)
{ {
if (obj.TryGetPropertyValue("curve", out var curve)) if (obj["curve"] is JsonValue curve)
{ {
if (curve.GetValueKind() == JsonValueKind.String) if (curve.GetValueKind() == JsonValueKind.String)
{ {
@@ -1661,9 +1659,9 @@ namespace SpineViewer.Spine.Implementations.SkeletonConverter
{ {
writer.WriteByte(SkeletonBinary.CURVE_BEZIER); writer.WriteByte(SkeletonBinary.CURVE_BEZIER);
writer.WriteFloat((float)curve); writer.WriteFloat((float)curve);
if (obj.TryGetPropertyValue("c2", out var c2)) writer.WriteFloat((float)c2); else writer.WriteFloat(0); if (obj["c2"] is JsonValue c2) writer.WriteFloat((float)c2); else writer.WriteFloat(0);
if (obj.TryGetPropertyValue("c3", out var c3)) writer.WriteFloat((float)c3); else writer.WriteFloat(1); if (obj["c3"] is JsonValue c3) writer.WriteFloat((float)c3); else writer.WriteFloat(1);
if (obj.TryGetPropertyValue("c4", out var c4)) writer.WriteFloat((float)c4); else writer.WriteFloat(1); if (obj["c4"] is JsonValue c4) writer.WriteFloat((float)c4); else writer.WriteFloat(1);
} }
} }
else else

View File

@@ -105,7 +105,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
} }
} }
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray()); SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray(); SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)]; AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器 skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
@@ -123,45 +123,11 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override float scale protected override float scale
{ {
get get => Math.Abs(skeleton.ScaleX);
{
if (skeletonBinary is not null)
return skeletonBinary.Scale;
else if (skeletonJson is not null)
return skeletonJson.Scale;
else
return 1f;
}
set set
{ {
// 保存状态 skeleton.ScaleX = Math.Sign(skeleton.ScaleX) * value;
var pos = position; skeleton.ScaleY = Math.Sign(skeleton.ScaleY) * value;
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
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
animationState = new AnimationState(animationStateData);
// 恢复状态
position = pos;
flipX = fX;
flipY = fY;
reloadSkins();
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
} }
} }
@@ -177,14 +143,22 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override bool flipX protected override bool flipX
{ {
get => skeleton.FlipX; get => skeleton.ScaleX < 0;
set => skeleton.FlipX = value; set
{
if (skeleton.ScaleX > 0 && value || skeleton.ScaleX < 0 && !value)
skeleton.ScaleX *= -1;
}
} }
protected override bool flipY protected override bool flipY
{ {
get => skeleton.FlipY; get => skeleton.ScaleY < 0;
set => skeleton.FlipY = value; 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 string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
@@ -199,7 +173,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void addSkin(string name) protected override void addSkin(string name)
{ {
if (skeletonData.FindSkin(name) is Skin sk) // default 不需要加载
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
{ {
// XXX: 3.7 及以下不支持 AddSkin // XXX: 3.7 及以下不支持 AddSkin
foreach (var (k, v) in sk.Attachments) foreach (var (k, v) in sk.Attachments)
@@ -221,7 +196,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void setAnimation(int track, string name) protected override void setAnimation(int track, string name)
{ {
if (name == EMPTY_ANIMATION) if (name == EMPTY_ANIMATION)
animationState.SetAnimation(track, EmptyAnimation, false); animationState.SetAnimation(track, EmptyAnimation, true);
else if (AnimationNames.Contains(name)) else if (AnimationNames.Contains(name))
animationState.SetAnimation(track, name, true); animationState.SetAnimation(track, name, true);
} }
@@ -242,8 +217,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
var maxDuration = 0f; var maxDuration = 0f;
var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
var tmpAnimationState = new AnimationState(animationStateData); var tmpAnimationState = new AnimationState(animationStateData);
tmpSkeleton.FlipX = skeleton.FlipX; tmpSkeleton.ScaleX = skeleton.ScaleX;
tmpSkeleton.FlipY = skeleton.FlipY; tmpSkeleton.ScaleY = skeleton.ScaleY;
tmpSkeleton.X = skeleton.X; tmpSkeleton.X = skeleton.X;
tmpSkeleton.Y = skeleton.Y; tmpSkeleton.Y = skeleton.Y;
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value)) foreach (var (name, _) in skinLoadStatus.Where(e => e.Value))

View File

@@ -104,7 +104,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
} }
} }
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray()); SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray(); SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)]; AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器 skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
@@ -122,68 +122,42 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override float scale protected override float scale
{ {
get get => Math.Abs(skeleton.ScaleX);
{
if (skeletonBinary is not null)
return skeletonBinary.Scale;
else if (skeletonJson is not null)
return skeletonJson.Scale;
else
return 1f;
}
set set
{ {
// 保存状态 skeleton.ScaleX = Math.Sign(skeleton.ScaleX) * value;
var pos = position; skeleton.ScaleY = Math.Sign(skeleton.ScaleY) * value;
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
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
animationState = new AnimationState(animationStateData);
// 恢复状态
position = pos;
flipX = fX;
flipY = fY;
reloadSkins();
for (int i = 0; i < animations.Length; i++) setAnimation(i, animations[i]);
} }
} }
protected override PointF position protected override PointF position
{ {
get => new(skeleton.X, skeleton.Y); get => new(skeleton.X, skeleton.Y);
set set
{ {
skeleton.X = value.X; skeleton.X = value.X;
skeleton.Y = value.Y; skeleton.Y = value.Y;
} }
} }
protected override bool flipX protected override bool flipX
{ {
get => skeleton.FlipX; get => skeleton.ScaleX < 0;
set => skeleton.FlipX = value; set
{
if (skeleton.ScaleX > 0 && value || skeleton.ScaleX < 0 && !value)
skeleton.ScaleX *= -1;
}
} }
protected override bool flipY protected override bool flipY
{ {
get => skeleton.FlipY; get => skeleton.ScaleY < 0;
set => skeleton.FlipY = value; 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 string getSlotAttachment(string slot) => skeleton.FindSlot(slot)?.Attachment?.Name ?? EMPTY_ATTACHMENT;
@@ -198,7 +172,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void addSkin(string name) protected override void addSkin(string name)
{ {
if (skeletonData.FindSkin(name) is Skin sk) // default 不需要加载
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
{ {
// XXX: 3.7 及以下不支持 AddSkin // XXX: 3.7 及以下不支持 AddSkin
foreach (var (k, v) in sk.Attachments) foreach (var (k, v) in sk.Attachments)
@@ -220,7 +195,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void setAnimation(int track, string name) protected override void setAnimation(int track, string name)
{ {
if (name == EMPTY_ANIMATION) if (name == EMPTY_ANIMATION)
animationState.SetAnimation(track, EmptyAnimation, false); animationState.SetAnimation(track, EmptyAnimation, true);
else if (AnimationNames.Contains(name)) else if (AnimationNames.Contains(name))
animationState.SetAnimation(track, name, true); animationState.SetAnimation(track, name, true);
} }
@@ -242,8 +217,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
var maxDuration = 0f; var maxDuration = 0f;
var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; var tmpSkeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) };
var tmpAnimationState = new AnimationState(animationStateData); var tmpAnimationState = new AnimationState(animationStateData);
tmpSkeleton.FlipX = skeleton.FlipX; tmpSkeleton.ScaleX = skeleton.ScaleX;
tmpSkeleton.FlipY = skeleton.FlipY; tmpSkeleton.ScaleY = skeleton.ScaleY;
tmpSkeleton.X = skeleton.X; tmpSkeleton.X = skeleton.X;
tmpSkeleton.Y = skeleton.Y; tmpSkeleton.Y = skeleton.Y;
foreach (var (name, _) in skinLoadStatus.Where(e => e.Value)) foreach (var (name, _) in skinLoadStatus.Where(e => e.Value))

View File

@@ -101,7 +101,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
} }
} }
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray()); SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray(); SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)]; AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器 skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
@@ -169,7 +169,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void addSkin(string name) protected override void addSkin(string name)
{ {
if (skeletonData.FindSkin(name) is Skin sk) // default 不需要加载
if (name != "default" && skeletonData.FindSkin(name) is Skin sk)
{ {
// XXX: 3.7 及以下不支持 AddSkin // XXX: 3.7 及以下不支持 AddSkin
foreach (var (k, v) in sk.Attachments) foreach (var (k, v) in sk.Attachments)
@@ -191,7 +192,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void setAnimation(int track, string name) protected override void setAnimation(int track, string name)
{ {
if (name == EMPTY_ANIMATION) if (name == EMPTY_ANIMATION)
animationState.SetAnimation(track, EmptyAnimation, false); animationState.SetAnimation(track, EmptyAnimation, true);
else if (AnimationNames.Contains(name)) else if (AnimationNames.Contains(name))
animationState.SetAnimation(track, name, true); animationState.SetAnimation(track, name, true);
} }

View File

@@ -108,7 +108,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
} }
} }
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray()); SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray(); SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)]; AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器 skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
@@ -176,7 +176,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void addSkin(string name) 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.Skin.AddSkin(sk);
skeleton.SetSlotsToSetupPose(); skeleton.SetSlotsToSetupPose();
@@ -196,7 +197,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void setAnimation(int track, string name) protected override void setAnimation(int track, string name)
{ {
if (name == EMPTY_ANIMATION) if (name == EMPTY_ANIMATION)
animationState.SetAnimation(track, EmptyAnimation, false); animationState.SetAnimation(track, EmptyAnimation, true);
else if (AnimationNames.Contains(name)) else if (AnimationNames.Contains(name))
animationState.SetAnimation(track, name, true); animationState.SetAnimation(track, name, true);
} }

View File

@@ -104,7 +104,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
} }
} }
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray()); SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray(); SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)]; AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器 skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
@@ -172,7 +172,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void addSkin(string name) 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.Skin.AddSkin(sk);
skeleton.SetSlotsToSetupPose(); skeleton.SetSlotsToSetupPose();
@@ -192,7 +193,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void setAnimation(int track, string name) protected override void setAnimation(int track, string name)
{ {
if (name == EMPTY_ANIMATION) if (name == EMPTY_ANIMATION)
animationState.SetAnimation(track, EmptyAnimation, false); animationState.SetAnimation(track, EmptyAnimation, true);
else if (AnimationNames.Contains(name)) else if (AnimationNames.Contains(name))
animationState.SetAnimation(track, name, true); animationState.SetAnimation(track, name, true);
} }

View File

@@ -104,7 +104,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
} }
} }
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray()); SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray(); SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)]; AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器 skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
@@ -172,7 +172,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void addSkin(string name) 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.Skin.AddSkin(sk);
skeleton.SetSlotsToSetupPose(); skeleton.SetSlotsToSetupPose();
@@ -192,7 +193,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void setAnimation(int track, string name) protected override void setAnimation(int track, string name)
{ {
if (name == EMPTY_ANIMATION) if (name == EMPTY_ANIMATION)
animationState.SetAnimation(track, EmptyAnimation, false); animationState.SetAnimation(track, EmptyAnimation, true);
else if (AnimationNames.Contains(name)) else if (AnimationNames.Contains(name))
animationState.SetAnimation(track, name, true); animationState.SetAnimation(track, name, true);
} }

View File

@@ -104,7 +104,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
} }
} }
SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray()); SlotAttachmentNames = slotAttachments.ToFrozenDictionary(item => item.Key, item => item.Value.Keys.ToImmutableArray());
SkinNames = skeletonData.Skins.Select(v => v.Name).Where(v => v != "default").ToImmutableArray(); SkinNames = skeletonData.Skins.Select(v => v.Name).ToImmutableArray();
AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)]; AnimationNames = [EMPTY_ANIMATION, .. skeletonData.Animations.Select(v => v.Name)];
skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器 skeleton = new Skeleton(skeletonData) { Skin = new(Guid.NewGuid().ToString()) }; // 挂载一个空皮肤当作容器
@@ -172,7 +172,8 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void addSkin(string name) 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.Skin.AddSkin(sk);
skeleton.SetSlotsToSetupPose(); skeleton.SetSlotsToSetupPose();
@@ -192,7 +193,7 @@ namespace SpineViewer.Spine.Implementations.SpineObject
protected override void setAnimation(int track, string name) protected override void setAnimation(int track, string name)
{ {
if (name == EMPTY_ANIMATION) if (name == EMPTY_ANIMATION)
animationState.SetAnimation(track, EmptyAnimation, false); animationState.SetAnimation(track, EmptyAnimation, true);
else if (AnimationNames.Contains(name)) else if (AnimationNames.Contains(name))
animationState.SetAnimation(track, name, true); animationState.SetAnimation(track, name, true);
} }

View File

@@ -93,8 +93,8 @@ namespace SpineViewer.Spine
tex.Display(); tex.Display();
Preview = tex.Texture.CopyToBitmap(); Preview = tex.Texture.CopyToBitmap();
// 初始化皮肤加载情况 // 初始化皮肤加载情况, 不需要记录 default
foreach (var n in SkinNames) skinLoadStatus[n] = false; foreach (var n in SkinNames.Where(v => v != "default")) skinLoadStatus[n] = false;
// 默认初始化10个动画空位 // 默认初始化10个动画空位
for (int i = 0; i < 10; i++) setAnimation(i, AnimationNames.First()); for (int i = 0; i < 10; i++) setAnimation(i, AnimationNames.First());
@@ -359,7 +359,7 @@ namespace SpineViewer.Spine
/// <summary> /// <summary>
/// 查询皮肤加载状态, 皮肤不存在时返回 false /// 查询皮肤加载状态, 皮肤不存在时返回 false
/// </summary> /// </summary>
public bool GetSkinStatus(string name) { lock (_lock) return skinLoadStatus.TryGetValue(name, out var status) && status; } public bool GetSkinStatus(string name) { lock (_lock) return name == "default" || skinLoadStatus.TryGetValue(name, out var status) && status; }
/// <summary> /// <summary>
/// 设置皮肤加载状态, 忽略不存在的皮肤 /// 设置皮肤加载状态, 忽略不存在的皮肤

View File

@@ -66,7 +66,7 @@ namespace SpineViewer.Spine.SpineView
private class SkinPropertyDescriptor(string name, Attribute[]? attributes) : PropertyDescriptor(name, attributes) private class SkinPropertyDescriptor(string name, Attribute[]? attributes) : PropertyDescriptor(name, attributes)
{ {
public override Type ComponentType => typeof(SpineSkinProperty); public override Type ComponentType => typeof(SpineSkinProperty);
public override bool IsReadOnly => false; public override bool IsReadOnly => Name == "default";
public override Type PropertyType => typeof(bool); public override Type PropertyType => typeof(bool);
public override bool CanResetValue(object component) => false; public override bool CanResetValue(object component) => false;
public override void ResetValue(object component) { } public override void ResetValue(object component) { }

View File

@@ -7,10 +7,11 @@
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath> <BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.12.5</Version> <Version>0.12.7</Version>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>appicon.ico</ApplicationIcon> <ApplicationIcon>appicon.ico</ApplicationIcon>
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
<GenerateResourceWarnOnBinaryFormatterUse>false</GenerateResourceWarnOnBinaryFormatterUse> <GenerateResourceWarnOnBinaryFormatterUse>false</GenerateResourceWarnOnBinaryFormatterUse>
</PropertyGroup> </PropertyGroup>

View File

@@ -79,6 +79,8 @@ namespace SpineViewer.Utils
} }
} }
private StandardValuesCollection standardValues;
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true; public override bool GetStandardValuesSupported(ITypeDescriptorContext? context) => true;
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context) public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
@@ -89,29 +91,77 @@ namespace SpineViewer.Utils
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context) public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
{ {
// 查找属性上的 StandardValuesAttribute if (standardValues is null)
var attribute = context?.PropertyDescriptor?.Attributes.OfType<StandardValuesAttribute>().FirstOrDefault(); {
StandardValuesCollection result; // 查找属性上的 StandardValuesAttribute
if (attribute != null) var attribute = context?.PropertyDescriptor?.Attributes.OfType<StandardValuesAttribute>().FirstOrDefault();
result = new StandardValuesCollection(attribute.StandardValues); if (attribute != null)
else standardValues = new StandardValuesCollection(attribute.StandardValues);
result = new StandardValuesCollection(Array.Empty<string>()); else
return result; 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 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);
public override object? GetValue(object? component) => component?.GetType().GetField(Name)?.GetValue(component) ?? default; byte b = byte.Parse(hex.Substring(5, 2), NumberStyles.HexNumber);
byte a = includeAlpha ? byte.Parse(hex.Substring(7, 2), NumberStyles.HexNumber) : (byte)255;
public override void SetValue(object? component, object? value) => component?.GetType().GetField(Name)?.SetValue(component, value); return new SFML.Graphics.Color(r, g, b, a);
} }
private static PropertyDescriptorCollection pdCollection = null; 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);
}
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) public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{ {
@@ -123,24 +173,48 @@ namespace SpineViewer.Utils
if (value is string s) if (value is string s)
{ {
s = s.Trim(); s = s.Trim();
if (s.StartsWith("#") && s.Length == 9)
try
{ {
try // 处理 #RRGGBBAA 和 #RRGGBB 格式
if (s.StartsWith("#"))
{ {
// 解析 R, G, B, A 分量注意16进制解析 if (s.Length == 9) // #RRGGBBAA
byte r = byte.Parse(s.Substring(1, 2), NumberStyles.HexNumber); return ParseHexColor(s, includeAlpha: true);
byte g = byte.Parse(s.Substring(3, 2), NumberStyles.HexNumber); if (s.Length == 7) // #RRGGBB
byte b = byte.Parse(s.Substring(5, 2), NumberStyles.HexNumber); return ParseHexColor(s, includeAlpha: false);
byte a = byte.Parse(s.Substring(7, 2), NumberStyles.HexNumber); 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); return new SFML.Graphics.Color(r, g, b, a);
} }
catch (Exception ex)
{ // 尝试解析为 KnownColor
throw new FormatException("无法解析颜色,确保格式为 #RRGGBBAA", ex); 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); return base.ConvertFrom(context, culture, value);
} }
@@ -156,6 +230,21 @@ namespace SpineViewer.Utils
return base.ConvertTo(context, culture, value, destinationType); 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) public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext? context, object value, Attribute[]? attributes)
{ {
pdCollection ??= new( pdCollection ??= new(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 85 KiB