Added randomize/reverse songs logic. Added initial UI-side locking logic.

This commit is contained in:
2026-08-03 22:38:16 -04:00
parent 99978c62c4
commit 2b78ac5667
6 changed files with 240 additions and 18 deletions

View File

@@ -16,6 +16,15 @@
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
</Style>
<Style TargetType="MenuFlyoutSubItem" BasedOn="{StaticResource DefaultMenuFlyoutSubItemStyle}">
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
</Style>
<Style TargetType="ToggleMenuFlyoutItem" BasedOn="{StaticResource DefaultToggleMenuFlyoutItemStyle}">
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
</Style>
<Style TargetType="ListViewItem" BasedOn="{StaticResource DefaultListViewItemStyle}">
<Setter Property="FontFamily" Value="Lexend,Noto Sans JP" />
</Style>

View File

@@ -147,6 +147,11 @@ public partial class PlaylistDetailViewModel : ViewModelBase
public IAsyncRelayCommand DeletePlaylistCommand { get; }
public IRelayCommand<SortItem> SortAllCommand { get; }
public IRelayCommand<SortItem> SortSelectedCommand { get; }
public IRelayCommand RandomizeAllCommand { get; }
public IRelayCommand RandomizeSelectedCommand { get; }
public IRelayCommand ReverseAllCommand { get; }
public IRelayCommand ReverseSelectedCommand { get; }
public IRelayCommand ToggleLockCommand { get; }
public bool IsUserUpdating { get; set; }
private bool _isUserInitiatingSongChange;
@@ -195,6 +200,11 @@ public partial class PlaylistDetailViewModel : ViewModelBase
DeletePlaylistCommand = new AsyncRelayCommand(DeletePlaylistAsync);
SortAllCommand = new RelayCommand<SortItem>(SortAllSongs);
SortSelectedCommand = new RelayCommand<SortItem>(SortSelectedSongs, _ => AreMultipleSongsSelected());
RandomizeAllCommand = new RelayCommand(RandomizeAllSongs);
RandomizeSelectedCommand = new RelayCommand(RandomizeSelectedSongs, AreMultipleSongsSelected);
ReverseAllCommand = new RelayCommand(ReverseAllSongs);
ReverseSelectedCommand = new RelayCommand(ReverseSelectedSongs, AreMultipleSongsSelected);
ToggleLockCommand = new RelayCommand(ToggleLock);
FilteredPlaylistSongs.CollectionChanged += OnFilteredPlaylistSongsCollectionChanged;
@@ -211,6 +221,8 @@ public partial class PlaylistDetailViewModel : ViewModelBase
PasteSongsCommand.NotifyCanExecuteChanged();
OpenFileLocationCommand.NotifyCanExecuteChanged();
SortSelectedCommand.NotifyCanExecuteChanged();
RandomizeSelectedCommand.NotifyCanExecuteChanged();
ReverseSelectedCommand.NotifyCanExecuteChanged();
}
private void OnFilteredPlaylistSongsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
@@ -669,7 +681,75 @@ public partial class PlaylistDetailViewModel : ViewModelBase
if (SelectedPlaylistSongs.Count < 2)
return;
string[] selectedPlaylistSongIds = [.. SelectedPlaylistSongs.Select(x => x.UID)];
Playlist.SortSongs([.. SelectedPlaylistSongs], [.. sortItem.SortOptions]);
RestoreSelectedSongs(selectedPlaylistSongIds);
}
private void RestoreSelectedSongs(string[] playlistSongIds)
{
_dispatcherQueue.TryEnqueue(() =>
{
PlaylistSong[] selectedPlaylistSongs = [.. Playlist?.Songs.Where(x => playlistSongIds.Contains(x.UID)) ?? []];
SelectedPlaylistSongs = new ObservableCollection<PlaylistSong>(selectedPlaylistSongs);
});
}
private void RandomizeAllSongs()
{
if (Playlist == null || Playlist.Songs.Count < 2)
return;
Playlist.Randomize([.. Playlist.Songs]);
}
private void RandomizeSelectedSongs()
{
if (Playlist == null || SelectedPlaylistSongs.Count < 2)
return;
string[] selectedPlaylistSongIds = [.. SelectedPlaylistSongs.Select(x => x.UID)];
Playlist.Randomize([.. SelectedPlaylistSongs]);
RestoreSelectedSongs(selectedPlaylistSongIds);
}
private void ReverseAllSongs()
{
if (Playlist == null || Playlist.Songs.Count < 2)
return;
Playlist.Reverse([.. Playlist.Songs]);
}
private void ReverseSelectedSongs()
{
if (Playlist == null || SelectedPlaylistSongs.Count < 2)
return;
string[] selectedPlaylistSongIds = [.. SelectedPlaylistSongs.Select(x => x.UID)];
Playlist.Reverse([.. SelectedPlaylistSongs]);
RestoreSelectedSongs(selectedPlaylistSongIds);
}
private void ToggleLock()
{
if (Playlist == null)
return;
if (Playlist.IsLocked)
{
Playlist.Unlock();
}
else
{
Playlist.Lock();
}
}
#endregion

View File

