From 932522a0ca91a5bdaab9f7400392d8f85a4ce9d6 Mon Sep 17 00:00:00 2001 From: Razmoth <32140579+Razmoth@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:06:03 +0400 Subject: [PATCH] - GUI imporvments. - Bug fixes. - Conversion performance improvments. --- Audio.GUI.Desktop/Audio.GUI.Desktop.csproj | 6 +- Audio.GUI/Audio.GUI.csproj | 4 +- Audio.GUI/Behaviours/FileDropBehaviour.cs | 45 +++ ...ScrollToEndOnCollectionChangedBehaviour.cs | 48 --- Audio.GUI/Controls/VLCPlayer.axaml.cs | 175 ----------- Audio.GUI/Models/EntryTreeNode.cs | 1 + Audio.GUI/ViewModels/EntryViewModel.cs | 10 + Audio.GUI/ViewModels/LogViewModel.cs | 24 +- Audio.GUI/ViewModels/MainViewModel.cs | 84 +++-- Audio.GUI/ViewModels/PlayerViewModel.cs | 123 ++++++++ Audio.GUI/ViewModels/TreeViewModel.cs | 75 ++--- Audio.GUI/Views/EntryView.axaml | 10 +- Audio.GUI/Views/LogView.axaml | 33 +- Audio.GUI/Views/MainView.axaml | 21 +- .../PlayerView.axaml} | 18 +- Audio.GUI/Views/PlayerView.axaml.cs | 11 + Audio.GUI/Views/TreeView.axaml | 7 +- Audio/AudioManager.cs | 10 +- Audio/Chunks/STID.cs | 2 +- .../HIRC/Utils/DecisionTree/DecisionTree.cs | 5 +- .../Utils/DecisionTree/DecisionTreeNode.cs | 7 + Audio/Conversion/Chunks/AKD.cs | 28 ++ Audio/Conversion/Chunks/EnvelopePoint.cs | 13 + Audio/Conversion/Codecs/Vorbis.cs | 297 +++++++++--------- Audio/Conversion/Utils/BitHelper.cs | 45 --- Audio/Conversion/Utils/BitStream.cs | 173 +++++++--- Audio/Conversion/Utils/BitValue.cs | 57 ---- Audio/Conversion/Utils/OGGStream.cs | 11 +- Audio/Conversion/WWiseCodebook.cs | 153 +++++---- Audio/Entries/EmbeddedSound.cs | 2 +- Audio/Entries/Folder.cs | 4 +- .../{TaggedEntry.cs => TaggedEntry{T}.cs} | 0 Audio/FNVID.cs | 34 +- 33 files changed, 805 insertions(+), 731 deletions(-) create mode 100644 Audio.GUI/Behaviours/FileDropBehaviour.cs delete mode 100644 Audio.GUI/Behaviours/ScrollToEndOnCollectionChangedBehaviour.cs delete mode 100644 Audio.GUI/Controls/VLCPlayer.axaml.cs create mode 100644 Audio.GUI/ViewModels/PlayerViewModel.cs rename Audio.GUI/{Controls/VLCPlayer.axaml => Views/PlayerView.axaml} (62%) create mode 100644 Audio.GUI/Views/PlayerView.axaml.cs create mode 100644 Audio/Conversion/Chunks/AKD.cs create mode 100644 Audio/Conversion/Chunks/EnvelopePoint.cs delete mode 100644 Audio/Conversion/Utils/BitHelper.cs delete mode 100644 Audio/Conversion/Utils/BitValue.cs rename Audio/Entries/{TaggedEntry.cs => TaggedEntry{T}.cs} (100%) diff --git a/Audio.GUI.Desktop/Audio.GUI.Desktop.csproj b/Audio.GUI.Desktop/Audio.GUI.Desktop.csproj index 65ba1bc..bf6f39c 100644 --- a/Audio.GUI.Desktop/Audio.GUI.Desktop.csproj +++ b/Audio.GUI.Desktop/Audio.GUI.Desktop.csproj @@ -13,13 +13,14 @@ - + + @@ -31,6 +32,7 @@ + @@ -39,7 +41,7 @@ - + diff --git a/Audio.GUI/Audio.GUI.csproj b/Audio.GUI/Audio.GUI.csproj index 9adcf72..829a596 100644 --- a/Audio.GUI/Audio.GUI.csproj +++ b/Audio.GUI/Audio.GUI.csproj @@ -29,8 +29,8 @@ - - VLCPlayer.axaml + + PlayerView.axaml EntryView.axaml diff --git a/Audio.GUI/Behaviours/FileDropBehaviour.cs b/Audio.GUI/Behaviours/FileDropBehaviour.cs new file mode 100644 index 0000000..b2c1641 --- /dev/null +++ b/Audio.GUI/Behaviours/FileDropBehaviour.cs @@ -0,0 +1,45 @@ +using Audio.GUI.ViewModels; +using Avalonia.Input; +using Avalonia.Platform.Storage; +using Avalonia.Xaml.Interactions.DragAndDrop; +using System.Collections.Generic; +using System.IO; + +namespace Audio.GUI.Behaviours; +public class FileDropBehaviour : DropHandlerBase +{ + public override bool Execute(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state) + { + if (targetContext is MainViewModel mainViewModel) + { + List files = []; + + foreach(IStorageItem? storageItem in e.Data.GetFiles() ?? []) + { + string? path = storageItem.TryGetLocalPath(); + if (!string.IsNullOrWhiteSpace(path)) + { + if (Directory.Exists(path)) + { + files.AddRange(Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)); + } + else if (File.Exists(path)) + { + files.Add(path); + } + } + } + + _ = mainViewModel.LoadFiles(files); + + return true; + } + + return false; + } + + public override bool Validate(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, object? state) + { + return targetContext is MainViewModel && e.Data.Contains(DataFormats.Files); + } +} diff --git a/Audio.GUI/Behaviours/ScrollToEndOnCollectionChangedBehaviour.cs b/Audio.GUI/Behaviours/ScrollToEndOnCollectionChangedBehaviour.cs deleted file mode 100644 index 3efe3c5..0000000 --- a/Audio.GUI/Behaviours/ScrollToEndOnCollectionChangedBehaviour.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Threading; -using Avalonia.Xaml.Interactivity; -using System.Collections.Specialized; - -namespace Audio.GUI.Behaviours; -public class ScrollToEndOnCollectionChangedBehaviour : Behavior -{ - protected override void OnAttached() - { - base.OnAttached(); - if (AssociatedObject != null) - { - AssociatedObject.Loaded += AssociatedObject_Loaded; - AssociatedObject.Unloaded += AssociatedObject_Unloaded; - } - } - protected override void OnDetaching() - { - base.OnDetaching(); - if (AssociatedObject != null) - { - AssociatedObject.Loaded -= AssociatedObject_Loaded; - AssociatedObject.Unloaded -= AssociatedObject_Unloaded; - } - } - private void AssociatedObject_Loaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e) - { - if (AssociatedObject?.Items is INotifyCollectionChanged notifyCollectionChanged) - { - notifyCollectionChanged.CollectionChanged += NotifyCollectionChanged_CollectionChanged; - } - } - private void AssociatedObject_Unloaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e) - { - if (AssociatedObject?.Items is INotifyCollectionChanged notifyCollectionChanged) - { - notifyCollectionChanged.CollectionChanged -= NotifyCollectionChanged_CollectionChanged; - } - } - private void NotifyCollectionChanged_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Add && AssociatedObject?.Items.Count > 0) - { - Dispatcher.UIThread.Post(() => AssociatedObject?.ScrollIntoView(AssociatedObject.Items.Count - 1)); - } - } -} diff --git a/Audio.GUI/Controls/VLCPlayer.axaml.cs b/Audio.GUI/Controls/VLCPlayer.axaml.cs deleted file mode 100644 index 4905db2..0000000 --- a/Audio.GUI/Controls/VLCPlayer.axaml.cs +++ /dev/null @@ -1,175 +0,0 @@ -using Audio.Entries; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Input; -using Avalonia.Interactivity; -using Avalonia.Threading; -using LibVLCSharp.Shared; -using System; -using System.IO; -using System.Threading.Tasks; - -namespace Audio.GUI.Controls; - -public partial class VLCPlayer : UserControl, IDisposable -{ - private readonly MediaPlayer _mediaPlayer; - private readonly LibVLC _context; - - private MemoryStream? _stream; - private bool _isLoading = false; - private bool _isSeeking = false; - - private Entry? _entry; - - public static readonly DirectProperty EntryProperty = - AvaloniaProperty.RegisterDirect(nameof(Entry), o => o.Entry, (o, v) => o.Entry = v); - - public Entry? Entry - { - get => _entry; - set => Task.Run(() => LoadAudio(value)); - } - - public VLCPlayer() - { - InitializeComponent(); - - _context = new(); - _mediaPlayer = new(_context); - _mediaPlayer.LengthChanged += MediaPlayer_LengthChanged; - _mediaPlayer.TimeChanged += MediaPlayer_TimeChanged; - _mediaPlayer.EndReached += MediaPlayer_EndReached; - - Thumb.DragCompletedEvent.AddClassHandler((o, e) => o.OnThumbDragCompleted(e)); - Thumb.DragStartedEvent.AddClassHandler((o, e) => o.OnThumbDragStarted(e)); - Button.IsCheckedChanged += Button_IsCheckedChanged; - SeekBar.ValueChanged += SeekBar_ValueChanged; - } - - private void MediaPlayer_EndReached(object? sender, EventArgs e) - { - Task.Run(() => - { - _mediaPlayer.Stop(); - Dispatcher.UIThread.Post(() => - { - SeekBar.Value = 0; - Button.IsChecked = false; - }, DispatcherPriority.Render); - }); - } - - private void MediaPlayer_LengthChanged(object? sender, MediaPlayerLengthChangedEventArgs e) - { - Dispatcher.UIThread.Post(() => SeekBar.Maximum = _mediaPlayer.Length, DispatcherPriority.Render); - } - - private void MediaPlayer_TimeChanged(object? sender, MediaPlayerTimeChangedEventArgs e) - { - Task.Run(() => - { - if (_isLoading) - { - _mediaPlayer.Stop(); - } - - if (!_isSeeking) - { - Dispatcher.UIThread.Post(() => - { - _isSeeking = true; - SeekBar.Value = _mediaPlayer.Time; - _isSeeking = false; - }, DispatcherPriority.Render); - } - }); - - } - private void Button_IsCheckedChanged(object? sender, RoutedEventArgs e) - { - if (e.Source is ToggleButton toggleButton) - { - if (toggleButton.IsChecked == true) - { - _mediaPlayer.Play(); - } - else - { - _mediaPlayer.Pause(); - } - } - } - private void SeekBar_ValueChanged(object? sender, RangeBaseValueChangedEventArgs e) - { - if (!_isSeeking && e.Source is Slider slider) - { - Seek(slider); - } - } - private void OnThumbDragStarted(VectorEventArgs e) - { - _isSeeking = true; - } - private void OnThumbDragCompleted(VectorEventArgs e) - { - _isSeeking = false; - if (e.Source is Thumb thumb && thumb.TemplatedParent is Slider slider) - { - Seek(slider); - } - } - private void Seek(Slider slider) - { - double scale = _mediaPlayer.Length / (slider.Maximum - slider.Minimum); - _mediaPlayer.Time = (long)(slider.Value * scale); - } - private void LoadAudio(Entry? entry) - { - if (entry == null) return; - else if (entry.Type == EntryType.Bank) - { - Logger.Warning("Playing Bank type is not supported !!"); - return; - } - - Logger.Info($"Attempting to load audio {entry.Location}"); - - Dispatcher.UIThread.Post(() => - { - _isLoading = true; - Button.IsChecked = false; - }, DispatcherPriority.Render); - - MemoryStream memoryStream = new(); - if (entry.TryConvert(memoryStream, out _)) - { - _stream?.Dispose(); - _stream = memoryStream; - - _mediaPlayer.Media = new Media(_context, new StreamMediaInput(_stream)); - _mediaPlayer.Play(); - _entry = entry; - - Logger.Info($"{entry.Location} loaded successfully"); - - Dispatcher.UIThread.Post(() => - { - _isLoading = false; - Button.IsChecked = true; - }, DispatcherPriority.Render); - return; - } - - Logger.Info($"Unable to load {entry.Location}"); - return; - } - public void Dispose() - { - _stream?.Dispose(); - _mediaPlayer.Dispose(); - _context.Dispose(); - GC.SuppressFinalize(this); - } -} diff --git a/Audio.GUI/Models/EntryTreeNode.cs b/Audio.GUI/Models/EntryTreeNode.cs index b6858fa..771ad76 100644 --- a/Audio.GUI/Models/EntryTreeNode.cs +++ b/Audio.GUI/Models/EntryTreeNode.cs @@ -31,6 +31,7 @@ public override bool HasMatch(string? searchText) { foreach (KeyValuePair, HashSet> evt in uintTag.Events) { + match |= regex.IsMatch(evt.Key.ToString()); foreach (EventTag tag in evt.Value) { match |= regex.IsMatch(tag.Type.ToString()); diff --git a/Audio.GUI/ViewModels/EntryViewModel.cs b/Audio.GUI/ViewModels/EntryViewModel.cs index e3218ee..da861cb 100644 --- a/Audio.GUI/ViewModels/EntryViewModel.cs +++ b/Audio.GUI/ViewModels/EntryViewModel.cs @@ -3,17 +3,26 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; namespace Audio.GUI.ViewModels; public partial class EntryViewModel : ViewModelBase { + private readonly PlayerViewModel _playerViewModel; + [ObservableProperty] private string infoText = ""; [ObservableProperty] private Entry? entry; + public PlayerViewModel PlayerViewModel => _playerViewModel; + + public EntryViewModel() + { + _playerViewModel = new(); + } partial void OnEntryChanged(Entry? value) { if (value != null) @@ -39,6 +48,7 @@ partial void OnEntryChanged(Entry? value) } InfoText = sb.ToString(); + Task.Run(() => _playerViewModel.LoadAudio(value)); } } } \ No newline at end of file diff --git a/Audio.GUI/ViewModels/LogViewModel.cs b/Audio.GUI/ViewModels/LogViewModel.cs index b00edb5..af36811 100644 --- a/Audio.GUI/ViewModels/LogViewModel.cs +++ b/Audio.GUI/ViewModels/LogViewModel.cs @@ -1,19 +1,41 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using Avalonia.Controls; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using System; using System.Collections.ObjectModel; +using System.Threading.Tasks; namespace Audio.GUI.ViewModels; public partial class LogViewModel : ViewModelBase, ILogger, IDisposable { + private bool _scrolling = false; + [ObservableProperty] private ObservableCollection logs = []; + public LogViewModel() { Logger.TryRegister(this); } + [RelayCommand] + public void Clear() + { + Logs.Clear(); + } + [RelayCommand] + public async Task ScrollToEnd(ScrollChangedEventArgs e) + { + if (!_scrolling && e.Source is ScrollViewer scrollViewer && e.ExtentDelta != Avalonia.Vector.Zero) + { + _scrolling = true; + await Dispatcher.UIThread.InvokeAsync(scrollViewer.ScrollToEnd); + _scrolling = false; + } + } public void Log(LogLevel logLevel, string message) { Logs.Add($"[{logLevel}]: {message}"); diff --git a/Audio.GUI/ViewModels/MainViewModel.cs b/Audio.GUI/ViewModels/MainViewModel.cs index 825040d..9bb5085 100644 --- a/Audio.GUI/ViewModels/MainViewModel.cs +++ b/Audio.GUI/ViewModels/MainViewModel.cs @@ -73,15 +73,8 @@ public async Task LoadFile() { IReadOnlyList files = await PlatformServiceProvider.StorageProvider.OpenFilePickerAsync(new() { Title = "Pick file(s)", AllowMultiple = true }); IEnumerable paths = files.Select(x => x.TryGetLocalPath() ?? ""); - if (paths.Any()) - { - _audioManager.Clear(); - int loaded = await Task.Run(() => _audioManager.LoadFiles(paths.ToArray())); - if (loaded > 0) - { - _treeViewModel.Update(); - } - } + + await LoadFiles(paths); } } @@ -102,15 +95,7 @@ public async Task LoadFolder() } } - if (files.Count != 0) - { - _audioManager.Clear(); - int loaded = await Task.Run(() => _audioManager.LoadFiles([.. files])); - if (loaded > 0) - { - _treeViewModel.Update(); - } - } + await LoadFiles(files); } } @@ -122,31 +107,6 @@ public async Task LoadFolder() [RelayCommand] public async Task ExportAll() => await ExportEntry([EntryType.Bank, EntryType.Sound, EntryType.EmbeddedSound, EntryType.External]); - - private async Task ExportEntry(IEnumerable types) - { - if (PlatformServiceProvider.StorageProvider != null) - { - IReadOnlyList folders = await PlatformServiceProvider.StorageProvider.OpenFolderPickerAsync(new() { AllowMultiple = false }); - - IStorageFolder? folder = folders.FirstOrDefault(); - if (folder != null) - { - string? path = folder.TryGetLocalPath(); - if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path)) - { - if (_treeViewModel.Checked.Any()) - { - await Task.Run(() => _audioManager.DumpEntries(path, _treeViewModel.Checked.OfType().Select(x => x.Entry))); - } - else - { - await Task.Run(() => _audioManager.DumpEntries(path, types)); - } - } - } - } - } [RelayCommand] public async Task ExportHierarchy() @@ -222,6 +182,44 @@ public async Task LoadEvents() await Dispatcher.UIThread.InvokeAsync(_treeViewModel.Update); } + public async Task LoadFiles(IEnumerable files) + { + if (files.Any()) + { + _audioManager.Clear(); + int loaded = await Task.Run(() => _audioManager.LoadFiles([.. files])); + if (loaded > 0) + { + await Dispatcher.UIThread.InvokeAsync(_treeViewModel.Update); + } + } + } + + private async Task ExportEntry(IEnumerable types) + { + if (PlatformServiceProvider.StorageProvider != null) + { + IReadOnlyList folders = await PlatformServiceProvider.StorageProvider.OpenFolderPickerAsync(new() { AllowMultiple = false }); + + IStorageFolder? folder = folders.FirstOrDefault(); + if (folder != null) + { + string? path = folder.TryGetLocalPath(); + if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path)) + { + if (_treeViewModel.Checked.Any()) + { + await Task.Run(() => _audioManager.DumpEntries(path, _treeViewModel.Checked.OfType().Select(x => x.Entry))); + } + else + { + await Task.Run(() => _audioManager.DumpEntries(path, types)); + } + } + } + } + } + partial void OnConvertChanged(bool value) { _audioManager.Convert = value; diff --git a/Audio.GUI/ViewModels/PlayerViewModel.cs b/Audio.GUI/ViewModels/PlayerViewModel.cs new file mode 100644 index 0000000..fc497f0 --- /dev/null +++ b/Audio.GUI/ViewModels/PlayerViewModel.cs @@ -0,0 +1,123 @@ +using Audio.Entries; +using CommunityToolkit.Mvvm.ComponentModel; +using LibVLCSharp.Shared; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; + +namespace Audio.GUI.ViewModels; + +public partial class PlayerViewModel : ViewModelBase, IDisposable +{ + private readonly MediaPlayer _mediaPlayer; + private readonly LibVLC _context; + + [ObservableProperty] + private float position; + [ObservableProperty] + private float volume; + [ObservableProperty] + private TimeSpan time; + [ObservableProperty] + private TimeSpan duration; + [ObservableProperty] + private bool isChecked; + + private MemoryStream? _stream; + + public PlayerViewModel() + { + _context = new(); + _mediaPlayer = new(_context); + _mediaPlayer.PositionChanged += MediaPlayer_PositionChanged; + _mediaPlayer.LengthChanged += MediaPlayer_LengthChanged; + _mediaPlayer.TimeChanged += MediaPlayer_TimeChanged; + _mediaPlayer.EndReached += MediaPlayer_EndReached; + + Volume = 100; + } + + public void LoadAudio(Entry? entry) + { + if (entry == null) return; + else if (entry.Type == EntryType.Bank) + { + Logger.Warning("Playing Bank type is not supported !!"); + return; + } + + Logger.Info($"Attempting to load audio {entry.Location}"); + + IsChecked = false; + MemoryStream memoryStream = new(); + if (entry.TryConvert(memoryStream, out _)) + { + _stream?.Dispose(); + _stream = memoryStream; + + Position = 0; + IsChecked = true; + _mediaPlayer.Media = new Media(_context, new StreamMediaInput(_stream)); + _mediaPlayer.Play(); + + Logger.Info($"{entry.Location} loaded successfully"); + return; + } + + + Logger.Info($"Unable to load {entry.Location}"); + return; + } + private void MediaPlayer_PositionChanged(object? sender, MediaPlayerPositionChangedEventArgs e) + { + position = e.Position * 100.0f; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(Position))); + } + private void MediaPlayer_LengthChanged(object? sender, MediaPlayerLengthChangedEventArgs e) + { + Duration = TimeSpan.FromMilliseconds(e.Length == -1 ? 0 : e.Length); + } + private void MediaPlayer_TimeChanged(object? sender, MediaPlayerTimeChangedEventArgs e) + { + Time = TimeSpan.FromMilliseconds(e.Time == -1 ? 0 : e.Time); + } + private void MediaPlayer_EndReached(object? sender, EventArgs e) + { + Position = 0; + isChecked = false; + OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsChecked))); + } + partial void OnPositionChanged(float value) + { + _mediaPlayer.Position = value / 100.0f; + } + partial void OnVolumeChanged(float value) + { + _mediaPlayer.Volume = (int)value; + } + partial void OnIsCheckedChanged(bool value) + { + switch (_mediaPlayer.State) + { + case VLCState.Ended: + _mediaPlayer.Stop(); + OnIsCheckedChanged(value); + break; + case VLCState.Paused: + case VLCState.Stopped: + _mediaPlayer.Play(); + break; + case VLCState.Playing: + _mediaPlayer.Pause(); + break; + } + } + public void Dispose() + { + _stream?.Dispose(); + _mediaPlayer.Dispose(); + _context.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Audio.GUI/ViewModels/TreeViewModel.cs b/Audio.GUI/ViewModels/TreeViewModel.cs index 4a7ed9a..a7d6b00 100644 --- a/Audio.GUI/ViewModels/TreeViewModel.cs +++ b/Audio.GUI/ViewModels/TreeViewModel.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Models.TreeDataGrid; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; @@ -39,9 +40,6 @@ public partial class TreeViewModel : ViewModelBase [ObservableProperty] private ObservableCollection nodes; - [ObservableProperty] - private Entry? selectedEntry; - private IPlatformServiceProvider PlatformServiceProvider { get @@ -66,7 +64,13 @@ public IEnumerable Checked } [DesignOnly(true)] - public TreeViewModel() { } + public TreeViewModel() + { + _audioManager = new(); + _entryViewModel = new(); + Nodes = []; + Source = new([]); + } public TreeViewModel(AudioManager audioManager, EntryViewModel entryViewModel) { @@ -94,17 +98,15 @@ public TreeViewModel(AudioManager audioManager, EntryViewModel entryViewModel) ), }, }; + Source.RowSelection!.SingleSelect = true; + Source.RowSelection!.SelectionChanged += TreeViewModel_SelectionChanged; } + [RelayCommand] public void Expand(TappedEventArgs e) { if (Source.RowSelection!.SelectedIndex > -1) { - if (Source.RowSelection!.SelectedItem is EntryTreeNode entryTreeNode) - { - _entryViewModel.Entry = entryTreeNode.Entry; - } - if (Source.RowSelection!.SelectedItem?.IsExpanded == true) { Source.Collapse(Source.RowSelection!.SelectedIndex); @@ -115,25 +117,14 @@ public void Expand(TappedEventArgs e) } } } - - [RelayCommand] - public void PreviewEntry(TappedEventArgs e) - { - if (e.Source is StyledElement styledElement && styledElement.DataContext is EntryTreeNode entryTreeNode) - { - SelectedEntry = entryTreeNode.Entry; - } - } - [RelayCommand] - public void Refresh(KeyEventArgs e) + public async Task Refresh(KeyEventArgs e) { if (e.Key == Key.Enter) { - Update(); + await Dispatcher.UIThread.InvokeAsync(Update); } } - [RelayCommand] public async Task Copy(KeyEventArgs e) { @@ -145,13 +136,18 @@ public async Task Copy(KeyEventArgs e) } } } - public void Update() { Nodes.Clear(); - BuildTree(_audioManager.Entries); } + private void TreeViewModel_SelectionChanged(object? sender, Avalonia.Controls.Selection.TreeSelectionModelSelectionChangedEventArgs e) + { + if (e.SelectedItems.FirstOrDefault() is EntryTreeNode entryTreeNode) + { + _entryViewModel.Entry = entryTreeNode.Entry; + } + } private void BuildTree(IEnumerable entries, TreeNode? parent = null, int index = 0) { foreach (IGrouping group in entries.Where(x => x.Location?.Split(_separators).Length > index).GroupBy(x => Path.ChangeExtension(x.Location?.Split(_separators)[index], null))) @@ -167,31 +163,24 @@ private void BuildTree(IEnumerable entries, TreeNode? parent = null, int else { node = new EntryTreeNode(group.First()) { Name = group.Key }; - if (!node.HasMatch(SearchText)) - { - node = null; - } } - if (node != null) + if (parent == null) { - if (parent == null) - { - Nodes.Add(node); - } - else - { - parent.Nodes.Add(node); - } + Nodes.Add(node); + } + else + { + parent.Nodes.Add(node); + } - BuildTree(group, node, index + 1); + BuildTree(group, node, index + 1); - for (int i = node.Nodes.Count - 1; i >= 0; i--) + for (int i = node.Nodes.Count - 1; i >= 0; i--) + { + if (node.Nodes[i] is TreeNode child && !child.HasMatch(SearchText) && child.Nodes.Count == 0) { - if (node.Nodes[i] is TreeNode child && child is not EntryTreeNode && child.Nodes.Count == 0) - { - node.Nodes.RemoveAt(i); - } + node.Nodes.RemoveAt(i); } } } diff --git a/Audio.GUI/Views/EntryView.axaml b/Audio.GUI/Views/EntryView.axaml index 9fd81c5..a5b61f8 100644 --- a/Audio.GUI/Views/EntryView.axaml +++ b/Audio.GUI/Views/EntryView.axaml @@ -2,11 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity" - xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions" xmlns:vm="clr-namespace:Audio.GUI.ViewModels" - xmlns:models="clr-namespace:Audio.GUI.Models" - xmlns:controls="clr-namespace:Audio.GUI.Controls" xmlns:views="clr-namespace:Audio.GUI.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Audio.GUI.Views.EntryView" @@ -16,8 +12,8 @@ to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) --> - - - + + + diff --git a/Audio.GUI/Views/LogView.axaml b/Audio.GUI/Views/LogView.axaml index c6a317b..b12395b 100644 --- a/Audio.GUI/Views/LogView.axaml +++ b/Audio.GUI/Views/LogView.axaml @@ -2,9 +2,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:vm="clr-namespace:Audio.GUI.ViewModels" xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity" - xmlns:vm="clr-namespace:Audio.GUI.ViewModels" - xmlns:behaviors="clr-namespace:Audio.GUI.Behaviours" + xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Audio.GUI.Views.LogView" x:DataType="vm:LogViewModel"> @@ -13,14 +13,29 @@ to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) --> - + - + + + - - - - - + + + + + + + + + + + + + + + + diff --git a/Audio.GUI/Views/MainView.axaml b/Audio.GUI/Views/MainView.axaml index 8f0a518..4a8058e 100644 --- a/Audio.GUI/Views/MainView.axaml +++ b/Audio.GUI/Views/MainView.axaml @@ -2,12 +2,11 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity" - xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions" - xmlns:iac="clr-namespace:Avalonia.Xaml.Interactions.Custom;assembly=Avalonia.Xaml.Interactions.Custom" - xmlns:views="clr-namespace:Audio.GUI.Views" xmlns:vm="clr-namespace:Audio.GUI.ViewModels" + xmlns:views="clr-namespace:Audio.GUI.Views" xmlns:behaviors="clr-namespace:Audio.GUI.Behaviours" + xmlns:idd="using:Avalonia.Xaml.Interactions.DragAndDrop" + xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Audio.GUI.Views.MainView" x:DataType="vm:MainViewModel"> @@ -40,10 +39,18 @@ - + + + + + + + - - + + + + diff --git a/Audio.GUI/Controls/VLCPlayer.axaml b/Audio.GUI/Views/PlayerView.axaml similarity index 62% rename from Audio.GUI/Controls/VLCPlayer.axaml rename to Audio.GUI/Views/PlayerView.axaml index d61891f..5c5a0e0 100644 --- a/Audio.GUI/Controls/VLCPlayer.axaml +++ b/Audio.GUI/Views/PlayerView.axaml @@ -2,8 +2,15 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:vm="using:Audio.GUI.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Audio.GUI.Controls.VLCPlayer"> + x:Class="Audio.GUI.Views.PlayerView" + x:DataType="vm:PlayerViewModel"> + + + +