using Avalonia; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media.Imaging; using Avalonia.Threading; using Avalonia.VisualTree; using Harmonia.Core.Playlists; using Harmonia.UI.ViewModels; using System; using System.Collections.Concurrent; using System.ComponentModel; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Harmonia.UI.Views; public partial class PlaylistView : UserControl { private readonly PlaylistViewModel _viewModel; private readonly ConcurrentDictionary _imageCancellationTokens = []; public PlaylistView() { InitializeComponent(); _viewModel = (PlaylistViewModel)DataContext!; _viewModel.PropertyChanging += OnViewModelPropertyChanging; _viewModel.PropertyChanged += OnViewModelPropertyChanged; } private async void OnBeforeFilteredSongsUpdate(object? sender, System.EventArgs e) { await Dispatcher.UIThread.InvokeAsync(SlideOutSongs); } private async void OnAfterFilteredSongsUpdate(object? sender, System.EventArgs e) { await Dispatcher.UIThread.InvokeAsync(SlideInSongs); } private void OnViewModelPropertyChanging(object? sender, PropertyChangingEventArgs e) { switch (e.PropertyName) { case nameof(_viewModel.PlayingSong): RemovePlayingSongClass(); break; } } private async void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(_viewModel.FilteredPlaylistSongs): await Dispatcher.UIThread.InvokeAsync(SlideInSongs); break; case nameof(_viewModel.PlayingSong): AddPlayingSongClass(); break; } } private async Task SlideOutSongs() { await Animations.Animations.SlideToRight(100, duration: 300).RunAsync(PlaylistListBox); } private async Task SlideInSongs() { await Animations.Animations.SlideFromRight(100, duration: 300).RunAsync(PlaylistListBox); } private void RemovePlayingSongClass() { PlaylistSong? playingSong = _viewModel.PlayingSong; if (playingSong == null) return; Control? control = PlaylistListBox.ContainerFromItem(playingSong); if (control is not ListBoxItem listBoxItem) return; if (listBoxItem.GetVisualChildren().FirstOrDefault() is not ContentPresenter contentPresentor) return; if (contentPresentor.GetVisualChildren().FirstOrDefault() is not Grid grid) return; grid.Classes.Remove("Playing"); } private void AddPlayingSongClass() { PlaylistSong? playingSong = _viewModel.PlayingSong; if (playingSong == null) return; PlaylistListBox.UpdateLayout(); PlaylistListBox.ScrollIntoView(playingSong); Control? control = PlaylistListBox.ContainerFromItem(playingSong); if (control is ListBoxItem listBoxItem) { listBoxItem.BringIntoView(); } else { PlaylistListBox.ScrollIntoView(playingSong); } if (control is not ListBoxItem listBoxItem2) return; if (listBoxItem2.GetVisualChildren().FirstOrDefault() is not ContentPresenter contentPresentor) return; if (contentPresentor.GetVisualChildren().FirstOrDefault() is not Grid grid) return; grid.Classes.Add("Playing"); } private async void ListBox_DoubleTapped(object? sender, TappedEventArgs e) { if (sender is ListBox listBox && listBox.SelectedItem is PlaylistSong playlistSong) { await _viewModel.PlaySongAsync(playlistSong); } } private void Image_Loaded(object? sender, RoutedEventArgs e) { if (sender is not Image image) return; if (image.DataContext is not PlaylistSong playlistSong) return; Task.Run(() => DoSomethingAsync(image, playlistSong)); } private async Task DoSomethingAsync(Image image, PlaylistSong playlistSong) { int hashCode = image.GetHashCode(); _imageCancellationTokens.TryGetValue(hashCode, out CancellationTokenSource? cancellationTokenSource); cancellationTokenSource?.Cancel(); cancellationTokenSource = new(); Bitmap? bitmap = await _viewModel.GetBitmapAsync(playlistSong, cancellationTokenSource.Token); if (bitmap == null) return; await Dispatcher.UIThread.InvokeAsync(() => SetSongImageSource(image, bitmap)); } private static void SetSongImageSource(Image image, Bitmap bitmap) { image.Source = bitmap; } private void Image_Unloaded(object? sender, RoutedEventArgs e) { if (sender is not Image image) return; if (image.DataContext is not PlaylistSong) return; if (image.Source is Bitmap bitmap) { bitmap.Dispose(); } image.Source = null; int hashCode = image.GetHashCode(); _imageCancellationTokens.TryGetValue(hashCode, out CancellationTokenSource? cancellationTokenSource); cancellationTokenSource?.Cancel(); } private void Grid_Loaded(object? sender, RoutedEventArgs e) { if (sender is not Grid grid) return; if (grid.Parent is not ListBoxItem listBoxItem) return; //SetupDragAndDrop(grid, d => d.Set(DataFormats.Text, // $"Text was dragged x times."), // DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link); if (PlaylistListBox.ItemFromContainer(listBoxItem) is not PlaylistSong playlistSong) return; if (playlistSong == _viewModel.PlayingSong) grid.Classes.Add("Playing"); //await Dispatcher.UIThread.InvokeAsync(() => SlideInPlaylistSong(sender)); } private async Task SlideOutPlaylistSong(object sender) { if (sender is not Animatable animatable) return; await Animations.Animations.SlideToRight(100, duration: 300).RunAsync(animatable); } private async Task SlideInPlaylistSong(object sender) { if (sender is not Animatable animatable) return; await Animations.Animations.SlideFromRight(100, duration: 300).RunAsync(animatable); } private void OnPlaylistListBoxDrop(object? sender, DragEventArgs e) { } //private void SetupDragAndDrop(IInputElement draggable, Action factory, DragDropEffects effects) //{ // draggable.PointerPressed += (sender, e) => StartDrag(sender, factory, e, effects); // AddHandler(DragDrop.DropEvent, Drop); // AddHandler(DragDrop.DragOverEvent, DragOver); // AddHandler(DragDrop.DragEnterEvent, DragEnter); // AddHandler(DragDrop.DragLeaveEvent, DragLeave); //} private async void StartDrag(object? sender, Action factory, PointerPressedEventArgs e, DragDropEffects effects) { if (e.GetCurrentPoint((Visual)sender!).Properties.IsLeftButtonPressed == false) { // It was a left click Console.WriteLine("Left mouse button pressed!"); e.Handled = true; return; } var dragData = new DataObject(); factory(dragData); var result = await DragDrop.DoDragDrop(e, dragData, effects); } private void DragOver(object? sender, DragEventArgs e) { if (e.Source == PlaylistListBox) return; //if (e.Source is Animatable animatable) //{ // //await Animations.Animations.SlideToRight(200).RunAsync(animatable); // animatable.P //} //if (e.Source is Grid grid) //{ // grid.Opacity = 0.5; //} //if (Equals(e.Source, MoveTarget)) //{ // e.DragEffects &= DragDropEffects.Move; //} //else //{ // e.DragEffects &= DragDropEffects.Copy; //} //// Only allow if the dragged data contains text or filenames. //if (!e.Data.Contains(DataFormats.Text) // && !e.Data.Contains(DataFormats.Files) // && !e.Data.Contains(CustomFormat)) // e.DragEffects = DragDropEffects.None; } private void DragEnter(object? sender, DragEventArgs e) { if (e.Source is ListBoxItem grid) { grid.Opacity = 0.5; //Animations.Animations.SlideToRight(200).RunAsync(grid); } } private void DragLeave(object? sender, DragEventArgs e) { if (e.Source is ListBoxItem grid) { grid.Opacity = 1; //Animations.Animations.SlideToRight(200).RunAsync(grid); } } private void Drop(object? sender, DragEventArgs e) { //if (Equals(e.Source, MoveTarget)) //{ // e.DragEffects &= DragDropEffects.Move; //} //else //{ // e.DragEffects &= DragDropEffects.Copy; //} //if (e.Data.Contains(DataFormats.Text)) //{ // DropState.Text = e.Data.GetText(); //} //else if (e.Data.Contains(CustomFormat)) //{ // DropState.Text = e.Data.Get(CustomFormat)?.ToString(); //} } }