From 2b39384b28b034c42581fbf77010512210f59900 Mon Sep 17 00:00:00 2001 From: ww-rm Date: Sun, 23 Mar 2025 01:34:44 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BB=A5=E5=8F=8A=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpineViewer/Controls/SpineListView.cs | 319 +++++++++--------- SpineViewer/Controls/SpinePreviewer.cs | 218 +++++++----- SpineViewer/Dialogs/AboutDialog.cs | 14 +- SpineViewer/Dialogs/BatchOpenSpineDialog.cs | 23 +- .../Dialogs/ConvertFileFormatDialog.cs | 12 +- SpineViewer/Dialogs/DiagnosticsDialog.cs | 4 +- SpineViewer/Dialogs/ExportPngDialog.cs | 24 +- SpineViewer/Dialogs/ExportPreviewDialog.cs | 23 +- SpineViewer/Dialogs/OpenSpineDialog.cs | 29 +- SpineViewer/Dialogs/ProgressDialog.cs | 20 +- SpineViewer/MainForm.cs | 93 ++++- SpineViewer/MessageBox.cs | 39 +++ SpineViewer/Program.cs | 6 +- SpineViewer/Spine/Spine.cs | 4 +- SpineViewer/Spine/Version.cs | 3 + 15 files changed, 524 insertions(+), 307 deletions(-) create mode 100644 SpineViewer/MessageBox.cs 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 { ///