diff --git a/SpineViewer/Resources/AppResource.cs b/SpineViewer/Resources/AppResource.cs index 7080b3f..0f076b0 100644 --- a/SpineViewer/Resources/AppResource.cs +++ b/SpineViewer/Resources/AppResource.cs @@ -19,6 +19,7 @@ namespace SpineViewer.Resources public static string Str_GeneratePreviewsTitle => Get("Str_GeneratePreviewsTitle"); public static string Str_DeletePreviewsTitle => Get("Str_DeletePreviewsTitle"); public static string Str_AddSpineObjectsTitle => Get("Str_AddSpineObjectsTitle"); + public static string Str_ReloadSpineObjectsTitle => Get("Str_ReloadSpineObjectsTitle"); public static string Str_CustomFFmpegExporterTitle => Get("Str_CustomFFmpegExporterTitle"); public static string Str_InfoPopup => Get("Str_InfoPopup"); diff --git a/SpineViewer/Resources/Strings/en.xaml b/SpineViewer/Resources/Strings/en.xaml index 411d34d..2b1301e 100644 --- a/SpineViewer/Resources/Strings/en.xaml +++ b/SpineViewer/Resources/Strings/en.xaml @@ -36,9 +36,10 @@ {0} items, {1} selected Add... Remove + Add from Clipboard + Reload Move Up Move Down - Add from Clipboard Copy Config Apply Config Save Config to File... @@ -120,6 +121,7 @@ Generate Previews Delete Previews Batch Add Spine Files + Reload Spine Files Information Warning diff --git a/SpineViewer/Resources/Strings/ja.xaml b/SpineViewer/Resources/Strings/ja.xaml index 52c3263..50d9bab 100644 --- a/SpineViewer/Resources/Strings/ja.xaml +++ b/SpineViewer/Resources/Strings/ja.xaml @@ -36,9 +36,10 @@ 全{0}件、選択中{1}件 追加... 削除 + クリップボードから追加 + 再読み込み 上へ移動 下へ移動 - クリップボードから追加 パラメーターをコピー パラメーターを適用 パラメータファイルを保存... @@ -120,6 +121,7 @@ プレビューを生成 プレビューを削除 一括でスケルトンファイルを追加 + スケルトンファイルの再読み込み 情報 警告 diff --git a/SpineViewer/Resources/Strings/zh.xaml b/SpineViewer/Resources/Strings/zh.xaml index dd75549..789e986 100644 --- a/SpineViewer/Resources/Strings/zh.xaml +++ b/SpineViewer/Resources/Strings/zh.xaml @@ -36,9 +36,10 @@ 共 {0} 项,已选择 {1} 项 添加... 移除 + 从剪贴板添加 + 重新加载 上移 下移 - 从剪贴板添加 复制参数 应用参数 保存参数文件... @@ -120,6 +121,7 @@ 生成预览图 删除预览图 批量添加骨骼文件 + 重新加载骨骼文件 提示信息 警告信息 diff --git a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs index ac6db0f..c87bf5a 100644 --- a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs @@ -15,6 +15,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; +using System.Windows.Shapes; using System.Windows.Shell; namespace SpineViewer.ViewModels.MainWindow @@ -111,7 +112,7 @@ namespace SpineViewer.ViewModels.MainWindow private void RemoveSpineObject_Execute(IList? args) { - if (args is null) return; + if (!RemoveSpineObject_CanExecute(args)) return; if (args.Count > 1) { @@ -137,6 +138,121 @@ namespace SpineViewer.ViewModels.MainWindow return true; } + /// + /// 从剪贴板文件列表添加模型 + /// + public RelayCommand Cmd_AddSpineObjectFromClipboard => _cmd_AddSpineObjectFromClipboard ??= new(AddSpineObjectFromClipboard_Execute); + private RelayCommand? _cmd_AddSpineObjectFromClipboard; + + private void AddSpineObjectFromClipboard_Execute() + { + if (!Clipboard.ContainsFileDropList()) return; + AddSpineObjectFromFileList(Clipboard.GetFileDropList().Cast().ToArray()); + } + + /// + /// 重新加载模型 + /// + public RelayCommand Cmd_ReloadSpineObject => _cmd_ReloadSpineObject ??= new(ReloadSpineObject_Execute, ReloadSpineObject_CanExecute); + private RelayCommand? _cmd_ReloadSpineObject; + + private void ReloadSpineObject_Execute(IList? args) + { + if (!ReloadSpineObject_CanExecute(args)) return; + + if (args.Count <= 1) + { + lock (_spineObjectModels.Lock) + { + var sp = (SpineObjectModel)args[0]; + var idx = _spineObjectModels.IndexOf(sp); + if (idx < 0) return; + + try + { + var spNew = new SpineObjectModel(sp.SkelPath, sp.AtlasPath); + spNew.Load(sp.Dump()); + _spineObjectModels[idx] = spNew; + sp.Dispose(); + } + catch (Exception ex) + { + _logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message); + _logger.Trace(ex.ToString()); + } + } + } + else + { + ProgressService.RunAsync((pr, ct) => ReloadSpineObjectsTask( + args.Cast().ToArray(), pr, ct), + AppResource.Str_ReloadSpineObjectsTitle + ); + } + } + + private bool ReloadSpineObject_CanExecute(IList? args) + { + if (args is null) return false; + if (args.Count <= 0) return false; + return true; + } + + private void ReloadSpineObjectsTask(SpineObjectModel[] spines, IProgressReporter reporter, CancellationToken ct) + { + int totalCount = spines.Length; + int success = 0; + int error = 0; + + _vmMain.ProgressState = TaskbarItemProgressState.Normal; + _vmMain.ProgressValue = 0; + + reporter.Total = totalCount; + reporter.Done = 0; + reporter.ProgressText = $"[0/{totalCount}]"; + for (int i = 0; i < totalCount; i++) + { + if (ct.IsCancellationRequested) break; + + var sp = spines[i]; + reporter.ProgressText = $"[{i}/{totalCount}] {sp.Name}"; + + lock (_spineObjectModels.Lock) + { + var idx = _spineObjectModels.IndexOf(sp); + if (idx >= 0) + { + try + { + var spNew = new SpineObjectModel(sp.SkelPath, sp.AtlasPath); + spNew.Load(sp.Dump()); + _spineObjectModels[idx] = spNew; + sp.Dispose(); + success++; + } + catch (Exception ex) + { + error++; + _logger.Error("Failed to reload spine {0}, {1}", sp.SkelPath, ex.Message); + _logger.Trace(ex.ToString()); + } + } + } + + reporter.Done = i + 1; + reporter.ProgressText = $"[{i + 1}/{totalCount}] {sp.Name}"; + _vmMain.ProgressValue = (i + 1f) / totalCount; + } + _vmMain.ProgressState = TaskbarItemProgressState.None; + + if (error > 0) + _logger.Warn("Batch reload {0} successfully, {1} failed", success, error); + else + _logger.Info("{0} skel reloaded successfully", success); + + _logger.LogCurrentProcessMemoryUsage(); + } + /// /// 模型上移一位 /// @@ -145,8 +261,7 @@ namespace SpineViewer.ViewModels.MainWindow private void MoveUpSpineObject_Execute(IList? args) { - if (args is null) return; - if (args.Count != 1) return; + if (!MoveUpSpineObject_CanExecute(args)) return; var sp = (SpineObjectModel)args[0]; lock (_spineObjectModels.Lock) { @@ -171,8 +286,7 @@ namespace SpineViewer.ViewModels.MainWindow private void MoveDownSpineObject_Execute(IList? args) { - if (args is null) return; - if (args.Count != 1) return; + if (!MoveDownSpineObject_CanExecute(args)) return; var sp = (SpineObjectModel)args[0]; lock (_spineObjectModels.Lock) { @@ -189,18 +303,6 @@ namespace SpineViewer.ViewModels.MainWindow return true; } - /// - /// 从剪贴板文件列表添加模型 - /// - public RelayCommand Cmd_AddSpineObjectFromClipboard => _cmd_AddSpineObjectFromClipboard ??= new(AddSpineObjectFromClipboard_Execute); - private RelayCommand? _cmd_AddSpineObjectFromClipboard; - - private void AddSpineObjectFromClipboard_Execute() - { - if (!Clipboard.ContainsFileDropList()) return; - AddSpineObjectFromFileList(Clipboard.GetFileDropList().Cast().ToArray()); - } - /// /// 复制模型参数 /// @@ -209,8 +311,7 @@ namespace SpineViewer.ViewModels.MainWindow private void CopySpineObjectConfig_Execute(IList? args) { - if (args is null) return; - if (args.Count != 1) return; + if (!CopySpineObjectConfig_CanExecute(args)) return; var sp = (SpineObjectModel)args[0]; _copiedSpineObjectConfigModel = sp.Dump(); _logger.Info("Copy config from model: {0}", sp.Name); @@ -231,9 +332,7 @@ namespace SpineViewer.ViewModels.MainWindow private void ApplySpineObjectConfig_Execute(IList? args) { - if (_copiedSpineObjectConfigModel is null) return; - if (args is null) return; - if (args.Count <= 0) return; + if (!ApplySpineObjectConfig_CanExecute(args)) return; foreach (SpineObjectModel sp in args) { sp.Load(_copiedSpineObjectConfigModel); diff --git a/SpineViewer/ViewModels/ProgressDialogViewModel.cs b/SpineViewer/ViewModels/ProgressDialogViewModel.cs index 982d80f..a677704 100644 --- a/SpineViewer/ViewModels/ProgressDialogViewModel.cs +++ b/SpineViewer/ViewModels/ProgressDialogViewModel.cs @@ -63,6 +63,7 @@ namespace SpineViewer.ViewModels private void Cancel_Execute() { + if (!Cancel_CanExecute()) return; if (!MessagePopupService.Quest(AppResource.Str_CancelQuest)) return; _cts.Cancel(); Cmd_Cancel.NotifyCanExecuteChanged(); diff --git a/SpineViewer/Views/MainWindow.xaml b/SpineViewer/Views/MainWindow.xaml index d6a82c1..12334c1 100644 --- a/SpineViewer/Views/MainWindow.xaml +++ b/SpineViewer/Views/MainWindow.xaml @@ -246,9 +246,10 @@ + + - @@ -262,6 +263,13 @@ InputGestureText="Delete" Command="{Binding Cmd_RemoveSpineObject}" CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/> + + - -