Compare commits

...

21 Commits

Author SHA1 Message Date
ww-rm
479a5e4da9 更新至v0.11.3 2025-03-26 20:33:48 +08:00
ww-rm
4829454877 update changelog 2025-03-26 20:33:31 +08:00
ww-rm
28664f6387 增加隐藏控制 2025-03-26 20:30:55 +08:00
ww-rm
1a08a23a9c 批量添加完成自动选中最后一项 2025-03-26 20:24:27 +08:00
ww-rm
16f344ff1b 增加纹理调试 2025-03-26 19:55:43 +08:00
ww-rm
693ce0e2e8 调整属性分组和注释 2025-03-26 19:52:29 +08:00
ww-rm
e6f533ea65 优化属性分组显示顺序 2025-03-26 18:54:35 +08:00
ww-rm
fcc21d63b0 优化排列顺序 2025-03-26 18:39:30 +08:00
ww-rm
afc0ffcb67 Merge branch 'main' of github.com:ww-rm/SpineViewer 2025-03-26 18:25:56 +08:00
ww-rm
9ffb9840e1 去除限制 2025-03-26 18:25:48 +08:00
ww-rm
4766ccf1b6 互换模型和画面参数面板位置 2025-03-26 18:23:59 +08:00
ww-rm
16b75c80a3 Update README.en.md 2025-03-26 17:00:08 +08:00
ww-rm
880f063046 优化分割条可感知宽度 2025-03-26 16:20:12 +08:00
ww-rm
723c11b886 更新至v0.11.2 2025-03-26 15:54:19 +08:00
ww-rm
5e074b1cf7 update changelog 2025-03-26 15:54:06 +08:00
ww-rm
71d2fee36e 修复纹理加载异常 2025-03-26 15:53:27 +08:00
ww-rm
7dc701464f 优化缩放实现 2025-03-26 15:43:54 +08:00
ww-rm
fd876ef90f 补充皮肤切换后的刷新 2025-03-26 15:25:55 +08:00
ww-rm
0597852178 增加皮肤属性 2025-03-26 15:20:59 +08:00
ww-rm
81b1333091 small change 2025-03-26 15:20:49 +08:00
ww-rm
7baebd79a6 update changelog 2025-03-26 14:32:07 +08:00
23 changed files with 457 additions and 429 deletions

View File

@@ -1,5 +1,24 @@
# CHANGELOG
## v0.11.3
- 增加模型隐藏设置属性
- 加宽面板分割条 (4 -> 8 像素)
- 优化属性面板分组显示
- 增加调试纹理
## v0.11.2
- 增加皮肤切换
- 优化模型缩放实现
- 修复部分情况纹理加载异常
## v0.11.1
- 增加 GIF 导出格式
- 增加逐个导出时可选自动时长
- 优化使用体验
## v0.11.0
- 完成导出系统, 支持完整的单帧和帧序列导出功能

View File

