Compare commits

...

225 Commits

Author SHA1 Message Date
ww-rm
9040e02025 Merge pull request #140 from ww-rm/dev/wpf
v0.16.10
2025-10-29 21:35:34 +08:00
ww-rm
b3ba073368 完善日志 2025-10-29 21:34:30 +08:00
ww-rm
332019a667 修复文件夹无法自动创建的bug 2025-10-29 21:00:38 +08:00
ww-rm
add9cf157d 修改压缩路径 2025-10-29 20:54:35 +08:00
ww-rm
8b0ea750d8 Merge pull request #139 from ww-rm/dev/wpf
v0.16.10
2025-10-29 20:45:24 +08:00
ww-rm
733739921d update changelog 2025-10-29 20:44:20 +08:00
ww-rm
e0f46f521a update to v0.16.10 2025-10-29 20:43:43 +08:00
ww-rm
aa4245ef2a add linux release 2025-10-29 20:42:31 +08:00
ww-rm
a262538eba 增加linux条件依赖 2025-10-29 19:48:23 +08:00
ww-rm
2e4a5a75c0 修复着色器语法兼容性错误 2025-10-29 19:33:45 +08:00
ww-rm
9331656431 修改项目配置 2025-10-28 22:03:39 +08:00
ww-rm
64bc12db06 Merge pull request #136 from ww-rm/dev/wpf
v0.16.9
2025-10-27 23:51:47 +08:00
ww-rm
7a29fee641 update readme 2025-10-27 23:50:39 +08:00
ww-rm
49f95ddbb7 add readme 2025-10-27 23:48:21 +08:00
ww-rm
317ee71882 update changelog 2025-10-27 23:45:16 +08:00
ww-rm
7780fbda28 update ignore 2025-10-27 23:43:57 +08:00
ww-rm
b54c6a1777 update to v0.16.9 2025-10-27 23:43:52 +08:00
ww-rm
617157044c 增加透明度参数 2025-10-27 23:33:25 +08:00
ww-rm
29d7e8d9d8 移除依赖库 2025-10-27 22:26:33 +08:00
ww-rm
701d1fcf90 增加日志 2025-10-27 07:35:05 +08:00
ww-rm
df36d46528 增加动态进度条 2025-10-27 00:00:44 +08:00
ww-rm
3459f3af03 修复进度回调done值错误 2025-10-26 23:59:57 +08:00
ww-rm
5498508700 移除不受支持的格式 2025-10-26 23:16:40 +08:00
ww-rm
a61bb43250 增加preview命令 2025-10-26 22:14:34 +08:00
ww-rm
aace461ae0 修改方法名 2025-10-26 22:05:48 +08:00
ww-rm
c02cec9a18 修改图像质量默认值为100 2025-10-26 22:01:40 +08:00
ww-rm
31daed9e81 移除不受支持的图像格式 2025-10-26 21:49:58 +08:00
ww-rm
997d55350d 修复可能的资源泄露 2025-10-26 21:28:11 +08:00
ww-rm
cc6d1b6c00 更新注释 2025-10-26 19:35:43 +08:00
ww-rm
e14c54c3a4 调整时间轴处理顺序 2025-10-26 17:35:51 +08:00
ww-rm
5eba515eac 增加 query 命令 2025-10-26 17:31:20 +08:00
ww-rm
f878530184 重构 2025-10-26 16:30:13 +08:00
ww-rm
81d9224658 增加参数验证 2025-10-26 16:22:49 +08:00
ww-rm
9d9edb8bc4 增加 export 命令 2025-10-26 16:16:43 +08:00
ww-rm
d3b5814c6f small change 2025-10-26 15:52:47 +08:00
ww-rm
aade44cffb 增加注释 2025-10-26 15:19:09 +08:00
ww-rm
c4956b9c16 重构 2025-10-26 13:26:47 +08:00
ww-rm
7ca431b214 增加System.CommandLine库 2025-10-25 17:19:27 +08:00
ww-rm
74538ddf74 apng和mov格式参数改为枚举量类型 2025-10-25 17:04:39 +08:00
ww-rm
779500ee8e 修改ApngPred属性名为PredMethod 2025-10-25 16:47:00 +08:00
ww-rm
ee7c9e9e54 Merge pull request #132 from jayng9663/dev/wpf
Add --warmup option to control physics warmup loops
2025-10-24 23:07:13 +08:00
ww-rm
d335645dc1 remove unnecessary frame loops 2025-10-19 20:46:42 +08:00
ww-rm
0893bd4b54 Merge pull request #133 from ww-rm/dev/wpf
v0.16.8
2025-10-19 20:19:48 +08:00
ww-rm
862926b43e update to v0.16.8 2025-10-19 20:18:11 +08:00
ww-rm
0324ba7971 update changelog 2025-10-19 20:18:02 +08:00
Jay
6a17ec0397 Add --warmup option to control physics warmup loops
Create a new --warmup argument to specify the number of warmup loops for physics before export. This allows users to control how many times the animation is pre-processed to stabilize physics.
2025-10-19 04:59:34 -07:00
ww-rm
53a7700798 增加单独的参数拷贝方式 2025-10-19 17:59:33 +08:00
ww-rm
30608e05bc 修改窗口默认大小 2025-10-19 15:16:05 +08:00
ww-rm
3dcd7b22ca 增加皮肤和插槽的全部启用禁用菜单项 2025-10-19 15:10:45 +08:00
ww-rm
dae5d0b7c7 增加侧边栏折叠功能 2025-10-19 01:01:34 +08:00
ww-rm
f5d3f93cde 增加侧边栏图标样式 2025-10-19 00:05:46 +08:00
ww-rm
dbd7c13c32 Merge branch 'dev/wpf' of github.com:ww-rm/SpineViewer into dev/wpf 2025-10-17 22:44:50 +08:00
ww-rm
b662d8f68a Merge pull request #131 from jhq223/feature/cli-enhancements
Feat(CLI): Add Single-Frame Export and Fix Related Bugs
2025-10-17 22:44:33 +08:00
ww-rm
02445d36e5 增加实时状态保存 2025-10-17 22:41:49 +08:00
ww-rm
b178e48e84 去除默认的最小化提示弹框 2025-10-16 23:54:07 +08:00
ww-rm
c90713ffe7 change tolower to tolowerinvariant 2025-10-16 22:33:36 +08:00
jhq223
dc472cf2a8 Fix: Resolve frame export logic and slot visibility issues
This commit addresses two critical bugs in the single-frame export functionality of the CLI tool.

1.  **Corrects Export Mode Detection for Ambiguous Formats (.webp):**
    - Previously, any output format also supported by the video exporter (like `.webp`) would incorrectly trigger video export mode, ignoring the `--time` argument intended for single-frame captures.
    - The logic is now updated to prioritize the presence of the `--time` argument. If this argument is provided, the tool is forced into single-frame export mode, correctly handling formats like static `.webp`.
    - This was implemented by changing the `time` variable to a nullable float (`float?`) to reliably detect if the argument was passed.

2.  **Fixes "Slot Not Found" Error for `--hide-slot`:**
    - The operation to hide slots was being performed *before* the animation was applied to the skeleton. This caused failures when trying to hide slots that are only activated or have attachments during a specific animation.
    - The slot visibility logic has been moved to execute *after* the animation state is set and the skeleton is updated to the target frame. This ensures that the skeleton is in its final pose, making all relevant slots available for modification.
2025-10-16 20:57:50 +08:00
jhq223
03c599264e feat(cli): Add single-frame image export
Extends the CLI to support exporting single frames as images (.png, .jpg, etc.) in addition to video.

The export logic now determines the output type based on the file extension of the `--output` path.

- Adds new arguments: `--time` to specify the frame and `--quality` for image compression.
- Uses `FrameExporter` for recognized image formats.
- Updates the help message with the new options.
2025-10-16 19:56:13 +08:00
ww-rm
8f7297bea5 Merge pull request #129 from jhq223/feature/cli-enhancements
feat: Add --skin and --hide-slot CLI arguments
2025-10-15 20:59:40 +08:00
jhq223
e4d655012b feat: Add --skin and --hide-slot CLI arguments 2025-10-15 16:01:18 +08:00
ww-rm
4b23c779d3 Merge pull request #124 from ww-rm/dev/wpf
v0.16.7
2025-10-06 14:04:13 +08:00
ww-rm
f5684a50dc update to v0.16.7 2025-10-06 14:03:19 +08:00
ww-rm
579ce9f944 update changelog 2025-10-06 14:03:03 +08:00
ww-rm
7aa88089b8 修复空帧导致的包围盒计算错误 2025-10-06 14:02:21 +08:00
ww-rm
be983f8407 修复窗口二次显示错误 2025-10-06 13:32:03 +08:00
ww-rm
249b930602 Merge pull request #122 from ww-rm/dev/wpf
v0.16.6
2025-10-04 20:55:59 +08:00
ww-rm
6472f378b7 update to v0.16.6 2025-10-04 20:53:29 +08:00
ww-rm
8672f0571c udpate changelog 2025-10-04 20:53:06 +08:00
ww-rm
e7a990c1bd 修复可能出现的0缩放错误 2025-10-04 20:50:08 +08:00
ww-rm
6727fa8e8f Merge pull request #120 from ww-rm/dev/wpf
v0.16.5
2025-10-04 16:59:08 +08:00
ww-rm
66d8c489b5 update to v0.16.5 2025-10-04 16:58:32 +08:00
ww-rm
1931c4713a update changelog 2025-10-04 16:58:09 +08:00
ww-rm
f19f172e7c 修复窗口联动显示问题 2025-10-04 16:56:32 +08:00
ww-rm
092fa76124 修复对于某些旧atlas没有size行的读取异常 2025-10-04 16:38:14 +08:00
ww-rm
a0b7db0a70 Merge pull request #119 from ww-rm/dev/wpf
v0.16.4
2025-10-04 00:11:15 +08:00
ww-rm
6438b46ea0 修复样式错误 2025-10-04 00:09:38 +08:00
ww-rm
2bf73db9d3 补充前景色绑定 2025-10-03 23:54:54 +08:00
ww-rm
03c4974c9f Merge pull request #118 from ww-rm/dev/wpf
v0.16.4
2025-10-03 23:50:05 +08:00
ww-rm
760fa3a451 update to v0.16.4 2025-10-03 23:49:12 +08:00
ww-rm
018d8f5330 update changelog 2025-10-03 23:48:25 +08:00
ww-rm
c9730e1a11 完善标题栏皮肤颜色切换 2025-10-03 23:45:35 +08:00
ww-rm
1f6e19e544 修改日志着色 2025-10-03 22:29:43 +08:00
ww-rm
a1a0777791 重构样式 2025-10-03 22:22:21 +08:00
ww-rm
887e3f76d2 增加程序皮肤首选项 2025-10-03 19:38:48 +08:00
ww-rm
8b622050fa 重构 2025-10-03 19:11:59 +08:00
ww-rm
20369aaf43 调整颜色按钮背景色固定白色 2025-10-03 16:17:26 +08:00
ww-rm
07c0e84b7d 修复颜色错误 2025-10-03 16:11:04 +08:00
ww-rm
6770acaffd small change 2025-10-03 13:34:45 +08:00
ww-rm
6201ccc7d1 去除独立颜色使用 2025-10-03 09:46:14 +08:00
ww-rm
965d1c469e 增加ColorPicker使用 2025-10-03 09:19:02 +08:00
ww-rm
b448ca8cb0 重构 2025-10-02 22:42:21 +08:00
ww-rm
2204eb6c75 增加apng格式并且调整部分布局结构 2025-10-02 22:18:05 +08:00
ww-rm
0abe063899 调整文件结构 2025-10-02 15:18:40 +08:00
ww-rm
6f9b357473 Merge pull request #116 from ww-rm/dev/wpf
v0.16.3
2025-10-02 14:22:11 +08:00
ww-rm
152d842043 update to v0.16.3 2025-10-02 14:21:35 +08:00
ww-rm
d16f97d574 update changelog 2025-10-02 14:21:18 +08:00
ww-rm
d28eabaca5 解决可能卡死问题 2025-10-02 14:19:53 +08:00
ww-rm
b730f677be 完善像素检测 2025-10-02 14:04:42 +08:00
ww-rm
8f8806417a 完善工作区加载模型逻辑 2025-10-02 11:44:51 +08:00
ww-rm
06694c9e89 补充注释 2025-10-02 11:29:13 +08:00
ww-rm
e9b0ce3db2 增加None命中测试等级 2025-10-02 11:19:22 +08:00
ww-rm
4c72608398 修改部分布局 2025-10-02 11:01:38 +08:00
ww-rm
7e99882fbf 增加命中测试等级选项 2025-10-02 10:32:24 +08:00
ww-rm
0d72d8749a 调整调试输出逻辑 2025-10-02 10:23:51 +08:00
ww-rm
d5b7a74520 增加Name属性 2025-10-02 10:17:12 +08:00
ww-rm
0202027edb 修复模型添加顺序错误 2025-10-02 09:32:17 +08:00
ww-rm
d1d32b6292 Merge pull request #115 from ww-rm/dev/wpf
v0.16.2
2025-10-01 23:48:03 +08:00
ww-rm
b32485e122 update to v0.16.2 2025-10-01 23:45:01 +08:00
ww-rm
36d578f4d4 update changelog 2025-10-01 23:44:42 +08:00
ww-rm
42bd5c2830 增加精确命中检测和插槽输出 2025-10-01 23:43:03 +08:00
ww-rm
44548618e8 精简代码 2025-10-01 19:58:39 +08:00
ww-rm
681b1be360 修复添加顺序错误 2025-10-01 18:45:03 +08:00
ww-rm
30dee9978c 重构并增加HitTest 2025-10-01 16:35:51 +08:00
ww-rm
47aafc7948 Merge pull request #113 from ww-rm/dev/wpf
v0.16.1
2025-09-30 22:19:01 +08:00
ww-rm
1d8e2efdff update to v0.16.1 2025-09-30 22:14:07 +08:00
ww-rm
dd504d32ca 修复3.4的附件残留问题 2025-09-30 14:47:00 +08:00
ww-rm
267c7b81c3 Merge pull request #111 from ww-rm/dev/wpf
update readme
2025-09-30 12:19:17 +08:00
ww-rm
c4a6fd9d86 update readme 2025-09-30 12:18:45 +08:00
ww-rm
6e46152e4c Merge pull request #110 from ww-rm/dev/wpf
update readme
2025-09-30 12:00:41 +08:00
ww-rm
f2f296e494 update readme 2025-09-30 11:59:51 +08:00
ww-rm
34f9eeff2c Merge pull request #109 from ww-rm/dev/wpf
v0.16.0
2025-09-30 11:49:45 +08:00
ww-rm
1278fefea2 update readme 2025-09-30 11:47:55 +08:00
ww-rm
48d46afcff update readme 2025-09-30 11:12:06 +08:00
ww-rm
6742dacaf2 update to v0.16.0 2025-09-30 10:53:06 +08:00
ww-rm
3337ecc03a update changelog 2025-09-30 10:52:10 +08:00
ww-rm
b9eaacd1f7 修复可能的3.4版本附件残留问题 2025-09-30 10:30:25 +08:00
ww-rm
a0ada51325 修复跨线程错误 2025-09-30 09:21:54 +08:00
ww-rm
8e03911957 修复可能的窗口大小不正确问题 2025-09-30 08:55:00 +08:00
ww-rm
0b3db0fd0d 增加IsShuttingDownFromTray标志位 2025-09-30 08:45:28 +08:00
ww-rm
bb2862ed4f 增加最小化至托盘图标功能 2025-09-30 01:53:14 +08:00
ww-rm
8c3be98b54 修复记忆状态中的长度单位错误 2025-09-30 00:28:05 +08:00
ww-rm
b76224c010 调整顺序 2025-09-30 00:00:32 +08:00
ww-rm
bd9f8d714a 增加开机自启功能和自启设置 2025-09-29 23:26:06 +08:00
ww-rm
6900968555 调整布局 2025-09-29 00:05:41 +08:00
ww-rm
741d334a92 切换桌面投影时自动设置预览分辨率为主屏幕分辨率 2025-09-28 22:20:48 +08:00
ww-rm
b583108afa 更新模板 2025-09-28 20:44:12 +08:00
ww-rm
f7ace4dfe9 Merge pull request #106 from ww-rm/dev/wpf
v0.15.19
2025-09-27 23:48:05 +08:00
ww-rm
7ce2bd5629 update to v0.15.19 2025-09-27 23:46:25 +08:00
ww-rm
41716df7b2 update changelog 2025-09-27 23:45:49 +08:00
ww-rm
fe9b9829e2 增加 wallpaper view 2025-09-27 23:43:54 +08:00
ww-rm
4365c2a008 small change 2025-09-27 23:41:31 +08:00
ww-rm
7896e072e7 移除LastState中背景图片的记忆 2025-09-27 21:24:15 +08:00
ww-rm
940397c673 增加Texture加载失败时的日志信息 2025-09-27 18:49:29 +08:00
ww-rm
d57ea781f0 修复由于可能存在问题的奇数长度顶点数组导致的数组越界问题 2025-09-27 18:48:54 +08:00
ww-rm
b74f2811a7 add SFMLRenderWindow 2025-09-27 18:20:06 +08:00
ww-rm
66223f952b 重载后自动选中列表模型 2025-09-24 23:54:05 +08:00
ww-rm
0443d5e3d5 Merge pull request #104 from ww-rm/dev/wpf
v0.15.18
2025-09-24 23:45:55 +08:00
ww-rm
0a0b6a08e9 update to v0.15.18 2025-09-24 23:44:52 +08:00
ww-rm
63af4a19e6 update readme 2025-09-24 23:44:43 +08:00
ww-rm
51f0446c18 update changelog 2025-09-24 23:43:16 +08:00
ww-rm
e965223284 增强支持的纹理图像格式 2025-09-24 23:00:21 +08:00
ww-rm
dbc15952cc 增加工作区背景图片参数 2025-09-22 23:30:02 +08:00
ww-rm
93b70509ec 增加日志输出 2025-09-22 23:27:23 +08:00
ww-rm
798883d4e0 增加背景图案选项 2025-09-22 23:23:01 +08:00
ww-rm
3e88e65bbd 增加托盘图标 2025-09-22 20:09:46 +08:00
ww-rm
0906817cd3 修复面板高度首次还原错误 2025-09-22 14:38:04 +08:00
ww-rm
37235fa7d0 修改预览图背景颜色为透明 2025-09-22 08:35:27 +08:00
ww-rm
72935d8f2b utf8 2025-09-21 22:08:16 +08:00
ww-rm
8a4095dae1 完善窗口日志显示 2025-09-21 22:06:00 +08:00
ww-rm
b59f228f2e refactor 2025-09-21 11:01:58 +08:00
ww-rm
a28cb3f424 Merge pull request #102 from ww-rm/dev/wpf
v0.15.17
2025-09-21 10:09:56 +08:00
ww-rm
05bb797a91 update to v0.15.17 2025-09-21 10:09:10 +08:00
ww-rm
eb0029a877 update changelog 2025-09-21 10:08:38 +08:00
ww-rm
ef0bfa85aa 修改图标配色 2025-09-21 10:07:27 +08:00
ww-rm
b5721e30a0 update readme 2025-09-21 01:21:38 +08:00
ww-rm
2c3b076b58 Merge pull request #101 from ww-rm/dev/wpf
v0.15.16
2025-09-21 01:14:57 +08:00
ww-rm
01e12f4524 增加打开单模型功能 2025-09-21 01:13:15 +08:00
ww-rm
a814d3d99a update to v0.15.16 2025-09-21 00:55:36 +08:00
ww-rm
6a4508dceb update changelog 2025-09-21 00:55:12 +08:00
ww-rm
b7d7274a5a 增加文件关联首选项 2025-09-21 00:55:01 +08:00
ww-rm
71359a4328 完善多选打开逻辑 2025-09-21 00:01:35 +08:00
ww-rm
3a3691bcca 增加单实例模式和命令行参数 2025-09-20 23:11:47 +08:00
ww-rm
3d649e36cc 完善画布焦点转移逻辑 2025-09-19 00:56:25 +08:00
ww-rm
a24db3c447 增加右键菜单移除全部模型 2025-09-17 23:51:05 +08:00
ww-rm
699a055707 选中项发生变化时转移焦点至模型列表 2025-09-17 23:34:28 +08:00
ww-rm
1f8ed1c31c 增加自动选中最后导入项 2025-09-17 23:28:48 +08:00
ww-rm
e2fc27663c 修改列表每次添加模型在开头 2025-09-17 20:13:03 +08:00
ww-rm
b3cd0b9349 Merge pull request #99 from ww-rm/dev/wpf
v0.15.15
2025-09-11 23:20:45 +08:00
ww-rm
1c545b8c37 update to v0.15.15 2025-09-11 23:19:24 +08:00
ww-rm
d660dd1c4a update changelog 2025-09-11 23:19:18 +08:00
ww-rm
9c0acf7302 增加报错信息 2025-09-11 23:17:13 +08:00
ww-rm
415df555c7 增加导入后自动选中最后一项 2025-09-08 21:50:11 +08:00
ww-rm
5ef13239da Merge pull request #97 from ww-rm/dev/wpf
v0.15.14
2025-09-08 00:07:36 +08:00
ww-rm
13ef873650 update to v0.15.14 2025-09-08 00:05:58 +08:00
ww-rm
78b9834f6c update changelog 2025-09-08 00:05:34 +08:00
ww-rm
672a695b20 增加上一次状态的保存和恢复 2025-09-08 00:00:49 +08:00
ww-rm
e9951ed79a 增加日志版本号输出 2025-09-05 11:36:52 +08:00
ww-rm
0c16b2f104 Merge pull request #93 from ww-rm/dev/wpf
v0.15.13
2025-09-04 20:09:18 +08:00
ww-rm
7628075420 update to v0.15.13 2025-09-04 20:08:08 +08:00
ww-rm
6f896bdaad update changelog 2025-09-04 20:07:54 +08:00
ww-rm
98930db4b6 增加预览画面首选项 2025-09-04 20:07:35 +08:00
ww-rm
c7493372e9 增加布局存储和还原 2025-09-04 19:27:10 +08:00
ww-rm
707aa7f570 Merge pull request #91 from ww-rm/dev/wpf
v0.15.12
2025-09-03 21:58:18 +08:00
ww-rm
99ff6f9f0a 增加轨道参数保存 2025-09-03 21:57:28 +08:00
ww-rm
be8193e235 Merge pull request #90 from ww-rm/dev/wpf
v0.15.12
2025-09-03 21:43:07 +08:00
ww-rm
21b6dbee4c 增加bug issue模板 2025-09-03 21:41:43 +08:00
ww-rm
f60418fecb update readme 2025-09-03 21:34:51 +08:00
ww-rm
1180c735c8 update to v0.15.12 2025-09-03 21:33:18 +08:00
ww-rm
3d8f6547e0 update changelog 2025-09-03 21:32:42 +08:00
ww-rm
99ec2704fe 增加单个轨道的时间因子和alpha混合 2025-09-03 21:30:31 +08:00
ww-rm
dbd2cef766 完善报错信息 2025-09-02 20:46:48 +08:00
ww-rm
212ecc2ff3 增加单个模型的时间因子参数 2025-09-02 00:32:02 +08:00
ww-rm
7806f9298d 增加半透明选中背景 2025-09-01 23:47:08 +08:00
ww-rm
3bc57a8990 增加最大帧率提示文本 2025-08-30 16:31:43 +08:00
ww-rm
67c9ea9291 移动轨道清除功能至右键菜单 2025-08-30 01:38:22 +08:00
ww-rm
f404acc834 修改默认标签页为模型列表 2025-08-21 23:00:40 +08:00
ww-rm
8e1f586d4f Merge pull request #86 from ww-rm/dev/wpf
fix bug
2025-08-20 22:44:47 +08:00
ww-rm
5dd1b84943 fix bug 2025-08-20 22:42:38 +08:00
ww-rm
ad190d8952 Merge pull request #85 from ww-rm/dev/wpf
v0.15.11
2025-08-20 22:36:14 +08:00
ww-rm
ddb11808a7 update to v0.15.11 2025-08-20 22:33:25 +08:00
ww-rm
9b1e26b2ac udpate changelog 2025-08-20 22:33:02 +08:00
ww-rm
c8e35a9aed 增加mov格式 2025-08-20 22:31:49 +08:00
ww-rm
4786b0434c 修复自定义导出参数构造错误 2025-08-19 18:02:21 +08:00
ww-rm
40bde84648 Merge pull request #81 from ww-rm/dev/wpf
v0.15.10
2025-08-18 18:54:13 +08:00
ww-rm
ebb2593526 update to v0.15.10 2025-08-18 18:52:16 +08:00
ww-rm
6a74204ba1 update readme 2025-08-18 18:51:33 +08:00
ww-rm
78c6c47300 update changelog 2025-08-18 18:48:55 +08:00
ww-rm
746a3decc8 补充插槽可见性属性值拷贝 2025-08-18 18:47:39 +08:00
ww-rm
6dfd25b760 修复插槽禁用功能 2025-08-18 18:39:27 +08:00
ww-rm
a697ccc923 Merge pull request #80 from ww-rm/dev/wpf 2025-08-18 01:26:40 +08:00
ww-rm
5bfa625868 更新至v0.15.9 2025-08-18 01:25:22 +08:00
ww-rm
0762a90fa9 update readme 2025-08-18 01:24:54 +08:00
ww-rm
a4926d905b update changelog 2025-08-18 01:24:47 +08:00
ww-rm
b08b6752cd imp v34 & v35 2025-08-18 01:23:31 +08:00
ww-rm
50b1c66e27 add spine34 & spine35 2025-08-17 16:16:59 +08:00
355 changed files with 28827 additions and 3165 deletions