@@ -167,9 +167,9 @@
<MenuFlyoutItem Text="Remove Missing" Command="{Binding RemoveMissingSongsCommand}">
</MenuFlyoutItem>
<MenuFlyoutItem Text="Lock Playlist">
</MenuFlyoutItem>
<ToggleMenuFlyoutItem Text="Lock Playlist" IsChecked="{Binding Playlist.IsLocked}">
</ToggleMenuFlyoutItem>
<MenuFlyoutSeparator></MenuFlyoutSeparator>
@@ -179,6 +179,11 @@
<MenuFlyoutSeparator></MenuFlyoutSeparator>
<MenuFlyoutSubItem x:Name="SortByMenuFlyoutSubItem" Text="Sort By...">
<MenuFlyoutSeparator></MenuFlyoutSeparator>
<MenuFlyoutItem Text="Randomize" Command="{Binding RandomizeAllCommand}">
</MenuFlyoutItem>
<MenuFlyoutItem Text="Reverse" Command="{Binding ReverseAllCommand}">
</MenuFlyoutItem>
</MenuFlyoutSubItem>
<MenuFlyoutSeparator></MenuFlyoutSeparator>
@@ -238,6 +243,11 @@
<MenuFlyoutSubItem.Icon>
<PathIcon Data="{StaticResource SortIcon}"></PathIcon>
</MenuFlyoutSubItem.Icon>
<MenuFlyoutSeparator></MenuFlyoutSeparator>
<MenuFlyoutItem Text="Randomize" Command="{Binding RandomizeSelectedCommand}">
</MenuFlyoutItem>
<MenuFlyoutItem Text="Reverse" Command="{Binding ReverseSelectedCommand}">
</MenuFlyoutItem>
</MenuFlyoutSubItem>
<MenuFlyoutSeparator></MenuFlyoutSeparator>
<MenuFlyoutItem Text="Cut" Command="{Binding CutSongsCommand}">

View File

@@ -1,5 +1,4 @@
using CommunityToolkit.WinUI;
using Harmonia.Core.Imaging;
using Harmonia.Core.Playlists;
using Harmonia.WinUI.ViewModels;
using Microsoft.UI.Dispatching;
@@ -7,14 +6,10 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.UI.Popups;
namespace Harmonia.WinUI.Views;
@@ -33,24 +28,28 @@ public sealed partial class PlaylistDetailView : UserControl
foreach (MenuFlyoutItemBase item in PlaylistListViewMenuFlyout.Items)
{
item.DataContextChanged += Item_DataContextChanged;
HookDataContextChanged(item);
}
int currentSortItemIndex = 0;
foreach (SortItem sortItem in _viewModel.SortItems)
{
SortByMenuFlyoutSubItem.Items.Add(new MenuFlyoutItem
SortByMenuFlyoutSubItem.Items.Insert(currentSortItemIndex, new MenuFlyoutItem
{
Text = sortItem.Name,
Command = _viewModel.SortAllCommand,
CommandParameter = sortItem
});
SortSelectedByMenuFlyoutSubItem.Items.Add(new MenuFlyoutItem
SortSelectedByMenuFlyoutSubItem.Items.Insert(currentSortItemIndex, new MenuFlyoutItem
{
Text = sortItem.Name,
Command = _viewModel.SortSelectedCommand,
CommandParameter = sortItem
});
currentSortItemIndex++;
}
_viewModel.SortSelectedCommand.CanExecuteChanged += (_, _) =>
@@ -74,15 +73,44 @@ public sealed partial class PlaylistDetailView : UserControl
private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(_viewModel.PlayingSong))
switch (e.PropertyName)
{
ListViewItem listViewItem = (ListViewItem)PlaylistListView.ContainerFromItem(_viewModel.PlayingSong);
UpdateListViewItemStyle(listViewItem, true);
case nameof(_viewModel.PlayingSong):
ListViewItem listViewItem = (ListViewItem)PlaylistListView.ContainerFromItem(_viewModel.PlayingSong);
UpdateListViewItemStyle(listViewItem, true);
break;
case nameof(_viewModel.Playlist):
ScrollToTop();
break;
case nameof(_viewModel.SelectedPlaylistSongs):
SyncListViewSelection();
break;
}
else if (e.PropertyName == nameof(_viewModel.Playlist))
}
private bool _isSyncingSelection;
private void SyncListViewSelection()
{
_isSyncingSelection = true;
try
{
ScrollToTop();
PlaylistListView.DeselectAll();
foreach (PlaylistSong playlistSong in _viewModel.SelectedPlaylistSongs)
{
int index = PlaylistListView.Items.IndexOf(playlistSong);
if (index >= 0)
{
PlaylistListView.SelectRange(new ItemIndexRange(index, 1));
}
}
}
finally
{
_isSyncingSelection = false;
}
}
@@ -170,6 +198,19 @@ public sealed partial class PlaylistDetailView : UserControl
}
}
private void HookDataContextChanged(MenuFlyoutItemBase item)
{
item.DataContextChanged += Item_DataContextChanged;
if (item is MenuFlyoutSubItem subItem)
{
foreach (MenuFlyoutItemBase childItem in subItem.Items)
{
HookDataContextChanged(childItem);
}
}
}
private void Item_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if (sender is not MenuFlyoutItemBase item)
@@ -268,6 +309,9 @@ public sealed partial class PlaylistDetailView : UserControl
private void PlaylistListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isSyncingSelection)
return;
if (sender is not ListView listView)
return;