diff --git a/SightKeeper.Avalonia/DataSets/Dialogs/CreateDataSetViewModel.cs b/SightKeeper.Avalonia/DataSets/Dialogs/CreateDataSetViewModel.cs index 4571528f..d41a2a50 100644 --- a/SightKeeper.Avalonia/DataSets/Dialogs/CreateDataSetViewModel.cs +++ b/SightKeeper.Avalonia/DataSets/Dialogs/CreateDataSetViewModel.cs @@ -16,7 +16,12 @@ internal sealed partial class CreateDataSetViewModel : DialogViewModel, ID protected override bool DefaultResult => false; public DataSetEditorViewModel DataSetEditor { get; } - public TagsEditorViewModel TagsEditor { get; } = new(); + + public TagsEditorViewModel TagsEditor + { + get => _tagsEditor; + private set => SetProperty(ref _tagsEditor, value); + } public ImmutableArray DataSetTypes { get; } = [ @@ -32,7 +37,7 @@ public CreateDataSetViewModel(DataSetEditorViewModel dataSetDataSetEditor) DataSetEditor.Resolution = DataSet.DefaultResolution; _dataSetType = DataSetTypes.First(); DataSetEditor.ErrorsChanged += OnDataSetEditorErrorsChanged; - _constructorDisposable = TagsEditor.IsValid.Subscribe(_ => ApplyCommand.NotifyCanExecuteChanged()); + _tagsEditorSubscription = TagsEditor.IsValid.Subscribe(_ => ApplyCommand.NotifyCanExecuteChanged()); } public void Dispose() @@ -40,11 +45,24 @@ public void Dispose() DataSetEditor.Dispose(); DataSetEditor.ErrorsChanged -= OnDataSetEditorErrorsChanged; TagsEditor.Dispose(); - _constructorDisposable.Dispose(); + _tagsEditorSubscription.Dispose(); } [ObservableProperty] private DataSetType _dataSetType; - private readonly IDisposable _constructorDisposable; + private IDisposable _tagsEditorSubscription; + private TagsEditorViewModel _tagsEditor = new(); + + partial void OnDataSetTypeChanged(DataSetType value) + { + _tagsEditorSubscription.Dispose(); + TagsEditor = value switch + { + DataSetType.Classifier or DataSetType.Detector => new TagsEditorViewModel(), + DataSetType.Poser2D or DataSetType.Poser3D => new PoserTagsEditorViewModel(), + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + _tagsEditorSubscription = TagsEditor.IsValid.Subscribe(_ => ApplyCommand.NotifyCanExecuteChanged()); + } private void OnDataSetEditorErrorsChanged(object? sender, DataErrorsChangedEventArgs e) { diff --git a/SightKeeper.Avalonia/DataSets/Dialogs/PoserTagViewModel.cs b/SightKeeper.Avalonia/DataSets/Dialogs/PoserTagViewModel.cs new file mode 100644 index 00000000..75344200 --- /dev/null +++ b/SightKeeper.Avalonia/DataSets/Dialogs/PoserTagViewModel.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Windows.Input; +using FluentValidation; +using SightKeeper.Application.DataSets.Tags; + +namespace SightKeeper.Avalonia.DataSets.Dialogs; + +internal sealed class PoserTagViewModel : TagViewModel, PoserTagData, IDisposable +{ + IReadOnlyCollection PoserTagData.KeyPointTags => _tagsEditor.Tags; + + public ICommand AddKeyPointTagCommand => _tagsEditor.AddTagCommand; + public IReadOnlyCollection KeyPointTags => _tagsEditor.Tags; + public BehaviorObservable IsKeyPointsValid => _tagsEditor.IsValid; + + public PoserTagViewModel(string name, IValidator validator) : base(name, validator) + { + } + + public void Dispose() + { + _tagsEditor.Dispose(); + } + + private readonly TagsEditorViewModel _tagsEditor = new(); +} \ No newline at end of file diff --git a/SightKeeper.Avalonia/DataSets/Dialogs/PoserTagsEditorViewModel.cs b/SightKeeper.Avalonia/DataSets/Dialogs/PoserTagsEditorViewModel.cs new file mode 100644 index 00000000..ef134942 --- /dev/null +++ b/SightKeeper.Avalonia/DataSets/Dialogs/PoserTagsEditorViewModel.cs @@ -0,0 +1,23 @@ +using System; +using System.Reactive.Disposables; +using SightKeeper.Application.DataSets.Tags; +using SightKeeper.Application.Extensions; + +namespace SightKeeper.Avalonia.DataSets.Dialogs; + +internal sealed class PoserTagsEditorViewModel : TagsEditorViewModel +{ + protected override TagViewModel CreateTagViewModel(string name, TagDataValidator validator) + { + PoserTagViewModel tag = new(name, validator); + tag.IsKeyPointsValid.Subscribe(_ => UpdateIsValid()).DisposeWith(_disposable); + return tag; + } + + protected override bool IsTagValid(TagViewModel tag) + { + return base.IsTagValid(tag) && ((PoserTagViewModel)tag).IsKeyPointsValid; + } + + private readonly CompositeDisposable _disposable = new(); +} \ No newline at end of file diff --git a/SightKeeper.Avalonia/DataSets/Dialogs/TagViewModel.cs b/SightKeeper.Avalonia/DataSets/Dialogs/TagViewModel.cs index e0fd6724..d9b47887 100644 --- a/SightKeeper.Avalonia/DataSets/Dialogs/TagViewModel.cs +++ b/SightKeeper.Avalonia/DataSets/Dialogs/TagViewModel.cs @@ -17,7 +17,7 @@ public event EventHandler? ErrorsChanged } public ViewModelValidator Validator { get; } - public bool HasErrors => Validator.HasErrors; + public virtual bool HasErrors => Validator.HasErrors; public TagViewModel(string name, IValidator validator) { diff --git a/SightKeeper.Avalonia/DataSets/Dialogs/TagsEditor.axaml b/SightKeeper.Avalonia/DataSets/Dialogs/TagsEditor.axaml index 3d0e5133..fa5d10a1 100644 --- a/SightKeeper.Avalonia/DataSets/Dialogs/TagsEditor.axaml +++ b/SightKeeper.Avalonia/DataSets/Dialogs/TagsEditor.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:SightKeeper.Avalonia.DataSets.Dialogs" xmlns:behaviors="clr-namespace:SightKeeper.Avalonia.Behaviors" + xmlns:icons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SightKeeper.Avalonia.DataSets.Dialogs.TagsEditor" x:DataType="local:TagsEditorViewModel"> @@ -29,11 +30,36 @@ - - + + + + + + + + + + + + + + + + + + + - + diff --git a/SightKeeper.Avalonia/DataSets/Dialogs/TagsEditorViewModel.cs b/SightKeeper.Avalonia/DataSets/Dialogs/TagsEditorViewModel.cs index 580e27e3..2689f514 100644 --- a/SightKeeper.Avalonia/DataSets/Dialogs/TagsEditorViewModel.cs +++ b/SightKeeper.Avalonia/DataSets/Dialogs/TagsEditorViewModel.cs @@ -11,7 +11,7 @@ namespace SightKeeper.Avalonia.DataSets.Dialogs; -internal sealed partial class TagsEditorViewModel : ViewModel, IDisposable +internal partial class TagsEditorViewModel : ViewModel, IDisposable { public BehaviorObservable IsValid => _isValid; public IReadOnlyCollection Tags => _tags; @@ -25,6 +25,11 @@ public void Dispose() } } + protected virtual TagViewModel CreateTagViewModel(string name, TagDataValidator validator) + { + return new TagViewModel(name, validator); + } + private readonly BehaviorSubject _isValid = new(true); private readonly AvaloniaList _tags = new(); @@ -32,7 +37,7 @@ public void Dispose() private void AddTag(string name) { TagDataValidator validator = new(Tags); - TagViewModel tag = new(name, validator); + TagViewModel tag = CreateTagViewModel(name, validator); tag.PropertyChanged += OnTagPropertyChanged; tag.ErrorsChanged += OnTagErrorsChanged; _tags.Add(tag); @@ -40,11 +45,21 @@ private void AddTag(string name) private void OnTagErrorsChanged(object? sender, DataErrorsChangedEventArgs e) { - bool isValid = Tags.All(tag => !tag.HasErrors); + UpdateIsValid(); + } + + protected void UpdateIsValid() + { + bool isValid = Tags.All(IsTagValid); if (IsValid != isValid) _isValid.OnNext(isValid); } + protected virtual bool IsTagValid(TagViewModel tag) + { + return !tag.HasErrors; + } + private void OnTagPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName != nameof(TagViewModel.Name))