修复插槽禁用功能

This commit is contained in:
ww-rm
2025-08-18 18:39:27 +08:00
parent 5bfa625868
commit 6dfd25b760
18 changed files with 135 additions and 21 deletions

View File

@@ -66,6 +66,8 @@ namespace Spine.Implementations.SpineWrappers.V21
} }
} }
public bool Disabled { get; set; }
public override string ToString() => _o.ToString(); public override string ToString() => _o.ToString();
} }
} }

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V34
} }
} }
public bool Disabled { get; set; }
public override string ToString() => _o.ToString(); public override string ToString() => _o.ToString();
} }
} }

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V35
} }
} }
public bool Disabled { get; set; }
public override string ToString() => _o.ToString(); public override string ToString() => _o.ToString();
} }
} }

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V36
} }
} }
public bool Disabled { get; set; }
public override string ToString() => _o.ToString(); public override string ToString() => _o.ToString();
} }
} }

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V37
} }
} }
public bool Disabled { get; set; }
public override string ToString() => _o.ToString(); public override string ToString() => _o.ToString();
} }
} }

View File

@@ -74,6 +74,8 @@ namespace Spine.Implementations.SpineWrappers.V38
} }
} }
public bool Disabled { get; set; }
public override string ToString() => _o.ToString(); public override string ToString() => _o.ToString();
} }
} }

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V40
} }
} }
public bool Disabled { get; set; }
public override string ToString() => _o.ToString(); public override string ToString() => _o.ToString();
} }
} }

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V41
} }
} }
public bool Disabled { get; set; }
public override string ToString() => _o.ToString(); public override string ToString() => _o.ToString();
} }
} }

View File

@@ -73,6 +73,8 @@ namespace Spine.Implementations.SpineWrappers.V42
} }
} }
public bool Disabled { get; set; }
public override string ToString() => _o.ToString(); public override string ToString() => _o.ToString();
} }
} }

View File