18
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,18 @@
---
name: 问题报告/Bug report
about: 报告可能的程序错误/Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
## 问题描述/Describe the bug
清晰完整的描述问题是什么以及如何发生的。/A clear and concise description of what the bug is.
## 复现方式(可选)/To Reproduce (Optional)
## 截图(可选)/Screenshots (Optional)**
如果有必要,提供报错时的有关截图。/If applicable, add screenshots to help explain your problem.
## 附件(可选)/Attachments (Optional)
请将会**出现问题的文件**以及**日志文件**打包成一个 ZIP 后作为附件贴在 issue 内,日志文件位于程序目录下的 `logs` 文件夹内。/Please compress the problematic files and the log files into a single ZIP archive and attach it to this issue. The log files are located in the `logs` folder under the program directory.

View File

@@ -11,6 +11,10 @@ jobs:
build-release:
if: ${{ github.event.pull_request.merged == true }}
runs-on: windows-latest
outputs:
version: ${{ steps.extract_version.outputs.version }}
upload_url: ${{ steps.create_release.outputs.upload_url }}
env:
PROJECT_NAME: SpineViewer
PROJ_CLI_NAME: SpineViewerCLI
@@ -27,21 +31,15 @@ jobs:
dotnet-version: "8.0.x"
- name: Extract version from csproj
id: extract_version
shell: pwsh
run: |
[xml]$proj = Get-Content "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj"
$VERSION_NUM = $proj.Project.PropertyGroup.Version
$VERSION_TAG = "v$VERSION_NUM".Trim()
"VERSION=$VERSION_TAG" >> $env:GITHUB_ENV
- name: Check Version Tag
shell: pwsh
run: |
if (-not $env:VERSION) {
Write-Error "Version tag not found in csproj file."
exit 1
}
Write-Host "Version tag found: $env:VERSION"
echo "Version tag found: $VERSION_TAG"
echo "version=$VERSION_TAG" >> $env:GITHUB_OUTPUT
echo "VERSION=$VERSION_TAG" >> $env:GITHUB_ENV
- name: Tag merge commit
shell: pwsh
@@ -63,19 +61,11 @@ jobs:
dotnet publish "$env:PROJECT_NAME\$env:PROJECT_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained"
dotnet publish "$env:PROJ_CLI_NAME\$env:PROJ_CLI_NAME.csproj" -c Release -r win-x64 --sc true -o "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained"
- name: Create release directory
- name: Compress Windows builds
shell: pwsh
run: |
New-Item -ItemType Directory -Path release -Force | Out-Null
- name: Compress FrameworkDependent version
shell: pwsh
run: |
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION.zip" -Force
- name: Compress SelfContained version
shell: pwsh
run: |
Compress-Archive -Path "publish\$env:PROJECT_NAME-$env:VERSION-SelfContained\*" -DestinationPath "release\$env:PROJECT_NAME-$env:VERSION-SelfContained.zip" -Force
- name: Create GitHub Release
@@ -89,7 +79,7 @@ jobs:
draft: false
prerelease: false
- name: Upload FrameworkDependent zip
- name: Upload Windows FrameworkDependent zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -99,7 +89,7 @@ jobs:
asset_name: ${{ env.PROJECT_NAME }}-${{ env.VERSION }}.zip
asset_content_type: application/zip
- name: Upload SelfContained zip
- name: Upload Windows SelfContained zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -108,3 +98,43 @@ jobs:
asset_path: release/${{ env.PROJECT_NAME }}-${{ env.VERSION }}-SelfContained.zip
asset_name: ${{ env.PROJECT_NAME }}-${{ env.VERSION }}-SelfContained.zip
asset_content_type: application/zip
build-release-linux:
needs: build-release
if: ${{ github.event.pull_request.merged == true }}
runs-on: ubuntu-latest
env:
PROJ_CLI_NAME: SpineViewerCLI
VERSION: ${{ needs.build-release.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-tags: true
- name: Setup .NET SDK
uses: actions/setup-dotnet@v3
with:
dotnet-version: "8.0.x"
- name: Publish Linux SelfContained version
run: |
dotnet publish "$PROJ_CLI_NAME/$PROJ_CLI_NAME.csproj" -c Release -r linux-x64 --sc true -o "publish/${PROJ_CLI_NAME}-${VERSION}-Linux-SelfContained"
- name: Compress Linux build
run: |
mkdir -p release
cd publish
zip -r "../release/${PROJ_CLI_NAME}-${VERSION}-Linux-SelfContained.zip" "${PROJ_CLI_NAME}-${VERSION}-Linux-SelfContained"
- name: Upload Linux zip to GitHub Release
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.build-release.outputs.upload_url }}
asset_path: release/${{ env.PROJ_CLI_NAME }}-${{ env.VERSION }}-Linux-SelfContained.zip
asset_name: ${{ env.PROJ_CLI_NAME }}-${{ env.VERSION }}-Linux-SelfContained.zip
asset_content_type: application/zip

2
.gitignore vendored
View File

@@ -396,3 +396,5 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
launchSettings.json

View File

@@ -1,5 +1,133 @@
# CHANGELOG
## v0.16.10
- 增加 Linux 平台 CLI 工具构建
## v0.16.9
- 重构 CLI 工具
## v0.16.8
- 去除首次的最小化提示弹框
- 窗口布局改变后实时保存
- 增加侧边栏图标和折叠功能
- 增加皮肤和插槽参数面板的全部启用/禁用菜单项
- 修改窗口默认大小
- 支持复制并应用单独的模型皮肤或插槽参数
## v0.16.7
- 修复空帧导致的包围盒计算错误
- 修复重复启动程序无法唤出界面的问题
## v0.16.6
- 修复控件尺寸为0时导致的画面缩放错误
## v0.16.5
- 修复对于无 size 行的旧 atlas 格式读取错误
- 修复托盘化之后无法联动显示窗口的问题
## v0.16.4
- 增加 apng 导出格式
- 增加颜色拾取器面板
- 增加程序皮肤(主题颜色)首选项
- 优化部分使用体验
## v0.16.3
- 修复加载工作区时的顺序错误
- 调整部分调试渲染的逻辑
- 完善命中检测逻辑
## v0.16.2
- 修复批量添加时的添加顺序错误
- 增加精确命中检测和插槽输出功能
- 部分代码重构
## v0.16.1
- 修复 3.4 版本存在的附件残留问题
## v0.16.0
- 增加最小化至托盘图标功能
- 调整部分参数项的顺序
- 增加开机自启和自启文件设置
- 切换桌面投影时自动设置预览分辨率为主屏幕分辨率
- 修复 3.4 版本下可能存在的附件残留问题
## v0.15.19
- 模型重载后选中最后一个重载模型
- 修复 3.4 版本可能的奇数顶点数组导致的越界崩溃问题
- 移除参数自动记录中的背景图片路径
- 增加测试性桌面投影功能
## v0.15.18
- 完善窗口日志颜色标记
- 修复预览图背景颜色为透明
- 修复面板高度首次还原错误
- 增加托盘图标
- 增加可选预览背景画面和填充模式
- 增强支持的纹理格式(例如 webp
## v0.15.17
- 修改图标配色
## v0.15.16
- 修改模型添加顺序, 每次向顶层添加
- 添加模型后自动选中最近添加的模型S
- 点击预览画面或者选中项发生变化时转移焦点至列表
- 增加移除全部菜单项
- 增加单例模式和命令行文件参数
- 增加文件关联设置
## v0.15.15
- 增加报错信息
- 导入后自动选中最后一项
## v0.15.14
- 将预览画面的首选项移动至上一次状态参数中
- 增加预览画面像素的自动保存和恢复
- 增加日志启动时的版本号输出
## v0.15.13
- 增加程序布局自动存储和还原
- 增加部分预览画面首选项
## v0.15.12
- 增加单个模型和单个轨道的时间因子
- 增加单个轨道的 Alpha 混合参数
- 调整轨道清除命令至右键菜单
- 设置默认标签页为模型
- 完善导入时的报错信息
## v0.15.11
- 修复自定义导出中参数构造错误
- 增加 mov 格式及参数说明
## v0.15.10
- 增加插槽可见性参数, 允许任何情况下对插槽启用和禁用对插槽的渲染
## v0.15.9
- 添加 V34 和 V35 版本支持
## v0.15.8
- 修复渲染纹理过程中可能的 null 错误

View File

@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.4</Version>
<Version>0.16.0</Version>
<UseWPF>true</UseWPF>
</PropertyGroup>

View File

@@ -1,92 +1,53 @@
//
// Copyright (c) 2004-2011 Jaroslaw Kowalski <jaak@jkowalski.net>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
using NLog;
using NLog.Conditions;
using NLog.Config;
using NLog;
using System.ComponentModel;
using NLog.Layouts;
using System.Windows;
namespace NLog.Windows.Wpf
{
[NLogConfigurationItem]
public class RichTextBoxRowColoringRule
{
static RichTextBoxRowColoringRule()
{
Default = new RichTextBoxRowColoringRule();
}
public RichTextBoxRowColoringRule()
: this(null, "Empty", "Empty", FontStyles.Normal, FontWeights.Normal)
{
}
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor, FontStyle fontStyle, FontWeight fontWeight)
{
Condition = condition;
FontColor = fontColor;
BackgroundColor = backColor;
Style = fontStyle;
Weight = fontWeight;
}
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor)
{
Condition = condition;
FontColor = fontColor;
BackgroundColor = backColor;
Style = FontStyles.Normal;
Weight = FontWeights.Normal;
}
public static RichTextBoxRowColoringRule Default { get; private set; }
[RequiredParameter]
public ConditionExpression Condition { get; set; }
[DefaultValue("Empty")]
public string FontColor { get; set; }
public Layout FontColor { get; set; }
public Layout BackgroundColor { get; set; }
[DefaultValue("Empty")]
public string BackgroundColor { get; set; }
public FontStyle FontStyle { get; set; }
public FontWeight FontWeight { get; set; }
public FontStyle Style { get; set; }
static RichTextBoxRowColoringRule()
{
RichTextBoxRowColoringRule.Default = new RichTextBoxRowColoringRule();
}
public FontWeight Weight { get; set; }
public RichTextBoxRowColoringRule() : this(null, "Empty", "Empty", FontStyles.Normal, FontWeights.Normal) { }
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor)
{
this.Condition = (ConditionExpression)condition;
this.FontColor = Layout.FromString(fontColor);
this.BackgroundColor = Layout.FromString(backColor);
this.FontStyle = FontStyles.Normal;
this.FontWeight = FontWeights.Normal;
}
public RichTextBoxRowColoringRule(string condition, string fontColor, string backColor, FontStyle fontStyle, FontWeight fontWeight)
{
this.Condition = (ConditionExpression)condition;
this.FontColor = Layout.FromString(fontColor);
this.BackgroundColor = Layout.FromString(backColor);
this.FontStyle = fontStyle;
this.FontWeight = fontWeight;
}
public bool CheckCondition(LogEventInfo logEvent)
{
return true.Equals(Condition.Evaluate(logEvent));
return true.Equals(this.Condition.Evaluate(logEvent));
}
}
}

