diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b342e1..f6acf95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## v0.16.3 + +- 修复加载工作区时的顺序错误 +- 调整部分调试渲染的逻辑 +- 完善命中检测逻辑 + ## v0.16.2 - 修复批量添加时的添加顺序错误 diff --git a/Spine/Implementations/V21/Skeleton21.cs b/Spine/Implementations/V21/Skeleton21.cs index e88fbe8..89d9e6d 100644 --- a/Spine/Implementations/V21/Skeleton21.cs +++ b/Spine/Implementations/V21/Skeleton21.cs @@ -52,6 +52,7 @@ namespace Spine.Implementations.V21 public Skeleton InnerObject => _o; + public string Name => _o.Data.Name; public float R { get => _o.R; set => _o.R = value; } public float G { get => _o.G; set => _o.G = value; } public float B { get => _o.B; set => _o.B = value; } diff --git a/Spine/Implementations/V34/Skeleton34.cs b/Spine/Implementations/V34/Skeleton34.cs index 706c021..cd6c71e 100644 --- a/Spine/Implementations/V34/Skeleton34.cs +++ b/Spine/Implementations/V34/Skeleton34.cs @@ -52,6 +52,7 @@ namespace Spine.Implementations.V34 public Skeleton InnerObject => _o; + public string Name => _o.Data.Name; public float R { get => _o.R; set => _o.R = value; } public float G { get => _o.G; set => _o.G = value; } public float B { get => _o.B; set => _o.B = value; } diff --git a/Spine/Implementations/V35/Skeleton35.cs b/Spine/Implementations/V35/Skeleton35.cs index baaa8b5..577a506 100644 --- a/Spine/Implementations/V35/Skeleton35.cs +++ b/Spine/Implementations/V35/Skeleton35.cs @@ -52,6 +52,7 @@ namespace Spine.Implementations.V35 public Skeleton InnerObject => _o; + public string Name => _o.Data.Name; public float R { get => _o.R; set => _o.R = value; } public float G { get => _o.G; set => _o.G = value; } public float B { get => _o.B; set => _o.B = value; } diff --git a/Spine/Implementations/V36/Skeleton36.cs b/Spine/Implementations/V36/Skeleton36.cs index ee81a72..0c78649 100644 --- a/Spine/Implementations/V36/Skeleton36.cs +++ b/Spine/Implementations/V36/Skeleton36.cs @@ -52,6 +52,7 @@ namespace Spine.Implementations.V36 public Skeleton InnerObject => _o; + public string Name => _o.Data.Name; public float R { get => _o.R; set => _o.R = value; } public float G { get => _o.G; set => _o.G = value; } public float B { get => _o.B; set => _o.B = value; } diff --git a/Spine/Implementations/V37/Skeleton37.cs b/Spine/Implementations/V37/Skeleton37.cs index d3e233d..9ab8dee 100644 --- a/Spine/Implementations/V37/Skeleton37.cs +++ b/Spine/Implementations/V37/Skeleton37.cs @@ -52,6 +52,7 @@ namespace Spine.Implementations.V37 public Skeleton InnerObject => _o; + public string Name => _o.Data.Name; public float R { get => _o.R; set => _o.R = value; } public float G { get => _o.G; set => _o.G = value; } public float B { get => _o.B; set => _o.B = value; } diff --git a/Spine/Implementations/V38/Skeleton38.cs b/Spine/Implementations/V38/Skeleton38.cs index 9990955..b62d973 100644 --- a/Spine/Implementations/V38/Skeleton38.cs +++ b/Spine/Implementations/V38/Skeleton38.cs @@ -52,6 +52,7 @@ namespace Spine.Implementations.V38 public Skeleton InnerObject => _o; + public string Name => _o.Data.Name; public float R { get => _o.R; set => _o.R = value; } public float G { get => _o.G; set => _o.G = value; } public float B { get => _o.B; set => _o.B = value; } diff --git a/Spine/Implementations/V40/Skeleton40.cs b/Spine/Implementations/V40/Skeleton40.cs index 38b4c16..024c45d 100644 --- a/Spine/Implementations/V40/Skeleton40.cs +++ b/Spine/Implementations/V40/Skeleton40.cs @@ -52,6 +52,7 @@ namespace Spine.Implementations.V40 public Skeleton InnerObject => _o; + public string Name => _o.Data.Name; public float R { get => _o.R; set => _o.R = value; } public float G { get => _o.G; set => _o.G = value; } public float B { get => _o.B; set => _o.B = value; } diff --git a/Spine/Implementations/V41/Skeleton41.cs b/Spine/Implementations/V41/Skeleton41.cs index cf3187f..e7a9d15 100644 --- a/Spine/Implementations/V41/Skeleton41.cs +++ b/Spine/Implementations/V41/Skeleton41.cs @@ -52,6 +52,7 @@ namespace Spine.Implementations.V41 public Skeleton InnerObject => _o; + public string Name => _o.Data.Name; public float R { get => _o.R; set => _o.R = value; } public float G { get => _o.G; set => _o.G = value; } public float B { get => _o.B; set => _o.B = value; } diff --git a/Spine/Implementations/V42/Skeleton42.cs b/Spine/Implementations/V42/Skeleton42.cs index a69434c..6ea97ce 100644 --- a/Spine/Implementations/V42/Skeleton42.cs +++ b/Spine/Implementations/V42/Skeleton42.cs @@ -60,6 +60,7 @@ namespace Spine.Implementations.V42 public Skeleton InnerObject => _o; + public string Name => _o.Data.Name; public float R { get => _o.R; set => _o.R = value; } public float G { get => _o.G; set => _o.G = value; } public float B { get => _o.B; set => _o.B = value; } diff --git a/Spine/Interfaces/ISkeleton.cs b/Spine/Interfaces/ISkeleton.cs index d2cc362..829a9da 100644 --- a/Spine/Interfaces/ISkeleton.cs +++ b/Spine/Interfaces/ISkeleton.cs @@ -15,6 +15,11 @@ namespace Spine.Interfaces /// public enum Physics { None, Reset, Update, Pose } + /// + /// 名称 + /// + public string Name { get; } + /// /// R /// diff --git a/Spine/Interfaces/SpineExtension.cs b/Spine/Interfaces/SpineExtension.cs index b6f3c0d..218c8fc 100644 --- a/Spine/Interfaces/SpineExtension.cs +++ b/Spine/Interfaces/SpineExtension.cs @@ -1,4 +1,4 @@ -using SkiaSharp; +using NLog; using Spine.Interfaces.Attachments; using System; using System.Collections.Generic; @@ -8,8 +8,33 @@ using System.Threading.Tasks; namespace Spine.Interfaces { + /// + /// 命中测试等级枚举值 + /// + public enum HitTestLevel { None, Bounds, Meshes, Pixels } + public static class SpineExtension { + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + private static readonly SFML.Graphics.RenderTexture _renderTex; // XXX: 在此保留一个静态变量, 并且没有使用 Dispose 进行资源释放 + + static SpineExtension() + { + _renderTex = new(1, 1); + _renderTex.SetActive(false); + } + + /// + /// 命中检测精确度等级 + /// + public static HitTestLevel HitTestLevel { get; set; } + + /// + /// 命中测试时输出命中的插槽名称 + /// + public static bool LogHitSlots { get; set; } + /// /// 获取当前状态包围盒 /// @@ -103,19 +128,17 @@ namespace Spine.Interfaces /// /// 命中测试, 当插槽全透明或者处于禁用或者骨骼处于未激活则无法命中 /// - /// 是否精确命中检测, 否则仅使用包围盒进行命中检测 - /// 调用方管理的缓存表 - public static bool HitTest(this ISlot self, float x, float y, bool precise = false, Dictionary cache = null) + public static bool HitTest(this ISlot self, float x, float y) { if (self.A <= 0 || !self.Bone.Active || self.Disabled) return false; - if (!precise) + if (HitTestLevel == HitTestLevel.None || HitTestLevel == HitTestLevel.Bounds) { self.GetBounds(out var bx, out var by, out var bw, out var bh); return x >= bx && x <= (bx + bw) && y >= by && y <= (by + bh); } - else + else if (HitTestLevel == HitTestLevel.Meshes || HitTestLevel == HitTestLevel.Pixels) { float[] vertices = new float[8]; int[] triangles; @@ -140,22 +163,7 @@ namespace Spine.Interfaces return false; } - SFML.Graphics.Image img = null; - if (cache is not null) - { - if (!cache.TryGetValue(tex, out img)) - { - img = cache[tex] = tex.CopyToImage(); - } - } - else - { - img = tex.CopyToImage(); - } - - bool hit = false; var trianglesLength = triangles.Length; - var texSize = img.Size; for (int i = 0; i + 2 < trianglesLength; i += 3) { var idx0 = triangles[i] << 1; @@ -173,6 +181,9 @@ namespace Spine.Interfaces // 判断是否全部同号 (或为 0, 点在边上) if ((c0 >= 0 && c1 >= 0 && c2 >= 0) || (c0 <= 0 && c1 <= 0 && c2 <= 0)) { + if (HitTestLevel == HitTestLevel.Meshes) + return true; + float u0 = uvs[idx0], v0 = uvs[idx0 + 1]; float u1 = uvs[idx1], v1 = uvs[idx1 + 1]; float u2 = uvs[idx2], v2 = uvs[idx2 + 1]; @@ -182,44 +193,62 @@ namespace Spine.Interfaces float w2 = c0 * inv; float u = u0 * w0 + u1 * w1 + u2 * w2; float v = v0 * w0 + v1 * w1 + v2 * w2; + var texW = tex.Size.X; + var texH = tex.Size.Y; - var pixel = img.GetPixel((uint)(u * texSize.X), (uint)(v * texSize.Y)); - hit = pixel.A > 0; - break; + // 把要判断的那个像素点渲出来 + using var view = _renderTex.GetView(); + using var vertexArray = new SFML.Graphics.VertexArray(SFML.Graphics.PrimitiveType.Points, 1); + vertexArray[0] = new(view.Center, new SFML.System.Vector2f(u * texW, v * texH)); + + // XXX: 此处 RenderTexture 不能临时用临时释放, 由于未知原因如果短时间快速创建释放 RenderTexture 可能让程序卡死 + _renderTex.SetActive(true); + _renderTex.Clear(SFML.Graphics.Color.Transparent); + _renderTex.Draw(vertexArray, new(tex)); + _renderTex.Display(); + _renderTex.SetActive(false); + + using var img = _renderTex.Texture.CopyToImage(); + var pixel = img.GetPixel(0, 0); + return pixel.A > 0; } } - - // 无缓存需要立即释放资源 - if (cache is null) - { - img.Dispose(); - } - - return hit; + return false; + } + else + { + throw new NotImplementedException(HitTestLevel.ToString()); } } /// /// 逐插槽的命中测试, 命中后会提前返回结果中止计算 /// - public static bool HitTest(this ISkeleton self, float x, float y, bool precise = false) + public static bool HitTest(this ISkeleton self, float x, float y) { - var cache = new Dictionary(); - bool hit = self.IterDrawOrder().Any(st => st.HitTest(x, y, precise, cache)); - foreach (var img in cache.Values) img.Dispose(); - return hit; - } + if (HitTestLevel == HitTestLevel.None) + { + self.GetBounds(out var bx, out var by, out var bw, out var bh); + return x >= bx && x <= (bx + bw) && y >= by && y <= (by + bh); + } - /// - /// 逐插槽的命中测试, 会完整计算所有插槽的命中情况并按顶层至底层的顺序返回命中的插槽 - /// /// 是否精确命中检测, 否则仅使用每个插槽的包围盒进行命中检测 - /// - public static ISlot[] HitTestFull(this ISkeleton self, float x, float y, bool precise = false) - { - var cache = new Dictionary(); - var hitSlots = self.IterDrawOrder().Where(st => st.HitTest(x, y, precise, cache)).Reverse().ToArray(); - foreach (var img in cache.Values) img.Dispose(); - return hitSlots; + bool hit = false; + string hitSlotName = ""; + foreach (var st in self.IterDrawOrder().Reverse()) + { + if (st.HitTest(x, y)) + { + hit = true; + hitSlotName = st.Name; + break; + } + } + + if (hit && LogHitSlots) + { + _logger.Debug("Hit ({0}): [{1}]", self.Name, hitSlotName); + } + return hit; } /// diff --git a/Spine/Spine.csproj b/Spine/Spine.csproj index a05726c..54283ae 100644 --- a/Spine/Spine.csproj +++ b/Spine/Spine.csproj @@ -7,7 +7,7 @@ net8.0-windows $(SolutionDir)out false - 0.16.2 + 0.16.3 diff --git a/Spine/SpineObject.cs b/Spine/SpineObject.cs index 0b1ce25..c911521 100644 --- a/Spine/SpineObject.cs +++ b/Spine/SpineObject.cs @@ -628,7 +628,7 @@ namespace Spine if (DebugRegions) { vt.Color = AttachmentLineColor; - foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled)) + foreach (var slot in _skeleton.IterDrawOrder().Where(s => s.A > 0 && s.Bone.Active && !s.Disabled)) { if (slot.Attachment is IRegionAttachment regionAttachment) { @@ -660,7 +660,7 @@ namespace Spine if (DebugMeshes) { vt.Color = MeshLineColor; - foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled)) + foreach (var slot in _skeleton.IterDrawOrder().Where(s => s.A > 0 && s.Bone.Active && !s.Disabled)) { if (slot.Attachment is IMeshAttachment meshAttachment) { @@ -696,7 +696,7 @@ namespace Spine if (DebugMeshHulls) { vt.Color = AttachmentLineColor; - foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled)) + foreach (var slot in _skeleton.IterDrawOrder().Where(s => s.A > 0 && s.Bone.Active && !s.Disabled)) { if (slot.Attachment is IMeshAttachment meshAttachment) { @@ -742,7 +742,7 @@ namespace Spine if (DebugClippings) { vt.Color = ClippingLineColor; - foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled)) + foreach (var slot in _skeleton.IterDrawOrder().Where(s => s.A > 0 && s.Bone.Active && !s.Disabled)) { if (slot.Attachment is IClippingAttachment clippingAttachment) { @@ -799,7 +799,7 @@ namespace Spine if (DebugBones) { var width = Math.Max(Math.Abs(_skeleton.ScaleX), Math.Abs(_skeleton.ScaleY)); - foreach (var bone in _skeleton.Bones.Where(b => b.Active)) + foreach (var bone in _skeleton.IterDrawOrder().Where(s => s.A > 0 && s.Bone.Active && !s.Disabled).Select(st => st.Bone)) { var boneLength = bone.Length; var p1 = new SFML.System.Vector2f(bone.WorldX, bone.WorldY); @@ -815,7 +815,7 @@ namespace Spine if (DebugBones) { var radius = Math.Max(Math.Abs(_skeleton.ScaleX), Math.Abs(_skeleton.ScaleY)); - foreach (var bone in _skeleton.Bones.Where(b => b.Active)) + foreach (var bone in _skeleton.IterDrawOrder().Where(s => s.A > 0 && s.Bone.Active && !s.Disabled).Select(st => st.Bone)) { DrawCirclePoint(target, new(bone.WorldX, bone.WorldY), BonePointColor, radius); } diff --git a/SpineViewer/Models/PreferenceModel.cs b/SpineViewer/Models/PreferenceModel.cs index 61beced..a4336b2 100644 --- a/SpineViewer/Models/PreferenceModel.cs +++ b/SpineViewer/Models/PreferenceModel.cs @@ -1,5 +1,6 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Spine.Interfaces; using SpineViewer.Services; using System; using System.Collections.Generic; @@ -89,7 +90,7 @@ namespace SpineViewer.Models private bool _renderSelectedOnly; [ObservableProperty] - private bool _usePreciseHitTest; + private HitTestLevel _hitTestLevel; [ObservableProperty] private bool _logHitSlots; diff --git a/SpineViewer/Models/SpineObjectModel.cs b/SpineViewer/Models/SpineObjectModel.cs index ddb920f..e3a17b7 100644 --- a/SpineViewer/Models/SpineObjectModel.cs +++ b/SpineViewer/Models/SpineObjectModel.cs @@ -429,19 +429,11 @@ namespace SpineViewer.Models } /// - /// 命中检测, 可选是否使用精确检测, 会有性能损失 + /// 命中检测 /// - public bool HitTest(float x, float y, bool precise = false) + public bool HitTest(float x, float y) { - lock (_lock) return _spineObject.Skeleton.HitTest(x, y, precise); - } - - /// - /// 完整的命中检测, 会检测所有插槽是否命中并返回命中的插槽名称 - /// - public string[] HitTestFull(float x, float y, bool precise = false) - { - lock (_lock) return _spineObject.Skeleton.HitTestFull(x, y, precise).Select(v => v.Name).ToArray(); + lock (_lock) return _spineObject.Skeleton.HitTest(x, y); } public SpineObjectConfigModel ObjectConfig diff --git a/SpineViewer/Resources/Strings/en.xaml b/SpineViewer/Resources/Strings/en.xaml index ffa0d04..be3fd86 100644 --- a/SpineViewer/Resources/Strings/en.xaml +++ b/SpineViewer/Resources/Strings/en.xaml @@ -120,10 +120,9 @@ Playback Speed Wallpaper View Render Selected Only - Use Precise Hit Testing - When enabled, click detection will be performed based on pixel transparency of the model. - Log Hit Slot Names - When enabled, the log box will output the model and slot information hit by each click operation (will not output when using Ctrl for multi-selection). + Hit Test Accuracy Level + Output Hit Test Slot Names + When enabled, the log box will output the model and slot information for each click hit test. Show Axis Background Color Background Image Path diff --git a/SpineViewer/Resources/Strings/ja.xaml b/SpineViewer/Resources/Strings/ja.xaml index 4759e47..6790f47 100644 --- a/SpineViewer/Resources/Strings/ja.xaml +++ b/SpineViewer/Resources/Strings/ja.xaml @@ -120,10 +120,9 @@ 再生速度 壁紙表示 選択のみレンダリング - 精密ヒットテストを使用 - 有効にすると、モデルのピクセル透過度に基づいてクリック判定を行います。 - ヒットしたスロット名を出力 - 有効にすると、ログボックスに各クリック操作でヒットしたモデルとスロットの情報を出力します(Ctrlを押しながら複数選択する場合は出力されません)。 + ヒットテスト精度レベル + ヒットテスト結果のスロット名を出力 + 有効にすると、ログボックスに各クリック操作で命中したモデルとスロットの情報が出力されます。 座標軸を表示 背景色 背景画像のパス diff --git a/SpineViewer/Resources/Strings/zh.xaml b/SpineViewer/Resources/Strings/zh.xaml index 13421bd..200e717 100644 --- a/SpineViewer/Resources/Strings/zh.xaml +++ b/SpineViewer/Resources/Strings/zh.xaml @@ -120,10 +120,9 @@ 播放速度 桌面投影 仅渲染选中 - 使用精确命中检测 - 启用后将会按像素透明度来检测点击操作是否命中了模型 - 输出命中的插槽名称 - 启用后将会在日志框内输出每一次点击操作命中的模型和插槽情况(按下 Ctrl 进行多选时不会输出) + 命中检测准确度等级 + 输出命中检测结果的插槽名称 + 启用后将会在日志框内输出每一次点击操作命中的模型和插槽情况 显示坐标轴 背景颜色 背景图片路径 diff --git a/SpineViewer/SpineViewer.csproj b/SpineViewer/SpineViewer.csproj index 86875c2..f814b67 100644 --- a/SpineViewer/SpineViewer.csproj +++ b/SpineViewer/SpineViewer.csproj @@ -7,7 +7,7 @@ net8.0-windows $(SolutionDir)out false - 0.16.2 + 0.16.3 WinExe true diff --git a/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs b/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs index df86279..ed9d098 100644 --- a/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs +++ b/SpineViewer/ViewModels/Exporters/FFmpegVideoExporterViewModel.cs @@ -18,7 +18,7 @@ namespace SpineViewer.ViewModels.Exporters { public class FFmpegVideoExporterViewModel(MainWindowViewModel vmMain) : VideoExporterViewModel(vmMain) { - public ImmutableArray VideoFormatOptions { get; } = Enum.GetValues().ToImmutableArray(); + public static ImmutableArray VideoFormatOptions { get; } = Enum.GetValues().ToImmutableArray(); public FFmpegVideoExporter.VideoFormat Format { get => _format; set => SetProperty(ref _format, value); } protected FFmpegVideoExporter.VideoFormat _format = FFmpegVideoExporter.VideoFormat.Mp4; diff --git a/SpineViewer/ViewModels/Exporters/FrameExporterViewModel.cs b/SpineViewer/ViewModels/Exporters/FrameExporterViewModel.cs index d0e752c..a30363a 100644 --- a/SpineViewer/ViewModels/Exporters/FrameExporterViewModel.cs +++ b/SpineViewer/ViewModels/Exporters/FrameExporterViewModel.cs @@ -20,7 +20,7 @@ namespace SpineViewer.ViewModels.Exporters { public class FrameExporterViewModel(MainWindowViewModel vmMain) : BaseExporterViewModel(vmMain) { - public ImmutableArray FrameFormatOptions { get; } = Enum.GetValues().ToImmutableArray(); + public static ImmutableArray FrameFormatOptions { get; } = Enum.GetValues().ToImmutableArray(); public SKEncodedImageFormat Format { get => _format; set => SetProperty(ref _format, value); } protected SKEncodedImageFormat _format = SKEncodedImageFormat.Png; diff --git a/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs b/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs index 02cfa54..fa461c4 100644 --- a/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/PreferenceViewModel.cs @@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.Input; using Microsoft.Win32; using NLog; using Spine.Implementations; +using Spine.Interfaces; using SpineViewer.Models; using SpineViewer.Natives; using SpineViewer.Services; @@ -109,7 +110,7 @@ namespace SpineViewer.ViewModels.MainWindow AppLanguage = AppLanguage, RenderSelectedOnly = RenderSelectedOnly, - UsePreciseHitTest = UsePreciseHitTest, + HitTestLevel = HitTestLevel, LogHitSlots = LogHitSlots, WallpaperView = WallpaperView, CloseToTray = CloseToTray, @@ -140,7 +141,7 @@ namespace SpineViewer.ViewModels.MainWindow AppLanguage = value.AppLanguage; RenderSelectedOnly = value.RenderSelectedOnly; - UsePreciseHitTest = value.UsePreciseHitTest; + HitTestLevel = value.HitTestLevel; LogHitSlots = value.LogHitSlots; WallpaperView = value.WallpaperView; CloseToTray = value.CloseToTray; @@ -252,6 +253,8 @@ namespace SpineViewer.ViewModels.MainWindow public static ImmutableArray AppLanguageOptions { get; } = Enum.GetValues().ToImmutableArray(); + public static ImmutableArray HitTestLevelOptions { get; } = Enum.GetValues().ToImmutableArray(); + public AppLanguage AppLanguage { get => ((App)App.Current).Language; @@ -264,16 +267,16 @@ namespace SpineViewer.ViewModels.MainWindow set => SetProperty(_vmMain.SFMLRendererViewModel.RenderSelectedOnly, value, v => _vmMain.SFMLRendererViewModel.RenderSelectedOnly = v); } - public bool UsePreciseHitTest + public HitTestLevel HitTestLevel { - get => _vmMain.SFMLRendererViewModel.UsePreciseHitTest; - set => SetProperty(_vmMain.SFMLRendererViewModel.UsePreciseHitTest, value, v => _vmMain.SFMLRendererViewModel.UsePreciseHitTest = v); + get => SpineExtension.HitTestLevel; + set => SetProperty(SpineExtension.HitTestLevel, value, v => SpineExtension.HitTestLevel = v); } public bool LogHitSlots { - get => _vmMain.SFMLRendererViewModel.LogHitSlots; - set => SetProperty(_vmMain.SFMLRendererViewModel.LogHitSlots, value, v => _vmMain.SFMLRendererViewModel.LogHitSlots = v); + get => SpineExtension.LogHitSlots; + set => SetProperty(SpineExtension.LogHitSlots, value, v => SpineExtension.LogHitSlots = v); } public bool WallpaperView diff --git a/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs b/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs index b574eb5..590b0fe 100644 --- a/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SFMLRendererViewModel.cs @@ -25,7 +25,7 @@ namespace SpineViewer.ViewModels.MainWindow { public class SFMLRendererViewModel : ObservableObject { - public ImmutableArray StretchOptions { get; } = Enum.GetValues().ToImmutableArray(); + public static ImmutableArray StretchOptions { get; } = Enum.GetValues().ToImmutableArray(); /// /// 日志器 @@ -250,26 +250,6 @@ namespace SpineViewer.ViewModels.MainWindow } private bool _renderSelectedOnly; - /// - /// 启用精确命中测试 - /// - public bool UsePreciseHitTest - { - get => _usePreciseHitTest; - set => SetProperty(ref _usePreciseHitTest, value); - } - private bool _usePreciseHitTest; - - /// - /// 启用完整的命中测试并在日志中输出命中测试的插槽结果 - /// - public bool LogHitSlots - { - get => _logHitSlots; - set => SetProperty(ref _logHitSlots, value); - } - private bool _logHitSlots; - /// /// 启用桌面投影 /// @@ -368,22 +348,9 @@ namespace SpineViewer.ViewModels.MainWindow if (_renderSelectedOnly) { bool hit = false; - if (!_logHitSlots) - { - // 只在被选中的对象里判断是否有效命中 - hit = _models.Any(m => m.IsSelected && m.HitTest(src.X, src.Y, _usePreciseHitTest)); - } - else - { - foreach (var sp in _models.Where(m => m.IsSelected)) - { - var slotNames = sp.HitTestFull(src.X, src.Y, _usePreciseHitTest); - if (slotNames.Length <= 0) continue; - hit = true; - _logger.Debug("Model Hit ({0}): [{1}]", sp.Name, string.Join(", ", slotNames)); - } - } + // 只在被选中的对象里判断是否有效命中 + hit = _models.Any(m => m.IsSelected && m.HitTest(src.X, src.Y)); // 如果没点到被选中的模型, 则不允许拖动 if (!hit) _draggingSrc = null; @@ -395,40 +362,19 @@ namespace SpineViewer.ViewModels.MainWindow // 没按 Ctrl 的情况下, 如果命中了已选中对象, 则就算普通命中 bool hit = false; - if (!_logHitSlots) + foreach (var sp in _models.Where(m => m.IsShown)) { - foreach (var sp in _models.Where(m => m.IsShown)) + if (!sp.HitTest(src.X, src.Y)) continue; + + hit = true; + + // 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的 + if (!sp.IsSelected) { - if (!sp.HitTest(src.X, src.Y, _usePreciseHitTest)) continue; - - hit = true; - - // 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的 - if (!sp.IsSelected) - { - RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); - RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp)); - } - break; - } - } - else - { - foreach (var sp in _models.Where(m => m.IsShown)) - { - var slotNames = sp.HitTestFull(src.X, src.Y, _usePreciseHitTest); - if (slotNames.Length <= 0) continue; - - // 如果点到了没被选中的东西, 则清空原先选中的, 改为只选中这一次点的 - // 仅判断顶层对象 (首次命中) - if (!hit && !sp.IsSelected) - { - RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); - RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp)); - } - hit = true; - _logger.Debug("Model Hit ({0}): [{1}]", sp.Name, string.Join(", ", slotNames)); + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Reset)); + RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Add, sp)); } + break; } // 如果点了空白的地方, 就清空选中列表 @@ -437,7 +383,7 @@ namespace SpineViewer.ViewModels.MainWindow else { // 按下 Ctrl 的情况就执行多选, 并且点空白处也不会清空选中, 如果点击了本来就是选中的则取消选中 - if (_models.FirstOrDefault(m => m.IsShown && m.HitTest(src.X, src.Y, _usePreciseHitTest), null) is SpineObjectModel sp) + if (_models.FirstOrDefault(m => m.IsShown && m.HitTest(src.X, src.Y), null) is SpineObjectModel sp) { if (sp.IsSelected) RequestSelectionChanging?.Invoke(this, new(NotifyCollectionChangedAction.Remove, sp)); diff --git a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs index 874f77f..a161261 100644 --- a/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SpineObjectListViewModel.cs @@ -111,7 +111,7 @@ namespace SpineViewer.ViewModels.MainWindow return; if (!DialogService.ShowOpenFileDialog(out var atlasFileName, AppResource.Str_OpenAtlasFileTitle)) return; - AddSpineObject(skelFileName, atlasFileName); + InsertSpineObject(skelFileName, atlasFileName); _logger.LogCurrentProcessMemoryUsage(); } @@ -479,7 +479,7 @@ namespace SpineViewer.ViewModels.MainWindow } else if (validPaths.Count > 0) { - AddSpineObject(validPaths[0]); + InsertSpineObject(validPaths[0]); _logger.LogCurrentProcessMemoryUsage(); } } @@ -506,7 +506,7 @@ namespace SpineViewer.ViewModels.MainWindow var skelPath = paths[i]; reporter.ProgressText = $"[{i}/{totalCount}] {skelPath}"; - if (AddSpineObject(skelPath)) + if (InsertSpineObject(skelPath)) success++; else error++; @@ -529,7 +529,7 @@ namespace SpineViewer.ViewModels.MainWindow /// 安全地在列表头添加一个模型, 发生错误会输出日志 /// /// 是否添加成功 - private bool AddSpineObject(string skelPath, string? atlasPath = null) + private bool InsertSpineObject(string skelPath, string? atlasPath = null) { try { @@ -599,7 +599,7 @@ namespace SpineViewer.ViewModels.MainWindow } else if (models.Count > 0) { - AddSpineObject(models[0]); + InsertSpineObject(models[0]); _logger.LogCurrentProcessMemoryUsage(); } } @@ -620,10 +620,10 @@ namespace SpineViewer.ViewModels.MainWindow { if (ct.IsCancellationRequested) break; - var cfg = models[i]; + var cfg = models[totalCount - 1 - i]; reporter.ProgressText = $"[{i}/{totalCount}] {cfg}"; - if (AddSpineObject(cfg)) + if (InsertSpineObject(cfg)) success++; else error++; @@ -653,7 +653,7 @@ namespace SpineViewer.ViewModels.MainWindow /// 安全地在列表头添加一个模型, 发生错误会输出日志 /// /// 是否添加成功 - private bool AddSpineObject(SpineObjectWorkspaceConfigModel cfg) + private bool InsertSpineObject(SpineObjectWorkspaceConfigModel cfg) { try { diff --git a/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs b/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs index 8f7bc69..444d25f 100644 --- a/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs +++ b/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs @@ -18,7 +18,7 @@ namespace SpineViewer.ViewModels.MainWindow private readonly ObservableCollection _slots = []; private readonly ObservableCollection _animationTracks = []; - public ImmutableArray PhysicsOptions { get; } = Enum.GetValues().ToImmutableArray(); + public static ImmutableArray PhysicsOptions { get; } = Enum.GetValues().ToImmutableArray(); public SpineObjectModel[] SelectedObjects { diff --git a/SpineViewer/Views/ExporterDialogs/FFmpegVideoExporterDialog.xaml b/SpineViewer/Views/ExporterDialogs/FFmpegVideoExporterDialog.xaml index 0260264..2969bad 100644 --- a/SpineViewer/Views/ExporterDialogs/FFmpegVideoExporterDialog.xaml +++ b/SpineViewer/Views/ExporterDialogs/FFmpegVideoExporterDialog.xaml @@ -5,8 +5,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:hc="https://handyorg.github.io/handycontrol" xmlns:local="clr-namespace:SpineViewer.Views.ExporterDialogs" - xmlns:exporters="clr-namespace:SpineViewer.ViewModels.Exporters" - d:DataContext="{d:DesignInstance Type=exporters:FFmpegVideoExporterViewModel}" + xmlns:vmexp="clr-namespace:SpineViewer.ViewModels.Exporters" + d:DataContext="{d:DesignInstance Type=vmexp:FFmpegVideoExporterViewModel}" mc:Ignorable="d" Title="{DynamicResource Str_FFmpegVideoExporterTitle}" Width="450" @@ -161,7 +161,7 @@