@@ -299,6 +299,30 @@ namespace Spine
/// </summary> /// </summary>
public bool DebugClippings { get; set; } public bool DebugClippings { get; set; }
/// <summary>
/// 获取插槽可见性, 如果不存在则默认返回 false
/// </summary>
public bool GetSlotVisible(string slotName)
{
if (_skeleton.SlotsByName.TryGetValue(slotName, out var slot))
return !slot.Disabled;
return false;
}
/// <summary>
/// 设置插槽可见性, 插槽不可见后将不会在任何渲染中出现, 插槽不存在则忽略操作
/// </summary>
/// <returns>操作是否成功, 插槽不存在则返回 false</returns>
public bool SetSlotVisible(string slotName, bool visible)
{
if (_skeleton.SlotsByName.TryGetValue(slotName, out var slot))
{
slot.Disabled = !visible;
return true;
}
return false;
}
/// <summary> /// <summary>
/// 获取某个插槽上的附件名, 插槽不存在或者无附件均返回 null /// 获取某个插槽上的附件名, 插槽不存在或者无附件均返回 null
/// </summary> /// </summary>
@@ -310,7 +334,7 @@ namespace Spine
} }
/// <summary> /// <summary>
/// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来清除附件 /// 设置某个插槽的附件, 如果不存在则忽略, 可以使用 null 来尝试清除附件
/// </summary> /// </summary>
/// <returns>是否操作成功</returns> /// <returns>是否操作成功</returns>
public bool SetAttachment(string slotName, string? attachmentName) public bool SetAttachment(string slotName, string? attachmentName)
@@ -471,7 +495,7 @@ namespace Spine
foreach (var slot in _skeleton.IterDrawOrder()) 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); _clipping.ClipEnd(slot);
continue; continue;
@@ -602,7 +626,7 @@ namespace Spine
if (DebugRegions) if (DebugRegions)
{ {
vt.Color = AttachmentLineColor; 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) if (slot.Attachment is IRegionAttachment regionAttachment)
{ {
@@ -634,7 +658,7 @@ namespace Spine
if (DebugMeshes) if (DebugMeshes)
{ {
vt.Color = MeshLineColor; 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) if (slot.Attachment is IMeshAttachment meshAttachment)
{ {
@@ -698,7 +722,7 @@ namespace Spine
if (DebugMeshHulls) if (DebugMeshHulls)
{ {
vt.Color = AttachmentLineColor; 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) if (slot.Attachment is IMeshAttachment meshAttachment)
{ {
@@ -767,7 +791,7 @@ namespace Spine
if (DebugClippings) if (DebugClippings)
{ {
vt.Color = ClippingLineColor; 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) if (slot.Attachment is IClippingAttachment clippingAttachment)
{ {

View File

@@ -53,5 +53,10 @@ namespace Spine.SpineWrappers
/// 使用的附件, 可以设置为 null 清空附件 /// 使用的附件, 可以设置为 null 清空附件
/// </summary> /// </summary>
public IAttachment? Attachment { get; set; } public IAttachment? Attachment { get; set; }
/// <summary>
/// 是否已禁用渲染该插槽
/// </summary>
public bool Disabled { get; set; }
} }
} }

View File

@@ -31,6 +31,8 @@ namespace SpineViewer.Models
public Dictionary<string, string?> SlotAttachment { get; set; } = []; public Dictionary<string, string?> SlotAttachment { get; set; } = [];
public List<string> DisabledSlots { get; set; } = [];
public List<string?> Animations { get; set; } = []; public List<string?> Animations { get; set; } = [];
public bool DebugTexture { get; set; } = true; public bool DebugTexture { get; set; } = true;

View File

@@ -85,6 +85,8 @@ namespace SpineViewer.Models
public event EventHandler<SkinStatusChangedEventArgs>? SkinStatusChanged; public event EventHandler<SkinStatusChangedEventArgs>? SkinStatusChanged;
public event EventHandler<SlotVisibleChangedEventArgs>? SlotVisibleChanged;
public event EventHandler<SlotAttachmentChangedEventArgs>? SlotAttachmentChanged; public event EventHandler<SlotAttachmentChangedEventArgs>? SlotAttachmentChanged;
public event EventHandler<AnimationChangedEventArgs>? AnimationChanged; public event EventHandler<AnimationChangedEventArgs>? AnimationChanged;
@@ -200,6 +202,19 @@ namespace SpineViewer.Models
public FrozenDictionary<string, ImmutableArray<string>> SlotAttachments => _slotAttachments; public FrozenDictionary<string, ImmutableArray<string>> 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) public string? GetAttachment(string slotName)
{ {
lock (_lock) return _spineObject.GetAttachment(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; 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: 处理空动画 // XXX: 处理空动画
config.Animations.AddRange(_spineObject.AnimationState.IterTracks().Select(tr => tr?.Animation.Name)); config.Animations.AddRange(_spineObject.AnimationState.IterTracks().Select(tr => tr?.Animation.Name));
@@ -422,6 +439,10 @@ namespace SpineViewer.Models
if (_spineObject.SetAttachment(slotName, attachmentName)) if (_spineObject.SetAttachment(slotName, attachmentName))
SlotAttachmentChanged?.Invoke(this, new(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: 处理空动画 // XXX: 处理空动画
_spineObject.AnimationState.ClearTracks(); _spineObject.AnimationState.ClearTracks();
int trackIndex = 0; int trackIndex = 0;
@@ -507,6 +528,12 @@ namespace SpineViewer.Models
public bool Status { get; } = status; 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 class SlotAttachmentChangedEventArgs(string slotName, string? attachmentName) : EventArgs
{ {
public string SlotName { get; } = slotName; public string SlotName { get; } = slotName;

View File

@@ -78,6 +78,8 @@
<s:String x:Key="Str_Slot">Slot</s:String> <s:String x:Key="Str_Slot">Slot</s:String>
<s:String x:Key="Str_ClearSlotsAttachment">Clear Slots Attachment</s:String> <s:String x:Key="Str_ClearSlotsAttachment">Clear Slots Attachment</s:String>
<s:String x:Key="Str_EnableSlots">Enable Slots</s:String>
<s:String x:Key="Str_DisableSlots">Disable Slots</s:String>
<s:String x:Key="Str_Animation">Animation</s:String> <s:String x:Key="Str_Animation">Animation</s:String>
<s:String x:Key="Str_AppendTrack">Add</s:String> <s:String x:Key="Str_AppendTrack">Add</s:String>

View File

@@ -78,6 +78,8 @@
<s:String x:Key="Str_Slot">スロット</s:String> <s:String x:Key="Str_Slot">スロット</s:String>
<s:String x:Key="Str_ClearSlotsAttachment">アタッチメントをクリア</s:String> <s:String x:Key="Str_ClearSlotsAttachment">アタッチメントをクリア</s:String>
<s:String x:Key="Str_EnableSlots">有効</s:String>
<s:String x:Key="Str_DisableSlots">無効</s:String>
<s:String x:Key="Str_Animation">アニメーション</s:String> <s:String x:Key="Str_Animation">アニメーション</s:String>
<s:String x:Key="Str_AppendTrack">追加</s:String> <s:String x:Key="Str_AppendTrack">追加</s:String>

View File

@@ -78,6 +78,8 @@
<s:String x:Key="Str_Slot">插槽</s:String> <s:String x:Key="Str_Slot">插槽</s:String>
<s:String x:Key="Str_ClearSlotsAttachment">清除附件</s:String> <s:String x:Key="Str_ClearSlotsAttachment">清除附件</s:String>
<s:String x:Key="Str_EnableSlots">启用</s:String>
<s:String x:Key="Str_DisableSlots">禁用</s:String>
<s:String x:Key="Str_Animation">动画</s:String> <s:String x:Key="Str_Animation">动画</s:String>
<s:String x:Key="Str_AppendTrack">添加</s:String> <s:String x:Key="Str_AppendTrack">添加</s:String>

View File

@@ -15,7 +15,7 @@ namespace SpineViewer.ViewModels.MainWindow
{ {
private SpineObjectModel[] _selectedObjects = []; private SpineObjectModel[] _selectedObjects = [];
private readonly ObservableCollection<SkinViewModel> _skins = []; private readonly ObservableCollection<SkinViewModel> _skins = [];
private readonly ObservableCollection<SlotAttachmentViewModel> _slots = []; private readonly ObservableCollection<SlotViewModel> _slots = [];
private readonly ObservableCollection<AnimationTrackViewModel> _animationTracks = []; private readonly ObservableCollection<AnimationTrackViewModel> _animationTracks = [];
public ImmutableArray<ISkeleton.Physics> PhysicsOptions { get; } = Enum.GetValues<ISkeleton.Physics>().ToImmutableArray(); public ImmutableArray<ISkeleton.Physics> PhysicsOptions { get; } = Enum.GetValues<ISkeleton.Physics>().ToImmutableArray();
@@ -324,11 +324,16 @@ namespace SpineViewer.ViewModels.MainWindow
args => { return args is not null && args.OfType<SkinViewModel>().Any(); } args => { return args is not null && args.OfType<SkinViewModel>().Any(); }
); );
public ObservableCollection<SlotAttachmentViewModel> Slots => _slots; public ObservableCollection<SlotViewModel> Slots => _slots;
public RelayCommand<IList?> Cmd_ClearSlotsAttachment { get; } = new( public RelayCommand<IList?> Cmd_EnableSlots { get; } = new(
args => { if (args is null) return; foreach (var s in args.OfType<SlotAttachmentViewModel>()) s.AttachmentName = null; }, args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = true; },
args => { return args is not null && args.OfType<SlotAttachmentViewModel>().Any(); } args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
);
public RelayCommand<IList?> Cmd_DisableSlots { get; } = new(
args => { if (args is null) return; foreach (var s in args.OfType<SlotViewModel>()) s.Visible = false; },
args => { return args is not null && args.OfType<SlotViewModel>().Any(); }
); );
public ObservableCollection<AnimationTrackViewModel> AnimationTracks => _animationTracks; public ObservableCollection<AnimationTrackViewModel> AnimationTracks => _animationTracks;
@@ -672,13 +677,13 @@ namespace SpineViewer.ViewModels.MainWindow
} }
} }
public class SlotAttachmentViewModel : ObservableObject public class SlotViewModel : ObservableObject
{ {
private readonly SpineObjectModel[] _spines; private readonly SpineObjectModel[] _spines;
private readonly string[] _attachmentNames = []; private readonly string[] _attachmentNames = [];
private readonly string _slotName; private readonly string _slotName;
public SlotAttachmentViewModel(string slotName, SpineObjectModel[] spines) public SlotViewModel(string slotName, SpineObjectModel[] spines)
{ {
_spines = spines; _spines = spines;
_slotName = slotName; _slotName = slotName;
@@ -694,6 +699,11 @@ namespace SpineViewer.ViewModels.MainWindow
// 使用弱引用, 则此 ViewModel 被释放时无需显式退订事件 // 使用弱引用, 则此 ViewModel 被释放时无需显式退订事件
foreach (var sp in _spines) foreach (var sp in _spines)
{ {
WeakEventManager<SpineObjectModel, SlotVisibleChangedEventArgs>.AddHandler(
sp,
nameof(sp.SlotVisibleChanged),
SingleModel_SlotVisibleChanged
);
WeakEventManager<SpineObjectModel, SlotAttachmentChangedEventArgs>.AddHandler( WeakEventManager<SpineObjectModel, SlotAttachmentChangedEventArgs>.AddHandler(
sp, sp,
nameof(sp.SlotAttachmentChanged), 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<string> AttachmentNames => _attachmentNames.AsReadOnly(); public ReadOnlyCollection<string> AttachmentNames => _attachmentNames.AsReadOnly();
public string SlotName => _slotName; 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) private void SingleModel_SlotAttachmentChanged(object? sender, SlotAttachmentChangedEventArgs e)
{ {
if (e.SlotName == _slotName) OnPropertyChanged(nameof(AttachmentName)); if (e.SlotName == _slotName) OnPropertyChanged(nameof(AttachmentName));

View File

@@ -524,8 +524,11 @@
<ListBox.ContextMenu> <ListBox.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{DynamicResource Str_ClearSlotsAttachment}" <MenuItem Header="{DynamicResource Str_EnableSlots}"
Command="{Binding Cmd_ClearSlotsAttachment}" Command="{Binding Cmd_EnableSlots}"
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
<MenuItem Header="{DynamicResource Str_DisableSlots}"
Command="{Binding Cmd_DisableSlots}"
CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/> CommandParameter="{Binding PlacementTarget.SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu> </ContextMenu>
</ListBox.ContextMenu> </ListBox.ContextMenu>
@@ -540,9 +543,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding SlotName}" HorizontalAlignment="Left"/> <Label Grid.Column="0" Content="{Binding SlotName}" HorizontalAlignment="Left"/>
<ComboBox Grid.Column="1" SelectedValue="{Binding AttachmentName}" ItemsSource="{Binding AttachmentNames}"/> <ComboBox Grid.Column="1" SelectedValue="{Binding AttachmentName}" ItemsSource="{Binding AttachmentNames}"/>
<Button Grid.Column="2" <ToggleButton Grid.Column="2" IsChecked="{Binding Visible}"/>
Command="{Binding Cmd_ClearAttachment}"
hc:IconElement.Geometry="{StaticResource Geo_Ban}"/>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>