@@ -1,7 +1,3 @@
Below is the translated English version of your README:
---
# [SpineViewer](https://github.com/ww-rm/SpineViewer)
[![Build and Release](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml/badge.svg)](https://github.com/ww-rm/SpineViewer/actions/workflows/dotnet-desktop.yml)
@@ -98,4 +94,4 @@ For more detailed instructions and usage, please refer to the [Wiki](https://git
*If you like this project, please give it a :star: and share it with others! :)*
[![Stargazers over time](https://starchart.cc/ww-rm/SpineViewer.svg?variant=adaptive)](https://starchart.cc/ww-rm/SpineViewer)
[![Stargazers over time](https://starchart.cc/ww-rm/SpineViewer.svg?variant=adaptive)](https://starchart.cc/ww-rm/SpineViewer)

View File

@@ -12,7 +12,6 @@ using SpineViewer.Spine;
using System.Reflection;
using System.Diagnostics;
using System.Collections.Specialized;
namespace SpineViewer.Controls
{
public partial class SpineListView : UserControl
@@ -166,14 +165,20 @@ namespace SpineViewer.Controls
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
}
// 选中最后一项
listView.Invoke(() =>
{
if (listView.Items.Count > 0)
{
listView.SelectedIndices.Clear();
listView.SelectedIndices.Add(listView.Items.Count - 1);
}
});
if (error > 0)
{
Program.Logger.Warn("Batch load {} successfully, {} failed", success, error);
}
else
{
Program.Logger.Info("{} skel loaded successfully", success);
}
Program.LogCurrentMemoryUsage();
}

View File

@@ -54,32 +54,32 @@ namespace SpineViewer.Controls
private class PreviewerProperty(SpinePreviewer previewer)
{
[TypeConverter(typeof(SizeConverter))]
[Category("导出"), DisplayName("分辨率")]
[Category("[0] "), DisplayName("")]
public Size Resolution { get => previewer.Resolution; set => previewer.Resolution = value; }
[TypeConverter(typeof(PointFConverter))]
[Category("导出"), DisplayName("画面中心点")]
[Category("[0] "), DisplayName("")]
public PointF Center { get => previewer.Center; set => previewer.Center = value; }
[Category("导出"), DisplayName("缩放")]
[Category("[0] "), DisplayName("")]
public float Zoom { get => previewer.Zoom; set => previewer.Zoom = value; }
[Category("导出"), DisplayName("旋转")]
[Category("[0] "), DisplayName("")]
public float Rotation { get => previewer.Rotation; set => previewer.Rotation = value; }
[Category("导出"), DisplayName("水平翻转")]
[Category("[0] "), DisplayName("")]
public bool FlipX { get => previewer.FlipX; set => previewer.FlipX = value; }
[Category("导出"), DisplayName("垂直翻转")]
[Category("[0] "), DisplayName("")]
public bool FlipY { get => previewer.FlipY; set => previewer.FlipY = value; }
[Category("导出"), DisplayName("仅渲染选中")]
[Category("[0] "), DisplayName("")]
public bool RenderSelectedOnly { get => previewer.RenderSelectedOnly; set => previewer.RenderSelectedOnly = value; }
[Category("预览"), DisplayName("显示坐标轴")]
[Category("[1] "), DisplayName("")]
public bool ShowAxis { get => previewer.ShowAxis; set => previewer.ShowAxis = value; }
[Category("预览"), DisplayName("最大帧率")]
[Category("[1] "), DisplayName("")]
public uint MaxFps { get => previewer.MaxFps; set => previewer.MaxFps = value; }
}
@@ -371,6 +371,16 @@ namespace SpineViewer.Controls
delta = Clock.ElapsedTime.AsSeconds();
Clock.Restart();
// 停止更新的时候只是时间不前进, 但是坐标变换还是要更新, 否则无法移动对象
if (!IsUpdating) delta = 0;
// 加上要快进的量
lock (_forwardDeltaLock)
{
delta += forwardDelta;
forwardDelta = 0;
}
RenderWindow.Clear(BackgroundColor);
if (ShowAxis)
@@ -389,24 +399,14 @@ namespace SpineViewer.Controls
{
lock (SpineListView.Spines)
{
var spines = SpineListView.Spines;
for (int i = spines.Count - 1; i >= 0; i--)
var spines = SpineListView.Spines.Where(sp => !sp.IsHidden).ToArray();
for (int i = spines.Length - 1; i >= 0; i--)
{
if (cancelToken is not null && cancelToken.IsCancellationRequested)
break; // 提前中止
var spine = spines[i];
// 停止更新的时候只是时间不前进, 但是坐标变换还是要更新, 否则无法移动对象
if (!IsUpdating) delta = 0;
// 加上要快进的量
lock (_forwardDeltaLock)
{
delta += forwardDelta;
forwardDelta = 0;
}
spine.Update(delta);
if (RenderSelectedOnly && !spine.IsSelected)
@@ -487,9 +487,11 @@ namespace SpineViewer.Controls
// 仅渲染选中模式禁止在画面里选择对象
if (RenderSelectedOnly)
{
// 只在被选中的对象里判断是否有效命中
bool hit = false;
foreach (int i in SpineListView.SelectedIndices)
{
if (spines[i].IsHidden) continue;
if (!spines[i].Bounds.Contains(src)) continue;
hit = true;
break;
@@ -500,12 +502,13 @@ namespace SpineViewer.Controls
}
else
{
// 没有按下 Ctrl 键就只选中点击的那个, 所以先清空选中列表
if ((ModifierKeys & Keys.Control) == 0)
{
// 没按 Ctrl 的情况下, 如果命中了已选中对象, 则就算普通命中
bool hit = false;
for (int i = 0; i < spines.Count; i++)
{
if (spines[i].IsHidden) continue;
if (!spines[i].Bounds.Contains(src)) continue;
hit = true;
@@ -524,10 +527,11 @@ namespace SpineViewer.Controls
}
else
{
// 按下 Ctrl 的情况就执行多选, 并且点空白处也不会清空选中
for (int i = 0; i < spines.Count; i++)
{
if (!spines[i].Bounds.Contains(src))
continue;
if (spines[i].IsHidden) continue;
if (!spines[i].Bounds.Contains(src)) continue;
SpineListView.SelectedIndices.Add(i);
break;
@@ -558,8 +562,12 @@ namespace SpineViewer.Controls
{
lock (SpineListView.Spines)
{
var spines = SpineListView.Spines;
foreach (int i in SpineListView.SelectedIndices)
SpineListView.Spines[i].Position += delta;
{
if (spines[i].IsHidden) continue;
spines[i].Position += delta;
}
}
}
draggingSrc = dst;

View File

@@ -39,7 +39,7 @@ namespace SpineViewer.Dialogs
PropertyInfo? labelProp = category.GetType().GetProperty("Label", BindingFlags.Instance | BindingFlags.Public);
if (labelProp == null) continue;
string? label = labelProp.GetValue(category) as string;
if (label != "导出") continue;
if (label != "[0] 导出") continue;
// 获取该分组下的所有属性项
PropertyInfo? gridItemsProp = category.GetType().GetProperty("GridItems", BindingFlags.Instance | BindingFlags.Public);

View File

@@ -60,32 +60,32 @@ namespace SpineViewer.Exporter
/// 输出文件夹
/// </summary>
[Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
[Category("导出"), DisplayName("输出文件夹"), Description("逐个导出时可以留空,将逐个导出到模型自身所在目录")]
[Category("[0] "), DisplayName(""), Description("")]
public string? OutputDir { get; set; } = null;
/// <summary>
/// 导出单个
/// </summary>
[Category("导出"), DisplayName("导出单个"), Description("是否将模型在同一个画面上导出单个文件,否则逐个导出模型")]
[Category("[0] "), DisplayName(""), Description("")]
public bool ExportSingle { get; set; } = false;
/// <summary>
/// 画面分辨率
/// </summary>
[TypeConverter(typeof(SizeConverter))]
[Category("导出"), DisplayName("分辨率"), Description("画面的宽高像素大小,请在预览画面参数面板进行调整")]
[Category("[0] "), DisplayName(""), Description("")]
public Size Resolution { get; }
/// <summary>
/// 渲染视窗
/// </summary>
[Category("导出"), DisplayName("视图"), Description("画面的视图参数,请在预览画面参数面板进行调整")]
[Category("[0] "), DisplayName(""), Description("")]
public SFML.Graphics.View View { get; }
/// <summary>
/// 是否仅渲染选中
/// </summary>
[Category("导出"), DisplayName("仅渲染选中"), Description("是否仅导出选中的模型,请在预览画面参数面板进行调整")]
[Category("[0] "), DisplayName(""), Description("")]
public bool RenderSelectedOnly { get; }
/// <summary>

View File

@@ -20,7 +20,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
/// 单帧画面格式
/// </summary>
[TypeConverter(typeof(ImageFormatConverter))]
[Category("单帧画面"), DisplayName("图像格式")]
[Category("[1] "), DisplayName("")]
public ImageFormat ImageFormat
{
get => imageFormat;
@@ -35,14 +35,14 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
/// <summary>
/// 文件名后缀
/// </summary>
[Category("单帧画面"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")]
[Category("[1] "), DisplayName(""), Description("")]
public string FileSuffix { get => imageFormat.GetSuffix(); }
/// <summary>
/// DPI
/// </summary>
[TypeConverter(typeof(SizeFConverter))]
[Category("单帧画面"), DisplayName("DPI"), Description("导出图像的每英寸像素数,用于调整图像的物理尺寸")]
[Category("[1] "), DisplayName("DPI"), Description("")]
public SizeF DPI
{
get => dpi;

View File

@@ -20,7 +20,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
/// 文件名后缀
/// </summary>
[TypeConverter(typeof(SFMLImageFileSuffixConverter))]
[Category("帧序列参数"), DisplayName("文件名后缀"), Description("帧文件的后缀,同时决定帧图像格式")]
[Category("[2] "), DisplayName(""), Description("")]
public string FileSuffix { get; set; } = ".png";
}
}

View File

@@ -22,14 +22,14 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
/// <summary>
/// 调色板最大颜色数量
/// </summary>
[Category("GIF 参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")]
[Category("[2] GIF "), DisplayName(""), Description("使, ")]
public uint MaxColors { get => maxColors; set => maxColors = Math.Clamp(value, 2, 256); }
private uint maxColors = 256;
/// <summary>
/// 透明度阈值
/// </summary>
[Category("GIF 参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
[Category("[2] GIF "), DisplayName(""), Description("")]
public byte AlphaThreshold { get => alphaThreshold; set => alphaThreshold = value; }
private byte alphaThreshold = 128;

View File

@@ -17,7 +17,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
/// <summary>
/// 导出时长
/// </summary>
[Category("视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长, 如果小于 0, 则在逐个导出时每个模型使用各自的当前动画时长")]
[Category("[1] "), DisplayName(""), Description(", 0, 使")]
public float Duration
{
get => duration;
@@ -28,7 +28,7 @@ namespace SpineViewer.Exporter.Implementations.ExportArgs
/// <summary>
/// 帧率
/// </summary>
[Category("视频参数"), DisplayName("帧率"), Description("每秒画面数")]
[Category("[1] "), DisplayName(""), Description("")]
public float FPS { get; set; } = 60;
public override string? Validate()

View File

@@ -25,7 +25,7 @@ namespace SpineViewer.Exporter.Implementations.Exporter
// 独立导出时如果 args.Duration 小于 0 则使用自己的动画时长
var duration = args.Duration;
if (duration < 0) duration = spine.GetAnimationDuration(spine.CurrentAnimation);
if (duration < 0) duration = spine.CurrentAnimationDuration;
float delta = 1f / args.FPS;
int total = Math.Max(1, (int)(duration * args.FPS)); // 至少导出 1 帧

View File

@@ -114,88 +114,88 @@
//
toolStripMenuItem_Open.Name = "toolStripMenuItem_Open";
toolStripMenuItem_Open.ShortcutKeys = Keys.Control | Keys.O;
toolStripMenuItem_Open.Size = new Size(270, 34);
toolStripMenuItem_Open.Size = new Size(254, 34);
toolStripMenuItem_Open.Text = "打开(&O)...";
toolStripMenuItem_Open.Click += toolStripMenuItem_Open_Click;
//
// toolStripMenuItem_BatchOpen
//
toolStripMenuItem_BatchOpen.Name = "toolStripMenuItem_BatchOpen";
toolStripMenuItem_BatchOpen.Size = new Size(270, 34);
toolStripMenuItem_BatchOpen.Size = new Size(254, 34);
toolStripMenuItem_BatchOpen.Text = "批量打开(&B)...";
toolStripMenuItem_BatchOpen.Click += toolStripMenuItem_BatchOpen_Click;
//
// toolStripSeparator1
//
toolStripSeparator1.Name = "toolStripSeparator1";
toolStripSeparator1.Size = new Size(267, 6);
toolStripSeparator1.Size = new Size(251, 6);
//
// toolStripMenuItem_Export
//
toolStripMenuItem_Export.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ExportFrame, toolStripMenuItem_ExportFrameSequence, toolStripMenuItem_ExportGif, toolStripMenuItem_ExportMkv, toolStripMenuItem_ExportMp4, toolStripMenuItem_ExportMov, toolStripMenuItem_ExportWebm });
toolStripMenuItem_Export.Name = "toolStripMenuItem_Export";
toolStripMenuItem_Export.Size = new Size(270, 34);
toolStripMenuItem_Export.Size = new Size(254, 34);
toolStripMenuItem_Export.Text = "导出(&E)";
//
// toolStripMenuItem_ExportFrame
//
toolStripMenuItem_ExportFrame.Name = "toolStripMenuItem_ExportFrame";
toolStripMenuItem_ExportFrame.Size = new Size(270, 34);
toolStripMenuItem_ExportFrame.Size = new Size(194, 34);
toolStripMenuItem_ExportFrame.Text = "单帧画面...";
toolStripMenuItem_ExportFrame.Click += toolStripMenuItem_Export_Click;
//
// toolStripMenuItem_ExportFrameSequence
//
toolStripMenuItem_ExportFrameSequence.Name = "toolStripMenuItem_ExportFrameSequence";
toolStripMenuItem_ExportFrameSequence.Size = new Size(270, 34);
toolStripMenuItem_ExportFrameSequence.Size = new Size(194, 34);
toolStripMenuItem_ExportFrameSequence.Text = "帧序列...";
toolStripMenuItem_ExportFrameSequence.Click += toolStripMenuItem_Export_Click;
//
// toolStripMenuItem_ExportGif
//
toolStripMenuItem_ExportGif.Name = "toolStripMenuItem_ExportGif";
toolStripMenuItem_ExportGif.Size = new Size(270, 34);
toolStripMenuItem_ExportGif.Size = new Size(194, 34);
toolStripMenuItem_ExportGif.Text = "GIF...";
toolStripMenuItem_ExportGif.Click += toolStripMenuItem_Export_Click;
//
// toolStripMenuItem_ExportMkv
//
toolStripMenuItem_ExportMkv.Name = "toolStripMenuItem_ExportMkv";
toolStripMenuItem_ExportMkv.Size = new Size(270, 34);
toolStripMenuItem_ExportMkv.Size = new Size(194, 34);
toolStripMenuItem_ExportMkv.Text = "MKV";
toolStripMenuItem_ExportMkv.Click += toolStripMenuItem_Export_Click;
//
// toolStripMenuItem_ExportMp4
//
toolStripMenuItem_ExportMp4.Name = "toolStripMenuItem_ExportMp4";
toolStripMenuItem_ExportMp4.Size = new Size(270, 34);
toolStripMenuItem_ExportMp4.Size = new Size(194, 34);
toolStripMenuItem_ExportMp4.Text = "MP4...";
toolStripMenuItem_ExportMp4.Click += toolStripMenuItem_Export_Click;
//
// toolStripMenuItem_ExportMov
//
toolStripMenuItem_ExportMov.Name = "toolStripMenuItem_ExportMov";
toolStripMenuItem_ExportMov.Size = new Size(270, 34);
toolStripMenuItem_ExportMov.Size = new Size(194, 34);
toolStripMenuItem_ExportMov.Text = "MOV...";
toolStripMenuItem_ExportMov.Click += toolStripMenuItem_Export_Click;
//
// toolStripMenuItem_ExportWebm
//
toolStripMenuItem_ExportWebm.Name = "toolStripMenuItem_ExportWebm";
toolStripMenuItem_ExportWebm.Size = new Size(270, 34);
toolStripMenuItem_ExportWebm.Size = new Size(194, 34);
toolStripMenuItem_ExportWebm.Text = "WebM...";
toolStripMenuItem_ExportWebm.Click += toolStripMenuItem_Export_Click;
//
// toolStripSeparator2
//
toolStripSeparator2.Name = "toolStripSeparator2";
toolStripSeparator2.Size = new Size(267, 6);
toolStripSeparator2.Size = new Size(251, 6);
//
// toolStripMenuItem_Exit
//
toolStripMenuItem_Exit.Name = "toolStripMenuItem_Exit";
toolStripMenuItem_Exit.ShortcutKeys = Keys.Alt | Keys.F4;
toolStripMenuItem_Exit.Size = new Size(270, 34);
toolStripMenuItem_Exit.Size = new Size(254, 34);
toolStripMenuItem_Exit.Text = "退出(&X)";
toolStripMenuItem_Exit.Click += toolStripMenuItem_Exit_Click;
//
@@ -263,7 +263,7 @@
rtbLog.Margin = new Padding(3, 2, 3, 2);
rtbLog.Name = "rtbLog";
rtbLog.ReadOnly = true;
rtbLog.Size = new Size(1728, 114);
rtbLog.Size = new Size(1728, 110);
rtbLog.TabIndex = 0;
rtbLog.Text = "";
rtbLog.WordWrap = false;
@@ -287,6 +287,7 @@
splitContainer_MainForm.Panel2.Cursor = Cursors.Default;
splitContainer_MainForm.Size = new Size(1728, 997);
splitContainer_MainForm.SplitterDistance = 879;
splitContainer_MainForm.SplitterWidth = 8;
splitContainer_MainForm.TabIndex = 3;
splitContainer_MainForm.TabStop = false;
splitContainer_MainForm.SplitterMoved += splitContainer_SplitterMoved;
@@ -310,6 +311,7 @@
splitContainer_Functional.Panel2.Cursor = Cursors.Default;
splitContainer_Functional.Size = new Size(1728, 879);
splitContainer_Functional.SplitterDistance = 747;
splitContainer_Functional.SplitterWidth = 8;
splitContainer_Functional.TabIndex = 2;
splitContainer_Functional.TabStop = false;
splitContainer_Functional.SplitterMoved += splitContainer_SplitterMoved;
@@ -333,6 +335,7 @@
splitContainer_Information.Panel2.Cursor = Cursors.Default;
splitContainer_Information.Size = new Size(747, 879);
splitContainer_Information.SplitterDistance = 399;
splitContainer_Information.SplitterWidth = 8;
splitContainer_Information.TabIndex = 1;
splitContainer_Information.TabStop = false;
splitContainer_Information.SplitterMoved += splitContainer_SplitterMoved;
@@ -364,7 +367,7 @@
propertyGrid_Spine.HelpVisible = false;
propertyGrid_Spine.Location = new Point(3, 26);
propertyGrid_Spine.Name = "propertyGrid_Spine";
propertyGrid_Spine.Size = new Size(338, 485);
propertyGrid_Spine.Size = new Size(334, 509);
propertyGrid_Spine.TabIndex = 0;
propertyGrid_Spine.ToolbarVisible = false;
propertyGrid_Spine.PropertyValueChanged += propertyGrid_PropertyValueChanged;
@@ -379,15 +382,16 @@
//
// splitContainer_Config.Panel1
//
splitContainer_Config.Panel1.Controls.Add(groupBox_SkelConfig);
splitContainer_Config.Panel1.Controls.Add(groupBox_PreviewConfig);
splitContainer_Config.Panel1.Cursor = Cursors.Default;
//
// splitContainer_Config.Panel2
//
splitContainer_Config.Panel2.Controls.Add(groupBox_PreviewConfig);
splitContainer_Config.Panel2.Controls.Add(groupBox_SkelConfig);
splitContainer_Config.Panel2.Cursor = Cursors.Default;
splitContainer_Config.Size = new Size(344, 879);
splitContainer_Config.SplitterDistance = 514;
splitContainer_Config.Size = new Size(340, 879);
splitContainer_Config.SplitterDistance = 333;
splitContainer_Config.SplitterWidth = 8;
splitContainer_Config.TabIndex = 0;
splitContainer_Config.TabStop = false;
splitContainer_Config.SplitterMoved += splitContainer_SplitterMoved;
@@ -399,7 +403,7 @@
groupBox_SkelConfig.Dock = DockStyle.Fill;
groupBox_SkelConfig.Location = new Point(0, 0);
groupBox_SkelConfig.Name = "groupBox_SkelConfig";
groupBox_SkelConfig.Size = new Size(344, 514);
groupBox_SkelConfig.Size = new Size(340, 538);
groupBox_SkelConfig.TabIndex = 0;
groupBox_SkelConfig.TabStop = false;
groupBox_SkelConfig.Text = "模型参数";
@@ -410,7 +414,7 @@
groupBox_PreviewConfig.Dock = DockStyle.Fill;
groupBox_PreviewConfig.Location = new Point(0, 0);
groupBox_PreviewConfig.Name = "groupBox_PreviewConfig";
groupBox_PreviewConfig.Size = new Size(344, 361);
groupBox_PreviewConfig.Size = new Size(340, 333);
groupBox_PreviewConfig.TabIndex = 1;
groupBox_PreviewConfig.TabStop = false;
groupBox_PreviewConfig.Text = "画面参数";
@@ -421,7 +425,7 @@
propertyGrid_Previewer.HelpVisible = false;
propertyGrid_Previewer.Location = new Point(3, 26);
propertyGrid_Previewer.Name = "propertyGrid_Previewer";
propertyGrid_Previewer.Size = new Size(338, 332);
propertyGrid_Previewer.Size = new Size(334, 304);
propertyGrid_Previewer.TabIndex = 1;
propertyGrid_Previewer.ToolbarVisible = false;
propertyGrid_Previewer.PropertyValueChanged += propertyGrid_PropertyValueChanged;
@@ -432,7 +436,7 @@
groupBox_Preview.Dock = DockStyle.Fill;
groupBox_Preview.Location = new Point(0, 0);
groupBox_Preview.Name = "groupBox_Preview";
groupBox_Preview.Size = new Size(977, 879);
groupBox_Preview.Size = new Size(973, 879);
groupBox_Preview.TabIndex = 1;
groupBox_Preview.TabStop = false;
groupBox_Preview.Text = "预览画面";
@@ -443,7 +447,7 @@
spinePreviewer.Location = new Point(3, 26);
spinePreviewer.Name = "spinePreviewer";
spinePreviewer.PropertyGrid = propertyGrid_Previewer;
spinePreviewer.Size = new Size(971, 850);
spinePreviewer.Size = new Size(967, 850);
spinePreviewer.SpineListView = spineListView;
spinePreviewer.TabIndex = 0;
//

View File

@@ -87,15 +87,6 @@ namespace SpineViewer
return;
}
lock (spineListView.Spines)
{
if (spineListView.Spines.Count <= 0)
{
MessageBox.Info("请至少打开一个骨骼文件");
return;
}
}
var exportArgs = ExportArgs.New(type, spinePreviewer.Resolution, spinePreviewer.GetView(), spinePreviewer.RenderSelectedOnly);
var exportDialog = new Dialogs.ExportDialog() { ExportArgs = exportArgs };
if (exportDialog.ShowDialog() != DialogResult.OK)
@@ -156,7 +147,7 @@ namespace SpineViewer
var worker = (BackgroundWorker)sender;
var exporter = (Exporter.Exporter)e.Argument;
spinePreviewer.StopRender();
lock (spineListView.Spines) { exporter.Export(spineListView.Spines.ToArray(), (BackgroundWorker)sender); }
lock (spineListView.Spines) { exporter.Export(spineListView.Spines.Where(sp => !sp.IsHidden).ToArray(), (BackgroundWorker)sender); }
e.Cancel = worker.CancellationPending;
spinePreviewer.StartRender();
}

View File

@@ -26,8 +26,6 @@ namespace SpineViewer.Spine.Implementations.Spine
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
@@ -75,13 +73,19 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
foreach (var skin in skeletonData.Skins)
skinNames.Add(skin.Name);
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
}
protected override void Dispose(bool disposing)
@@ -109,7 +113,8 @@ namespace SpineViewer.Spine.Implementations.Spine
var position = Position;
var flipX = FlipX;
var flipY = FlipY;
var savedTrack0 = animationState.GetCurrent(0);
var animation = CurrentAnimation;
var skin = CurrentSkin;
var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null)
@@ -132,21 +137,8 @@ namespace SpineViewer.Spine.Implementations.Spine
Position = position;
FlipX = flipX;
FlipY = flipY;
// 恢复原本 Track0 上所有动画
if (savedTrack0 is not null)
{
var entry = animationState.SetAnimation(0, savedTrack0.Animation.Name, true);
entry.Time = savedTrack0.Time;
// 2.1.x 没有提供 Next 访问器,故放弃还原后续动画,问题不大,因为预览画面目前不需要连续播放不同动画,只需要循环同一个动画
//var savedEntry = savedTrack0.Next;
//while (savedEntry is not null)
//{
// entry = animationState.AddAnimation(0, savedEntry.Animation.Name, true, 0);
// entry.Time = savedEntry.TrackTime;
// savedEntry = savedEntry.Next;
//}
}
CurrentAnimation = animation;
CurrentSkin = skin;
}
}
@@ -185,6 +177,18 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetToSetupPose();
Update(0);
}
}
public override RectangleF Bounds
{
get
@@ -333,7 +337,11 @@ namespace SpineViewer.Spine.Implementations.Spine
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
@@ -378,9 +386,12 @@ namespace SpineViewer.Spine.Implementations.Spine
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
//clipping.ClipEnd();
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
// 包围盒
if (IsDebug && IsSelected && DebugBounds)
{

View File

@@ -26,8 +26,6 @@ namespace SpineViewer.Spine.Implementations.Spine
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
@@ -74,13 +72,19 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
foreach (var skin in skeletonData.Skins)
skinNames.Add(skin.Name);
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
}
protected override void Dispose(bool disposing)
@@ -108,7 +112,8 @@ namespace SpineViewer.Spine.Implementations.Spine
var position = Position;
var flipX = FlipX;
var flipY = FlipY;
var savedTrack0 = animationState.GetCurrent(0);
var animation = CurrentAnimation;
var skin = CurrentSkin;
var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null)
@@ -131,20 +136,8 @@ namespace SpineViewer.Spine.Implementations.Spine
Position = position;
FlipX = flipX;
FlipY = flipY;
// 恢复原本 Track0 上所有动画
if (savedTrack0 is not null)
{
var entry = animationState.SetAnimation(0, savedTrack0.Animation.Name, true);
entry.TrackTime = savedTrack0.TrackTime;
var savedEntry = savedTrack0.Next;
while (savedEntry is not null)
{
entry = animationState.AddAnimation(0, savedEntry.Animation.Name, true, 0);
entry.TrackTime = savedEntry.TrackTime;
savedEntry = savedEntry.Next;
}
}
CurrentAnimation = animation;
CurrentSkin = skin;
}
}
@@ -183,6 +176,18 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetToSetupPose();
Update(0);
}
}
public override RectangleF Bounds
{
get
@@ -289,7 +294,11 @@ namespace SpineViewer.Spine.Implementations.Spine
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
@@ -329,13 +338,16 @@ namespace SpineViewer.Spine.Implementations.Spine
clipping.ClipEnd(slot);
}
clipping.ClipEnd();
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
clipping.ClipEnd();
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
// 包围盒
if (IsDebug && IsSelected && DebugBounds)

View File

@@ -23,8 +23,6 @@ namespace SpineViewer.Spine.Implementations.Spine
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
@@ -72,6 +70,9 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
foreach (var skin in skeletonData.Skins)
skinNames.Add(skin.Name);
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
@@ -79,7 +80,9 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
}
protected override void Dispose(bool disposing)
@@ -190,6 +193,18 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetToSetupPose();
Update(0);
}
}
public override RectangleF Bounds
{
get
@@ -297,7 +312,11 @@ namespace SpineViewer.Spine.Implementations.Spine
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
@@ -337,13 +356,16 @@ namespace SpineViewer.Spine.Implementations.Spine
clipping.ClipEnd(slot);
}
clipping.ClipEnd();
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
clipping.ClipEnd();
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
// 包围盒
if (IsDebug && IsSelected && DebugBounds)

View File

@@ -26,8 +26,9 @@ namespace SpineViewer.Spine.Implementations.Spine
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
// 似乎是不需要设置的, 因为存在某些 png 和 atlas 大小不同的情况, 一般是有一些缩放, 如果设置了反而渲染异常
// page.width = (int)texture.Size.X;
// page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
@@ -75,6 +76,9 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
foreach (var skin in skeletonData.Skins)
skinNames.Add(skin.Name);
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
@@ -82,7 +86,9 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
}
protected override void Dispose(bool disposing)
@@ -95,58 +101,11 @@ namespace SpineViewer.Spine.Implementations.Spine
public override float Scale
{
get
{
if (skeletonBinary is not null)
return skeletonBinary.Scale;
else if (skeletonJson is not null)
return skeletonJson.Scale;
else
return 1f;
}
get => Math.Abs(skeleton.ScaleX);
set
{
// 保存状态
var position = Position;
var flipX = FlipX;
var flipY = FlipY;
var savedTrack0 = animationState.GetCurrent(0);
var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null)
{
skeletonBinary.Scale = val;
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
else if (skeletonJson is not null)
{
skeletonJson.Scale = val;
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
// reload skel-dependent data
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
// 恢复状态
Position = position;
FlipX = flipX;
FlipY = flipY;
// 恢复原本 Track0 上所有动画
if (savedTrack0 is not null)
{
var entry = animationState.SetAnimation(0, savedTrack0.Animation.Name, true);
entry.TrackTime = savedTrack0.TrackTime;
var savedEntry = savedTrack0.Next;
while (savedEntry is not null)
{
entry = animationState.AddAnimation(0, savedEntry.Animation.Name, true, 0);
entry.TrackTime = savedEntry.TrackTime;
savedEntry = savedEntry.Next;
}
}
skeleton.ScaleX = Math.Sign(skeleton.ScaleX) * value;
skeleton.ScaleY = Math.Sign(skeleton.ScaleY) * value;
}
}
@@ -193,6 +152,18 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetToSetupPose();
Update(0);
}
}
public override RectangleF Bounds
{
get
@@ -300,7 +271,11 @@ namespace SpineViewer.Spine.Implementations.Spine
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
@@ -340,15 +315,18 @@ namespace SpineViewer.Spine.Implementations.Spine
clipping.ClipEnd(slot);
}
clipping.ClipEnd();
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
clipping.ClipEnd();
// 包围盒
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
// 调试包围盒
if (IsDebug && IsSelected && DebugBounds)
{
var bounds = Bounds;

View File

@@ -25,8 +25,6 @@ namespace SpineViewer.Spine.Implementations.Spine
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
@@ -74,6 +72,9 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
foreach (var skin in skeletonData.Skins)
skinNames.Add(skin.Name);
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
@@ -81,7 +82,9 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
}
protected override void Dispose(bool disposing)
@@ -94,58 +97,11 @@ namespace SpineViewer.Spine.Implementations.Spine
public override float Scale
{
get
{
if (skeletonBinary is not null)
return skeletonBinary.Scale;
else if (skeletonJson is not null)
return skeletonJson.Scale;
else
return 1f;
}
get => Math.Abs(skeleton.ScaleX);
set
{
// 保存状态
var position = Position;
var flipX = FlipX;
var flipY = FlipY;
var savedTrack0 = animationState.GetCurrent(0);
var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null)
{
skeletonBinary.Scale = val;
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
else if (skeletonJson is not null)
{
skeletonJson.Scale = val;
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
// reload skel-dependent data
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
// 恢复状态
Position = position;
FlipX = flipX;
FlipY = flipY;
// 恢复原本 Track0 上所有动画
if (savedTrack0 is not null)
{
var entry = animationState.SetAnimation(0, savedTrack0.Animation.Name, true);
entry.TrackTime = savedTrack0.TrackTime;
var savedEntry = savedTrack0.Next;
while (savedEntry is not null)
{
entry = animationState.AddAnimation(0, savedEntry.Animation.Name, true, 0);
entry.TrackTime = savedEntry.TrackTime;
savedEntry = savedEntry.Next;
}
}
skeleton.ScaleX = Math.Sign(skeleton.ScaleX) * value;
skeleton.ScaleY = Math.Sign(skeleton.ScaleY) * value;
}
}
@@ -192,6 +148,18 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetToSetupPose();
Update(0);
}
}
public override RectangleF Bounds
{
get
@@ -299,7 +267,11 @@ namespace SpineViewer.Spine.Implementations.Spine
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
@@ -339,13 +311,16 @@ namespace SpineViewer.Spine.Implementations.Spine
clipping.ClipEnd(slot);
}
clipping.ClipEnd();
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
clipping.ClipEnd();
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
// 包围盒
if (IsDebug && IsSelected && DebugBounds)

