Added ContextMenu/Flyout animations. Added platform services. Use bitmap cache for all views.

This commit is contained in:
2025-03-18 09:31:32 -04:00
parent 7c70eb3814
commit 9214e97100
17 changed files with 649 additions and 169 deletions

View File

@@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Harmonia.UI.ViewModels"
xmlns:converter="clr-namespace:Harmonia.UI.Converters"
xmlns:controls="clr-namespace:Harmonia.UI.Controls"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
DataContext="{x:Static vm:ViewModelLocator.PlaylistViewModel}"
Loaded="OnLoaded"
@@ -46,11 +47,109 @@
</Style>
</UserControl.Styles>
<Grid Margin="0">
<Canvas Background="#99000000"></Canvas> <!-- Was 99 !-->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Canvas Background="#99000000" Grid.RowSpan="2"></Canvas> <!-- Was 99 !-->
<Grid Grid.Row="0" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" HorizontalAlignment="Stretch" Watermark="Filter" Text="{Binding Filter, Mode=TwoWay}"></TextBox>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Foreground="#7FD184" Margin="10 0 0 0">
<Button.Content>
<PathIcon Data="{StaticResource SemiIconPlus}"></PathIcon>
</Button.Content>
<Button.Flyout>
<controls:AnimatedMenuFlyout Placement="Bottom">
<MenuItem Command="{Binding AddFilesCommand}" HotKey="Insert" InputGesture="Insert">
<MenuItem.Header>
<TextBlock Text="Add Files..." Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource SemiIconFile}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding AddFolderCommand}" HotKey="Ctrl+Insert" InputGesture="Ctrl+Insert">
<MenuItem.Header>
<TextBlock Text="Add Folder..." Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource SemiIconFolder}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
</controls:AnimatedMenuFlyout>
</Button.Flyout>
</Button>
<Button Foreground="#F9F9F9" Margin="10 0 0 0">
<Button.Content>
<PathIcon Data="{StaticResource SemiIconMore}"></PathIcon>
</Button.Content>
<Button.Flyout>
<controls:AnimatedMenuFlyout Placement="Bottom">
<MenuItem Command="{Binding RefreshTagsCommand}">
<MenuItem.Header>
<TextBlock Text="Refresh Tags" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource RefreshIcon}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding RemoveDuplicateSongsCommand}">
<MenuItem.Header>
<TextBlock Text="Remove Duplicates" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
</MenuItem>
<MenuItem Command="{Binding RemoveMissingSongsCommand}">
<MenuItem.Header>
<TextBlock Text="Remove Missing" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
</MenuItem>
<MenuItem>
<MenuItem.Header>
<TextBlock Text="Lock Playlist" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource LockIcon}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem>
<MenuItem.Styles>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="#ff99a4"/>
</Style>
</MenuItem.Styles>
<MenuItem.Header>
<TextBlock Text="Remove Playlist" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource DeleteIcon}" Foreground="#ff99a4"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem>
<MenuItem.Header>
<TextBlock Text="Settings" Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
<MenuItem.Icon>
<PathIcon Data="{StaticResource SettingsIcon}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
</controls:AnimatedMenuFlyout>
</Button.Flyout>
</Button>
</StackPanel>
</Grid>
<ListBox
Name="PlaylistListBox"
ItemsSource="{Binding PlaylistSongs}"
Grid.Row="1"
ItemsSource="{Binding FilteredPlaylistSongs, Mode=OneWay}"
SelectedItems="{Binding SelectedPlaylistSongs, Mode=OneWay}"
DragDrop.AllowDrop="True"
DoubleTapped="ListBox_DoubleTapped"
SelectionMode="Multiple">
<!--<ListBox.Styles>
@@ -82,7 +181,7 @@
<Canvas Background="#19000000"></Canvas>
</Grid>
</Border>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Song.Title, Mode=OneWay}" Classes="SongTitle" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Song, Mode=OneWay, Converter={StaticResource SongTitle}}" Classes="SongTitle" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Song.Artists, Mode=OneWay, Converter={StaticResource ArtistsToString}}" Classes="SongSubtitle" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Song.Album, Mode=OneWay}" Classes="SongSubtitle" />
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Song.Length.TotalSeconds, Mode=OneWay, Converter={StaticResource SecondsToString}}" Classes="SongMetaData" />
@@ -104,7 +203,7 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Command="{Binding AddFilesCommand}" HotKey="Insert" InputGesture="Insert">
<!--<MenuItem Command="{Binding AddFilesCommand}" HotKey="Insert" InputGesture="Insert">
<MenuItem.Header>
<TextBlock Text="Add Files..." Margin="8 0 8 0"></TextBlock>
</MenuItem.Header>
@@ -120,7 +219,7 @@
<PathIcon Data="{StaticResource SemiIconFolder}"></PathIcon>
</MenuItem.Icon>
</MenuItem>
<Separator />
<Separator />-->
<MenuItem Command="{Binding RemoveSongsCommand}" HotKey="Delete" InputGesture="Delete">
<MenuItem.Styles>
<Style Selector="TextBlock">

