diff --git a/SpineViewer/Models/SpineObjectConfigModel.cs b/SpineViewer/Models/SpineObjectConfigModel.cs index 351ac3e..49df019 100644 --- a/SpineViewer/Models/SpineObjectConfigModel.cs +++ b/SpineViewer/Models/SpineObjectConfigModel.cs @@ -1,4 +1,5 @@ using Spine.Interfaces; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.IO; @@ -13,6 +14,15 @@ namespace SpineViewer.Models { public class SpineObjectConfigModel { + public class TrackConfigModel + { + public string AnimationName { get; set; } = ""; + + public float TimeScale { get; set; } = 1f; + + public float Alpha { get; set; } = 1f; + } + public bool UsePma { get; set; } public string Physics { get; set; } = ISkeleton.Physics.Update.ToString(); @@ -57,14 +67,6 @@ namespace SpineViewer.Models public bool DebugClippings { get; set; } - } - - public class TrackConfigModel - { - public string AnimationName { get; set; } = ""; - - public float TimeScale { get; set; } = 1f; - - public float Alpha { get; set; } = 1f; + public override string ToString() => JsonHelper.Serialize(this); } } diff --git a/SpineViewer/Models/SpineObjectModel.cs b/SpineViewer/Models/SpineObjectModel.cs index e3a17b7..ee0dd97 100644 --- a/SpineViewer/Models/SpineObjectModel.cs +++ b/SpineViewer/Models/SpineObjectModel.cs @@ -91,6 +91,8 @@ namespace SpineViewer.Models public event EventHandler? TrackPropertyChanged; + #region 参数面板实现 + public SpineVersion Version => _spineObject.Version; public string AssetsDir => _spineObject.AssetsDir; @@ -407,6 +409,8 @@ namespace SpineViewer.Models set { lock (_lock) SetProperty(_spineObject.DebugClippings, value, v => _spineObject.DebugClippings = v); } } + #endregion + public void Update(float delta) { lock (_lock) _spineObject.Update(delta); @@ -493,41 +497,57 @@ namespace SpineViewer.Models return config; } } - set + set => ApplyObjectConfig(value); + } + + public void ApplyObjectConfig(SpineObjectConfigModel m, SpineObjectConfigApplyFlag flag = SpineObjectConfigApplyFlag.All) + { + lock (_lock) { - - lock (_lock) + if (flag == SpineObjectConfigApplyFlag.All) { - _spineObject.Skeleton.ScaleX = value.Scale; - _spineObject.Skeleton.ScaleY = value.Scale; + _spineObject.Skeleton.ScaleX = m.Scale; + _spineObject.Skeleton.ScaleY = m.Scale; OnPropertyChanged(nameof(Scale)); - SetProperty(_spineObject.Skeleton.ScaleX < 0, value.FlipX, v => _spineObject.Skeleton.ScaleX *= -1, nameof(FlipX)); - SetProperty(_spineObject.Skeleton.ScaleY < 0, value.FlipY, v => _spineObject.Skeleton.ScaleY *= -1, nameof(FlipY)); - SetProperty(_spineObject.Skeleton.X, value.X, v => _spineObject.Skeleton.X = v, nameof(X)); - SetProperty(_spineObject.Skeleton.Y, value.Y, v => _spineObject.Skeleton.Y = v, nameof(Y)); - SetProperty(_spineObject.UsePma, value.UsePma, v => _spineObject.UsePma = v, nameof(UsePma)); - SetProperty(_spineObject.Physics, Enum.Parse(value.Physics ?? "Update", true), v => _spineObject.Physics = v, nameof(Physics)); - SetProperty(_spineObject.AnimationState.TimeScale, value.TimeScale, v => _spineObject.AnimationState.TimeScale = v, nameof(TimeScale)); + SetProperty(_spineObject.Skeleton.ScaleX < 0, m.FlipX, v => _spineObject.Skeleton.ScaleX *= -1, nameof(FlipX)); + SetProperty(_spineObject.Skeleton.ScaleY < 0, m.FlipY, v => _spineObject.Skeleton.ScaleY *= -1, nameof(FlipY)); + SetProperty(_spineObject.Skeleton.X, m.X, v => _spineObject.Skeleton.X = v, nameof(X)); + SetProperty(_spineObject.Skeleton.Y, m.Y, v => _spineObject.Skeleton.Y = v, nameof(Y)); + SetProperty(_spineObject.UsePma, m.UsePma, v => _spineObject.UsePma = v, nameof(UsePma)); + SetProperty(_spineObject.Physics, Enum.Parse(m.Physics ?? "Update", true), v => _spineObject.Physics = v, nameof(Physics)); + SetProperty(_spineObject.AnimationState.TimeScale, m.TimeScale, v => _spineObject.AnimationState.TimeScale = v, nameof(TimeScale)); + } - foreach (var name in _spineObject.Data.Skins.Select(v => v.Name).Except(value.LoadedSkins)) + if (flag == SpineObjectConfigApplyFlag.All || flag == SpineObjectConfigApplyFlag.Skin) + { + foreach (var name in _spineObject.Data.Skins.Select(v => v.Name).Except(m.LoadedSkins)) if (_spineObject.SetSkinStatus(name, false)) SkinStatusChanged?.Invoke(this, new(name, false)); - foreach (var name in value.LoadedSkins) + foreach (var name in m.LoadedSkins) if (_spineObject.SetSkinStatus(name, true)) SkinStatusChanged?.Invoke(this, new(name, true)); + } - foreach (var (slotName, attachmentName) in value.SlotAttachment) + if (flag == SpineObjectConfigApplyFlag.SlotAttachement) + { + foreach (var (slotName, attachmentName) in m.SlotAttachment) if (_spineObject.SetAttachment(slotName, attachmentName)) SlotAttachmentChanged?.Invoke(this, new(slotName, attachmentName)); + } - foreach (var slotName in value.DisabledSlots) + if (flag == SpineObjectConfigApplyFlag.SlotVisibility) + { + foreach (var slotName in m.DisabledSlots) if (_spineObject.SetSlotVisible(slotName, false)) SlotVisibleChanged?.Invoke(this, new(slotName, false)); + } + if (flag == SpineObjectConfigApplyFlag.All) + { // XXX: 处理空动画 _spineObject.AnimationState.ClearTracks(); int trackIndex = 0; - foreach (var trConfig in value.Animations) + foreach (var trConfig in m.Animations) { if (trConfig is not null && !string.IsNullOrEmpty(trConfig.AnimationName)) { @@ -545,16 +565,16 @@ namespace SpineViewer.Models _spineObject.Skeleton.SetSlotsToSetupPose(); } - SetProperty(_spineObject.DebugTexture, value.DebugTexture, v => _spineObject.DebugTexture = v, nameof(DebugTexture)); - SetProperty(_spineObject.DebugBounds, value.DebugBounds, v => _spineObject.DebugBounds = v, nameof(DebugBounds)); - SetProperty(_spineObject.DebugBones, value.DebugBones, v => _spineObject.DebugBones = v, nameof(DebugBones)); - SetProperty(_spineObject.DebugRegions, value.DebugRegions, v => _spineObject.DebugRegions = v, nameof(DebugRegions)); - SetProperty(_spineObject.DebugMeshHulls, value.DebugMeshHulls, v => _spineObject.DebugMeshHulls = v, nameof(DebugMeshHulls)); - SetProperty(_spineObject.DebugMeshes, value.DebugMeshes, v => _spineObject.DebugMeshes = v, nameof(DebugMeshes)); - SetProperty(_spineObject.DebugBoundingBoxes, value.DebugBoundingBoxes, v => _spineObject.DebugBoundingBoxes = v, nameof(DebugBoundingBoxes)); - SetProperty(_spineObject.DebugPaths, value.DebugPaths, v => _spineObject.DebugPaths = v, nameof(DebugPaths)); - SetProperty(_spineObject.DebugPoints, value.DebugPoints, v => _spineObject.DebugPoints = v, nameof(DebugPoints)); - SetProperty(_spineObject.DebugClippings, value.DebugClippings, v => _spineObject.DebugClippings = v, nameof(DebugClippings)); + SetProperty(_spineObject.DebugTexture, m.DebugTexture, v => _spineObject.DebugTexture = v, nameof(DebugTexture)); + SetProperty(_spineObject.DebugBounds, m.DebugBounds, v => _spineObject.DebugBounds = v, nameof(DebugBounds)); + SetProperty(_spineObject.DebugBones, m.DebugBones, v => _spineObject.DebugBones = v, nameof(DebugBones)); + SetProperty(_spineObject.DebugRegions, m.DebugRegions, v => _spineObject.DebugRegions = v, nameof(DebugRegions)); + SetProperty(_spineObject.DebugMeshHulls, m.DebugMeshHulls, v => _spineObject.DebugMeshHulls = v, nameof(DebugMeshHulls)); + SetProperty(_spineObject.DebugMeshes, m.DebugMeshes, v => _spineObject.DebugMeshes = v, nameof(DebugMeshes)); + SetProperty(_spineObject.DebugBoundingBoxes, m.DebugBoundingBoxes, v => _spineObject.DebugBoundingBoxes = v, nameof(DebugBoundingBoxes)); + SetProperty(_spineObject.DebugPaths, m.DebugPaths, v => _spineObject.DebugPaths = v, nameof(DebugPaths)); + SetProperty(_spineObject.DebugPoints, m.DebugPoints, v => _spineObject.DebugPoints = v, nameof(DebugPoints)); + SetProperty(_spineObject.DebugClippings, m.DebugClippings, v => _spineObject.DebugClippings = v, nameof(DebugClippings)); } } } @@ -613,6 +633,17 @@ namespace SpineViewer.Models #endregion } + /// + /// 可选的应用部分模型参数项 + /// + public enum SpineObjectConfigApplyFlag + { + All, + Skin, + SlotAttachement, + SlotVisibility, + } + public class SkinStatusChangedEventArgs(string name, bool status) : EventArgs { public string Name { get; } = name; diff --git a/SpineViewer/Resources/Strings/en.xaml b/SpineViewer/Resources/Strings/en.xaml index dea11f7..418e0c5 100644 --- a/SpineViewer/Resources/Strings/en.xaml +++ b/SpineViewer/Resources/Strings/en.xaml @@ -45,7 +45,11 @@ Reload Move Up Move Down + Model Config Copy Config + Copy Parameters (Skin Only) + Copy Parameters (Slot Attachments Only) + Copy Parameters (Slot Visibility Only) Apply Config Save Config to File... Apply Config from File... diff --git a/SpineViewer/Resources/Strings/ja.xaml b/SpineViewer/Resources/Strings/ja.xaml index f4919c5..766eb13 100644 --- a/SpineViewer/Resources/Strings/ja.xaml +++ b/SpineViewer/Resources/Strings/ja.xaml @@ -45,7 +45,11 @@ 再読み込み 上へ移動 下へ移動 + モデル設定 パラメーターをコピー + パラメーターをコピー(スキンのみ) + パラメーターをコピー(スロットのアタッチメントのみ) + パラメーターをコピー(スロットの表示状態のみ) パラメーターを適用 パラメータファイルを保存... パラメータファイルを適用... diff --git a/SpineViewer/Resources/Strings/zh.xaml b/SpineViewer/Resources/Strings/zh.xaml index 14af92e..95d462f 100644 --- a/SpineViewer/Resources/Strings/zh.xaml +++ b/SpineViewer/Resources/Strings/zh.xaml @@ -45,7 +45,11 @@ 重新加载 上移 下移 + 模型参数 复制参数 + 复制参数(仅皮肤) + 复制参数(仅插槽附件) + 复制参数(仅插槽可见性) 应用参数 保存参数文件... 应用参数文件... diff --git a/SpineViewer/Resources/Theme.xaml b/SpineViewer/Resources/Theme.xaml index bfff007..fb363f5 100644 --- a/SpineViewer/Resources/Theme.xaml +++ b/SpineViewer/Resources/Theme.xaml @@ -34,7 +34,7 @@ diff --git a/SpineViewer/Utils/JsonHelper.cs b/SpineViewer/Utils/JsonHelper.cs index 6efb3d2..f37c220 100644 --- a/SpineViewer/Utils/JsonHelper.cs +++ b/SpineViewer/Utils/JsonHelper.cs @@ -32,6 +32,7 @@ namespace SpineViewer.Utils private static readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true, + IndentSize = 4, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, AllowTrailingCommas = true, ReadCommentHandling = JsonCommentHandling.Skip @@ -47,7 +48,6 @@ namespace SpineViewer.Utils if (!quietForNotExist) { _logger.Error("Json file {0} not found", path); - MessagePopupService.Error($"Json file {path} not found"); } } else @@ -62,13 +62,11 @@ namespace SpineViewer.Utils return true; } _logger.Error("Null data in file {0}", path); - MessagePopupService.Error($"Null data in file {path}"); } catch (Exception ex) { _logger.Trace(ex.ToString()); _logger.Error("Failed to read json file {0}, {1}", path, ex.Message); - MessagePopupService.Error($"Failed to read json file {path}, {ex.ToString()}"); } } obj = default; @@ -90,11 +88,24 @@ namespace SpineViewer.Utils { _logger.Trace(ex.ToString()); _logger.Error("Failed to save json file {0}, {1}", path, ex.Message); - MessagePopupService.Error($"Failed to save json file {path}, {ex.ToString()}"); return false; } return true; } + + public static string Serialize(T obj) + { + try + { + return JsonSerializer.Serialize(obj, _jsonOptions); + } + catch (Exception ex) + { + _logger.Trace(ex.ToString()); + _logger.Error("Failed to serialize json object {0}", ex.Message); + return string.Empty; + } + } } public class ColorJsonConverter : JsonConverter diff --git a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs index c60d7e6..a6cda79 100644 --- a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs @@ -34,6 +34,7 @@ namespace SpineViewer.ViewModels.MainWindow /// 临时对象, 存储复制的模型参数 /// private SpineObjectConfigModel? _copiedSpineObjectConfigModel = null; + private SpineObjectConfigApplyFlag _copiedConfigFlag = SpineObjectConfigApplyFlag.All; public SpineObjectListViewModel(MainWindowViewModel mainViewModel) { @@ -99,6 +100,127 @@ namespace SpineViewer.ViewModels.MainWindow } } + /// + /// 从路径列表添加对象 + /// + /// 可以是文件和文件夹 + public void AddSpineObjectFromFileList(IEnumerable paths) + { + List validPaths = []; + foreach (var path in paths) + { + if (File.Exists(path)) + { + var lowerPath = path.ToLowerInvariant(); + if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith)) + validPaths.Add(path); + } + else if (Directory.Exists(path)) + { + foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)) + { + var lowerPath = file.ToLowerInvariant(); + if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith)) + validPaths.Add(file); + } + } + } + + if (validPaths.Count > 1) + { + if (validPaths.Count > 100) + { + if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_TooManyItemsToAddQuest, validPaths.Count))) + return; + } + ProgressService.RunAsync((pr, ct) => AddSpineObjectsTask( + validPaths.ToArray(), pr, ct), + AppResource.Str_AddSpineObjectsTitle + ); + } + else if (validPaths.Count > 0) + { + InsertSpineObject(validPaths[0]); + _logger.LogCurrentProcessMemoryUsage(); + } + } + + /// + /// 用于后台添加模型的任务方法 + /// + private void AddSpineObjectsTask(string[] paths, IProgressReporter reporter, CancellationToken ct) + { + int totalCount = paths.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 skelPath = paths[i]; + reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}"; + + if (InsertSpineObject(skelPath)) + success++; + else + error++; + + reporter.Done = i + 1; + reporter.ProgressText = $"[{i + 1}/{totalCount}] {skelPath}"; + _vmMain.ProgressValue = (i + 1f) / totalCount; + } + _vmMain.ProgressState = TaskbarItemProgressState.None; + + if (error > 0) + _logger.Warn("Batch load {0} successfully, {1} failed", success, error); + else + _logger.Info("{0} skel loaded successfully", success); + + _logger.LogCurrentProcessMemoryUsage(); + } + + /// + /// 安全地在列表头添加一个模型, 发生错误会输出日志 + /// + /// 是否添加成功 + private bool InsertSpineObject(string skelPath, string? atlasPath = null) + { + try + { + var sp = new SpineObjectModel(skelPath, atlasPath); + lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp); + if (Application.Current.Dispatcher.CheckAccess()) + { + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp)); + } + else + { + Application.Current.Dispatcher.Invoke(() => + { + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp)); + }); + } + return true; + } + catch (Exception ex) + { + _logger.Trace(ex.ToString()); + _logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message); + } + return false; + } + + #region 模型列表管理菜单项实现 + /// /// 弹窗添加单模型命令 /// @@ -349,18 +471,53 @@ namespace SpineViewer.ViewModels.MainWindow return true; } + #endregion + + #region 模型参数管理菜单项实现 + /// /// 复制模型参数 /// - public RelayCommand Cmd_CopySpineObjectConfig => _cmd_CopySpineObjectConfig ??= new(CopySpineObjectConfig_Execute, CopySpineObjectConfig_CanExecute); + public RelayCommand Cmd_CopySpineObjectConfig => _cmd_CopySpineObjectConfig ??= new( + args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.All), + CopySpineObjectConfig_CanExecute + ); private RelayCommand? _cmd_CopySpineObjectConfig; - private void CopySpineObjectConfig_Execute(IList? args) + /// + /// 复制模型参数 (仅皮肤) + /// + public RelayCommand Cmd_CopySpineObjectSkinConfig => _cmd_CopySpineObjectSkinConfig ??= new( + args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.Skin), + CopySpineObjectConfig_CanExecute + ); + private RelayCommand? _cmd_CopySpineObjectSkinConfig; + + /// + /// 复制模型参数 (仅插槽附件) + /// + public RelayCommand Cmd_CopySpineObjectSlotAttachmentConfig => _cmd_CopySpineObjectSlotAttachmentConfig ??= new( + args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.SlotAttachement), + CopySpineObjectConfig_CanExecute + ); + private RelayCommand? _cmd_CopySpineObjectSlotAttachmentConfig; + + /// + /// 复制模型参数 (仅插槽可见性) + /// + public RelayCommand Cmd_CopySpineObjectSlotVisibilityConfig => _cmd_CopySpineObjectSlotVisibilityConfig ??= new( + args => CopySpineObjectConfig_Execute(args, SpineObjectConfigApplyFlag.SlotVisibility), + CopySpineObjectConfig_CanExecute + ); + private RelayCommand? _cmd_CopySpineObjectSlotVisibilityConfig; + + private void CopySpineObjectConfig_Execute(IList? args, SpineObjectConfigApplyFlag flag) { if (!CopySpineObjectConfig_CanExecute(args)) return; var sp = (SpineObjectModel)args[0]; _copiedSpineObjectConfigModel = sp.ObjectConfig; - _logger.Info("Copy config from model: {0}", sp.Name); + _copiedConfigFlag = flag; + _logger.Info("Copy config[{0}] from model: {1}", flag, sp.Name); } private bool CopySpineObjectConfig_CanExecute(IList? args) @@ -381,8 +538,8 @@ namespace SpineViewer.ViewModels.MainWindow if (!ApplySpineObjectConfig_CanExecute(args)) return; foreach (SpineObjectModel sp in args) { - sp.ObjectConfig = _copiedSpineObjectConfigModel; - _logger.Info("Apply config to model: {0}", sp.Name); + sp.ApplyObjectConfig(_copiedSpineObjectConfigModel, _copiedConfigFlag); + _logger.Info("Apply config[{0}] to model: {1}", _copiedConfigFlag, sp.Name); } } @@ -439,124 +596,9 @@ namespace SpineViewer.ViewModels.MainWindow return true; } - /// - /// 从路径列表添加对象 - /// - /// 可以是文件和文件夹 - public void AddSpineObjectFromFileList(IEnumerable paths) - { - List validPaths = []; - foreach (var path in paths) - { - if (File.Exists(path)) - { - var lowerPath = path.ToLowerInvariant(); - if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith)) - validPaths.Add(path); - } - else if (Directory.Exists(path)) - { - foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)) - { - var lowerPath = file.ToLowerInvariant(); - if (SpineObject.PossibleSuffixMapping.Keys.Any(lowerPath.EndsWith)) - validPaths.Add(file); - } - } - } + #endregion - if (validPaths.Count > 1) - { - if (validPaths.Count > 100) - { - if (!MessagePopupService.OKCancel(string.Format(AppResource.Str_TooManyItemsToAddQuest, validPaths.Count))) - return; - } - ProgressService.RunAsync((pr, ct) => AddSpineObjectsTask( - validPaths.ToArray(), pr, ct), - AppResource.Str_AddSpineObjectsTitle - ); - } - else if (validPaths.Count > 0) - { - InsertSpineObject(validPaths[0]); - _logger.LogCurrentProcessMemoryUsage(); - } - } - - /// - /// 用于后台添加模型的任务方法 - /// - private void AddSpineObjectsTask(string[] paths, IProgressReporter reporter, CancellationToken ct) - { - int totalCount = paths.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 skelPath = paths[i]; - reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}"; - - if (InsertSpineObject(skelPath)) - success++; - else - error++; - - reporter.Done = i + 1; - reporter.ProgressText = $"[{i + 1}/{totalCount}] {skelPath}"; - _vmMain.ProgressValue = (i + 1f) / totalCount; - } - _vmMain.ProgressState = TaskbarItemProgressState.None; - - if (error > 0) - _logger.Warn("Batch load {0} successfully, {1} failed", success, error); - else - _logger.Info("{0} skel loaded successfully", success); - - _logger.LogCurrentProcessMemoryUsage(); - } - - /// - /// 安全地在列表头添加一个模型, 发生错误会输出日志 - /// - /// 是否添加成功 - private bool InsertSpineObject(string skelPath, string? atlasPath = null) - { - try - { - var sp = new SpineObjectModel(skelPath, atlasPath); - lock (_spineObjectModels.Lock) _spineObjectModels.Insert(0, sp); - if (Application.Current.Dispatcher.CheckAccess()) - { - RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); - RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp)); - } - else - { - Application.Current.Dispatcher.Invoke(() => - { - RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); - RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp)); - }); - } - return true; - } - catch (Exception ex) - { - _logger.Trace(ex.ToString()); - _logger.Error("Failed to load: {0}, {1}", skelPath, ex.Message); - } - return false; - } + #region 工作区参数实现 public List LoadedSpineObjects { @@ -681,5 +723,7 @@ namespace SpineViewer.ViewModels.MainWindow } return false; } + + #endregion } } diff --git a/SpineViewer/Views/MainWindow.xaml b/SpineViewer/Views/MainWindow.xaml index dbc4429..98cb0c3 100644 --- a/SpineViewer/Views/MainWindow.xaml +++ b/SpineViewer/Views/MainWindow.xaml @@ -170,21 +170,32 @@ Command="{Binding Cmd_MoveDownSpineObject}" CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/> - + - + + + - + - - +