View File

@@ -25,8 +25,6 @@ namespace SpineViewer.Spine.Implementations.Spine
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
@@ -74,6 +72,9 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
foreach (var skin in skeletonData.Skins)
skinNames.Add(skin.Name);
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
@@ -81,7 +82,9 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
}
protected override void Dispose(bool disposing)
@@ -94,58 +97,11 @@ namespace SpineViewer.Spine.Implementations.Spine
public override float Scale
{
get
{
if (skeletonBinary is not null)
return skeletonBinary.Scale;
else if (skeletonJson is not null)
return skeletonJson.Scale;
else
return 1f;
}
get => Math.Abs(skeleton.ScaleX);
set
{
// 保存状态
var position = Position;
var flipX = FlipX;
var flipY = FlipY;
var savedTrack0 = animationState.GetCurrent(0);
var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null)
{
skeletonBinary.Scale = val;
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
else if (skeletonJson is not null)
{
skeletonJson.Scale = val;
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
// reload skel-dependent data
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
// 恢复状态
Position = position;
FlipX = flipX;
FlipY = flipY;
// 恢复原本 Track0 上所有动画
if (savedTrack0 is not null)
{
var entry = animationState.SetAnimation(0, savedTrack0.Animation.Name, true);
entry.TrackTime = savedTrack0.TrackTime;
var savedEntry = savedTrack0.Next;
while (savedEntry is not null)
{
entry = animationState.AddAnimation(0, savedEntry.Animation.Name, true, 0);
entry.TrackTime = savedEntry.TrackTime;
savedEntry = savedEntry.Next;
}
}
skeleton.ScaleX = Math.Sign(skeleton.ScaleX) * value;
skeleton.ScaleY = Math.Sign(skeleton.ScaleY) * value;
}
}
@@ -192,6 +148,18 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetToSetupPose();
Update(0);
}
}
public override RectangleF Bounds
{
get
@@ -299,7 +267,11 @@ namespace SpineViewer.Spine.Implementations.Spine
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
@@ -339,13 +311,16 @@ namespace SpineViewer.Spine.Implementations.Spine
clipping.ClipEnd(slot);
}
clipping.ClipEnd();
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
clipping.ClipEnd();
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
// 包围盒
if (IsDebug && IsSelected && DebugBounds)