View File

@@ -2,17 +2,11 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Harmonia.Core.Engine;
using Harmonia.Core.Playlists;
using Harmonia.UI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
@@ -21,22 +15,32 @@ namespace Harmonia.UI.Views;
public partial class PlaylistView : UserControl
{
private readonly PlaylistViewModel _viewModel;
private readonly IAudioEngine _audioEngine;
private readonly ConcurrentDictionary<int, CancellationTokenSource> _imageCancellationTokens = [];
private IStorageProvider? _storageProvider;
public PlaylistView()
{
InitializeComponent();
_viewModel = (PlaylistViewModel)DataContext!;
_audioEngine = App.ServiceProvider.GetRequiredService<IAudioEngine>();
_viewModel = (PlaylistViewModel)DataContext!;
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
}
private async void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(_viewModel.FilteredPlaylistSongs))
{
await Dispatcher.UIThread.InvokeAsync(SlideInSongs);
}
}
private async Task SlideInSongs()
{
await Animations.Animations.SlideFromRight(100, duration: 300).RunAsync(PlaylistListBox);
}
private void OnLoaded(object? sender, RoutedEventArgs e)
{
_storageProvider = TopLevel.GetTopLevel(this)?.StorageProvider;
//_storageProvider = TopLevel.GetTopLevel(this)?.StorageProvider;
}
private async void ListBox_DoubleTapped(object? sender, TappedEventArgs e)
@@ -85,7 +89,7 @@ public partial class PlaylistView : UserControl
if (sender is not Image image)
return;
if (image.DataContext is not PlaylistSong playlistSong)
if (image.DataContext is not PlaylistSong)
return;
if (image.Source is Bitmap bitmap)
@@ -100,49 +104,4 @@ public partial class PlaylistView : UserControl
_imageCancellationTokens.TryGetValue(hashCode, out CancellationTokenSource? cancellationTokenSource);
cancellationTokenSource?.Cancel();
}
//private async void AddFiles_Click(object? sender, RoutedEventArgs e)
//{
// if (_storageProvider == null)
// return;
// FilePickerOpenOptions openOptions = new()
// {
// FileTypeFilter = [GetAudioFileTypes()],
// AllowMultiple = true
// };
// IReadOnlyList<IStorageFile> result = await _storageProvider.OpenFilePickerAsync(openOptions);
// string[] fileNames = [.. result.Select(file => file.TryGetLocalPath() ?? string.Empty)];
// CancellationToken cancellationToken = default;
// await _viewModel.AddFilesAsync(fileNames, cancellationToken);
//}
//private FilePickerFileType GetAudioFileTypes()
//{
// return new("Audo Files")
// {
// Patterns = [.. _audioEngine.SupportedFormats]
// };
//}
//private async void AddFolder_Click(object? sender, RoutedEventArgs e)
//{
// if (_storageProvider == null)
// return;
// FolderPickerOpenOptions options = new()
// {
// AllowMultiple = true
// };
// IReadOnlyList<IStorageFolder> folders = await _storageProvider.OpenFolderPickerAsync(options);
// if (folders.Count == 0)
// return;
// CancellationToken cancellationToken = default;
// await _viewModel.AddFolderAsync(folders[0].TryGetLocalPath() ?? string.Empty, cancellationToken);
//}
}