View File

@@ -1,34 +1,27 @@
using NLog.Config;
using NLog.Layouts;
using NLog;
using NLog.Common;
using NLog.Config;
using NLog.Targets;
using NLog;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows;
namespace NLog.Windows.Wpf
{
// TODO: 完善日志实现
[Target("RichTextBox")]
public sealed class RichTextBoxTarget : TargetWithLayout
{
private int lineCount;
private int _width = 500;
private int _height = 500;
private static readonly TypeConverter colorConverter = new ColorConverter();
public static ReadOnlyCollection<RichTextBoxRowColoringRule> DefaultRowColoringRules { get; } = CreateDefaultColoringRules();
static RichTextBoxTarget()
private static ReadOnlyCollection<RichTextBoxRowColoringRule> CreateDefaultColoringRules()
{
var rules = new List<RichTextBoxRowColoringRule>()
return new List<RichTextBoxRowColoringRule>()
{
new RichTextBoxRowColoringRule("level == LogLevel.Fatal", "White", "Red", FontStyles.Normal, FontWeights.Bold),
new RichTextBoxRowColoringRule("level == LogLevel.Error", "Red", "Empty", FontStyles.Italic, FontWeights.Bold),
@@ -36,221 +29,253 @@ namespace NLog.Windows.Wpf
new RichTextBoxRowColoringRule("level == LogLevel.Info", "Black", "Empty"),
new RichTextBoxRowColoringRule("level == LogLevel.Debug", "Gray", "Empty"),
new RichTextBoxRowColoringRule("level == LogLevel.Trace", "DarkGray", "Empty", FontStyles.Italic, FontWeights.Normal),
};
DefaultRowColoringRules = rules.AsReadOnly();
}.AsReadOnly();
}
public RichTextBoxTarget()
{
WordColoringRules = new List<RichTextBoxWordColoringRule>();
RowColoringRules = new List<RichTextBoxRowColoringRule>();
ToolWindow = true;
}
private delegate void DelSendTheMessageToRichTextBox(string logMessage, RichTextBoxRowColoringRule rule);
private delegate void FormCloseDelegate();
public static ReadOnlyCollection<RichTextBoxRowColoringRule> DefaultRowColoringRules { get; private set; }
public RichTextBoxTarget() { }
public string ControlName { get; set; }
public string FormName { get; set; }
public string WindowName { get; set; }
[DefaultValue(false)]
public bool UseDefaultRowColoringRules { get; set; }
[ArrayParameter(typeof(RichTextBoxRowColoringRule), "row-coloring")]
public IList<RichTextBoxRowColoringRule> RowColoringRules { get; private set; }
[ArrayParameter(typeof(RichTextBoxWordColoringRule), "word-coloring")]
public IList<RichTextBoxWordColoringRule> WordColoringRules { get; private set; }
[DefaultValue(true)]
public bool ToolWindow { get; set; }
public bool ShowMinimized { get; set; }
public int Width
{
get { return _width; }
set { _width = value; }
}
public int Height
{
get { return _height; }
set { _height = value; }
}
public bool AutoScroll { get; set; }
public int MaxLines { get; set; }
internal Window TargetForm { get; set; }
[ArrayParameter(typeof(RichTextBoxRowColoringRule), "row-coloring")]
public IList<RichTextBoxRowColoringRule> RowColoringRules { get; } = new List<RichTextBoxRowColoringRule>();
internal RichTextBox TargetRichTextBox { get; set; }
[ArrayParameter(typeof(RichTextBoxWordColoringRule), "word-coloring")]
public IList<RichTextBoxWordColoringRule> WordColoringRules { get; } = new List<RichTextBoxWordColoringRule>();
internal bool CreatedForm { get; set; }
[NLogConfigurationIgnoreProperty]
public Window TargetWindow { get; set; }
[NLogConfigurationIgnoreProperty]
public RichTextBox TargetRichTextBox { get; set; }
protected override void InitializeTarget()
{
TargetRichTextBox = Application.Current.MainWindow.FindName(ControlName) as RichTextBox;
base.InitializeTarget();
if (TargetRichTextBox != null)
return;
if (TargetRichTextBox != null) return;
//this.TargetForm = FormHelper.CreateForm(this.FormName, this.Width, this.Height, false, this.ShowMinimized, this.ToolWindow);
//this.CreatedForm = true;
var openFormByName = Application.Current.Windows.Cast<Window>().FirstOrDefault(x => x.GetType().Name == FormName);
if (openFormByName != null)
if (WindowName == null)
{
TargetForm = openFormByName;
if (string.IsNullOrEmpty(ControlName))
{
// throw new NLogConfigurationException("Rich text box control name must be specified for " + GetType().Name + ".");
Trace.WriteLine("Rich text box control name must be specified for " + GetType().Name + ".");
}
CreatedForm = false;
TargetRichTextBox = TargetForm.FindName(ControlName) as RichTextBox;
if (TargetRichTextBox == null)
{
// throw new NLogConfigurationException("Rich text box control '" + ControlName + "' cannot be found on form '" + FormName + "'.");
Trace.WriteLine("Rich text box control '" + ControlName + "' cannot be found on form '" + FormName + "'.");
}
HandleError("WindowName should be specified for {0}.{1}", GetType().Name, Name);
return;
}
if (TargetRichTextBox == null)
if (string.IsNullOrEmpty(ControlName))
{
TargetForm = new Window
{
Name = FormName,
Width = Width,
Height = Height,
WindowStyle = ToolWindow ? WindowStyle.ToolWindow : WindowStyle.None,
WindowState = ShowMinimized ? WindowState.Minimized : WindowState.Normal,
Title = "NLog Messages"
};
TargetForm.Show();
TargetRichTextBox = new RichTextBox { Name = ControlName };
var style = new Style(typeof(Paragraph));
TargetRichTextBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
style.Setters.Add(new Setter(Block.MarginProperty, new Thickness(0, 0, 0, 0)));
TargetRichTextBox.Resources.Add(typeof(Paragraph), style);
TargetForm.Content = TargetRichTextBox;
CreatedForm = true;
HandleError("Rich text box control name must be specified for {0}.{1}", GetType().Name, Name);
return;
}
var targetWindow = Application.Current.Windows.OfType<Window>().FirstOrDefault(w => w.Name == WindowName);
if (targetWindow == null)
{
InternalLogger.Info("{0}: WindowName '{1}' not found", this, WindowName);
return;
}
var targetControl = targetWindow.FindName(ControlName) as RichTextBox;
if (targetControl == null)
{
InternalLogger.Info("{0}: WIndowName '{1}' does not contain ControlName '{2}'", this, WindowName, ControlName);
return;
}
AttachToControl(targetWindow, targetControl);
}
private static void HandleError(string message, params object[] args)
{
if (LogManager.ThrowExceptions)
{
throw new NLogConfigurationException(string.Format(message, args));
}
InternalLogger.Error(message, args);
}
private void AttachToControl(Window window, RichTextBox textboxControl)
{
InternalLogger.Info("{0}: Attaching target to textbox {1}.{2}", this, window.Name, textboxControl.Name);
DetachFromControl();
TargetWindow = window;
TargetRichTextBox = textboxControl;
}
private void DetachFromControl()
{
TargetWindow = null;
TargetRichTextBox = null;
}
protected override void CloseTarget()
{
if (CreatedForm)
{
try
{
TargetForm.Dispatcher.Invoke(() =>
{
TargetForm.Close();
TargetForm = null;
});
}
catch
{
}
}
DetachFromControl();
}
protected override void Write(LogEventInfo logEvent)
{
RichTextBoxRowColoringRule matchingRule = RowColoringRules.FirstOrDefault(rr => rr.CheckCondition(logEvent));
if (UseDefaultRowColoringRules && matchingRule == null)
RichTextBox textbox = TargetRichTextBox;
if (textbox == null || textbox.Dispatcher.HasShutdownStarted || textbox.Dispatcher.HasShutdownFinished)
{
foreach (var rr in DefaultRowColoringRules.Where(rr => rr.CheckCondition(logEvent)))
{
matchingRule = rr;
break;
}
//no last logged textbox
InternalLogger.Trace("{0}: Attached Textbox is {1}, skipping logging", this, textbox == null ? "null" : "disposed");
return;
}
if (matchingRule == null)
{
matchingRule = RichTextBoxRowColoringRule.Default;
}
var logMessage = Layout.Render(logEvent);
if (Application.Current == null) return;
string logMessage = RenderLogEvent(Layout, logEvent);
RichTextBoxRowColoringRule matchingRule = FindMatchingRule(logEvent);
_ = DoSendMessageToTextbox(logMessage, matchingRule, logEvent);
}
private bool DoSendMessageToTextbox(string logMessage, RichTextBoxRowColoringRule rule, LogEventInfo logEvent)
{
RichTextBox textbox = TargetRichTextBox;
try
{
if (Application.Current.Dispatcher.CheckAccess() == false)
if (textbox != null && !textbox.Dispatcher.HasShutdownStarted && !textbox.Dispatcher.HasShutdownFinished)
{
Application.Current.Dispatcher.Invoke(() => SendTheMessageToRichTextBox(logMessage, matchingRule));
}
else
{
SendTheMessageToRichTextBox(logMessage, matchingRule);
if (!textbox.Dispatcher.CheckAccess())
{
textbox.Dispatcher.BeginInvoke(() => SendTheMessageToRichTextBox(textbox, logMessage, rule, logEvent));
}
else
{
SendTheMessageToRichTextBox(textbox, logMessage, rule, logEvent);
}
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
InternalLogger.Warn(ex, "{0}: Failed to append RichTextBox", this);
}
private static Color GetColorFromString(string color, Brush defaultColor)
{
if (color == "Empty")
{
return defaultColor is SolidColorBrush solidBrush ? solidBrush.Color : Colors.White;
}
return (Color)colorConverter.ConvertFromString(color);
}
private void SendTheMessageToRichTextBox(string logMessage, RichTextBoxRowColoringRule rule)
{
RichTextBox rtbx = TargetRichTextBox;
var tr = new TextRange(rtbx.Document.ContentEnd, rtbx.Document.ContentEnd);
tr.Text = logMessage + "\n";
tr.ApplyPropertyValue(TextElement.ForegroundProperty,
new SolidColorBrush(GetColorFromString(rule.FontColor, (Brush)tr.GetPropertyValue(TextElement.ForegroundProperty)))
);
tr.ApplyPropertyValue(TextElement.BackgroundProperty,
new SolidColorBrush(GetColorFromString(rule.BackgroundColor, (Brush)tr.GetPropertyValue(TextElement.BackgroundProperty)))
);
tr.ApplyPropertyValue(TextElement.FontStyleProperty, rule.Style);
tr.ApplyPropertyValue(TextElement.FontWeightProperty, rule.Weight);
if (MaxLines > 0)
{
lineCount++;
if (lineCount > MaxLines)
if (LogManager.ThrowExceptions)
{
tr = new TextRange(rtbx.Document.ContentStart, rtbx.Document.ContentEnd);
tr.Text.Remove(0, tr.Text.IndexOf('\n'));
lineCount--;
throw;
}
}
return false;
}
private RichTextBoxRowColoringRule FindMatchingRule(LogEventInfo logEvent)
{
//custom rules first
if (RowColoringRules.Count > 0)
{
foreach (RichTextBoxRowColoringRule coloringRule in RowColoringRules)
{
if (coloringRule.CheckCondition(logEvent))
{
return coloringRule;
}
}
}
if (UseDefaultRowColoringRules && DefaultRowColoringRules != null)
{
foreach (RichTextBoxRowColoringRule coloringRule in DefaultRowColoringRules)
{
if (coloringRule.CheckCondition(logEvent))
{
return coloringRule;
}
}
}
return RichTextBoxRowColoringRule.Default;
}
private void SendTheMessageToRichTextBox(RichTextBox textBox, string logMessage, RichTextBoxRowColoringRule rule, LogEventInfo logEvent)
{
if (textBox == null) return;
var document = textBox.Document;
// 插入文本(带换行)
var tr = new TextRange(document.ContentEnd, document.ContentEnd)
{
Text = logMessage + Environment.NewLine
};
// 设置行级样式
var fgColor = rule.FontColor?.Render(logEvent);
var bgColor = rule.BackgroundColor?.Render(logEvent);
tr.ApplyPropertyValue(TextElement.ForegroundProperty,
string.IsNullOrEmpty(fgColor) || fgColor == "Empty"
? textBox.Foreground
: new SolidColorBrush((Color)ColorConverter.ConvertFromString(fgColor)));
tr.ApplyPropertyValue(TextElement.BackgroundProperty,
string.IsNullOrEmpty(bgColor) || bgColor == "Empty"
? Brushes.Transparent
: new SolidColorBrush((Color)ColorConverter.ConvertFromString(bgColor)));
tr.ApplyPropertyValue(TextElement.FontStyleProperty, rule.FontStyle);
tr.ApplyPropertyValue(TextElement.FontWeightProperty, rule.FontWeight);
// Word coloring在刚插入的范围内做匹配
if (WordColoringRules.Count > 0)
{
foreach (var wordRule in WordColoringRules)
{
var pattern = wordRule.Regex?.Render(logEvent) ?? string.Empty;
var text = wordRule.Text?.Render(logEvent) ?? string.Empty;
var wholeWords = wordRule.WholeWords.RenderValue(logEvent);
var ignoreCase = wordRule.IgnoreCase.RenderValue(logEvent);
var regex = wordRule.ResolveRegEx(pattern, text, wholeWords, ignoreCase);
var matches = regex.Matches(tr.Text);
foreach (Match match in matches)
{
// 匹配到的部分范围
var start = tr.Start.GetPositionAtOffset(match.Index, LogicalDirection.Forward);
var endPos = tr.Start.GetPositionAtOffset(match.Index + match.Length, LogicalDirection.Backward);
if (start == null || endPos == null) continue;
var wordRange = new TextRange(start, endPos);
var wordFg = wordRule.FontColor?.Render(logEvent);
var wordBg = wordRule.BackgroundColor?.Render(logEvent);
wordRange.ApplyPropertyValue(TextElement.ForegroundProperty,
string.IsNullOrEmpty(wordFg) || wordFg == "Empty"
? tr.GetPropertyValue(TextElement.ForegroundProperty)
: new SolidColorBrush((Color)ColorConverter.ConvertFromString(wordFg)));
wordRange.ApplyPropertyValue(TextElement.BackgroundProperty,
string.IsNullOrEmpty(wordBg) || wordBg == "Empty"
? tr.GetPropertyValue(TextElement.BackgroundProperty)
: new SolidColorBrush((Color)ColorConverter.ConvertFromString(wordBg)));
wordRange.ApplyPropertyValue(TextElement.FontStyleProperty, wordRule.FontStyle);
wordRange.ApplyPropertyValue(TextElement.FontWeightProperty, wordRule.FontWeight);
}
}
}
// 限制最大行数
if (MaxLines > 0)
{
while (document.Blocks.Count > MaxLines)
{
document.Blocks.Remove(document.Blocks.FirstBlock);
}
}
// 自动滚动到最后
if (AutoScroll)
{
rtbx.ScrollToEnd();
textBox.ScrollToEnd();
}
}
}
}
}

View File