View File

@@ -25,8 +25,6 @@ namespace SpineViewer.Spine.Implementations.Spine
texture.Repeated = true;
page.rendererObject = texture;
page.width = (int)texture.Size.X;
page.height = (int)texture.Size.Y;
}
public void Unload(object texture)
@@ -74,6 +72,9 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
foreach (var skin in skeletonData.Skins)
skinNames.Add(skin.Name);
animationStateData = new AnimationStateData(skeletonData);
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
@@ -81,7 +82,9 @@ namespace SpineViewer.Spine.Implementations.Spine
foreach (var anime in skeletonData.Animations)
animationNames.Add(anime.Name);
CurrentAnimation = DefaultAnimationName;
// 取最后一个作为初始, 尽可能去显示非默认的内容
CurrentAnimation = animationNames.Last();
CurrentSkin = skinNames.Last();
}
protected override void Dispose(bool disposing)
@@ -94,58 +97,11 @@ namespace SpineViewer.Spine.Implementations.Spine
public override float Scale
{
get
{
if (skeletonBinary is not null)
return skeletonBinary.Scale;
else if (skeletonJson is not null)
return skeletonJson.Scale;
else
return 1f;
}
get => Math.Abs(skeleton.ScaleX);
set
{
// 保存状态
var position = Position;
var flipX = FlipX;
var flipY = FlipY;
var savedTrack0 = animationState.GetCurrent(0);
var val = Math.Max(value, SCALE_MIN);
if (skeletonBinary is not null)
{
skeletonBinary.Scale = val;
skeletonData = skeletonBinary.ReadSkeletonData(SkelPath);
}
else if (skeletonJson is not null)
{
skeletonJson.Scale = val;
skeletonData = skeletonJson.ReadSkeletonData(SkelPath);
}
// reload skel-dependent data
animationStateData = new AnimationStateData(skeletonData) { DefaultMix = animationStateData.DefaultMix };
skeleton = new Skeleton(skeletonData);
animationState = new AnimationState(animationStateData);
// 恢复状态
Position = position;
FlipX = flipX;
FlipY = flipY;
// 恢复原本 Track0 上所有动画
if (savedTrack0 is not null)
{
var entry = animationState.SetAnimation(0, savedTrack0.Animation.Name, true);
entry.TrackTime = savedTrack0.TrackTime;
var savedEntry = savedTrack0.Next;
while (savedEntry is not null)
{
entry = animationState.AddAnimation(0, savedEntry.Animation.Name, true, 0);
entry.TrackTime = savedEntry.TrackTime;
savedEntry = savedEntry.Next;
}
}
skeleton.ScaleX = Math.Sign(skeleton.ScaleX) * value;
skeleton.ScaleY = Math.Sign(skeleton.ScaleY) * value;
}
}
@@ -192,6 +148,18 @@ namespace SpineViewer.Spine.Implementations.Spine
}
}
public override string CurrentSkin
{
get => skeleton.Skin?.Name ?? "default";
set
{
if (!skinNames.Contains(value)) return;
skeleton.SetSkin(value);
skeleton.SetToSetupPose();
Update(0);
}
}
public override RectangleF Bounds
{
get
@@ -299,7 +267,11 @@ namespace SpineViewer.Spine.Implementations.Spine
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
vertexArray.Clear();
}
states.BlendMode = blendMode;
@@ -339,13 +311,16 @@ namespace SpineViewer.Spine.Implementations.Spine
clipping.ClipEnd(slot);
}
clipping.ClipEnd();
if (UsePremultipliedAlpha && (states.BlendMode == BlendModeSFML.Normal || states.BlendMode == BlendModeSFML.Additive))
states.Shader = FragmentShader;
else
states.Shader = null;
target.Draw(vertexArray, states);
clipping.ClipEnd();
// 调试纹理
if (!IsDebug || DebugTexture)
target.Draw(vertexArray, states);
// 包围盒
if (IsDebug && IsSelected && DebugBounds)

