diff --git a/Spine/Implementations/SpineWrappers/V21/Slot21.cs b/Spine/Implementations/SpineWrappers/V21/Slot21.cs
index 19a7321..d88eb07 100644
--- a/Spine/Implementations/SpineWrappers/V21/Slot21.cs
+++ b/Spine/Implementations/SpineWrappers/V21/Slot21.cs
@@ -66,6 +66,8 @@ namespace Spine.Implementations.SpineWrappers.V21
}
}
+ public bool Disabled { get; set; }
+
public override string ToString() => _o.ToString();
}
}
diff --git a/Spine/Implementations/SpineWrappers/V34/Slot34.cs b/Spine/Implementations/SpineWrappers/V34/Slot34.cs
index bcb406f..b98b193 100644
--- a/Spine/Implementations/SpineWrappers/V34/Slot34.cs
+++ b/Spine/Implementations/SpineWrappers/V34/Slot34.cs
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V34
}
}
+ public bool Disabled { get; set; }
+
public override string ToString() => _o.ToString();
}
}
diff --git a/Spine/Implementations/SpineWrappers/V35/Slot35.cs b/Spine/Implementations/SpineWrappers/V35/Slot35.cs
index 538b1d2..f202e03 100644
--- a/Spine/Implementations/SpineWrappers/V35/Slot35.cs
+++ b/Spine/Implementations/SpineWrappers/V35/Slot35.cs
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V35
}
}
+ public bool Disabled { get; set; }
+
public override string ToString() => _o.ToString();
}
}
diff --git a/Spine/Implementations/SpineWrappers/V36/Slot36.cs b/Spine/Implementations/SpineWrappers/V36/Slot36.cs
index 8b595d8..430c0b5 100644
--- a/Spine/Implementations/SpineWrappers/V36/Slot36.cs
+++ b/Spine/Implementations/SpineWrappers/V36/Slot36.cs
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V36
}
}
+ public bool Disabled { get; set; }
+
public override string ToString() => _o.ToString();
}
}
diff --git a/Spine/Implementations/SpineWrappers/V37/Slot37.cs b/Spine/Implementations/SpineWrappers/V37/Slot37.cs
index 28a68a7..55c5330 100644
--- a/Spine/Implementations/SpineWrappers/V37/Slot37.cs
+++ b/Spine/Implementations/SpineWrappers/V37/Slot37.cs
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V37
}
}
+ public bool Disabled { get; set; }
+
public override string ToString() => _o.ToString();
}
}
diff --git a/Spine/Implementations/SpineWrappers/V38/Slot38.cs b/Spine/Implementations/SpineWrappers/V38/Slot38.cs
index d640a1c..6255fca 100644
--- a/Spine/Implementations/SpineWrappers/V38/Slot38.cs
+++ b/Spine/Implementations/SpineWrappers/V38/Slot38.cs
@@ -74,6 +74,8 @@ namespace Spine.Implementations.SpineWrappers.V38
}
}
+ public bool Disabled { get; set; }
+
public override string ToString() => _o.ToString();
}
}
diff --git a/Spine/Implementations/SpineWrappers/V40/Slot40.cs b/Spine/Implementations/SpineWrappers/V40/Slot40.cs
index 6467a5b..14f703d 100644
--- a/Spine/Implementations/SpineWrappers/V40/Slot40.cs
+++ b/Spine/Implementations/SpineWrappers/V40/Slot40.cs
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V40
}
}
+ public bool Disabled { get; set; }
+
public override string ToString() => _o.ToString();
}
}
diff --git a/Spine/Implementations/SpineWrappers/V41/Slot41.cs b/Spine/Implementations/SpineWrappers/V41/Slot41.cs
index 138edb6..9ddd689 100644
--- a/Spine/Implementations/SpineWrappers/V41/Slot41.cs
+++ b/Spine/Implementations/SpineWrappers/V41/Slot41.cs
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V41
}
}
+ public bool Disabled { get; set; }
+
public override string ToString() => _o.ToString();
}
}
diff --git a/Spine/Implementations/SpineWrappers/V42/Slot42.cs b/Spine/Implementations/SpineWrappers/V42/Slot42.cs
index 4f7dee0..283c011 100644
--- a/Spine/Implementations/SpineWrappers/V42/Slot42.cs
+++ b/Spine/Implementations/SpineWrappers/V42/Slot42.cs
@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V42
}
}
+ public bool Disabled { get; set; }
+
public override string ToString() => _o.ToString();
}
}
diff --git a/Spine/SpineObject.cs b/Spine/SpineObject.cs
index bc60f13..6914208 100644
--- a/Spine/SpineObject.cs
+++ b/Spine/SpineObject.cs
@@ -299,6 +299,30 @@ namespace Spine
///
public bool DebugClippings { get; set; }
+ ///
+ /// 获取插槽可见性, 如果不存在则默认返回 false
+ ///
+ public bool GetSlotVisible(string slotName)
+ {
+ if (_skeleton.SlotsByName.TryGetValue(slotName, out var slot))
+ return !slot.Disabled;
+ return false;
+ }
+
+ ///
+ /// 设置插槽可见性, 插槽不可见后将不会在任何渲染中出现, 插槽不存在则忽略操作
+ ///
+ /// 操作是否成功, 插槽不存在则返回 false
+ public bool SetSlotVisible(string slotName, bool visible)
+ {
+ if (_skeleton.SlotsByName.TryGetValue(slotName, out var slot))
+ {
+ slot.Disabled = !visible;
+ return true;
+ }
+ return false;
+ }
+
///
/// 获取某个插槽上的附件名, 插槽不存在或者无附件均返回 null
///
@@ -310,7 +334,7 @@ namespace Spine
}
///
- /// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来清除附件
+ /// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来尝试清除附件
///
/// 是否操作成功
public bool SetAttachment(string slotName, string? attachmentName)
@@ -471,7 +495,7 @@ namespace Spine
foreach (var slot in _skeleton.IterDrawOrder())
{
- if (slot.A <= 0 || !slot.Bone.Active)
+ if (slot.A <= 0 || !slot.Bone.Active || slot.Disabled)
{
_clipping.ClipEnd(slot);
continue;
@@ -602,7 +626,7 @@ namespace Spine
if (DebugRegions)
{
vt.Color = AttachmentLineColor;
- foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
+ foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
{
if (slot.Attachment is IRegionAttachment regionAttachment)
{
@@ -634,7 +658,7 @@ namespace Spine
if (DebugMeshes)
{
vt.Color = MeshLineColor;
- foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
+ foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
{
if (slot.Attachment is IMeshAttachment meshAttachment)
{
@@ -698,7 +722,7 @@ namespace Spine
if (DebugMeshHulls)
{
vt.Color = AttachmentLineColor;
- foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
+ foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
{
if (slot.Attachment is IMeshAttachment meshAttachment)
{
@@ -767,7 +791,7 @@ namespace Spine
if (DebugClippings)
{
vt.Color = ClippingLineColor;
- foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active))
+ foreach (var slot in _skeleton.Slots.Where(s => s.Bone.Active && !s.Disabled))
{
if (slot.Attachment is IClippingAttachment clippingAttachment)
{
diff --git a/Spine/SpineWrappers/ISlot.cs b/Spine/SpineWrappers/ISlot.cs
index ab12933..27ac9ac 100644
--- a/Spine/SpineWrappers/ISlot.cs
+++ b/Spine/SpineWrappers/ISlot.cs
@@ -53,5 +53,10 @@ namespace Spine.SpineWrappers
/// 使用的附件, 可以设置为 null 清空附件
///
public IAttachment? Attachment { get; set; }
+
+ ///
+ /// 是否已禁用渲染该插槽
+ ///
+ public bool Disabled { get; set; }
}
}
diff --git a/SpineViewer/Models/SpineObjectConfigModel.cs b/SpineViewer/Models/SpineObjectConfigModel.cs
index 27a1730..8b92b71 100644
--- a/SpineViewer/Models/SpineObjectConfigModel.cs
+++ b/SpineViewer/Models/SpineObjectConfigModel.cs
@@ -31,6 +31,8 @@ namespace SpineViewer.Models
public Dictionary SlotAttachment { get; set; } = [];
+ public List DisabledSlots { get; set; } = [];
+
public List Animations { get; set; } = [];
public bool DebugTexture { get; set; } = true;
diff --git a/SpineViewer/Models/SpineObjectModel.cs b/SpineViewer/Models/SpineObjectModel.cs
index 292a6db..71237f9 100644
--- a/SpineViewer/Models/SpineObjectModel.cs
+++ b/SpineViewer/Models/SpineObjectModel.cs
@@ -85,6 +85,8 @@ namespace SpineViewer.Models
public event EventHandler? SkinStatusChanged;
+ public event EventHandler? SlotVisibleChanged;
+
public event EventHandler? SlotAttachmentChanged;
public event EventHandler? AnimationChanged;
@@ -200,6 +202,19 @@ namespace SpineViewer.Models
public FrozenDictionary> SlotAttachments => _slotAttachments;
+ public bool GetSlotVisible(string slotName)
+ {
+ lock (_lock) return _spineObject.GetSlotVisible(slotName);
+ }
+
+ public bool SetSlotVisible(string slotName, bool visible)
+ {
+ bool changed = false;
+ lock (_lock) changed = _spineObject.SetSlotVisible(slotName, visible);
+ if (changed) SlotVisibleChanged?.Invoke(this, new(slotName, visible));
+ return changed;
+ }
+
public string? GetAttachment(string slotName)
{
lock (_lock) return _spineObject.GetAttachment(slotName);
@@ -390,6 +405,8 @@ namespace SpineViewer.Models
foreach (var slot in _spineObject.Skeleton.Slots) config.SlotAttachment[slot.Name] = slot.Attachment?.Name;
+ config.DisabledSlots = _spineObject.Skeleton.Slots.Where(it => it.Disabled).Select(it => it.Name).ToList();
+
// XXX: 处理空动画
config.Animations.AddRange(_spineObject.AnimationState.IterTracks().Select(tr => tr?.Animation.Name));
@@ -422,6 +439,10 @@ namespace SpineViewer.Models
if (_spineObject.SetAttachment(slotName, attachmentName))
SlotAttachmentChanged?.Invoke(this, new(slotName, attachmentName));
+ foreach (var slotName in value.DisabledSlots)
+ if (_spineObject.SetSlotVisible(slotName, false))
+ SlotVisibleChanged?.Invoke(this, new(slotName, false));
+
// XXX: 处理空动画
_spineObject.AnimationState.ClearTracks();
int trackIndex = 0;
@@ -507,6 +528,12 @@ namespace SpineViewer.Models
public bool Status { get; } = status;
}
+ public class SlotVisibleChangedEventArgs(string slotName, bool visible) : EventArgs
+ {
+ public string SlotName { get; } = slotName;
+ public bool Visible { get; } = visible;
+ }
+
public class SlotAttachmentChangedEventArgs(string slotName, string? attachmentName) : EventArgs
{
public string SlotName { get; } = slotName;
diff --git a/SpineViewer/Resources/Strings/en.xaml b/SpineViewer/Resources/Strings/en.xaml
index 4f4399d..2787fdb 100644
--- a/SpineViewer/Resources/Strings/en.xaml
+++ b/SpineViewer/Resources/Strings/en.xaml
@@ -78,6 +78,8 @@
Slot
Clear Slots Attachment
+ Enable Slots
+ Disable Slots
Animation
Add
diff --git a/SpineViewer/Resources/Strings/ja.xaml b/SpineViewer/Resources/Strings/ja.xaml
index 86dcaf9..803a881 100644
--- a/SpineViewer/Resources/Strings/ja.xaml
+++ b/SpineViewer/Resources/Strings/ja.xaml
@@ -78,6 +78,8 @@
スロット
アタッチメントをクリア
+ 有効
+ 無効
アニメーション
追加
diff --git a/SpineViewer/Resources/Strings/zh.xaml b/SpineViewer/Resources/Strings/zh.xaml
index 2a3eab0..773e757 100644
--- a/SpineViewer/Resources/Strings/zh.xaml
+++ b/SpineViewer/Resources/Strings/zh.xaml
@@ -78,6 +78,8 @@
插槽
清除附件
+ 启用
+ 禁用
动画
添加
diff --git a/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs b/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs
index 19fb81a..df0a56d 100644
--- a/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs
+++ b/SpineViewer/ViewModels/MainWindow/SpineObjectTabViewModel.cs
@@ -15,7 +15,7 @@ namespace SpineViewer.ViewModels.MainWindow
{
private SpineObjectModel[] _selectedObjects = [];
private readonly ObservableCollection _skins = [];
- private readonly ObservableCollection _slots = [];
+ private readonly ObservableCollection _slots = [];
private readonly ObservableCollection _animationTracks = [];
public ImmutableArray PhysicsOptions { get; } = Enum.GetValues().ToImmutableArray();
@@ -324,11 +324,16 @@ namespace SpineViewer.ViewModels.MainWindow
args => { return args is not null && args.OfType().Any(); }
);
- public ObservableCollection Slots => _slots;
+ public ObservableCollection Slots => _slots;
- public RelayCommand Cmd_ClearSlotsAttachment { get; } = new(
- args => { if (args is null) return; foreach (var s in args.OfType()) s.AttachmentName = null; },
- args => { return args is not null && args.OfType().Any(); }
+ public RelayCommand Cmd_EnableSlots { get; } = new(
+ args => { if (args is null) return; foreach (var s in args.OfType()) s.Visible = true; },
+ args => { return args is not null && args.OfType().Any(); }
+ );
+
+ public RelayCommand Cmd_DisableSlots { get; } = new(
+ args => { if (args is null) return; foreach (var s in args.OfType()) s.Visible = false; },
+ args => { return args is not null && args.OfType().Any(); }
);
public ObservableCollection AnimationTracks => _animationTracks;
@@ -672,13 +677,13 @@ namespace SpineViewer.ViewModels.MainWindow
}
}
- public class SlotAttachmentViewModel : ObservableObject
+ public class SlotViewModel : ObservableObject
{
private readonly SpineObjectModel[] _spines;
private readonly string[] _attachmentNames = [];
private readonly string _slotName;
- public SlotAttachmentViewModel(string slotName, SpineObjectModel[] spines)
+ public SlotViewModel(string slotName, SpineObjectModel[] spines)
{
_spines = spines;
_slotName = slotName;
@@ -694,6 +699,11 @@ namespace SpineViewer.ViewModels.MainWindow
// 使用弱引用, 则此 ViewModel 被释放时无需显式退订事件
foreach (var sp in _spines)
{
+ WeakEventManager.AddHandler(
+ sp,
+ nameof(sp.SlotVisibleChanged),
+ SingleModel_SlotVisibleChanged
+ );
WeakEventManager.AddHandler(
sp,
nameof(sp.SlotAttachmentChanged),
@@ -707,9 +717,6 @@ namespace SpineViewer.ViewModels.MainWindow
}
}
- public RelayCommand Cmd_ClearAttachment => _cmd_ClearAttachment ??= new(() => AttachmentName = null);
- private RelayCommand? _cmd_ClearAttachment;
-
public ReadOnlyCollection AttachmentNames => _attachmentNames.AsReadOnly();
public string SlotName => _slotName;
@@ -733,6 +740,30 @@ namespace SpineViewer.ViewModels.MainWindow
}
}
+ public bool? Visible
+ {
+ get
+ {
+ if (_spines.Length <= 0) return null;
+ var val = _spines[0].GetSlotVisible(_slotName);
+ if (_spines.Skip(1).Any(it => it.GetSlotVisible(_slotName) != val)) return null;
+ return val;
+ }
+
+ set
+ {
+ if (_spines.Length <= 0) return;
+ if (value is null) return;
+ foreach (var sp in _spines) sp.SetSlotVisible(_slotName, (bool)value);
+ OnPropertyChanged();
+ }
+ }
+
+ private void SingleModel_SlotVisibleChanged(object? sender, SlotVisibleChangedEventArgs e)
+ {
+ if (e.SlotName == _slotName) OnPropertyChanged(nameof(Visible));
+ }
+
private void SingleModel_SlotAttachmentChanged(object? sender, SlotAttachmentChangedEventArgs e)
{
if (e.SlotName == _slotName) OnPropertyChanged(nameof(AttachmentName));
diff --git a/SpineViewer/Views/MainWindow.xaml b/SpineViewer/Views/MainWindow.xaml
index 90625c3..a23091b 100644
--- a/SpineViewer/Views/MainWindow.xaml
+++ b/SpineViewer/Views/MainWindow.xaml
@@ -524,8 +524,11 @@
-
+
@@ -540,9 +543,7 @@
-
+