diff --git a/SpineViewer/Controls/SpineListView.cs b/SpineViewer/Controls/SpineListView.cs
index ec05a75..2bfb018 100644
--- a/SpineViewer/Controls/SpineListView.cs
+++ b/SpineViewer/Controls/SpineListView.cs
@@ -17,16 +17,19 @@ namespace SpineViewer.Controls
{
public partial class SpineListView : UserControl
{
+ ///
+ /// 显示骨骼信息的属性面板
+ ///
[Category("自定义"), Description("用于显示骨骼属性的属性页")]
public PropertyGrid? PropertyGrid { get; set; }
///
- /// 获取数组快照, 访问时必须使用 lock 语句锁定对象本身
+ /// Spine 列表只读视图, 访问时必须使用 lock 语句锁定视图本身
///
public readonly ReadOnlyCollection Spines;
///
- /// Spine 列表, 访问时必须使用 lock 语句锁定 Spines
+ /// Spine 列表, 访问时必须使用 lock 语句锁定只读视图 Spines
///
private readonly List spines = [];
@@ -37,16 +40,60 @@ namespace SpineViewer.Controls
}
///
- /// listView.SelectedIndices
+ /// 选中的索引
///
- public ListView.SelectedIndexCollection SelectedIndices { get => listView.SelectedIndices; }
+ public ListView.SelectedIndexCollection SelectedIndices => listView.SelectedIndices;
///
- /// 弹出添加对话框
+ /// 弹出添加对话框在末尾添加
///
- public void Add()
+ public void Add() => Insert();
+
+ ///
+ /// 弹出添加对话框在指定位置之前插入一项, 如果索引无效则在末尾添加
+ ///
+ private void Insert(int index = -1)
{
- Insert();
+ var dialog = new Dialogs.OpenSpineDialog();
+ if (dialog.ShowDialog() != DialogResult.OK)
+ return;
+ Insert(dialog.Result, index);
+ }
+
+ ///
+ /// 从结果在指定位置之前插入一项, 如果索引无效则在末尾添加
+ ///
+ private void Insert(Dialogs.OpenSpineDialogResult result, int index = -1)
+ {
+ try
+ {
+ var spine = Spine.Spine.New(result.Version, result.SkelPath, result.AtlasPath);
+
+ // 如果索引无效则在末尾添加
+ if (index < 0 || index > listView.Items.Count)
+ index = listView.Items.Count;
+
+ // 锁定外部的读操作
+ lock (Spines)
+ {
+ spines.Insert(index, spine);
+ listView.SmallImageList.Images.Add(spine.ID, spine.Preview);
+ listView.LargeImageList.Images.Add(spine.ID, spine.Preview);
+ }
+ listView.Items.Insert(index, new ListViewItem(spine.Name, spine.ID) { ToolTipText = spine.SkelPath });
+
+ // 选中新增项
+ listView.SelectedIndices.Clear();
+ listView.SelectedIndices.Add(index);
+ }
+ catch (Exception ex)
+ {
+ Program.Logger.Error(ex.ToString());
+ Program.Logger.Error("Failed to load {} {}", result.SkelPath, result.AtlasPath);
+ MessageBox.Error(ex.ToString(), "骨骼加载失败");
+ }
+
+ Program.LogCurrentMemoryUsage();
}
///
@@ -57,13 +104,118 @@ namespace SpineViewer.Controls
var openDialog = new Dialogs.BatchOpenSpineDialog();
if (openDialog.ShowDialog() != DialogResult.OK)
return;
+ BatchAdd(openDialog.Result);
+ }
+ ///
+ /// 从结果批量添加
+ ///
+ public void BatchAdd(Dialogs.BatchOpenSpineDialogResult result)
+ {
var progressDialog = new Dialogs.ProgressDialog();
progressDialog.DoWork += BatchAdd_Work;
- progressDialog.RunWorkerAsync(openDialog.Result);
+ progressDialog.RunWorkerAsync(result);
progressDialog.ShowDialog();
}
+ ///
+ /// 批量添加后台任务
+ ///
+ private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
+ {
+ var worker = sender as BackgroundWorker;
+ var arguments = e.Argument as Dialogs.BatchOpenSpineDialogResult;
+ var skelPaths = arguments.SkelPaths;
+ var version = arguments.Version;
+
+ int totalCount = skelPaths.Length;
+ int success = 0;
+ int error = 0;
+
+ worker.ReportProgress(0, $"已处理 0/{totalCount}");
+ for (int i = 0; i < totalCount; i++)
+ {
+ if (worker.CancellationPending)
+ {
+ e.Cancel = true;
+ break;
+ }
+
+ var skelPath = skelPaths[i];
+
+ try
+ {
+ var spine = Spine.Spine.New(version, skelPath);
+ var preview = spine.Preview;
+ lock (Spines) { spines.Add(spine); }
+ listView.Invoke(() =>
+ {
+ listView.SmallImageList.Images.Add(spine.ID, preview);
+ listView.LargeImageList.Images.Add(spine.ID, preview);
+ listView.Items.Add(new ListViewItem(spine.Name, spine.ID) { ToolTipText = spine.SkelPath });
+ });
+ success++;
+ }
+ catch (Exception ex)
+ {
+ Program.Logger.Error(ex.ToString());
+ Program.Logger.Error("Failed to load {}", skelPath);
+ error++;
+ }
+
+ worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
+ }
+
+ if (error > 0)
+ {
+ Program.Logger.Warn("Batch load {} successfully, {} failed", success, error);
+ }
+ else
+ {
+ Program.Logger.Info("{} skel loaded successfully", success);
+ }
+
+ Program.LogCurrentMemoryUsage();
+ }
+
+ ///
+ /// 从拖放/复制的路径列表添加
+ ///
+ private void AddFromFileDrop(IEnumerable paths)
+ {
+ List validPaths = [];
+ foreach (var path in paths)
+ {
+ if (File.Exists(path))
+ {
+ if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower()))
+ validPaths.Add(path);
+ }
+ else if (Directory.Exists(path))
+ {
+ foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
+ {
+ if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
+ validPaths.Add(file);
+ }
+ }
+ }
+
+ if (validPaths.Count > 1)
+ {
+ if (validPaths.Count > 100)
+ {
+ if (MessageBox.Quest($"共发现 {validPaths.Count} 个可加载骨骼,数量较多,是否一次性全部加载?") == DialogResult.Cancel)
+ return;
+ }
+ BatchAdd(new Dialogs.BatchOpenSpineDialogResult(Spine.Version.Auto, validPaths.ToArray()));
+ }
+ else if (validPaths.Count > 0)
+ {
+ Insert(new Dialogs.OpenSpineDialogResult(Spine.Version.Auto, validPaths[0]));
+ }
+ }
+
private void listView_SelectedIndexChanged(object sender, EventArgs e)
{
if (PropertyGrid is not null)
@@ -227,12 +379,13 @@ namespace SpineViewer.Controls
if (listView.SelectedIndices.Count > 1)
{
- if (MessageBox.Show($"确定移除所选 {listView.SelectedIndices.Count} 项吗?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
+ if (MessageBox.Quest($"确定移除所选 {listView.SelectedIndices.Count} 项吗?") != DialogResult.OK)
return;
}
foreach (var i in listView.SelectedIndices.Cast().OrderByDescending(x => x))
{
+ listView.Items.RemoveAt(i);
lock (Spines)
{
var spine = spines[i];
@@ -241,7 +394,6 @@ namespace SpineViewer.Controls
listView.LargeImageList.Images.RemoveByKey(spine.ID);
spine.Dispose();
}
- listView.Items.RemoveAt(i);
}
}
@@ -324,18 +476,17 @@ namespace SpineViewer.Controls
if (listView.Items.Count <= 0)
return;
- if (MessageBox.Show($"确认移除所有 {listView.Items.Count} 项吗?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK)
+ if (MessageBox.Quest($"确认移除所有 {listView.Items.Count} 项吗?") != DialogResult.OK)
return;
+ listView.Items.Clear();
lock (Spines)
{
- foreach (var spine in spines)
- spine.Dispose();
+ foreach (var spine in spines) spine.Dispose();
spines.Clear();
listView.SmallImageList.Images.Clear();
listView.LargeImageList.Images.Clear();
}
- listView.Items.Clear();
if (PropertyGrid is not null)
PropertyGrid.SelectedObject = null;
}
@@ -393,145 +544,5 @@ namespace SpineViewer.Controls
{
listView.View = View.Details;
}
-
- ///
- /// 弹出添加对话框在指定位置之前插入一项
- ///
- private void Insert(int index = -1)
- {
- var dialog = new Dialogs.OpenSpineDialog();
- if (dialog.ShowDialog() != DialogResult.OK)
- return;
-
- Insert(dialog.Result, index);
- }
-
- private void Insert(Dialogs.OpenSpineDialogResult result, int index = -1)
- {
- try
- {
- var spine = Spine.Spine.New(result.Version, result.SkelPath, result.AtlasPath);
-
- // 如果索引无效则在末尾添加
- if (index < 0 || index > listView.Items.Count)
- index = listView.Items.Count;
-
- // 锁定外部的读操作
- lock (Spines)
- {
- spines.Insert(index, spine);
- listView.SmallImageList.Images.Add(spine.ID, spine.Preview);
- listView.LargeImageList.Images.Add(spine.ID, spine.Preview);
- }
- listView.Items.Insert(index, new ListViewItem(spine.Name, spine.ID) { ToolTipText = spine.SkelPath });
-
- // 选中新增项
- listView.SelectedIndices.Clear();
- listView.SelectedIndices.Add(index);
- }
- catch (Exception ex)
- {
- Program.Logger.Error(ex.ToString());
- Program.Logger.Error("Failed to load {} {}", result.SkelPath, result.AtlasPath);
- MessageBox.Show(ex.ToString(), "骨骼加载失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
- }
-
- Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
- }
-
- private void BatchAdd_Work(object? sender, DoWorkEventArgs e)
- {
- var worker = sender as BackgroundWorker;
- var arguments = e.Argument as Dialogs.BatchOpenSpineDialogResult;
- var skelPaths = arguments.SkelPaths;
- var version = arguments.Version;
-
- int totalCount = skelPaths.Length;
- int success = 0;
- int error = 0;
-
- worker.ReportProgress(0, $"已处理 0/{totalCount}");
- for (int i = 0; i < totalCount; i++)
- {
- if (worker.CancellationPending)
- {
- e.Cancel = true;
- break;
- }
-
- var skelPath = skelPaths[i];
-
- try
- {
- var spine = Spine.Spine.New(version, skelPath);
- var preview = spine.Preview;
- lock (Spines) { spines.Add(spine); }
- listView.Invoke(() =>
- {
- listView.SmallImageList.Images.Add(spine.ID, preview);
- listView.LargeImageList.Images.Add(spine.ID, preview);
- listView.Items.Add(new ListViewItem(spine.Name, spine.ID) { ToolTipText = spine.SkelPath });
- });
- success++;
- }
- catch (Exception ex)
- {
- Program.Logger.Error(ex.ToString());
- Program.Logger.Error("Failed to load {}", skelPath);
- error++;
- }
-
- worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
- }
-
- if (error > 0)
- {
- Program.Logger.Warn("Batch load {} successfully, {} failed", success, error);
- }
- else
- {
- Program.Logger.Info("{} skel loaded successfully", success);
- }
-
- Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
- }
-
- private void AddFromFileDrop(string[] paths)
- {
- List validPaths = [];
- foreach (var path in paths)
- {
- if (File.Exists(path))
- {
- if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(path).ToLower()))
- validPaths.Add(path);
- }
- else if (Directory.Exists(path))
- {
- foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
- {
- if (Spine.Spine.CommonSkelSuffix.Contains(Path.GetExtension(file).ToLower()))
- validPaths.Add(file);
- }
- }
- }
-
- if (validPaths.Count > 1)
- {
- if (validPaths.Count > 100)
- {
- if (MessageBox.Show($"共发现 {validPaths.Count} 个可加载骨骼,数量较大,是否一次性全部加载?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.Cancel)
- return;
- }
- var progressDialog = new Dialogs.ProgressDialog();
- progressDialog.DoWork += BatchAdd_Work;
- progressDialog.RunWorkerAsync(new Dialogs.BatchOpenSpineDialogResult(Spine.Version.Auto, validPaths.ToArray()));
- progressDialog.ShowDialog();
- }
- else if (validPaths.Count > 0)
- {
- Insert(new Dialogs.OpenSpineDialogResult(Spine.Version.Auto, validPaths[0]));
- }
- }
}
}
diff --git a/SpineViewer/Controls/SpinePreviewer.cs b/SpineViewer/Controls/SpinePreviewer.cs
index b12160a..bb5642e 100644
--- a/SpineViewer/Controls/SpinePreviewer.cs
+++ b/SpineViewer/Controls/SpinePreviewer.cs
@@ -16,14 +16,10 @@ namespace SpineViewer.Controls
public partial class SpinePreviewer : UserControl
{
///
- /// 包装类, 用于 PropertyGrid 显示
+ /// 包装类, 用于属性面板显示
///
- private class PreviewerProperty
+ private class PreviewerProperty(SpinePreviewer previewer)
{
- private readonly SpinePreviewer previewer;
-
- public PreviewerProperty(SpinePreviewer previewer) { this.previewer = previewer; }
-
[TypeConverter(typeof(SizeConverter))]
[Category("导出"), DisplayName("分辨率")]
public Size Resolution { get => previewer.Resolution; set => previewer.Resolution = value; }
@@ -51,9 +47,15 @@ namespace SpineViewer.Controls
public uint MaxFps { get => previewer.MaxFps; set => previewer.MaxFps = value; }
}
+ ///
+ /// 要绑定的 Spine 列表控件
+ ///
[Category("自定义"), Description("相关联的 SpineListView")]
public SpineListView? SpineListView { get; set; }
+ ///
+ /// 属性信息面板
+ ///
[Category("自定义"), Description("用于显示画面属性的属性页")]
public PropertyGrid? PropertyGrid
{
@@ -67,20 +69,59 @@ namespace SpineViewer.Controls
}
private PropertyGrid? propertyGrid;
+ ///
+ /// 画面缩放最大值
+ ///
public const float ZOOM_MAX = 1000f;
+
+ ///
+ /// 画面缩放最小值
+ ///
public const float ZOOM_MIN = 0.001f;
+ ///
+ /// 预览画面背景色
+ ///
private static readonly SFML.Graphics.Color BackgroundColor = new(105, 105, 105);
+
+ ///
+ /// 预览画面坐标轴颜色
+ ///
private static readonly SFML.Graphics.Color AxisColor = new(220, 220, 220);
+
+ ///
+ /// TODO: 转移到 Spine 对象
+ ///
private static readonly SFML.Graphics.Color BoundsColor = new(120, 200, 0);
+ ///
+ /// 坐标轴顶点缓冲区
+ ///
private readonly SFML.Graphics.VertexArray AxisVertex = new(SFML.Graphics.PrimitiveType.Lines, 2);
+
+ ///
+ /// TODO: 转移到 Spine 对象
+ ///
private readonly SFML.Graphics.VertexArray BoundsRect = new(SFML.Graphics.PrimitiveType.LineStrip, 5);
+ ///
+ /// 渲染窗口
+ ///
private readonly SFML.Graphics.RenderWindow RenderWindow;
+
+ ///
+ /// 帧间隔计时器
+ ///
private readonly SFML.System.Clock Clock = new();
+
+ ///
+ /// 画面拖放对象世界坐标源点
+ ///
private SFML.System.Vector2f? draggingSrc = null;
+ ///
+ /// 渲染任务
+ ///
private Task? task = null;
private CancellationTokenSource? cancelToken = null;
@@ -239,13 +280,6 @@ namespace SpineViewer.Controls
public uint MaxFps { get => maxFps; set { RenderWindow.SetFramerateLimit(value); maxFps = value; } }
private uint maxFps = 60;
- ///
- /// RenderWindow.View
- ///
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- [Browsable(false)]
- public SFML.Graphics.View View { get => RenderWindow.GetView(); }
-
public SpinePreviewer()
{
InitializeComponent();
@@ -259,6 +293,11 @@ namespace SpineViewer.Controls
MaxFps = 30;
}
+ ///
+ /// 预览画面帧参数
+ ///
+ public SpinePreviewerFrameArgs GetFrameArgs() => new(Resolution, RenderWindow.GetView());
+
///
/// 开始预览
///
@@ -283,6 +322,75 @@ namespace SpineViewer.Controls
task = null;
}
+ ///
+ /// 渲染任务
+ ///
+ private void RenderTask()
+ {
+ try
+ {
+ RenderWindow.SetActive(true);
+
+ float delta;
+ while (cancelToken is not null && !cancelToken.IsCancellationRequested)
+ {
+ delta = Clock.ElapsedTime.AsSeconds();
+ Clock.Restart();
+
+ RenderWindow.Clear(BackgroundColor);
+
+ if (ShowAxis)
+ {
+ // 画一个很长的坐标轴, 用 1e9 比较合适
+ AxisVertex[0] = new(new(-1e9f, 0), AxisColor);
+ AxisVertex[1] = new(new(1e9f, 0), AxisColor);
+ RenderWindow.Draw(AxisVertex);
+ AxisVertex[0] = new(new(0, -1e9f), AxisColor);
+ AxisVertex[1] = new(new(0, 1e9f), AxisColor);
+ RenderWindow.Draw(AxisVertex);
+ }
+
+ // 渲染 Spine
+ if (SpineListView is not null)
+ {
+ lock (SpineListView.Spines)
+ {
+ var spines = SpineListView.Spines;
+ for (int i = spines.Count - 1; i >= 0; i--)
+ {
+ if (cancelToken is not null && cancelToken.IsCancellationRequested)
+ break; // 提前中止
+
+ var spine = spines[i];
+ spine.Update(delta);
+
+ spine.IsDebug = true;
+ RenderWindow.Draw(spine);
+ spine.IsDebug = false;
+
+ // TODO: 增加渲染模式(仅选中), 包围盒转移到 Spine 类
+ if (spine.IsSelected)
+ {
+ var bounds = spine.Bounds;
+ BoundsRect[0] = BoundsRect[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
+ BoundsRect[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
+ BoundsRect[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
+ BoundsRect[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
+ RenderWindow.Draw(BoundsRect);
+ }
+ }
+ }
+ }
+
+ RenderWindow.Display();
+ }
+ }
+ finally
+ {
+ RenderWindow.SetActive(false);
+ }
+ }
+
private void SpinePreviewer_SizeChanged(object sender, EventArgs e)
{
if (RenderWindow is null)
@@ -391,11 +499,8 @@ namespace SpineViewer.Controls
{
lock (SpineListView.Spines)
{
- foreach (var spine in SpineListView.Spines)
- {
- if (spine.IsSelected)
- spine.Position += delta;
- }
+ foreach (int i in SpineListView.SelectedIndices)
+ SpineListView.Spines[i].Position += delta;
}
}
draggingSrc = dst;
@@ -426,71 +531,22 @@ namespace SpineViewer.Controls
Zoom *= (e.Delta > 0 ? 1.1f : 0.9f);
PropertyGrid?.Refresh();
}
+ }
- private void RenderTask()
- {
- try
- {
- RenderWindow.SetActive(true);
+ ///
+ /// 预览画面帧参数
+ ///
+ public class SpinePreviewerFrameArgs(Size resolution, SFML.Graphics.View view)
+ {
+ ///
+ /// 分辨率
+ ///
+ public Size Resolution => resolution;
- float delta;
- while (cancelToken is not null && !cancelToken.IsCancellationRequested)
- {
- delta = Clock.ElapsedTime.AsSeconds();
- Clock.Restart();
-
- RenderWindow.Clear(BackgroundColor);
-
- if (ShowAxis)
- {
- // 画一个很长的坐标轴, 用 1e9 比较合适
- AxisVertex[0] = new(new(-1e9f, 0), AxisColor);
- AxisVertex[1] = new(new(1e9f, 0), AxisColor);
- RenderWindow.Draw(AxisVertex);
- AxisVertex[0] = new(new(0, -1e9f), AxisColor);
- AxisVertex[1] = new(new(0, 1e9f), AxisColor);
- RenderWindow.Draw(AxisVertex);
- }
-
- // 渲染 Spine
- if (SpineListView is not null)
- {
- lock (SpineListView.Spines)
- {
- var spines = SpineListView.Spines;
- for (int i = spines.Count - 1; i >= 0; i--)
- {
- if (cancelToken is not null && cancelToken.IsCancellationRequested)
- break; // 提前中止
-
- var spine = spines[i];
- spine.Update(delta);
-
- spine.IsDebug = true;
- RenderWindow.Draw(spine);
- spine.IsDebug = false;
-
- if (spine.IsSelected)
- {
- var bounds = spine.Bounds;
- BoundsRect[0] = BoundsRect[4] = new(new(bounds.Left, bounds.Top), BoundsColor);
- BoundsRect[1] = new(new(bounds.Right, bounds.Top), BoundsColor);
- BoundsRect[2] = new(new(bounds.Right, bounds.Bottom), BoundsColor);
- BoundsRect[3] = new(new(bounds.Left, bounds.Bottom), BoundsColor);
- RenderWindow.Draw(BoundsRect);
- }
- }
- }
- }
-
- RenderWindow.Display();
- }
- }
- finally
- {
- RenderWindow.SetActive(false);
- }
- }
+ ///
+ /// 渲染视窗
+ ///
+ public SFML.Graphics.View View => view;
}
}
diff --git a/SpineViewer/Dialogs/AboutDialog.cs b/SpineViewer/Dialogs/AboutDialog.cs
index 36e042f..b123164 100644
--- a/SpineViewer/Dialogs/AboutDialog.cs
+++ b/SpineViewer/Dialogs/AboutDialog.cs
@@ -15,16 +15,12 @@ namespace SpineViewer.Dialogs
public AboutDialog()
{
InitializeComponent();
- this.label_Version.Text = $"v{InformationalVersion}";
+ Text = $"关于 {Program.Name}";
+ label_Version.Text = $"v{InformationalVersion}";
}
- public string InformationalVersion
- {
- get
- {
- return Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion;
- }
- }
+ public string InformationalVersion =>
+ Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion;
private void linkLabel_RepoUrl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
@@ -36,7 +32,7 @@ namespace SpineViewer.Dialogs
else
{
Clipboard.SetText(url);
- MessageBox.Show(this, "链接已复制到剪贴板,请前往浏览器进行访问", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info("链接已复制到剪贴板,请前往浏览器进行访问");
}
}
}
diff --git a/SpineViewer/Dialogs/BatchOpenSpineDialog.cs b/SpineViewer/Dialogs/BatchOpenSpineDialog.cs
index e3879e5..387f777 100644
--- a/SpineViewer/Dialogs/BatchOpenSpineDialog.cs
+++ b/SpineViewer/Dialogs/BatchOpenSpineDialog.cs
@@ -13,6 +13,9 @@ namespace SpineViewer.Dialogs
{
public partial class BatchOpenSpineDialog : Form
{
+ ///
+ /// 对话框结果, 取消时为 null
+ ///
public BatchOpenSpineDialogResult Result { get; private set; }
public BatchOpenSpineDialog()
@@ -46,7 +49,7 @@ namespace SpineViewer.Dialogs
if (listBox_FilePath.Items.Count <= 0)
{
- MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info("未选择任何文件");
return;
}
@@ -54,14 +57,14 @@ namespace SpineViewer.Dialogs
{
if (!File.Exists(p))
{
- MessageBox.Show($"{p}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info($"{p}", "skel文件不存在");
return;
}
}
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
{
- MessageBox.Show($"{version.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info($"{version.GetName()} 版本尚未实现(咕咕咕~)");
return;
}
@@ -75,9 +78,19 @@ namespace SpineViewer.Dialogs
}
}
+ ///
+ /// 批量打开对话框结果
+ ///
public class BatchOpenSpineDialogResult(Spine.Version version, string[] skelPaths)
{
- public Spine.Version Version { get; } = version;
- public string[] SkelPaths { get; } = skelPaths;
+ ///
+ /// 版本
+ ///
+ public Spine.Version Version => version;
+
+ ///
+ /// 路径列表
+ ///
+ public string[] SkelPaths => skelPaths;
}
}
diff --git a/SpineViewer/Dialogs/ConvertFileFormatDialog.cs b/SpineViewer/Dialogs/ConvertFileFormatDialog.cs
index 05f193b..9a3907d 100644
--- a/SpineViewer/Dialogs/ConvertFileFormatDialog.cs
+++ b/SpineViewer/Dialogs/ConvertFileFormatDialog.cs
@@ -13,6 +13,8 @@ namespace SpineViewer.Dialogs
{
public partial class ConvertFileFormatDialog : Form
{
+ // TODO: 增加版本转换选项
+ // TODO: 使用结果包装类
public string[] SkelPaths { get; private set; }
public Spine.Version SourceVersion { get; private set; }
public Spine.Version TargetVersion { get; private set; }
@@ -62,7 +64,7 @@ namespace SpineViewer.Dialogs
if (listBox_FilePath.Items.Count <= 0)
{
- MessageBox.Show("未选择任何文件", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info("未选择任何文件");
return;
}
@@ -70,26 +72,26 @@ namespace SpineViewer.Dialogs
{
if (!File.Exists(p))
{
- MessageBox.Show($"{p}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info($"{p}", "skel文件不存在");
return;
}
}
if (!SkeletonConverter.ImplementedVersions.Contains(sourceVersion))
{
- MessageBox.Show($"{sourceVersion.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info($"{sourceVersion.GetName()} 版本尚未实现(咕咕咕~)");
return;
}
if (!SkeletonConverter.ImplementedVersions.Contains(targetVersion))
{
- MessageBox.Show($"{targetVersion.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info($"{targetVersion.GetName()} 版本尚未实现(咕咕咕~)");
return;
}
if (jsonSource == jsonTarget && sourceVersion == targetVersion)
{
- MessageBox.Show($"不需要转换相同的格式和版本", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info($"不需要转换相同的格式和版本");
return;
}
diff --git a/SpineViewer/Dialogs/DiagnosticsDialog.cs b/SpineViewer/Dialogs/DiagnosticsDialog.cs
index aabb0f4..3be1a08 100644
--- a/SpineViewer/Dialogs/DiagnosticsDialog.cs
+++ b/SpineViewer/Dialogs/DiagnosticsDialog.cs
@@ -82,11 +82,11 @@ namespace SpineViewer.Dialogs
private void button_Copy_Click(object sender, EventArgs e)
{
- var selectedObject = propertyGrid.SelectedObject as DiagnosticsInformation;
+ var selectedObject = (DiagnosticsInformation)propertyGrid.SelectedObject;
var properties = selectedObject.GetType().GetProperties();
var result = string.Join(Environment.NewLine, properties.Select(p => $"{p.Name}\t{p.GetValue(selectedObject)?.ToString()}"));
Clipboard.SetText(result);
- MessageBox.Show(this, "已复制", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info("已复制");
}
}
}
diff --git a/SpineViewer/Dialogs/ExportPngDialog.cs b/SpineViewer/Dialogs/ExportPngDialog.cs
index f5f83d6..ddf6397 100644
--- a/SpineViewer/Dialogs/ExportPngDialog.cs
+++ b/SpineViewer/Dialogs/ExportPngDialog.cs
@@ -12,6 +12,8 @@ namespace SpineViewer.Dialogs
{
public partial class ExportPngDialog : Form
{
+ // TODO: 该对话框要合并到统一的导出参数对话框
+ // TODO: 使用结果包装类
public string OutputDir { get; private set; }
public float Duration { get; private set; }
public uint Fps { get; private set; }
@@ -40,27 +42,23 @@ namespace SpineViewer.Dialogs
var outputDir = textBox_OutputDir.Text;
if (File.Exists(outputDir))
{
- MessageBox.Show("输出文件夹无效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info("输出文件夹无效");
return;
}
if (!Directory.Exists(outputDir))
{
- if (MessageBox.Show($"文件夹 {outputDir} 不存在,是否创建?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
+ if (MessageBox.Quest($"文件夹 {outputDir} 不存在,是否创建?") != DialogResult.OK)
+ return;
+
+ try
{
- try
- {
- Directory.CreateDirectory(outputDir);
- }
- catch (Exception ex)
- {
- Program.Logger.Error(ex.ToString());
- MessageBox.Show(ex.ToString(), "文件夹创建失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
- return;
- }
+ Directory.CreateDirectory(outputDir);
}
- else
+ catch (Exception ex)
{
+ Program.Logger.Error(ex.ToString());
+ MessageBox.Error(ex.ToString(), "文件夹创建失败");
return;
}
}
diff --git a/SpineViewer/Dialogs/ExportPreviewDialog.cs b/SpineViewer/Dialogs/ExportPreviewDialog.cs
index bf56126..0353e59 100644
--- a/SpineViewer/Dialogs/ExportPreviewDialog.cs
+++ b/SpineViewer/Dialogs/ExportPreviewDialog.cs
@@ -12,6 +12,7 @@ namespace SpineViewer.Dialogs
{
public partial class ExportPreviewDialog: Form
{
+ // TODO: 用单独的结果包装类
public string OutputDir { get; private set; }
public uint PreviewWidth { get; private set; }
public uint PreviewHeight { get; private set; }
@@ -40,27 +41,23 @@ namespace SpineViewer.Dialogs
var outputDir = textBox_OutputDir.Text;
if (File.Exists(outputDir))
{
- MessageBox.Show("输出文件夹无效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info("输出文件夹无效");
return;
}
if (!Directory.Exists(outputDir))
{
- if (MessageBox.Show($"文件夹 {outputDir} 不存在,是否创建?", "操作确认", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
+ if (MessageBox.Quest($"文件夹 {outputDir} 不存在,是否创建?") != DialogResult.OK)
+ return;
+
+ try
{
- try
- {
- Directory.CreateDirectory(outputDir);
- }
- catch (Exception ex)
- {
- Program.Logger.Error(ex.ToString());
- MessageBox.Show(ex.ToString(), "文件夹创建失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
- return;
- }
+ Directory.CreateDirectory(outputDir);
}
- else
+ catch (Exception ex)
{
+ Program.Logger.Error(ex.ToString());
+ MessageBox.Error(ex.ToString(), "文件夹创建失败");
return;
}
}
diff --git a/SpineViewer/Dialogs/OpenSpineDialog.cs b/SpineViewer/Dialogs/OpenSpineDialog.cs
index 0812188..e754998 100644
--- a/SpineViewer/Dialogs/OpenSpineDialog.cs
+++ b/SpineViewer/Dialogs/OpenSpineDialog.cs
@@ -12,6 +12,9 @@ namespace SpineViewer.Dialogs
{
public partial class OpenSpineDialog : Form
{
+ ///
+ /// 对话框结果
+ ///
public OpenSpineDialogResult Result { get; private set; }
public OpenSpineDialog()
@@ -54,7 +57,7 @@ namespace SpineViewer.Dialogs
if (!File.Exists(skelPath))
{
- MessageBox.Show($"{skelPath}", "skel文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info($"{skelPath}", "skel文件不存在");
return;
}
else
@@ -68,7 +71,7 @@ namespace SpineViewer.Dialogs
}
else if (!File.Exists(atlasPath))
{
- MessageBox.Show($"{atlasPath}", "atlas文件不存在", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info($"{atlasPath}", "atlas文件不存在");
return;
}
else
@@ -78,7 +81,7 @@ namespace SpineViewer.Dialogs
if (version != Spine.Version.Auto && !Spine.Spine.ImplementedVersions.Contains(version))
{
- MessageBox.Show($"{version.GetName()} 版本尚未实现(咕咕咕~)", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info($"{version.GetName()} 版本尚未实现(咕咕咕~)");
return;
}
@@ -92,10 +95,24 @@ namespace SpineViewer.Dialogs
}
}
+ ///
+ /// 打开骨骼对话框结果
+ ///
public class OpenSpineDialogResult(Spine.Version version, string skelPath, string? atlasPath = null)
{
- public Spine.Version Version { get; } = version;
- public string SkelPath { get; } = skelPath;
- public string? AtlasPath { get; } = atlasPath;
+ ///
+ /// 版本
+ ///
+ public Spine.Version Version => version;
+
+ ///
+ /// skel 文件路径
+ ///
+ public string SkelPath => skelPath;
+
+ ///
+ /// atlas 文件路径
+ ///
+ public string? AtlasPath => atlasPath;
}
}
diff --git a/SpineViewer/Dialogs/ProgressDialog.cs b/SpineViewer/Dialogs/ProgressDialog.cs
index 19397ed..b1fb3c6 100644
--- a/SpineViewer/Dialogs/ProgressDialog.cs
+++ b/SpineViewer/Dialogs/ProgressDialog.cs
@@ -12,15 +12,25 @@ namespace SpineViewer.Dialogs
{
public partial class ProgressDialog : Form
{
+ ///
+ /// BackgroundWorker.DoWork 接口暴露
+ ///
[Category("自定义"), Description("BackgroundWorker 的 DoWork 事件")]
public event DoWorkEventHandler? DoWork
{
- add { backgroundWorker.DoWork += value; }
- remove { backgroundWorker.DoWork -= value; }
+ add => backgroundWorker.DoWork += value;
+ remove => backgroundWorker.DoWork -= value;
}
- public void RunWorkerAsync() { backgroundWorker.RunWorkerAsync(); }
- public void RunWorkerAsync(object? argument) { backgroundWorker.RunWorkerAsync(argument); }
+ ///
+ /// 启动后台执行
+ ///
+ public void RunWorkerAsync() => backgroundWorker.RunWorkerAsync();
+
+ ///
+ /// 使用给定参数启动后台执行
+ ///
+ public void RunWorkerAsync(object? argument) => backgroundWorker.RunWorkerAsync(argument);
public ProgressDialog()
{
@@ -38,7 +48,7 @@ namespace SpineViewer.Dialogs
if (e.Error != null)
{
Program.Logger.Error(e.Error.ToString());
- MessageBox.Show(e.Error.ToString(), "执行出错", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ MessageBox.Error(e.Error.ToString(), "执行出错");
DialogResult = DialogResult.Abort;
}
else if (e.Cancelled)
diff --git a/SpineViewer/MainForm.cs b/SpineViewer/MainForm.cs
index 0ebcad9..6103afd 100644
--- a/SpineViewer/MainForm.cs
+++ b/SpineViewer/MainForm.cs
@@ -1,9 +1,13 @@
-using NLog;
+using FFMpegCore.Pipes;
+using FFMpegCore;
+using NLog;
+using SFML.System;
using SpineViewer.Spine;
using System.ComponentModel;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Nodes;
+using FFMpegCore.Enums;
namespace SpineViewer
{
@@ -65,11 +69,12 @@ namespace SpineViewer
private void toolStripMenuItem_Export_Click(object sender, EventArgs e)
{
+ // TODO: 改成统一导出调用
lock (spineListView.Spines)
{
if (spineListView.Spines.Count <= 0)
{
- MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info("请至少打开一个骨骼文件");
return;
}
}
@@ -90,7 +95,7 @@ namespace SpineViewer
{
if (spineListView.Spines.Count <= 0)
{
- MessageBox.Show("请至少打开一个骨骼文件", "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Info("请至少打开一个骨骼文件");
return;
}
}
@@ -131,9 +136,70 @@ namespace SpineViewer
progressDialog.ShowDialog();
}
+ //IEnumerable testExport(int fps)
+ //{
+ // var duration = 2f;
+ // var resolution = spinePreviewer.Resolution;
+ // var delta = 1f / fps;
+ // var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
+
+ // var spinesReverse = spineListView.Spines.Reverse();
+
+ // // 重置动画时间
+ // foreach (var spine in spinesReverse)
+ // spine.CurrentAnimation = spine.CurrentAnimation;
+
+ // // 逐帧导出
+ // var success = 0;
+ // for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
+ // {
+ // using var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
+ // tex.SetView(spinePreviewer.View);
+ // tex.Clear(SFML.Graphics.Color.Transparent);
+
+ // foreach (var spine in spinesReverse)
+ // {
+ // tex.Draw(spine);
+ // spine.Update(delta);
+ // }
+
+ // tex.Display();
+ // Debug.WriteLine($"ThreadID: {Environment.CurrentManagedThreadId}");
+ // var frame = tex.Texture.CopyToFrame();
+ // tex.Dispose();
+ // yield return frame;
+
+ // success++;
+ // }
+
+ // Program.Logger.Info("Exporting done: {}/{}", success, frameCount);
+ //}
+
private void toolStripMenuItem_ManageResource_Click(object sender, EventArgs e)
{
-
+ //spinePreviewer.StopPreview();
+
+ //lock (spineListView.Spines)
+ //{
+ // //var fps = 24;
+ // ////foreach (var i in testExport(fps))
+ // //// _ = i;
+ // ////var t = testExport(fps).ToArray();
+ // ////var a = testExport(fps).GetEnumerator();
+ // ////while (a.MoveNext());
+ // //var videoFramesSource = new RawVideoPipeSource(testExport(fps)) { FrameRate = fps };
+ // //var outputPath = @"C:\Users\ljh\Desktop\test\a.mov";
+ // //var task = FFMpegArguments
+ // // .FromPipeInput(videoFramesSource)
+ // // .OutputToFile(outputPath, true
+ // // , options => options
+ // // //.WithCustomArgument("-vf \"split[s0][s1];[s0]palettegen=reserve_transparent=1[p];[s1][p]paletteuse=alpha_threshold=128\""))
+ // // .WithCustomArgument("-c:v prores_ks -profile:v 4444 -pix_fmt yuva444p10le"))
+ // // .ProcessAsynchronously();
+ // //task.Wait();
+ //}
+
+ //spinePreviewer.StartPreview();
}
private void toolStripMenuItem_About_Click(object sender, EventArgs e)
@@ -146,13 +212,16 @@ namespace SpineViewer
(new Dialogs.DiagnosticsDialog()).ShowDialog();
}
- private void splitContainer_SplitterMoved(object sender, SplitterEventArgs e) { ActiveControl = null; }
+ private void splitContainer_SplitterMoved(object sender, SplitterEventArgs e) => ActiveControl = null;
- private void splitContainer_MouseUp(object sender, MouseEventArgs e) { ActiveControl = null; }
+ private void splitContainer_MouseUp(object sender, MouseEventArgs e) => ActiveControl = null;
- private void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e) { (sender as PropertyGrid)?.Refresh(); }
+ private void propertyGrid_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e) => (sender as PropertyGrid)?.Refresh();
- private void spinePreviewer_MouseUp(object sender, MouseEventArgs e) { propertyGrid_Spine.Refresh(); }
+ private void spinePreviewer_MouseUp(object sender, MouseEventArgs e)
+ {
+ propertyGrid_Spine.Refresh();
+ }
private void ExportPng_Work(object? sender, DoWorkEventArgs e)
{
@@ -163,9 +232,11 @@ namespace SpineViewer
var fps = arguments.Fps;
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
- var resolution = spinePreviewer.Resolution;
+ var frameArgs = spinePreviewer.GetFrameArgs();
+
+ var resolution = frameArgs.Resolution;
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
- tex.SetView(spinePreviewer.View);
+ tex.SetView(frameArgs.View);
var delta = 1f / fps;
var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
@@ -284,7 +355,7 @@ namespace SpineViewer
Program.Logger.Info("{} preview saved successfully", success);
}
- Program.Logger.Info($"Current memory usage: {Program.Process.WorkingSet64 / 1024.0 / 1024.0:F2} MB");
+ Program.LogCurrentMemoryUsage();
}
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
diff --git a/SpineViewer/MessageBox.cs b/SpineViewer/MessageBox.cs
new file mode 100644
index 0000000..cf9b249
--- /dev/null
+++ b/SpineViewer/MessageBox.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace SpineViewer
+{
+ ///
+ /// 弹窗消息静态类
+ ///
+ public static class MessageBox
+ {
+ ///
+ /// 提示弹窗
+ ///
+ public static void Info(string text, string title = "提示信息") =>
+ System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Information);
+
+ ///
+ /// 警告弹窗
+ ///
+ public static void Warn(string text, string title = "警告信息") =>
+ System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+
+ ///
+ /// 错误弹窗
+ ///
+ public static void Error(string text, string title = "错误信息") =>
+ System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
+
+ ///
+ /// 操作确认弹窗
+ ///
+ public static DialogResult Quest(string text, string title = "操作确认") =>
+ System.Windows.Forms.MessageBox.Show(text, title, MessageBoxButtons.OKCancel, MessageBoxIcon.Question);
+ }
+}
diff --git a/SpineViewer/Program.cs b/SpineViewer/Program.cs
index 9b97608..127c06e 100644
--- a/SpineViewer/Program.cs
+++ b/SpineViewer/Program.cs
@@ -55,7 +55,7 @@ namespace SpineViewer
catch (Exception ex)
{
Logger.Fatal(ex.ToString());
- MessageBox.Show(ex.ToString(), "程序已崩溃", MessageBoxButtons.OK, MessageBoxIcon.Stop);
+ MessageBox.Error(ex.ToString(), "程序已崩溃");
}
}
@@ -83,5 +83,9 @@ namespace SpineViewer
LogManager.Configuration = config;
}
+ ///
+ /// 输出当前内存使用情况
+ ///
+ public static void LogCurrentMemoryUsage() => Logger.Info("Current memory usage: {:F2} MB", Process.WorkingSet64 / 1024.0 / 1024.0);
}
}
\ No newline at end of file
diff --git a/SpineViewer/Spine/Spine.cs b/SpineViewer/Spine/Spine.cs
index cd6a2ea..408872f 100644
--- a/SpineViewer/Spine/Spine.cs
+++ b/SpineViewer/Spine/Spine.cs
@@ -112,7 +112,7 @@ namespace SpineViewer.Spine
FragmentShader = null;
Program.Logger.Error(ex.ToString());
Program.Logger.Error("Failed to load fragment shader");
- MessageBox.Show("Fragment shader 加载失败,预乘Alpha通道属性失效", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ MessageBox.Warn("Fragment shader 加载失败,预乘Alpha通道属性失效");
}
}
@@ -415,7 +415,7 @@ namespace SpineViewer.Spine
protected SFML.Graphics.VertexArray vertexArray = new(SFML.Graphics.PrimitiveType.Triangles);
///
- /// SFML.Graphics.Drawable 接口实现
+ /// SFML.Graphics.Drawable 接口实现 TODO: 增加调试内容绘制
///
public abstract void Draw(SFML.Graphics.RenderTarget target, SFML.Graphics.RenderStates states);
diff --git a/SpineViewer/Spine/Version.cs b/SpineViewer/Spine/Version.cs
index 77f21c3..ae41742 100644
--- a/SpineViewer/Spine/Version.cs
+++ b/SpineViewer/Spine/Version.cs
@@ -9,6 +9,9 @@ using System.Threading.Tasks;
namespace SpineViewer.Spine
{
+ ///
+ /// Spine 版本静态辅助类
+ ///
public static class VersionHelper
{
///