@@ -1,119 +1,59 @@
//
// Copyright (c) 2004-2011 Jaroslaw Kowalski <jaak@jkowalski.net>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
using NLog.Config;
using NLog.Layouts;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Windows;
using NLog.Config;
namespace NLog.Windows.Wpf
{
[NLogConfigurationItem]
[NLogConfigurationItem]
public class RichTextBoxWordColoringRule
{
private Regex compiledRegex;
public Layout Regex { get; set; }
public Layout Text { get; set; }
public Layout<bool> WholeWords { get; set; }
public Layout<bool> IgnoreCase { get; set; }
public RichTextBoxWordColoringRule()
public Layout FontColor { get; set; }
public Layout BackgroundColor { get; set; }
public FontStyle FontStyle { get; set; }
public FontWeight FontWeight { get; set; }
internal Regex ResolveRegEx(string pattern, string text, bool wholeWords, bool ignoreCase)
{
FontColor = "Empty";
BackgroundColor = "Empty";
if (string.IsNullOrEmpty(pattern) && text != null)
{
pattern = System.Text.RegularExpressions.Regex.Escape(text);
if (wholeWords)
pattern = "\b" + pattern + "\b";
}
RegexOptions options = RegexOptions.None;
if (ignoreCase)
options |= RegexOptions.IgnoreCase;
return new Regex(pattern, options); // RegEx-Cache
}
public RichTextBoxWordColoringRule() : this(null, "Empty", "Empty", FontStyles.Normal, FontWeights.Normal) { }
public RichTextBoxWordColoringRule(string text, string fontColor, string backgroundColor)
{
Text = text;
FontColor = fontColor;
BackgroundColor = backgroundColor;
Style = FontStyles.Normal;
Weight = FontWeights.Normal;
this.Text = text;
this.FontColor = Layout.FromString(fontColor);
this.BackgroundColor = Layout.FromString(backgroundColor);
this.FontStyle = FontStyles.Normal;
this.FontWeight = FontWeights.Normal;
}
public RichTextBoxWordColoringRule(string text, string textColor, string backgroundColor, FontStyle fontStyle, FontWeight fontWeight)
{
Text = text;
FontColor = textColor;
BackgroundColor = backgroundColor;
Style = fontStyle;
Weight = fontWeight;
this.Text = text;
this.FontColor = Layout.FromString(textColor);
this.BackgroundColor = Layout.FromString(backgroundColor);
this.FontStyle = fontStyle;
this.FontWeight = fontWeight;
}
public string Regex { get; set; }
public string Text { get; set; }
[DefaultValue(false)]
public bool WholeWords { get; set; }
[DefaultValue(false)]
public bool IgnoreCase { get; set; }
public FontStyle Style { get; set; }
public FontWeight Weight { get; set; }
public Regex CompiledRegex
{
get
{
if (compiledRegex == null)
{
string regexpression = Regex;
if (regexpression == null && Text != null)
{
regexpression = System.Text.RegularExpressions.Regex.Escape(Text);
if (WholeWords)
{
regexpression = "\b" + regexpression + "\b";
}
}
RegexOptions regexOptions = RegexOptions.Compiled;
if (IgnoreCase)
{
regexOptions |= RegexOptions.IgnoreCase;
}
compiledRegex = new Regex(regexpression, regexOptions);
}
return compiledRegex;
}
}
[DefaultValue("Empty")]
public string FontColor { get; set; }
[DefaultValue("Empty")]
public string BackgroundColor { get; set; }
}
}

View File