View File

@@ -185,11 +185,6 @@ namespace SpineViewer.Spine
return (Spine)Activator.CreateInstance(spineType, skelPath, atlasPath);
}
/// <summary>
/// 标识符
/// </summary>
public readonly string ID = Guid.NewGuid().ToString();
/// <summary>
/// 构造函数
/// </summary>
@@ -217,85 +212,93 @@ namespace SpineViewer.Spine
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
protected virtual void Dispose(bool disposing) { preview?.Dispose(); }
#region |
#region | [0]
/// <summary>
/// 获取所属版本
/// </summary>
[TypeConverter(typeof(VersionConverter))]
[Category("基本信息"), DisplayName("运行时版本")]
[Category("[0] "), DisplayName("")]
public Version Version { get; }
/// <summary>
/// 资源所在完整目录
/// </summary>
[Category("基本信息"), DisplayName("资源目录")]
[Category("[0] "), DisplayName("")]
public string AssetsDir { get; }
/// <summary>
/// skel 文件完整路径
/// </summary>
[Category("基本信息"), DisplayName("skel文件路径")]
[Category("[0] "), DisplayName("skel文件路径")]
public string SkelPath { get; }
/// <summary>
/// atlas 文件完整路径
/// </summary>
[Category("基本信息"), DisplayName("atlas文件路径")]
[Category("[0] "), DisplayName("atlas文件路径")]
public string AtlasPath { get; }
/// <summary>
/// 名称
/// </summary>
[Category("基本信息"), DisplayName("名称")]
[Category("[0] "), DisplayName("")]
public string Name { get; }
/// <summary>
/// 获取所属文件版本
/// </summary>
[Category("基本信息"), DisplayName("文件版本")]
[Category("[0] "), DisplayName("")]
public abstract string FileVersion { get; }
#endregion
#region |
#region | [1]
/// <summary>
/// 是否被隐藏, 被隐藏的模型将仅仅在列表显示, 不参与其他行为
/// </summary>
[Category("[1] "), DisplayName("")]
public bool IsHidden { get; set; } = false;
/// <summary>
/// 是否使用预乘Alpha
/// </summary>
[Category("[1] "), DisplayName("Alpha通道")]
public bool UsePremultipliedAlpha { get; set; } = true;
#endregion
#region | [2]
/// <summary>
/// 缩放比例
/// </summary>
[Category("变换"), DisplayName("缩放比例")]
[Category("[2] "), DisplayName("")]
public abstract float Scale { get; set; }
/// <summary>
/// 位置
/// </summary>
[TypeConverter(typeof(PointFConverter))]
[Category("变换"), DisplayName("位置")]
[Category("[2] "), DisplayName("")]
public abstract PointF Position { get; set; }
/// <summary>
/// 水平翻转
/// </summary>
[Category("变换"), DisplayName("水平翻转")]
[Category("[2] "), DisplayName("")]
public abstract bool FlipX { get; set; }
/// <summary>
/// 垂直翻转
/// </summary>
[Category("变换"), DisplayName("垂直翻转")]
[Category("[2] "), DisplayName("")]
public abstract bool FlipY { get; set; }
#endregion
#region |
/// <summary>
/// 是否使用预乘Alpha
/// </summary>
[Category("画面"), DisplayName("预乘Alpha通道")]
public bool UsePremultipliedAlpha { get; set; } = true;
#endregion
#region | [3]
/// <summary>
/// 包含的所有动画名称
@@ -304,29 +307,84 @@ namespace SpineViewer.Spine
public ReadOnlyCollection<string> AnimationNames { get => animationNames.AsReadOnly(); }
protected List<string> animationNames = [EMPTY_ANIMATION];
/// <summary>
/// 默认动画名称
/// </summary>
[Browsable(false)]
public string DefaultAnimationName { get => animationNames.Last(); }
#region |
/// <summary>
/// 当前动画名称, 如果设置的动画不存在则忽略
/// </summary>
[TypeConverter(typeof(AnimationConverter))]
[Category("动画"), DisplayName("当前动画")]
[Category("[3] "), DisplayName("")]
public abstract string CurrentAnimation { get; set; }
/// <summary>
/// 当前动画时长
/// </summary>
[Category("动画"), DisplayName("当前动画时长")]
[Category("[3] "), DisplayName("")]
public float CurrentAnimationDuration { get => GetAnimationDuration(CurrentAnimation); }
/// <summary>
/// 包含的所有皮肤名称
/// </summary>
[Browsable(false)]
public ReadOnlyCollection<string> SkinNames { get => skinNames.AsReadOnly(); }
protected List<string> skinNames = [];
/// <summary>
/// 当前皮肤名称, 如果设置的皮肤不存在则忽略
/// </summary>
[TypeConverter(typeof(SkinConverter))]
[Category("[3] "), DisplayName("")]
public abstract string CurrentSkin { get; set; }
#endregion
#region | [4]
/// <summary>
/// 显示调试
/// </summary>
[Browsable(false)]
public bool IsDebug { get; set; } = false;
/// <summary>
/// 包围盒颜色
/// </summary>
protected static readonly SFML.Graphics.Color BoundsColor = new(120, 200, 0);
/// <summary>
/// 包围盒顶点数组
/// </summary>
protected readonly SFML.Graphics.VertexArray boundsVertices = new(SFML.Graphics.PrimitiveType.LineStrip, 5);
/// <summary>
/// 显示纹理
/// </summary>
[Category("[4] "), DisplayName("")]
public bool DebugTexture { get; set; } = true;
/// <summary>
/// 显示包围盒
/// </summary>
[Category("[4] "), DisplayName("")]
public bool DebugBounds { get; set; } = true;
/// <summary>
/// 显示骨骼
/// </summary>
[Category("[4] "), DisplayName("")]
public bool DebugBones { get; set; } = false;
#endregion
/// <summary>
/// 标识符
/// </summary>
public readonly string ID = Guid.NewGuid().ToString();
/// <summary>
/// 是否被选中
/// </summary>
[Browsable(false)]
public bool IsSelected { get; set; } = false;
/// <summary>
/// 骨骼包围盒
/// </summary>
@@ -395,43 +453,8 @@ namespace SpineViewer.Spine
/// <summary>
/// 更新内部状态
/// </summary>
/// <param name="delta">时间间隔</param>
public abstract void Update(float delta);
/// <summary>
/// 是否被选中
/// </summary>
[Browsable(false)]
public bool IsSelected { get; set; } = false;
/// <summary>
/// 显示调试
/// </summary>
[Browsable(false)]
public bool IsDebug { get; set; } = false;
/// <summary>
/// 包围盒颜色
/// </summary>
protected static readonly SFML.Graphics.Color BoundsColor = new(120, 200, 0);
/// <summary>
/// 包围盒顶点数组
/// </summary>
protected readonly SFML.Graphics.VertexArray boundsVertices = new(SFML.Graphics.PrimitiveType.LineStrip, 5);
/// <summary>
/// 显示包围盒
/// </summary>
[Category("调试"), DisplayName("显示包围盒")]
public bool DebugBounds { get; set; } = true;
/// <summary>
/// 显示骨骼
/// </summary>
[Category("调试"), DisplayName("显示骨骼(TODO)")]
public bool DebugBones { get; set; } = false;
#region SFML.Graphics.Drawable
/// <summary>

View File

@@ -58,4 +58,38 @@ namespace SpineViewer.Spine
return base.GetStandardValues(context);
}
}
public class SkinConverter : StringConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
{
// 支持标准值列表
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
{
// 排他模式,只有下拉列表中的值可选
return true;
}
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
{
if (context?.Instance is Spine obj)
{
return new StandardValuesCollection(obj.SkinNames);
}
else if (context?.Instance is Spine[] spines)
{
if (spines.Length > 0)
{
IEnumerable<string> common = spines[0].SkinNames;
foreach (var spine in spines.Skip(1))
common = common.Intersect(spine.SkinNames);
return new StandardValuesCollection(common.ToArray());
}
}
return base.GetStandardValues(context);
}
}
}

View File

@@ -8,7 +8,7 @@
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<BaseOutputPath>$(SolutionDir)out</BaseOutputPath>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Version>0.11.1</Version>
<Version>0.11.3</Version>
<OutputType>WinExe</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>appicon.ico</ApplicationIcon>