@@ -8,30 +8,40 @@
A simple and user-friendly Spine file viewer and exporter with multi-language support (Chinese/English/Japanese).
![previewer](img/preview.webp)
![previewer](https://github.com/user-attachments/assets/697ae86f-ddf0-445d-951c-cf04f5206e40)
https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
## Features
* Supports multiple versions of Spine files.
* Batch open files via drag-and-drop or copy-paste.
* Batch preview functionality.
* List-based multi-skeleton viewing and render order management.
* Batch adjustment of skeleton parameters using multi-selection.
* Multi-track animation settings.
* Skin and custom slot attachment settings.
* Debug rendering support.
* Fullscreen preview mode.
* Export to single frame/image sequence/animated GIF/video formats.
* Automatic resolution batch export.
* FFmpeg custom export support.
* Program parameter saving.
* ...
- Multiple versions of Spine files
- Batch file opening via drag-and-drop or copy-paste
- Batch preview
- List-based multi-skeleton viewing and render order management
- Multi-selection in lists for batch skeleton parameter settings
- Multi-track animation settings
- Skin and custom slot attachment settings
- Custom slot visibility
- Debug rendering
- Playback speed adjustment for view/model/track timelines
- Track alpha blending parameter settings
- Fullscreen preview
- Export to single frame, image sequence, animated GIF, or video file
- Automatic resolution batch export
- Custom export with FFmpeg
- Program parameter saving
- File extension association
- Texture images in formats other than PNG
- Launch at startup with persistent dynamic wallpaper
- ......
### Supported Spine Versions
| Version | View & Export |
| :-----: | :------------------: |
| `2.1.x` | :white\_check\_mark: |
| `3.4.x` | :white\_check\_mark: |
| `3.5.x` | :white\_check\_mark: |
| `3.6.x` | :white\_check\_mark: |
| `3.7.x` | :white\_check\_mark: |
| `3.8.x` | :white\_check\_mark: |
@@ -71,14 +81,14 @@ In the menu, go to "File" -> "Preferences..." -> "Language," select your desired
The program is organized into a left-right layout:
* **Left Panel:** Functionality panel.
* **Right Panel:** Preview display.
- **Left Panel:** Functionality panel.
- **Right Panel:** Preview display.
The left panel includes three sub-panels:
* **Browse:** Preview the content of a specified folder without importing files into the program. This panel allows generating `.webp` previews for models or importing selected models.
* **Model:** Lists imported models for rendering. Parameters and rendering order can be adjusted here, along with other model-related functionalities.
* **Display:** Adjust parameters for the right-side preview display.
- **Browse:** Preview the content of a specified folder without importing files into the program. This panel allows generating `.webp` previews for models or importing selected models.
- **Model:** Lists imported models for rendering. Parameters and rendering order can be adjusted here, along with other model-related functionalities.
- **Display:** Adjust parameters for the right-side preview display.
Hover your mouse over buttons, labels, or input fields to see help text for most UI elements.
@@ -94,10 +104,10 @@ The Model panel supports right-click menus, some shortcuts, and batch adjustment
For preview display adjustments:
* **Left-click:** Select and drag models. Hold `Ctrl` for multi-selection, synchronized with the left-side list.
* **Right-click:** Drag the entire display.
* **Scroll wheel:** Zoom in/out. Hold `Ctrl` to scale selected models.
* **Render selected-only mode:** In this mode, the preview only shows selected models, and selection status can only be changed via the left-side list.
- **Left-click:** Select and drag models. Hold `Ctrl` for multi-selection, synchronized with the left-side list.
- **Right-click:** Drag the entire display.
- **Scroll wheel:** Zoom in/out. Hold `Ctrl` to scale selected models.
- **Render selected-only mode:** In this mode, the preview only shows selected models, and selection status can only be changed via the left-side list.
The buttons below the preview display allow time adjustments, serving as a simple playback control.
@@ -109,9 +119,17 @@ Use the right-click menu in the Model panel to export selected items.
Key export parameters include:
* **Output folder:** Optional. When not specified, output is saved to the respective model folder; otherwise, all output is saved to the provided folder.
* **Export single:** By default, each model is exported independently. Selecting "Export single" renders all selected models in a single frame, producing a unified output.
* **Auto resolution:** Ignores the preview resolution and viewport parameters, exporting output at the actual size of the content. For animations/videos, the output matches the size required for full visibility.
- **Output folder:** Optional. When not specified, output is saved to the respective model folder; otherwise, all output is saved to the provided folder.
- **Export single:** By default, each model is exported independently. Selecting "Export single" renders all selected models in a single frame, producing a unified output.
- **Auto resolution:** Ignores the preview resolution and viewport parameters, exporting output at the actual size of the content. For animations/videos, the output matches the size required for full visibility.
### Dynamic Wallpaper
Dynamic wallpaper is implemented through desktop projection, allowing the content of the current preview to be projected onto the desktop in real time.
You can enable or disable desktop projection from the program preferences or the right-click menu of the tray icon. After adjusting the model and display parameters, you can save the current configuration as a workspace file for convenient restoration later.
If you want the wallpaper to stay active after startup, you can enable auto-start in the preferences and specify which workspace file should be loaded when the program launches.
### More Information
@@ -119,12 +137,13 @@ For detailed usage and documentation, see the [Wiki](https://github.com/ww-rm/Sp
## Acknowledgements
* [spine-runtimes](https://github.com/EsotericSoftware/spine-runtimes)
* [SFML.Net](https://github.com/SFML/SFML.Net)
* [FFMpegCore](https://github.com/rosenbjerg/FFMpegCore)
* [HandyControl](https://github.com/HandyOrg/HandyControl)
* [NLog](https://github.com/NLog/NLog)
* [SkiaSharp](https://github.com/mono/SkiaSharp)
- [spine-runtimes](https://github.com/EsotericSoftware/spine-runtimes)
- [SFML.Net](https://github.com/SFML/SFML.Net)
- [FFMpegCore](https://github.com/rosenbjerg/FFMpegCore)
- [HandyControl](https://github.com/HandyOrg/HandyControl)
- [NLog](https://github.com/NLog/NLog)
- [SkiaSharp](https://github.com/mono/SkiaSharp)
- [Spectre.Console](https://github.com/spectreconsole/spectre.console)
---

View File

@@ -4,11 +4,15 @@
[![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)
![Languages](https://img.shields.io/badge/Languages-中文%20%7C%20English%20%7C%20日本語-blue)
[中文](README.md) | [English](README.en.md)
一个简单好用的 Spine 文件查看&导出程序, 支持中/英/日多语言界面.
Spine 文件查看&导出程序, 同时也是支持 Spine 的动态壁纸程序.
![previewer](img/preview.webp)
![previewer](https://github.com/user-attachments/assets/697ae86f-ddf0-445d-951c-cf04f5206e40)
https://github.com/user-attachments/assets/37b6b730-088a-4352-827a-c338127a16f0
## 功能
@@ -19,12 +23,18 @@
- 支持列表多选批量设置骨骼参数
- 支持多轨道动画设置
- 支持皮肤/自定义插槽附件设置
- 支持自定义插槽可见性
- 支持调试渲染
- 支持画面/模型/轨道时间倍速设置
- 支持设置轨道 Alpha 混合参数
- 支持全屏预览
- 支持单帧/动图/视频文件导出
- 支持自动分辨率批量导出
- 支持 FFmpeg 自定义导出
- 支持程序参数保存
- 支持文件后缀关联
- 支持非 png 格式的纹理图片格式
- 支持开机自启常驻动态壁纸
- ......
### Spine 版本支持
@@ -32,6 +42,8 @@
| 版本 | 查看&导出 |
| :---: | :---: |
| `2.1.x` | :white_check_mark: |
| `3.4.x` | :white_check_mark: |
| `3.5.x` | :white_check_mark: |
| `3.6.x` | :white_check_mark: |
| `3.7.x` | :white_check_mark: |
| `3.8.x` | :white_check_mark: |
@@ -110,6 +122,14 @@
- 导出单个. 默认是每个模型独立导出, 即对模型列表进行批量操作, 如果选择仅导出单个, 那么被导出的所有模型将在同一个画面上被渲染, 输出产物只有一份.
- 自动分辨率. 该模式会忽略预览画面的分辨率和视区参数, 导出产物的分辨率与被导出内容的实际大小一致, 如果是动图或者视频则会与完整显示动画的必需大小一致.
### 动态壁纸
动态壁纸通过桌面投影实现, 可以将当前预览画面上的内容实时投影至桌面.
在程序首选项或者托盘图标右键菜单中可以进行桌面投影的启用与否, 模型和画面参数调整完成后, 可以将当前参数保存为工作区文件, 方便之后恢复该配置.
如果希望开机自启常驻壁纸, 也可以在首选项中启用开机自启, 并且设置启动后需要加载的工作区文件.
### 更多
更为详细的使用方法和说明见 [Wiki](https://github.com/ww-rm/SpineViewer/wiki), 有使用上的问题或者 BUG 可以提个 [Issue](https://github.com/ww-rm/SpineViewer/issues).
@@ -122,6 +142,7 @@
- [HandyControl](https://github.com/HandyOrg/HandyControl)
- [NLog](https://github.com/NLog/NLog)
- [SkiaSharp](https://github.com/mono/SkiaSharp)
- [Spectre.Console](https://github.com/spectreconsole/spectre.console)
---

View File

@@ -64,10 +64,10 @@ namespace SFMLRenderer
hs?.Dispose();
}
private nint HwndMessageHook(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled)
private IntPtr HwndMessageHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
_renderWindow?.DispatchEvents();
return nint.Zero;
return IntPtr.Zero;
}
}
}

View File

@@ -240,8 +240,8 @@ namespace SFMLRenderer
if (RenderWindow is null) return;
float parentW = (float)sizeInfo.NewSize.Width;
float parentH = (float)sizeInfo.NewSize.Height;
float renderW = (float)_hwndHost.ActualWidth;
float renderH = (float)_hwndHost.ActualHeight;
float renderW = _resolution.X;
float renderH = _resolution.Y;
float scale = Math.Min(parentW / renderW, parentH / renderH); // 两方向取较小值, 保证 parent 覆盖 render
renderW *= scale;
renderH *= scale;

View File

@@ -0,0 +1,171 @@
using SFML.Graphics;
using SFML.System;
using SFML.Window;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace SFMLRenderer
{
public class SFMLRenderWindow : RenderWindow, ISFMLRenderer
{
private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromMilliseconds(10) };
public SFMLRenderWindow(VideoMode mode, string title, Styles style) : base(mode, title, style)
{
SetActive(false);
_timer.Tick += (s, e) => DispatchEvents();
_timer.Start();
RendererCreated?.Invoke(this, EventArgs.Empty);
}
public event EventHandler? RendererCreated;
public event EventHandler? RendererDisposing
{
add => throw new NotImplementedException();
remove => throw new NotImplementedException();
}
public event EventHandler<MouseMoveEventArgs>? CanvasMouseMove
{
add { MouseMoved += value; }
remove { MouseMoved -= value; }
}
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonPressed
{
add { MouseButtonPressed += value; }
remove { MouseButtonPressed -= value; }
}
public event EventHandler<MouseButtonEventArgs>? CanvasMouseButtonReleased
{
add { MouseButtonReleased += value; }
remove { MouseButtonReleased -= value; }
}
public event EventHandler<MouseWheelScrollEventArgs>? CanvasMouseWheelScrolled
{
add { MouseWheelScrolled += value; }
remove { MouseWheelScrolled -= value; }
}
public Vector2u Resolution
{
get => Size;
set => Size = value;
}
public Vector2f Center
{
get
{
using var view = GetView();
return view.Center;
}
set
{
using var view = GetView();
view.Center = value;
SetView(view);
}
}
public float Zoom
{
get
{
using var view = GetView();
return Math.Abs(Size.X / view.Size.X); // XXX: 仅使用宽度进行缩放计算
}
set
{
value = Math.Abs(value);
if (value <= 0) return;
using var view = GetView();
var signX = Math.Sign(view.Size.X);
var signY = Math.Sign(view.Size.Y);
var resolution = Size;
view.Size = new(resolution.X / value * signX, resolution.Y / value * signY);
SetView(view);
}
}
public float Rotation
{
get
{
using var view = GetView();
return view.Rotation;
}
set
{
using var view = GetView();
view.Rotation = value;
SetView(view);
}
}
public bool FlipX
{
get
{
using var view = GetView();
return view.Size.X < 0;
}
set
{
using var view = GetView();
var size = view.Size;
if (size.X > 0 && value || size.X < 0 && !value)
size.X *= -1;
view.Size = size;
SetView(view);
}
}
public bool FlipY
{
get
{
using var view = GetView();
return view.Size.Y < 0;
}
set
{
using var view = GetView();
var size = view.Size;
if (size.Y > 0 && value || size.Y < 0 && !value)
size.Y *= -1;
view.Size = size;
SetView(view);
}
}
public uint MaxFps
{
get => _maxFps;
set
{
SetFramerateLimit(value);
_maxFps = value;
}
}
private uint _maxFps = 0;
public bool VerticalSync
{
get => _verticalSync;
set
{
SetVerticalSyncEnabled(value);
_verticalSync = value;
}
}
private bool _verticalSync = false;
}
}

View File

@@ -4,10 +4,11 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<TargetFramework>net8.0-windows</TargetFramework>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.15.4</Version>
<Version>0.16.6</Version>
<UseWPF>true</UseWPF>
</PropertyGroup>

View File

@@ -60,8 +60,9 @@ namespace Spine.Exporters
if (!string.IsNullOrEmpty(_codec)) options.WithVideoCodec(_codec);
if (!string.IsNullOrEmpty(_pixelFormat)) options.ForcePixelFormat(_pixelFormat);
if (!string.IsNullOrEmpty(_bitrate)) options.WithCustomArgument($"-b:v {_bitrate}");
if (!string.IsNullOrEmpty(_filter)) options.WithCustomArgument($"-vf unpremultiply=inplace=1, {_customArgs}");
if (!string.IsNullOrEmpty(_filter)) options.WithCustomArgument($"-vf \"unpremultiply=inplace=1, {_filter}\"");
else options.WithCustomArgument("-vf unpremultiply=inplace=1");
if (!string.IsNullOrEmpty(_customArgs)) options.WithCustomArgument($"{_customArgs}");
}
/// <summary>

View File

@@ -1,18 +1,14 @@
using SFML.Graphics;
using SFML.System;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace SpineViewer.Extensions
namespace Spine.Exporters
{
public static class SFMLExtension
public static class Extension
{
/// <summary>
/// 获取适合指定画布参数下能够覆盖包围盒的画布视区包围盒
@@ -53,31 +49,11 @@ namespace SpineViewer.Extensions
public static FloatRect GetBounds(this View self)
{
return new(
self.Center.X - self.Size.X / 2,
self.Center.Y - self.Size.Y / 2,
self.Size.X,
self.Center.X - self.Size.X / 2,
self.Center.Y - self.Size.Y / 2,
self.Size.X,
self.Size.Y
);
}
public static FloatRect ToFloatRect(this Rect self)
{
return new((float)self.X, (float)self.Y, (float)self.Width, (float)self.Height);
}
public static Vector2f ToVector2f(this Size self)
{
return new((float)self.Width, (float)self.Height);
}
public static Vector2u ToVector2u(this Size self)
{
return new((uint)self.Width, (uint)self.Height);
}
public static Vector2i ToVector2i(this Size self)
{
return new((int)self.Width, (int)self.Height);
}
}
}

View File

@@ -28,9 +28,38 @@ namespace Spine.Exporters
{
Gif,
Webp,
Apng,
Mp4,
Webm,
Mkv,
Mov,
}
/// <summary>
/// Apng 格式预测器算法
/// </summary>
public enum ApngPredMethod
{
None = 0,
Sub = 1,
Up = 2,
Avg = 3,
Paeth = 4,
Mixed = 5,
}
/// <summary>
/// Mov prores_ks 编码器 profile 参数
/// </summary>
public enum MovProfile
{
Auto = -1,
Proxy = 0,
Light = 1,
Standard = 2,
High = 3,
Yuv4444 = 4,
Yuv4444Extreme = 5,
}
/// <summary>
@@ -40,29 +69,41 @@ namespace Spine.Exporters
private VideoFormat _format = VideoFormat.Mp4;
/// <summary>
/// 动图是否循环
/// [Gif/Webp/Apng] 动图是否循环
/// </summary>
public bool Loop { get => _loop; set => _loop = value; }
private bool _loop = true;
/// <summary>
/// 质量
/// [Webp] 质量
/// </summary>
public int Quality { get => _quality; set => _quality = Math.Clamp(value, 0, 100); }
private int _quality = 75;
/// <summary>
/// 无损压缩 (Webp)
/// [Webp] 无损压缩
/// </summary>
public bool Lossless { get => _lossless; set => _lossless = value; }
private bool _lossless = false;
/// <summary>
/// CRF
/// [Apng] 预测器算法
/// </summary>
public ApngPredMethod PredMethod { get => _predMethod; set => _predMethod = value; }
private ApngPredMethod _predMethod = ApngPredMethod.Mixed;
/// <summary>
/// [Mp4/Webm/Mkv] CRF
/// </summary>
public int Crf { get => _crf; set => _crf = Math.Clamp(value, 0, 63); }
private int _crf = 23;
/// <summary>
/// [Mov] prores_ks 编码器的配置等级, 越高质量越好, 只有 <see cref="MovProfile.Yuv4444"> 及以上才有透明通道
/// </summary>
public MovProfile Profile { get => _profile; set => _profile = value; }
private MovProfile _profile = MovProfile.Yuv4444Extreme;
/// <summary>
/// 获取的一帧, 结果是预乘的
/// </summary>
@@ -86,9 +127,11 @@ namespace Spine.Exporters
{
VideoFormat.Gif => SetGifOptions,
VideoFormat.Webp => SetWebpOptions,
VideoFormat.Apng => SetApngOptions,
VideoFormat.Mp4 => SetMp4Options,
VideoFormat.Webm => SetWebmOptions,
VideoFormat.Mkv => SetMkvOptions,
VideoFormat.Mov => SetMovOptions,
_ => throw new NotImplementedException(),
};
@@ -110,8 +153,8 @@ namespace Spine.Exporters
{
// Gif 固定使用 256 调色板和 128 透明度阈值
var v = "split [s0][s1]";
var s0 = "[s0] palettegen=reserve_transparent=1:max_colors=256 [p]";
var s1 = "[s1][p] paletteuse=dither=bayer:alpha_threshold=128";
var s0 = "[s0] palettegen=max_colors=256 [p]";
var s1 = "[s1][p] paletteuse=alpha_threshold=128";
var customArgs = $"-vf \"unpremultiply=inplace=1, {v};{s0};{s1}\" -loop {(_loop ? 0 : -1)}";
options.ForceFormat("gif")
.WithCustomArgument(customArgs);
@@ -124,6 +167,13 @@ namespace Spine.Exporters
.WithCustomArgument(customArgs);
}
private void SetApngOptions(FFMpegArgumentOptions options)
{
var customArgs = $"-vf unpremultiply=inplace=1 -plays {(_loop ? 0 : 1)} -pred {(int)_predMethod}";
options.ForceFormat("apng").WithVideoCodec("apng").ForcePixelFormat("rgba")
.WithCustomArgument(customArgs);
}
private void SetMp4Options(FFMpegArgumentOptions options)
{
// XXX: windows 默认播放器在播放 MP4 格式时对于 libx264 编码器只支持 yuv420p 的像素格式
@@ -151,5 +201,13 @@ namespace Spine.Exporters
.WithCustomArgument(customArgs);
}
private void SetMovOptions(FFMpegArgumentOptions options)
{
var customArgs = "-vf unpremultiply=inplace=1";
options.ForceFormat("mov").WithVideoCodec("prores_ks").ForcePixelFormat("yuva444p10le")
.WithFastStart()
.WithCustomArgument($"-profile {(int)_profile}")
.WithCustomArgument(customArgs);
}
}
}

View File

@@ -18,11 +18,27 @@ namespace Spine.Exporters
public FrameExporter(uint width = 100, uint height = 100) : base(width, height) { }
public FrameExporter(Vector2u resolution) : base(resolution) { }
public SKEncodedImageFormat Format { get => _format; set => _format = value; }
public SKEncodedImageFormat Format
{
get => _format;
set {
switch (value)
{
case SKEncodedImageFormat.Jpeg:
case SKEncodedImageFormat.Png:
case SKEncodedImageFormat.Webp:
_format = value;
break;
default:
_logger.Warn("Omit unsupported exporter format: {0}", value);
break;
}
}
}
protected SKEncodedImageFormat _format = SKEncodedImageFormat.Png;
public int Quality { get => _quality; set => _quality = Math.Clamp(value, 0, 100); }
protected int _quality = 80;
protected int _quality = 100;
public override void Export(string output, params SpineObject[] spines)
{
@@ -33,5 +49,15 @@ namespace Spine.Exporters
using var stream = File.OpenWrite(output);
data.SaveTo(stream);
}
/// <summary>
/// 获取帧图像, 结果是预乘的
/// </summary>
public SKImage ExportMemoryImage(params SpineObject[] spines)
{
using var frame = GetFrame(spines);
var info = new SKImageInfo(frame.Width, frame.Height, SKColorType.Rgba8888, SKAlphaType.Premul);
return SKImage.FromPixelCopy(info, frame.Image.Pixels);
}
}
}

View File

@@ -24,7 +24,7 @@ namespace Spine.Exporters
int frameCount = GetFrameCount();
int frameIdx = 0;
_progressReporter?.Invoke(frameCount, 0, $"[{frameIdx}/{frameCount}] {output}");
_progressReporter?.Invoke(frameCount, 0, $"[0/{frameCount}] {output}"); // 导出帧序列单独在此处调用进度报告
foreach (var frame in GetFrames(spines))
{
if (ct.IsCancellationRequested)
@@ -37,7 +37,7 @@ namespace Spine.Exporters
var savePath = Path.Combine(output, $"frame_{_fps}_{frameIdx:d6}.png");
var info = new SKImageInfo(frame.Width, frame.Height, SKColorType.Rgba8888, SKAlphaType.Premul);
_progressReporter?.Invoke(frameCount, frameIdx, $"[{frameIdx + 1}/{frameCount}] {savePath}");
_progressReporter?.Invoke(frameCount, frameIdx + 1, $"[{frameIdx + 1}/{frameCount}] {savePath}");
try
{
using var skImage = SKImage.FromPixelCopy(info, frame.Image.Pixels);

View File

@@ -92,7 +92,7 @@ namespace Spine.Exporters
}
/// <summary>
/// 生成帧序列
/// 生成帧序列, 用于导出帧序列
/// </summary>
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SpineObject[] spines)
{
@@ -121,14 +121,14 @@ namespace Spine.Exporters
}
/// <summary>
/// 生成帧序列, 支持中途取消和进度输出
/// 生成帧序列, 支持中途取消和进度输出, 用于动图视频等单个文件输出
/// </summary>
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SpineObject[] spines, string output, CancellationToken ct)
{
int frameCount = GetFrameCount();
int frameIdx = 0;
_progressReporter?.Invoke(frameCount, 0, $"[{frameIdx}/{frameCount}] {output}");
_progressReporter?.Invoke(frameCount, 0, $"[0/{frameCount}] {output}");
foreach (var frame in GetFrames(spines))
{
if (ct.IsCancellationRequested)
@@ -138,7 +138,7 @@ namespace Spine.Exporters
break;
}
_progressReporter?.Invoke(frameCount, frameIdx, $"[{frameIdx + 1}/{frameCount}] {output}");
_progressReporter?.Invoke(frameCount, frameIdx + 1, $"[{frameIdx + 1}/{frameCount}] {output}");
yield return frame;
frameIdx++;
}

View File

@@ -1,16 +1,21 @@
using System;
using NLog;
using SFML.Graphics;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.SpineWrappers
namespace Spine.Implementations
{
/// <summary>
/// 实现不同版本的 TextureLoader
/// </summary>
public class TextureLoader :
SpineRuntime21.TextureLoader,
SpineRuntime34.TextureLoader,
SpineRuntime35.TextureLoader,
SpineRuntime36.TextureLoader,
SpineRuntime37.TextureLoader,
SpineRuntime38.TextureLoader,
@@ -18,6 +23,8 @@ namespace Spine.SpineWrappers
SpineRuntime41.TextureLoader,
SpineRuntime42.TextureLoader
{
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// 默认的全局纹理加载器
/// </summary>
@@ -38,37 +45,38 @@ namespace Spine.SpineWrappers
/// </summary>
public bool ForceMipmap { get; set; }
private SFML.Graphics.Texture ReadTexture(string path)
private Texture ReadTexture(string path)
{
if (ForcePremul)
if (!File.Exists(path))
{
using var image = new SFML.Graphics.Image(path);
var width = image.Size.X;
var height = image.Size.Y;
var pixels = image.Pixels;
var size = width * height * 4;
for (int i = 0; i < size; i += 4)
{
byte a = pixels[i + 3];
if (a == 0)
{
pixels[i + 0] = 0;
pixels[i + 1] = 0;
pixels[i + 2] = 0;
}
else if (a != 255)
{
float f = a / 255f;
pixels[i + 0] = (byte)(pixels[i + 0] * f);
pixels[i + 1] = (byte)(pixels[i + 1] * f);
pixels[i + 2] = (byte)(pixels[i + 2] * f);
}
}
var tex = new SFML.Graphics.Texture(width, height);
tex.Update(pixels);
return tex;
_logger.Error($"Texture file not found, {path}");
throw new FileNotFoundException("Texture file not found", path);
}
return new(path);
using var codec = SKCodec.Create(path, out var result);
if (codec is null || result != SKCodecResult.Success)
{
_logger.Error($"Failed to create codec '{path}', {result}");
throw new InvalidOperationException($"Failed to create codec '{path}', {result}");
}
var width = codec.Info.Width;
var height = codec.Info.Height;
// 判断是否需要强制预乘
var alphaType = ForcePremul ? SKAlphaType.Premul : SKAlphaType.Unpremul;
var info = new SKImageInfo(width, height, SKColorType.Rgba8888, alphaType);
result = codec.GetPixels(info, out var pixels);
if (result != SKCodecResult.Success)
{
_logger.Error($"Failed to decode image '{path}', {result}");
throw new InvalidOperationException($"Failed to decode image '{path}', {result}");
}
Texture tex = new((uint)width, (uint)height);
tex.Update(pixels);
return tex;
}
public virtual void Load(SpineRuntime21.AtlasPage page, string path)
@@ -104,6 +112,100 @@ namespace Spine.SpineWrappers
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
// 有些旧的 atlas 会省略 size 行, 这时需要在读取纹理时赋值
if (page.width <= 0 || page.height <= 0)
{
var texSize = texture.Size;
page.width = (int)texSize.X;
page.height = (int)texSize.Y;
}
}
public virtual void Load(SpineRuntime34.AtlasPage page, string path)
{
var texture = ReadTexture(path);
if (page.magFilter == SpineRuntime34.TextureFilter.Linear)
{
texture.Smooth = true;
}
if (page.uWrap == SpineRuntime34.TextureWrap.Repeat && page.vWrap == SpineRuntime34.TextureWrap.Repeat)
{
texture.Repeated = true;
}
switch (page.minFilter)
{
case SpineRuntime34.TextureFilter.Linear:
texture.Smooth = true;
break;
case SpineRuntime34.TextureFilter.MipMap:
case SpineRuntime34.TextureFilter.MipMapNearestNearest:
texture.GenerateMipmap();
break;
case SpineRuntime34.TextureFilter.MipMapLinearNearest:
case SpineRuntime34.TextureFilter.MipMapNearestLinear:
case SpineRuntime34.TextureFilter.MipMapLinearLinear:
texture.Smooth = true;
texture.GenerateMipmap();
break;
}
if (ForceNearest) texture.Smooth = false;
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
// 有些旧的 atlas 会省略 size 行, 这时需要在读取纹理时赋值
if (page.width <= 0 || page.height <= 0)
{
var texSize = texture.Size;
page.width = (int)texSize.X;
page.height = (int)texSize.Y;
}
}
public virtual void Load(SpineRuntime35.AtlasPage page, string path)
{
var texture = ReadTexture(path);
if (page.magFilter == SpineRuntime35.TextureFilter.Linear)
{
texture.Smooth = true;
}
if (page.uWrap == SpineRuntime35.TextureWrap.Repeat && page.vWrap == SpineRuntime35.TextureWrap.Repeat)
{
texture.Repeated = true;
}
switch (page.minFilter)
{
case SpineRuntime35.TextureFilter.Linear:
texture.Smooth = true;
break;
case SpineRuntime35.TextureFilter.MipMap:
case SpineRuntime35.TextureFilter.MipMapNearestNearest:
texture.GenerateMipmap();
break;
case SpineRuntime35.TextureFilter.MipMapLinearNearest:
case SpineRuntime35.TextureFilter.MipMapNearestLinear:
case SpineRuntime35.TextureFilter.MipMapLinearLinear:
texture.Smooth = true;
texture.GenerateMipmap();
break;
}
if (ForceNearest) texture.Smooth = false;
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
// 有些旧的 atlas 会省略 size 行, 这时需要在读取纹理时赋值
if (page.width <= 0 || page.height <= 0)
{
var texSize = texture.Size;
page.width = (int)texSize.X;
page.height = (int)texSize.Y;
}
}
public virtual void Load(SpineRuntime36.AtlasPage page, string path)
@@ -139,6 +241,14 @@ namespace Spine.SpineWrappers
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
// 有些旧的 atlas 会省略 size 行, 这时需要在读取纹理时赋值
if (page.width <= 0 || page.height <= 0)
{
var texSize = texture.Size;
page.width = (int)texSize.X;
page.height = (int)texSize.Y;
}
}
public virtual void Load(SpineRuntime37.AtlasPage page, string path)
@@ -174,6 +284,14 @@ namespace Spine.SpineWrappers
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
// 有些旧的 atlas 会省略 size 行, 这时需要在读取纹理时赋值
if (page.width <= 0 || page.height <= 0)
{
var texSize = texture.Size;
page.width = (int)texSize.X;
page.height = (int)texSize.Y;
}
}
public virtual void Load(SpineRuntime38.AtlasPage page, string path)
@@ -210,9 +328,13 @@ namespace Spine.SpineWrappers
page.rendererObject = texture;
// 似乎是不需要设置的, 因为存在某些 png 和 atlas 大小不同的情况, 一般是有一些缩放, 如果设置了反而渲染异常
// page.width = (int)texture.Size.X;
// page.height = (int)texture.Size.Y;
// 有些旧的 atlas 会省略 size 行, 这时需要在读取纹理时赋值
if (page.width <= 0 || page.height <= 0)
{
var texSize = texture.Size;
page.width = (int)texSize.X;
page.height = (int)texSize.Y;
}
}
public virtual void Load(SpineRuntime40.AtlasPage page, string path)
@@ -248,6 +370,14 @@ namespace Spine.SpineWrappers
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
// 有些旧的 atlas 会省略 size 行, 这时需要在读取纹理时赋值
if (page.width <= 0 || page.height <= 0)
{
var texSize = texture.Size;
page.width = (int)texSize.X;
page.height = (int)texSize.Y;
}
}
public virtual void Load(SpineRuntime41.AtlasPage page, string path)
@@ -283,6 +413,14 @@ namespace Spine.SpineWrappers
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
// 有些旧的 atlas 会省略 size 行, 这时需要在读取纹理时赋值
if (page.width <= 0 || page.height <= 0)
{
var texSize = texture.Size;
page.width = (int)texSize.X;
page.height = (int)texSize.Y;
}
}
public virtual void Load(SpineRuntime42.AtlasPage page, string path)
@@ -318,11 +456,19 @@ namespace Spine.SpineWrappers
if (ForceMipmap) texture.GenerateMipmap();
page.rendererObject = texture;
// 有些旧的 atlas 会省略 size 行, 这时需要在读取纹理时赋值
if (page.width <= 0 || page.height <= 0)
{
var texSize = texture.Size;
page.width = (int)texSize.X;
page.height = (int)texSize.Y;
}
}
public virtual void Unload(object texture)
{
((SFML.Graphics.Texture)texture).Dispose();
((Texture)texture).Dispose();
}
}
}

View File

@@ -1,4 +1,4 @@
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime21;
using System;
using System.Collections.Generic;
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V21
namespace Spine.Implementations.V21
{
internal sealed class Animation21(Animation innerObject) : IAnimation
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21
namespace Spine.Implementations.V21
{
internal sealed class AnimationState21(AnimationState innerObject, SpineObjectData21 data) : IAnimationState
{
@@ -35,7 +35,7 @@ namespace Spine.Implementations.SpineWrappers.V21
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
@@ -64,7 +64,7 @@ namespace Spine.Implementations.SpineWrappers.V21
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
@@ -93,7 +93,7 @@ namespace Spine.Implementations.SpineWrappers.V21
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;

View File

@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21.Attachments
namespace Spine.Implementations.V21.Attachments
{
internal abstract class Attachment21(Attachment innerObject) : IAttachment
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V21;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21.Attachments
namespace Spine.Implementations.V21.Attachments
{
internal sealed class BoundingBoxAttachment21(BoundingBoxAttachment innerObject) :
Attachment21(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V21.Attachments
public override BoundingBoxAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot21 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V21;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21.Attachments
namespace Spine.Implementations.V21.Attachments
{
internal sealed class MeshAttachment21(MeshAttachment innerObject) :
Attachment21(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V21.Attachments
public override MeshAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot21 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V21;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21.Attachments
namespace Spine.Implementations.V21.Attachments
{
internal sealed class RegionAttachment21(RegionAttachment innerObject) :
Attachment21(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V21.Attachments
public override RegionAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot21 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V21;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21.Attachments
namespace Spine.Implementations.V21.Attachments
{
internal sealed class SkinnedMeshAttachment21(SkinnedMeshAttachment innerObject) :
Attachment21(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V21.Attachments
public override SkinnedMeshAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot21 st)
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21
namespace Spine.Implementations.V21
{
internal sealed class Bone21(Bone innerObject, Bone21? parent = null) : IBone
{

View File

@@ -5,10 +5,10 @@ using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using Spine.SpineWrappers;
using SpineRuntime21;
using Spine.Interfaces;
namespace Spine.Implementations.SpineWrappers.V21
namespace Spine.Implementations.V21
{
internal sealed class Skeleton21 : ISkeleton
{
@@ -52,6 +52,7 @@ namespace Spine.Implementations.SpineWrappers.V21
public Skeleton InnerObject => _o;
public string Name => _o.Data.Name;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
@@ -95,11 +96,6 @@ namespace Spine.Implementations.SpineWrappers.V21
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public void GetBounds(out float x, out float y, out float w, out float h)
{
_o.GetBounds(out x, out y, out w, out h);
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -1,5 +1,5 @@
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using Spine.Utils;
using SpineRuntime21;
using System;
@@ -8,7 +8,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V21
namespace Spine.Implementations.V21
{
internal sealed class SkeletonClipping21 : ISkeletonClipping
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime21;
namespace Spine.Implementations.SpineWrappers.V21
namespace Spine.Implementations.V21
{
internal sealed class Skin21 : ISkin
{

View File

@@ -5,10 +5,11 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using SpineRuntime21;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.SpineWrappers.V21
namespace Spine.Implementations.V21
{
internal sealed class Slot21 : ISlot
{
@@ -39,7 +40,7 @@ namespace Spine.Implementations.SpineWrappers.V21
public float A { get => _o.A; set => _o.A = value; }
public IBone Bone => _bone;
public Spine.SpineWrappers.Attachments.IAttachment? Attachment
public IAttachment? Attachment
{
get
{
@@ -66,6 +67,8 @@ namespace Spine.Implementations.SpineWrappers.V21
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -6,12 +6,12 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using SpineRuntime21;
using Spine.Implementations.SpineWrappers.V21.Attachments;
using Spine.Implementations.V21.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.SpineWrappers.V21
namespace Spine.Implementations.V21
{
[SpineImplementation(2, 1)]
internal sealed class SpineObjectData21 : SpineObjectData
@@ -26,12 +26,19 @@ namespace Spine.Implementations.SpineWrappers.V21
private readonly ImmutableArray<IAnimation> _animations;
private readonly FrozenDictionary<string, IAnimation> _animationsByName;
public SpineObjectData21(string skelPath, string atlasPath, Spine.SpineWrappers.TextureLoader textureLoader)
public SpineObjectData21(string skelPath, string atlasPath, TextureLoader textureLoader)
: base(skelPath, atlasPath, textureLoader)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
_atlas = new Atlas(atlasPath, textureLoader);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
try
{
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V21
{
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
catch
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V21
{
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V21
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}
// 加载动画数据

View File

@@ -1,4 +1,4 @@
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime21;
using System;
using System.Collections.Generic;
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V21
namespace Spine.Implementations.V21
{
internal sealed class TrackEntry21(TrackEntry innerObject, AnimationState21 animationState, SpineObjectData21 data): ITrackEntry
{
@@ -34,7 +34,7 @@ namespace Spine.Implementations.SpineWrappers.V21
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
@@ -63,7 +63,7 @@ namespace Spine.Implementations.SpineWrappers.V21
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
@@ -92,7 +92,7 @@ namespace Spine.Implementations.SpineWrappers.V21
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;

View File

@@ -0,0 +1,23 @@
using Spine.Interfaces;
using SpineRuntime34;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.V34
{
internal sealed class Animation34(Animation innerObject) : IAnimation
{
private readonly Animation _o = innerObject;
public Animation InnerObject => _o;
public string Name => _o.Name;
public float Duration => _o.Duration;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Interfaces;
using SpineRuntime34;
namespace Spine.Implementations.V34
{
internal sealed class AnimationState34(AnimationState innerObject, SpineObjectData34 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData34 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry34> _trackEntryPool = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
public AnimationState InnerObject => _o;
#pragma warning disable CS0067
// NOTE: 3.4 以下没有这两个事件
public event IAnimationState.TrackEntryDelegate? Interrupt;
public event IAnimationState.TrackEntryDelegate? Dispose;
#pragma warning restore CS0067
public event IAnimationState.TrackEntryDelegate? Start
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Start -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? End
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.End -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Complete
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Complete -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
public void Update(float delta) => _o.Update(delta);
public void Apply(ISkeleton skeleton)
{
if (skeleton is Skeleton34 skel)
{
_o.Apply(skel.InnerObject);
return;
}
throw new ArgumentException($"Received {skeleton.GetType().Name}", nameof(skeleton));
}
/// <summary>
/// 获取 <see cref="ITrackEntry"/> 对象, 不存在则创建
/// </summary>
public ITrackEntry GetTrackEntry(TrackEntry trackEntry)
{
if (!_trackEntryPool.TryGetValue(trackEntry, out var tr))
_trackEntryPool[trackEntry] = tr = new(trackEntry, this, _data);
return tr;
}
public IEnumerable<ITrackEntry?> IterTracks() => _o.Tracks.Select(t => t is null ? null : GetTrackEntry(t));
public ITrackEntry? GetCurrent(int index) { var t = _o.GetCurrent(index); return t is null ? null : GetTrackEntry(t); }
public void ClearTrack(int index) => _o.ClearTrack(index);
public void ClearTracks() => _o.ClearTracks();
public ITrackEntry SetAnimation(int trackIndex, string animationName, bool loop)
=> GetTrackEntry(_o.SetAnimation(trackIndex, animationName, loop));
public ITrackEntry SetAnimation(int trackIndex, IAnimation animation, bool loop)
{
if (animation is Animation34 anime)
return GetTrackEntry(_o.SetAnimation(trackIndex, anime.InnerObject, loop));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) => GetTrackEntry(_o.SetEmptyAnimation(trackIndex, mixDuration));
public void SetEmptyAnimations(float mixDuration) => _o.SetEmptyAnimations(mixDuration);
public ITrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay)
=> GetTrackEntry(_o.AddAnimation(trackIndex, animationName, loop, delay));
public ITrackEntry AddAnimation(int trackIndex, IAnimation animation, bool loop, float delay)
{
if (animation is Animation34 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.V34.Attachments
{
internal abstract class Attachment34(Attachment innerObject) : IAttachment
{
private readonly Attachment _o = innerObject;
public virtual Attachment InnerObject => _o;
public string Name => _o.Name;
public abstract int ComputeWorldVertices(ISlot slot, ref float[] worldVertices);
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V34;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.V34.Attachments
{
internal sealed class BoundingBoxAttachment34(BoundingBoxAttachment innerObject) :
Attachment34(innerObject),
IBoundingBoxAttachment
{
private readonly BoundingBoxAttachment _o = innerObject;
public override BoundingBoxAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot34 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V34;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.V34.Attachments
{
internal sealed class MeshAttachment34(MeshAttachment innerObject) :
Attachment34(innerObject),
IMeshAttachment
{
private readonly MeshAttachment _o = innerObject;
public override MeshAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot34 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
public int[] Triangles => _o.Triangles;
public int HullLength => _o.HullLength;
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V34;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.V34.Attachments
{
internal sealed class PathAttachment34(PathAttachment innerObject) :
Attachment34(innerObject),
IPathAttachment
{
private readonly PathAttachment _o = innerObject;
public override PathAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot34 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V34;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime34;
namespace Spine.Implementations.V34.Attachments
{
internal sealed class RegionAttachment34(RegionAttachment innerObject) :
Attachment34(innerObject),
IRegionAttachment
{
private readonly RegionAttachment _o = innerObject;
public override RegionAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot34 st)
{
if (worldVertices.Length < 8) worldVertices = new float[8];
_o.ComputeWorldVertices(st.InnerObject.Bone, worldVertices);
return 8;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot34)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Interfaces;
using SpineRuntime34;
namespace Spine.Implementations.V34
{
internal sealed class Bone34(Bone innerObject, Bone34? parent = null) : IBone
{
private readonly Bone _o = innerObject;
private readonly Bone34? _parent = parent;
public Bone InnerObject => _o;
public string Name => _o.Data.Name;
public int Index => _o.Data.Index;
public IBone? Parent => _parent;
public bool Active => true; // NOTE: 3.7 及以下没有 Active 属性, 此处总是返回 true
public float Length => _o.Data.Length;
public float WorldX => _o.WorldX;
public float WorldY => _o.WorldY;
public float A => _o.A;
public float B => _o.B;
public float C => _o.C;
public float D => _o.D;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using SpineRuntime34;
using Spine.Interfaces;
namespace Spine.Implementations.V34
{
internal sealed class Skeleton34 : ISkeleton
{
private readonly Skeleton _o;
private readonly SpineObjectData34 _data;
private readonly ImmutableArray<IBone> _bones;
private readonly FrozenDictionary<string, IBone> _bonesByName;
private readonly ImmutableArray<ISlot> _slots;
private readonly FrozenDictionary<string, ISlot> _slotsByName;
private Skin34? _skin;
public Skeleton34(Skeleton innerObject, SpineObjectData34 data)
{
_o = innerObject;
_data = data;
List<Bone34> bones = [];
Dictionary<string, IBone> bonesByName = [];
foreach (var b in _o.Bones)
{
var bone = new Bone34(b, b.Parent is null ? null : bones[b.Parent.Data.Index]);
bones.Add(bone);
bonesByName[bone.Name] = bone;
}
_bones = bones.Cast<IBone>().ToImmutableArray();
_bonesByName = bonesByName.ToFrozenDictionary();
List<Slot34> slots = [];
Dictionary<string, ISlot> slotsByName = [];
foreach (var s in _o.Slots)
{
var slot = new Slot34(s, _data, bones[s.Bone.Data.Index]);
slots.Add(slot);
slotsByName[slot.Name] = slot;
}
_slots = slots.Cast<ISlot>().ToImmutableArray();
_slotsByName = slotsByName.ToFrozenDictionary();
}
public Skeleton InnerObject => _o;
public string Name => _o.Data.Name;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public float X { get => _o.X; set => _o.X = value; }
public float Y { get => _o.Y; set => _o.Y = value; }
public float ScaleX { get => _o.ScaleX; set => _o.ScaleX = value; }
public float ScaleY { get => _o.ScaleY; set => _o.ScaleY = value; }
public ImmutableArray<IBone> Bones => _bones;
public FrozenDictionary<string, IBone> BonesByName => _bonesByName;
public ImmutableArray<ISlot> Slots => _slots;
public FrozenDictionary<string, ISlot> SlotsByName => _slotsByName;
public ISkin? Skin
{
get => _skin;
set
{
if (value is null)
{
_o.Skin = null;
_skin = null;
return;
}
if (value is Skin34 sk)
{
_o.Skin = sk.InnerObject;
_skin = sk;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
public void UpdateCache() => _o.UpdateCache();
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
public void SetToSetupPose() => _o.SetToSetupPose();
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,42 @@
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using Spine.Utils;
using SpineRuntime34;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.V34
{
internal sealed class SkeletonClipping34 : ISkeletonClipping
{
public bool IsClipping => false;
public float[] ClippedVertices { get; private set; } = [];
public int ClippedVerticesLength { get; private set; } = 0;
public int[] ClippedTriangles { get; private set; } = [];
public int ClippedTrianglesLength { get; private set; } = 0;
public float[] ClippedUVs { get; private set; } = [];
public void ClipEnd(ISlot slot) { }
public void ClipEnd() { }
public void ClipStart(ISlot slot, IClippingAttachment clippingAttachment) { }
public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs)
{
ClippedVertices = vertices.ToArray();
ClippedVerticesLength = verticesLength;
ClippedTriangles = triangles.ToArray();
ClippedTrianglesLength = trianglesLength;
ClippedUVs = uvs.ToArray();
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Interfaces;
using SpineRuntime34;
namespace Spine.Implementations.V34
{
internal sealed class Skin34 : ISkin
{
private readonly Skin _o;
/// <summary>
/// 使用指定名字创建空皮肤
/// </summary>
public Skin34(string name) => _o = new(name);
/// <summary>
/// 包装已有皮肤对象
/// </summary>
public Skin34(Skin innerObject) => _o = innerObject;
public Skin InnerObject => _o;
public string Name => _o.Name;
public void AddSkin(ISkin skin)
{
if (skin is Skin34 sk)
{
// NOTE: 3.7 及以下不支持 AddSkin
foreach (var (k, v) in sk._o.Attachments)
_o.AddAttachment(k.slotIndex, k.name, v);
return;
}
throw new ArgumentException($"Received {skin.GetType().Name}", nameof(skin));
}
public void Clear() => _o.Attachments.Clear();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using SpineRuntime34;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.V34
{
internal sealed class Slot34 : ISlot
{
private readonly Slot _o;
private readonly SpineObjectData34 _data;
private readonly Bone34 _bone;
private readonly SFML.Graphics.BlendMode _blendMode;
public Slot34(Slot innerObject, SpineObjectData34 data, Bone34 bone)
{
_o = innerObject;
_data = data;
_bone = bone;
_blendMode = _o.Data.BlendMode switch
{
BlendMode.normal => SFMLBlendMode.NormalPma,
BlendMode.additive => SFMLBlendMode.AdditivePma,
BlendMode.multiply => SFMLBlendMode.MultiplyPma,
BlendMode.screen => SFMLBlendMode.ScreenPma,
_ => throw new NotImplementedException($"{_o.Data.BlendMode}"),
};
}
public Slot InnerObject => _o;
public string Name => _o.Data.Name;
public int Index => _o.Data.Index;
public SFML.Graphics.BlendMode Blend => _blendMode;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public IBone Bone => _bone;
public IAttachment? Attachment
{
get
{
if (_o.Attachment is Attachment att)
{
return _data.SlotAttachments[Name][att.Name];
}
return null;
}
set
{
if (value is null)
{
_o.Attachment = null;
return;
}
if (value is Attachments.Attachment34 att)
{
_o.Attachment = att.InnerObject;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Collections.Frozen;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using SpineRuntime34;
using Spine.Implementations.V34.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.V34
{
[SpineImplementation(3, 4)]
internal sealed class SpineObjectData34 : SpineObjectData
{
private readonly Atlas _atlas;
private readonly SkeletonData _skeletonData;
private readonly AnimationStateData _animationStateData;
private readonly ImmutableArray<ISkin> _skins;
private readonly FrozenDictionary<string, ISkin> _skinsByName;
private readonly FrozenDictionary<string, FrozenDictionary<string, IAttachment>> _slotAttachments;
private readonly ImmutableArray<IAnimation> _animations;
private readonly FrozenDictionary<string, IAnimation> _animationsByName;
public SpineObjectData34(string skelPath, string atlasPath, TextureLoader textureLoader)
: base(skelPath, atlasPath, textureLoader)
{
// 加载 atlas
try
{
_atlas = new Atlas(atlasPath, textureLoader);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
try
{
if (Utf8Validator.IsUtf8(skelPath))
{
try
{
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
else
{
try
{
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
}
catch (Exception ex)
{
_atlas.Dispose();
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}
// 加载动画数据
_animationStateData = new AnimationStateData(_skeletonData);
// 整理皮肤和附件
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
List<ISkin> skins = [];
Dictionary<string, ISkin> skinsByName = [];
foreach (var s in _skeletonData.Skins)
{
var skin = new Skin34(s);
skins.Add(skin);
skinsByName[s.Name] = skin;
foreach (var (k, att) in s.Attachments)
{
var slotName = _skeletonData.Slots.Items[k.slotIndex].Name;
if (!slotAttachments.TryGetValue(slotName, out var attachments))
slotAttachments[slotName] = attachments = [];
attachments[att.Name] = att switch
{
RegionAttachment regionAtt => new RegionAttachment34(regionAtt),
MeshAttachment meshAtt => new MeshAttachment34(meshAtt),
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment34(bbAtt),
PathAttachment pathAtt => new PathAttachment34(pathAtt),
_ => throw new InvalidOperationException($"Unrecognized attachment type {att.GetType().FullName}")
};
}
}
_slotAttachments = slotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.ToFrozenDictionary());
_skins = skins.ToImmutableArray();
_skinsByName = skinsByName.ToFrozenDictionary();
// 整理所有动画数据
List<IAnimation> animations = [];
Dictionary<string, IAnimation> animationsByName = [];
foreach (var a in _skeletonData.Animations)
{
var anime = new Animation34(a);
animations.Add(anime);
animationsByName[anime.Name] = anime;
}
_animations = animations.ToImmutableArray();
_animationsByName = animationsByName.ToFrozenDictionary();
}
public override string SkeletonVersion => _skeletonData.Version;
public override ImmutableArray<ISkin> Skins => _skins;
public override FrozenDictionary<string, ISkin> SkinsByName => _skinsByName;
public override FrozenDictionary<string, FrozenDictionary<string, IAttachment>> SlotAttachments => _slotAttachments;
public override float DefaultMix { get => _animationStateData.DefaultMix; set => _animationStateData.DefaultMix = value; }
public override ImmutableArray<IAnimation> Animations => _animations;
public override FrozenDictionary<string, IAnimation> AnimationsByName => _animationsByName;
protected override void DisposeAtlas() => _atlas.Dispose();
public override ISkeleton CreateSkeleton() => new Skeleton34(new(_skeletonData), this);
public override IAnimationState CreateAnimationState() => new AnimationState34(new(_animationStateData), this);
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping34();
public override ISkin CreateSkin(string name) => new Skin34(name);
}
}

View File

@@ -0,0 +1,135 @@
using Spine.Interfaces;
using SpineRuntime34;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.V34
{
internal sealed class TrackEntry34(TrackEntry innerObject, AnimationState34 animationState, SpineObjectData34 data): ITrackEntry
{
private readonly TrackEntry _o = innerObject;
private readonly AnimationState34 _animationState = animationState;
private readonly SpineObjectData34 _data = data;
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
public TrackEntry InnerObject => _o;
#pragma warning disable CS0067
// 3.4 及以下没有这两个事件
public event IAnimationState.TrackEntryDelegate? Interrupt;
public event IAnimationState.TrackEntryDelegate? Dispose;
#pragma warning restore CS0067
public event IAnimationState.TrackEntryDelegate? Start
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Start -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? End
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.End -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Complete
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Complete -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public int TrackIndex { get => _o.TrackIndex; }
public IAnimation Animation { get => _data.AnimationsByName[_o.Animation.Name]; }
public ITrackEntry? Next { get { var t = _o.Next; return t is null ? null : _animationState.GetTrackEntry(t); } }
public bool Loop { get => _o.Loop; set => _o.Loop = value; }
public float TrackTime { get => _o.Time; set => _o.Time = value; }
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
public float Alpha { get => _o.Mix; set => _o.Mix = value; }
public float MixDuration { get => _o.MixDuration; set => _o.MixDuration = value; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,23 @@
using Spine.Interfaces;
using SpineRuntime35;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.V35
{
internal sealed class Animation35(Animation innerObject) : IAnimation
{
private readonly Animation _o = innerObject;
public Animation InnerObject => _o;
public string Name => _o.Name;
public float Duration => _o.Duration;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Interfaces;
using SpineRuntime35;
namespace Spine.Implementations.V35
{
internal sealed class AnimationState35(AnimationState innerObject, SpineObjectData35 data) : IAnimationState
{
private readonly AnimationState _o = innerObject;
private readonly SpineObjectData35 _data = data;
private readonly Dictionary<TrackEntry, TrackEntry35> _trackEntryPool = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
public AnimationState InnerObject => _o;
public event IAnimationState.TrackEntryDelegate? Start
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Start -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Interrupt
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Interrupt += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Interrupt -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? End
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.End -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Complete
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Complete -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Dispose
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Dispose += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Dispose -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
public void Update(float delta) => _o.Update(delta);
public void Apply(ISkeleton skeleton)
{
if (skeleton is Skeleton35 skel)
{
_o.Apply(skel.InnerObject);
return;
}
throw new ArgumentException($"Received {skeleton.GetType().Name}", nameof(skeleton));
}
/// <summary>
/// 获取 <see cref="ITrackEntry"/> 对象, 不存在则创建
/// </summary>
public ITrackEntry GetTrackEntry(TrackEntry trackEntry)
{
if (!_trackEntryPool.TryGetValue(trackEntry, out var tr))
_trackEntryPool[trackEntry] = tr = new(trackEntry, this, _data);
return tr;
}
public IEnumerable<ITrackEntry?> IterTracks() => _o.Tracks.Select(t => t is null ? null : GetTrackEntry(t));
public ITrackEntry? GetCurrent(int index) { var t = _o.GetCurrent(index); return t is null ? null : GetTrackEntry(t); }
public void ClearTrack(int index) => _o.ClearTrack(index);
public void ClearTracks() => _o.ClearTracks();
public ITrackEntry SetAnimation(int trackIndex, string animationName, bool loop)
=> GetTrackEntry(_o.SetAnimation(trackIndex, animationName, loop));
public ITrackEntry SetAnimation(int trackIndex, IAnimation animation, bool loop)
{
if (animation is Animation35 anime)
return GetTrackEntry(_o.SetAnimation(trackIndex, anime.InnerObject, loop));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry SetEmptyAnimation(int trackIndex, float mixDuration) => GetTrackEntry(_o.SetEmptyAnimation(trackIndex, mixDuration));
public void SetEmptyAnimations(float mixDuration) => _o.SetEmptyAnimations(mixDuration);
public ITrackEntry AddAnimation(int trackIndex, string animationName, bool loop, float delay)
=> GetTrackEntry(_o.AddAnimation(trackIndex, animationName, loop, delay));
public ITrackEntry AddAnimation(int trackIndex, IAnimation animation, bool loop, float delay)
{
if (animation is Animation35 anime)
return GetTrackEntry(_o.AddAnimation(trackIndex, anime.InnerObject, loop, delay));
throw new ArgumentException($"Received {animation.GetType().Name}", nameof(animation));
}
public ITrackEntry AddEmptyAnimation(int trackIndex, float mixDuration, float delay)
=> GetTrackEntry(_o.AddEmptyAnimation(trackIndex, mixDuration, delay));
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.V35.Attachments
{
internal abstract class Attachment35(Attachment innerObject) : IAttachment
{
private readonly Attachment _o = innerObject;
public virtual Attachment InnerObject => _o;
public string Name => _o.Name;
public abstract int ComputeWorldVertices(ISlot slot, ref float[] worldVertices);
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V35;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.V35.Attachments
{
internal sealed class BoundingBoxAttachment35(BoundingBoxAttachment innerObject) :
Attachment35(innerObject),
IBoundingBoxAttachment
{
private readonly BoundingBoxAttachment _o = innerObject;
public override BoundingBoxAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V35;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.V35.Attachments
{
internal sealed class ClippingAttachment35(ClippingAttachment innerObject) :
Attachment35(innerObject),
IClippingAttachment
{
private readonly ClippingAttachment _o = innerObject;
public override ClippingAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V35;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.V35.Attachments
{
internal sealed class MeshAttachment35(MeshAttachment innerObject) :
Attachment35(innerObject),
IMeshAttachment
{
private readonly MeshAttachment _o = innerObject;
public override MeshAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
public int[] Triangles => _o.Triangles;
public int HullLength => _o.HullLength;
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V35;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.V35.Attachments
{
internal sealed class PathAttachment35(PathAttachment innerObject) :
Attachment35(innerObject),
IPathAttachment
{
private readonly PathAttachment _o = innerObject;
public override PathAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
var length = _o.WorldVerticesLength;
if (worldVertices.Length < length) worldVertices = new float[length];
_o.ComputeWorldVertices(st.InnerObject, worldVertices);
return length;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V35;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.V35.Attachments
{
internal sealed class PointAttachment35(PointAttachment innerObject) :
Attachment35(innerObject),
IPointAttachment
{
private readonly PointAttachment _o = innerObject;
public override PointAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
if (worldVertices.Length < 2) worldVertices = new float[2];
_o.ComputeWorldPosition(st.InnerObject.Bone, out worldVertices[0], out worldVertices[1]);
return 2;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Implementations.V35;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime35;
namespace Spine.Implementations.V35.Attachments
{
internal sealed class RegionAttachment35(RegionAttachment innerObject) :
Attachment35(innerObject),
IRegionAttachment
{
private readonly RegionAttachment _o = innerObject;
public override RegionAttachment InnerObject => _o;
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot35 st)
{
if (worldVertices.Length < 8) worldVertices = new float[8];
_o.ComputeWorldVertices(st.InnerObject.Bone, worldVertices, 0);
return 8;
}
throw new ArgumentException($"Invalid slot type. Expected {nameof(Slot35)}, but received {slot.GetType().Name}", nameof(slot));
}
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public SFML.Graphics.Texture RendererObject => (SFML.Graphics.Texture)((AtlasRegion)_o.RendererObject).page.rendererObject;
public float[] UVs => _o.UVs;
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Interfaces;
using SpineRuntime35;
namespace Spine.Implementations.V35
{
internal sealed class Bone35(Bone innerObject, Bone35? parent = null) : IBone
{
private readonly Bone _o = innerObject;
private readonly Bone35? _parent = parent;
public Bone InnerObject => _o;
public string Name => _o.Data.Name;
public int Index => _o.Data.Index;
public IBone? Parent => _parent;
public bool Active => true; // NOTE: 3.7 及以下没有 Active 属性, 此处总是返回 true
public float Length => _o.Data.Length;
public float WorldX => _o.WorldX;
public float WorldY => _o.WorldY;
public float A => _o.A;
public float B => _o.B;
public float C => _o.C;
public float D => _o.D;
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using SpineRuntime35;
using Spine.Interfaces;
namespace Spine.Implementations.V35
{
internal sealed class Skeleton35 : ISkeleton
{
private readonly Skeleton _o;
private readonly SpineObjectData35 _data;
private readonly ImmutableArray<IBone> _bones;
private readonly FrozenDictionary<string, IBone> _bonesByName;
private readonly ImmutableArray<ISlot> _slots;
private readonly FrozenDictionary<string, ISlot> _slotsByName;
private Skin35? _skin;
public Skeleton35(Skeleton innerObject, SpineObjectData35 data)
{
_o = innerObject;
_data = data;
List<Bone35> bones = [];
Dictionary<string, IBone> bonesByName = [];
foreach (var b in _o.Bones)
{
var bone = new Bone35(b, b.Parent is null ? null : bones[b.Parent.Data.Index]);
bones.Add(bone);
bonesByName[bone.Name] = bone;
}
_bones = bones.Cast<IBone>().ToImmutableArray();
_bonesByName = bonesByName.ToFrozenDictionary();
List<Slot35> slots = [];
Dictionary<string, ISlot> slotsByName = [];
foreach (var s in _o.Slots)
{
var slot = new Slot35(s, _data, bones[s.Bone.Data.Index]);
slots.Add(slot);
slotsByName[slot.Name] = slot;
}
_slots = slots.Cast<ISlot>().ToImmutableArray();
_slotsByName = slotsByName.ToFrozenDictionary();
}
public Skeleton InnerObject => _o;
public string Name => _o.Data.Name;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public float X { get => _o.X; set => _o.X = value; }
public float Y { get => _o.Y; set => _o.Y = value; }
public float ScaleX { get => _o.ScaleX; set => _o.ScaleX = value; }
public float ScaleY { get => _o.ScaleY; set => _o.ScaleY = value; }
public ImmutableArray<IBone> Bones => _bones;
public FrozenDictionary<string, IBone> BonesByName => _bonesByName;
public ImmutableArray<ISlot> Slots => _slots;
public FrozenDictionary<string, ISlot> SlotsByName => _slotsByName;
public ISkin? Skin
{
get => _skin;
set
{
if (value is null)
{
_o.Skin = null;
_skin = null;
return;
}
if (value is Skin35 sk)
{
_o.Skin = sk.InnerObject;
_skin = sk;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public IEnumerable<ISlot> IterDrawOrder() => _o.DrawOrder.Select(s => _slots[s.Data.Index]);
public void UpdateCache() => _o.UpdateCache();
public void UpdateWorldTransform(ISkeleton.Physics physics) => _o.UpdateWorldTransform();
public void SetToSetupPose() => _o.SetToSetupPose();
public void SetBonesToSetupPose() => _o.SetBonesToSetupPose();
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,56 @@
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using Spine.Utils;
using SpineRuntime35;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.V35
{
internal sealed class SkeletonClipping35 : ISkeletonClipping
{
private readonly SkeletonClipping _o = new();
public bool IsClipping => _o.IsClipping();
public float[] ClippedVertices => _o.ClippedVertices.Items;
public int ClippedVerticesLength => _o.ClippedVertices.Count;
public int[] ClippedTriangles => _o.ClippedTriangles.Items;
public int ClippedTrianglesLength => _o.ClippedTriangles.Count;
public float[] ClippedUVs => _o.ClippedUVs.Items;
public void ClipTriangles(float[] vertices, int verticesLength, int[] triangles, int trianglesLength, float[] uvs)
=> _o.ClipTriangles(vertices, verticesLength, triangles, trianglesLength, uvs);
public void ClipStart(ISlot slot, IClippingAttachment clippingAttachment)
{
if (slot is Slot35 st && clippingAttachment is Attachments.ClippingAttachment35 att)
{
_o.ClipStart(st.InnerObject, att.InnerObject);
return;
}
throw new ArgumentException($"Received {slot.GetType().Name} {clippingAttachment.GetType().Name}");
}
public void ClipEnd(ISlot slot)
{
if (slot is Slot35 st)
{
_o.ClipEnd(st.InnerObject);
return;
}
throw new ArgumentException($"Received {slot.GetType().Name}", nameof(slot));
}
public void ClipEnd() => _o.ClipEnd();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Interfaces;
using SpineRuntime35;
namespace Spine.Implementations.V35
{
internal sealed class Skin35 : ISkin
{
private readonly Skin _o;
/// <summary>
/// 使用指定名字创建空皮肤
/// </summary>
public Skin35(string name) => _o = new(name);
/// <summary>
/// 包装已有皮肤对象
/// </summary>
public Skin35(Skin innerObject) => _o = innerObject;
public Skin InnerObject => _o;
public string Name => _o.Name;
public void AddSkin(ISkin skin)
{
if (skin is Skin35 sk)
{
// NOTE: 3.7 及以下不支持 AddSkin
foreach (var (k, v) in sk._o.Attachments)
_o.AddAttachment(k.slotIndex, k.name, v);
return;
}
throw new ArgumentException($"Received {skin.GetType().Name}", nameof(skin));
}
public void Clear() => _o.Attachments.Clear();
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using SpineRuntime35;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.V35
{
internal sealed class Slot35 : ISlot
{
private readonly Slot _o;
private readonly SpineObjectData35 _data;
private readonly Bone35 _bone;
private readonly SFML.Graphics.BlendMode _blendMode;
public Slot35(Slot innerObject, SpineObjectData35 data, Bone35 bone)
{
_o = innerObject;
_data = data;
_bone = bone;
_blendMode = _o.Data.BlendMode switch
{
BlendMode.Normal => SFMLBlendMode.NormalPma,
BlendMode.Additive => SFMLBlendMode.AdditivePma,
BlendMode.Multiply => SFMLBlendMode.MultiplyPma,
BlendMode.Screen => SFMLBlendMode.ScreenPma,
_ => throw new NotImplementedException($"{_o.Data.BlendMode}"),
};
}
public Slot InnerObject => _o;
public string Name => _o.Data.Name;
public int Index => _o.Data.Index;
public SFML.Graphics.BlendMode Blend => _blendMode;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
public float A { get => _o.A; set => _o.A = value; }
public IBone Bone => _bone;
public IAttachment? Attachment
{
get
{
if (_o.Attachment is Attachment att)
{
return _data.SlotAttachments[Name][att.Name];
}
return null;
}
set
{
if (value is null)
{
_o.Attachment = null;
return;
}
if (value is Attachments.Attachment35 att)
{
_o.Attachment = att.InnerObject;
return;
}
throw new ArgumentException($"Received {value.GetType().Name}", nameof(value));
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Collections.Frozen;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using SpineRuntime35;
using Spine.Implementations.V35.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.V35
{
[SpineImplementation(3, 5)]
internal sealed class SpineObjectData35 : SpineObjectData
{
private readonly Atlas _atlas;
private readonly SkeletonData _skeletonData;
private readonly AnimationStateData _animationStateData;
private readonly ImmutableArray<ISkin> _skins;
private readonly FrozenDictionary<string, ISkin> _skinsByName;
private readonly FrozenDictionary<string, FrozenDictionary<string, IAttachment>> _slotAttachments;
private readonly ImmutableArray<IAnimation> _animations;
private readonly FrozenDictionary<string, IAnimation> _animationsByName;
public SpineObjectData35(string skelPath, string atlasPath, TextureLoader textureLoader)
: base(skelPath, atlasPath, textureLoader)
{
// 加载 atlas
try
{
_atlas = new Atlas(atlasPath, textureLoader);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
try
{
if (Utf8Validator.IsUtf8(skelPath))
{
try
{
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
else
{
try
{
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
}
catch (Exception ex)
{
_atlas.Dispose();
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}
// 加载动画数据
_animationStateData = new AnimationStateData(_skeletonData);
// 整理皮肤和附件
Dictionary<string, Dictionary<string, IAttachment>> slotAttachments = [];
List<ISkin> skins = [];
Dictionary<string, ISkin> skinsByName = [];
foreach (var s in _skeletonData.Skins)
{
var skin = new Skin35(s);
skins.Add(skin);
skinsByName[s.Name] = skin;
foreach (var (k, att) in s.Attachments)
{
var slotName = _skeletonData.Slots.Items[k.slotIndex].Name;
if (!slotAttachments.TryGetValue(slotName, out var attachments))
slotAttachments[slotName] = attachments = [];
attachments[att.Name] = att switch
{
RegionAttachment regionAtt => new RegionAttachment35(regionAtt),
MeshAttachment meshAtt => new MeshAttachment35(meshAtt),
ClippingAttachment clipAtt => new ClippingAttachment35(clipAtt),
BoundingBoxAttachment bbAtt => new BoundingBoxAttachment35(bbAtt),
PathAttachment pathAtt => new PathAttachment35(pathAtt),
PointAttachment ptAtt => new PointAttachment35(ptAtt),
_ => throw new InvalidOperationException($"Unrecognized attachment type {att.GetType().FullName}")
};
}
}
_slotAttachments = slotAttachments.ToFrozenDictionary(it => it.Key, it => it.Value.ToFrozenDictionary());
_skins = skins.ToImmutableArray();
_skinsByName = skinsByName.ToFrozenDictionary();
// 整理所有动画数据
List<IAnimation> animations = [];
Dictionary<string, IAnimation> animationsByName = [];
foreach (var a in _skeletonData.Animations)
{
var anime = new Animation35(a);
animations.Add(anime);
animationsByName[anime.Name] = anime;
}
_animations = animations.ToImmutableArray();
_animationsByName = animationsByName.ToFrozenDictionary();
}
public override string SkeletonVersion => _skeletonData.Version;
public override ImmutableArray<ISkin> Skins => _skins;
public override FrozenDictionary<string, ISkin> SkinsByName => _skinsByName;
public override FrozenDictionary<string, FrozenDictionary<string, IAttachment>> SlotAttachments => _slotAttachments;
public override float DefaultMix { get => _animationStateData.DefaultMix; set => _animationStateData.DefaultMix = value; }
public override ImmutableArray<IAnimation> Animations => _animations;
public override FrozenDictionary<string, IAnimation> AnimationsByName => _animationsByName;
protected override void DisposeAtlas() => _atlas.Dispose();
public override ISkeleton CreateSkeleton() => new Skeleton35(new(_skeletonData), this);
public override IAnimationState CreateAnimationState() => new AnimationState35(new(_animationStateData), this);
public override ISkeletonClipping CreateSkeletonClipping() => new SkeletonClipping35();
public override ISkin CreateSkin(string name) => new Skin35(name);
}
}

View File

@@ -0,0 +1,185 @@
using Spine.Interfaces;
using SpineRuntime35;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.V35
{
internal sealed class TrackEntry35(TrackEntry innerObject, AnimationState35 animationState, SpineObjectData35 data): ITrackEntry
{
private readonly TrackEntry _o = innerObject;
private readonly AnimationState35 _animationState = animationState;
private readonly SpineObjectData35 _data = data;
private readonly Dictionary<IAnimationState.TrackEntryDelegate, AnimationState.TrackEntryDelegate> _eventMapping = [];
private readonly Dictionary<IAnimationState.TrackEntryDelegate, int> _eventCount = [];
public TrackEntry InnerObject => _o;
public event IAnimationState.TrackEntryDelegate? Start
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Start -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Interrupt
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Interrupt += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Interrupt -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? End
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.End -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Complete
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Complete -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public event IAnimationState.TrackEntryDelegate? Dispose
{
add
{
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Dispose += f;
_eventCount[value]++;
}
remove
{
if (value is null) return;
if (_eventMapping.TryGetValue(value, out var f))
{
_o.Dispose -= f;
_eventCount[value]--;
if (_eventCount[value] <= 0)
{
_eventMapping.Remove(value);
_eventCount.Remove(value);
}
}
}
}
public int TrackIndex { get => _o.TrackIndex; }
public IAnimation Animation { get => _data.AnimationsByName[_o.Animation.Name]; }
public ITrackEntry? Next { get { var t = _o.Next; return t is null ? null : _animationState.GetTrackEntry(t); } }
public bool Loop { get => _o.Loop; set => _o.Loop = value; }
public float TrackTime { get => _o.TrackTime; set => _o.TrackTime = value; }
public float TimeScale { get => _o.TimeScale; set => _o.TimeScale = value; }
public float Alpha { get => _o.Alpha; set => _o.Alpha = value; }
public float MixDuration { get => _o.MixDuration; set => _o.MixDuration = value; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -1,4 +1,4 @@
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime36;
using System;
using System.Collections.Generic;
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V36
namespace Spine.Implementations.V36
{
internal sealed class Animation36(Animation innerObject) : IAnimation
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36
namespace Spine.Implementations.V36
{
internal sealed class AnimationState36(AnimationState innerObject, SpineObjectData36 data) : IAnimationState
{
@@ -27,7 +27,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
@@ -56,7 +56,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Interrupt += f;
@@ -85,7 +85,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
@@ -114,7 +114,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
@@ -143,7 +143,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Dispose += f;

View File

@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
namespace Spine.Implementations.V36.Attachments
{
internal abstract class Attachment36(Attachment innerObject) : IAttachment
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V36;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
namespace Spine.Implementations.V36.Attachments
{
internal sealed class BoundingBoxAttachment36(BoundingBoxAttachment innerObject) :
Attachment36(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V36.Attachments
public override BoundingBoxAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot36 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V36;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
namespace Spine.Implementations.V36.Attachments
{
internal sealed class ClippingAttachment36(ClippingAttachment innerObject) :
Attachment36(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V36.Attachments
public override ClippingAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot36 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V36;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
namespace Spine.Implementations.V36.Attachments
{
internal sealed class MeshAttachment36(MeshAttachment innerObject) :
Attachment36(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V36.Attachments
public override MeshAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot36 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V36;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
namespace Spine.Implementations.V36.Attachments
{
internal sealed class PathAttachment36(PathAttachment innerObject) :
Attachment36(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V36.Attachments
public override PathAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot36 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V36;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
namespace Spine.Implementations.V36.Attachments
{
internal sealed class PointAttachment36(PointAttachment innerObject) :
Attachment36(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V36.Attachments
public override PointAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot36 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V36;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36.Attachments
namespace Spine.Implementations.V36.Attachments
{
internal sealed class RegionAttachment36(RegionAttachment innerObject) :
Attachment36(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V36.Attachments
public override RegionAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot36 st)
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36
namespace Spine.Implementations.V36
{
internal sealed class Bone36(Bone innerObject, Bone36? parent = null) : IBone
{

View File

@@ -5,10 +5,10 @@ using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using Spine.SpineWrappers;
using SpineRuntime36;
using Spine.Interfaces;
namespace Spine.Implementations.SpineWrappers.V36
namespace Spine.Implementations.V36
{
internal sealed class Skeleton36 : ISkeleton
{
@@ -52,6 +52,7 @@ namespace Spine.Implementations.SpineWrappers.V36
public Skeleton InnerObject => _o;
public string Name => _o.Data.Name;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
@@ -95,12 +96,6 @@ namespace Spine.Implementations.SpineWrappers.V36
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public void GetBounds(out float x, out float y, out float w, out float h)
{
float[] _ = [];
_o.GetBounds(out x, out y, out w, out h, ref _);
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -1,5 +1,5 @@
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using Spine.Utils;
using SpineRuntime36;
using System;
@@ -8,7 +8,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V36
namespace Spine.Implementations.V36
{
internal sealed class SkeletonClipping36 : ISkeletonClipping
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime36;
namespace Spine.Implementations.SpineWrappers.V36
namespace Spine.Implementations.V36
{
internal sealed class Skin36 : ISkin
{

View File

@@ -5,10 +5,11 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using SpineRuntime36;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.SpineWrappers.V36
namespace Spine.Implementations.V36
{
internal sealed class Slot36 : ISlot
{
@@ -46,7 +47,7 @@ namespace Spine.Implementations.SpineWrappers.V36
public float A { get => _o.A; set => _o.A = value; }
public IBone Bone => _bone;
public Spine.SpineWrappers.Attachments.IAttachment? Attachment
public IAttachment? Attachment
{
get
{
@@ -73,6 +74,8 @@ namespace Spine.Implementations.SpineWrappers.V36
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -6,12 +6,12 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using SpineRuntime36;
using Spine.Implementations.SpineWrappers.V36.Attachments;
using Spine.Implementations.V36.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.SpineWrappers.V36
namespace Spine.Implementations.V36
{
[SpineImplementation(3, 6)]
internal sealed class SpineObjectData36 : SpineObjectData
@@ -26,12 +26,19 @@ namespace Spine.Implementations.SpineWrappers.V36
private readonly ImmutableArray<IAnimation> _animations;
private readonly FrozenDictionary<string, IAnimation> _animationsByName;
public SpineObjectData36(string skelPath, string atlasPath, Spine.SpineWrappers.TextureLoader textureLoader)
public SpineObjectData36(string skelPath, string atlasPath, TextureLoader textureLoader)
: base(skelPath, atlasPath, textureLoader)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
_atlas = new Atlas(atlasPath, textureLoader);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
try
{
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V36
{
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
catch
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V36
{
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V36
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}
// 加载动画数据

View File

@@ -1,4 +1,4 @@
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime36;
using System;
using System.Collections.Generic;
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V36
namespace Spine.Implementations.V36
{
internal sealed class TrackEntry36(TrackEntry innerObject, AnimationState36 animationState, SpineObjectData36 data): ITrackEntry
{
@@ -26,7 +26,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
@@ -55,7 +55,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Interrupt += f;
@@ -84,7 +84,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
@@ -113,7 +113,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
@@ -142,7 +142,7 @@ namespace Spine.Implementations.SpineWrappers.V36
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Dispose += f;

View File

@@ -1,4 +1,4 @@
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime37;
using System;
using System.Collections.Generic;
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V37
namespace Spine.Implementations.V37
{
internal sealed class Animation37(Animation innerObject) : IAnimation
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37
namespace Spine.Implementations.V37
{
internal sealed class AnimationState37(AnimationState innerObject, SpineObjectData37 data) : IAnimationState
{
@@ -27,7 +27,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
@@ -56,7 +56,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Interrupt += f;
@@ -85,7 +85,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
@@ -114,7 +114,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
@@ -143,7 +143,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Dispose += f;

View File

@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
namespace Spine.Implementations.V37.Attachments
{
internal abstract class Attachment37(Attachment innerObject) : IAttachment
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V37;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
namespace Spine.Implementations.V37.Attachments
{
internal sealed class BoundingBoxAttachment37(BoundingBoxAttachment innerObject) :
Attachment37(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V37.Attachments
public override BoundingBoxAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot37 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V37;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
namespace Spine.Implementations.V37.Attachments
{
internal sealed class ClippingAttachment37(ClippingAttachment innerObject) :
Attachment37(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V37.Attachments
public override ClippingAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot37 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V37;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
namespace Spine.Implementations.V37.Attachments
{
internal sealed class MeshAttachment37(MeshAttachment innerObject) :
Attachment37(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V37.Attachments
public override MeshAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot37 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V37;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
namespace Spine.Implementations.V37.Attachments
{
internal sealed class PathAttachment37(PathAttachment innerObject) :
Attachment37(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V37.Attachments
public override PathAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot37 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V37;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
namespace Spine.Implementations.V37.Attachments
{
internal sealed class PointAttachment37(PointAttachment innerObject) :
Attachment37(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V37.Attachments
public override PointAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot37 st)
{

View File

@@ -3,10 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers.Attachments;
using Spine.Implementations.V37;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37.Attachments
namespace Spine.Implementations.V37.Attachments
{
internal sealed class RegionAttachment37(RegionAttachment innerObject) :
Attachment37(innerObject),
@@ -16,7 +18,7 @@ namespace Spine.Implementations.SpineWrappers.V37.Attachments
public override RegionAttachment InnerObject => _o;
public override int ComputeWorldVertices(Spine.SpineWrappers.ISlot slot, ref float[] worldVertices)
public override int ComputeWorldVertices(ISlot slot, ref float[] worldVertices)
{
if (slot is Slot37 st)
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37
namespace Spine.Implementations.V37
{
internal sealed class Bone37(Bone innerObject, Bone37? parent = null) : IBone
{

View File

@@ -5,10 +5,10 @@ using System.Text;
using System.Threading.Tasks;
using System.Collections.Frozen;
using System.Collections.Immutable;
using Spine.SpineWrappers;
using SpineRuntime37;
using Spine.Interfaces;
namespace Spine.Implementations.SpineWrappers.V37
namespace Spine.Implementations.V37
{
internal sealed class Skeleton37 : ISkeleton
{
@@ -52,6 +52,7 @@ namespace Spine.Implementations.SpineWrappers.V37
public Skeleton InnerObject => _o;
public string Name => _o.Data.Name;
public float R { get => _o.R; set => _o.R = value; }
public float G { get => _o.G; set => _o.G = value; }
public float B { get => _o.B; set => _o.B = value; }
@@ -95,12 +96,6 @@ namespace Spine.Implementations.SpineWrappers.V37
public void SetSlotsToSetupPose() => _o.SetSlotsToSetupPose();
public void Update(float delta) => _o.Update(delta);
public void GetBounds(out float x, out float y, out float w, out float h)
{
float[] _ = [];
_o.GetBounds(out x, out y, out w, out h, ref _);
}
public override string ToString() => _o.ToString();
}
}

View File

@@ -1,5 +1,5 @@
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using Spine.Utils;
using SpineRuntime37;
using System;
@@ -8,7 +8,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V37
namespace Spine.Implementations.V37
{
internal sealed class SkeletonClipping37 : ISkeletonClipping
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime37;
namespace Spine.Implementations.SpineWrappers.V37
namespace Spine.Implementations.V37
{
internal sealed class Skin37 : ISkin
{

View File

@@ -5,10 +5,11 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using SpineRuntime37;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.SpineWrappers.V37
namespace Spine.Implementations.V37
{
internal sealed class Slot37 : ISlot
{
@@ -46,7 +47,7 @@ namespace Spine.Implementations.SpineWrappers.V37
public float A { get => _o.A; set => _o.A = value; }
public IBone Bone => _bone;
public Spine.SpineWrappers.Attachments.IAttachment? Attachment
public IAttachment? Attachment
{
get
{
@@ -73,6 +74,8 @@ namespace Spine.Implementations.SpineWrappers.V37
}
}
public bool Disabled { get; set; }
public override string ToString() => _o.ToString();
}
}

View File

@@ -6,12 +6,12 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.Utils;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using SpineRuntime37;
using Spine.Implementations.SpineWrappers.V37.Attachments;
using Spine.Implementations.V37.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
namespace Spine.Implementations.SpineWrappers.V37
namespace Spine.Implementations.V37
{
[SpineImplementation(3, 7)]
internal sealed class SpineObjectData37 : SpineObjectData
@@ -26,12 +26,19 @@ namespace Spine.Implementations.SpineWrappers.V37
private readonly ImmutableArray<IAnimation> _animations;
private readonly FrozenDictionary<string, IAnimation> _animationsByName;
public SpineObjectData37(string skelPath, string atlasPath, Spine.SpineWrappers.TextureLoader textureLoader)
public SpineObjectData37(string skelPath, string atlasPath, TextureLoader textureLoader)
: base(skelPath, atlasPath, textureLoader)
{
// 加载 atlas
try { _atlas = new Atlas(atlasPath, textureLoader); }
catch (Exception ex) { throw new InvalidDataException($"Failed to load atlas '{atlasPath}'", ex); }
try
{
_atlas = new Atlas(atlasPath, textureLoader);
}
catch (Exception ex)
{
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load atlas '{atlasPath}'");
}
try
{
@@ -41,8 +48,9 @@ namespace Spine.Implementations.SpineWrappers.V37
{
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
catch
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -52,8 +60,9 @@ namespace Spine.Implementations.SpineWrappers.V37
{
_skeletonData = new SkeletonBinary(_atlas).ReadSkeletonData(skelPath);
}
catch
catch (Exception ex)
{
_logger.Trace(ex.ToString());
_skeletonData = new SkeletonJson(_atlas).ReadSkeletonData(skelPath);
}
}
@@ -61,7 +70,8 @@ namespace Spine.Implementations.SpineWrappers.V37
catch (Exception ex)
{
_atlas.Dispose();
throw new InvalidDataException($"Failed to load skeleton file {skelPath}", ex);
_logger.Trace(ex.ToString());
throw new InvalidDataException($"Failed to load skeleton file {skelPath}");
}
// 加载动画数据

View File

@@ -1,4 +1,4 @@
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime37;
using System;
using System.Collections.Generic;
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V37
namespace Spine.Implementations.V37
{
internal sealed class TrackEntry37(TrackEntry innerObject, AnimationState37 animationState, SpineObjectData37 data): ITrackEntry
{
@@ -26,7 +26,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
@@ -55,7 +55,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Interrupt += f;
@@ -84,7 +84,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
@@ -113,7 +113,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
@@ -142,7 +142,7 @@ namespace Spine.Implementations.SpineWrappers.V37
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(_animationState.GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(_animationState.GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Dispose += f;

View File

@@ -1,4 +1,4 @@
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime38;
using System;
using System.Collections.Generic;
@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Spine.Implementations.SpineWrappers.V38
namespace Spine.Implementations.V38
{
internal sealed class Animation38(Animation innerObject) : IAnimation
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.Interfaces;
using SpineRuntime38;
namespace Spine.Implementations.SpineWrappers.V38
namespace Spine.Implementations.V38
{
internal sealed class AnimationState38(AnimationState innerObject, SpineObjectData38 data) : IAnimationState
{
@@ -27,7 +27,7 @@ namespace Spine.Implementations.SpineWrappers.V38
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Start += f;
@@ -56,7 +56,7 @@ namespace Spine.Implementations.SpineWrappers.V38
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Interrupt += f;
@@ -85,7 +85,7 @@ namespace Spine.Implementations.SpineWrappers.V38
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.End += f;
@@ -114,7 +114,7 @@ namespace Spine.Implementations.SpineWrappers.V38
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Complete += f;
@@ -143,7 +143,7 @@ namespace Spine.Implementations.SpineWrappers.V38
if (value is null) return;
if (!_eventMapping.TryGetValue(value, out var f))
{
_eventMapping[value] = f = (TrackEntry t) => value(GetTrackEntry(t));
_eventMapping[value] = f = (t) => value(GetTrackEntry(t));
_eventCount[value] = 0;
}
_o.Dispose += f;

View File

@@ -3,12 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Spine.SpineWrappers;
using Spine.SpineWrappers.Attachments;
using Spine.Interfaces;
using Spine.Interfaces.Attachments;
using SpineRuntime38;
using SpineRuntime38.Attachments;
namespace Spine.Implementations.SpineWrappers.V38.Attachments
namespace Spine.Implementations.V38.Attachments
{
internal abstract class Attachment38(Attachment innerObject) : IAttachment
{

Some files were not shown because too many files have changed in this diff Show More