Highlight active playlist. Various optimizations and fixes.

This commit is contained in:
2026-08-05 00:50:56 -04:00
parent 3c3cf37cd5
commit 1d45826c38
14 changed files with 327 additions and 55 deletions

View File

@@ -173,7 +173,7 @@ public class AudioPlayer : IAudioPlayer
return; return;
} }
int currentIndex = Playlist.Songs.IndexOf(CurrentPlaylistSong); int currentIndex = Playlist.IndexOf(CurrentPlaylistSong);
int nextIndex = currentIndex + 1; int nextIndex = currentIndex + 1;
if (nextIndex > Playlist.Songs.Count - 1) if (nextIndex > Playlist.Songs.Count - 1)
@@ -200,7 +200,7 @@ public class AudioPlayer : IAudioPlayer
return; return;
} }
int currentIndex = Playlist.Songs.IndexOf(CurrentPlaylistSong); int currentIndex = Playlist.IndexOf(CurrentPlaylistSong);
int nextIndex = currentIndex - 1; int nextIndex = currentIndex - 1;
if (nextIndex < 0) if (nextIndex < 0)
@@ -233,7 +233,7 @@ public class AudioPlayer : IAudioPlayer
{ {
if (Playlist == null || Playlist.Songs.Contains(song) == false) if (Playlist == null || Playlist.Songs.Contains(song) == false)
{ {
Playlist? newPlaylist = _playlistManager.GetPlaylist(song); Playlist? newPlaylist = _playlistManager.FindPlaylistContaining(song);
if (newPlaylist == null) if (newPlaylist == null)
return false; return false;

View File

@@ -8,7 +8,7 @@ public interface IPlaylistManager
Task InitializeAsync(); Task InitializeAsync();
Task<Playlist> AddPlaylistAsync(); Task<Playlist> AddPlaylistAsync();
void RemovePlaylist(Playlist playlist); void RemovePlaylist(Playlist playlist);
Playlist? GetPlaylist(PlaylistSong playlistSong); Playlist? FindPlaylistContaining(PlaylistSong playlistSong);
event EventHandler? CurrentPlaylistChanged; event EventHandler? CurrentPlaylistChanged;
event EventHandler<PlaylistAddedEventArgs> PlaylistAdded; event EventHandler<PlaylistAddedEventArgs> PlaylistAdded;

View File

@@ -4,10 +4,5 @@ namespace Harmonia.Core.Playlists;
public interface IPlaylistRepository : IRepository<Playlist> public interface IPlaylistRepository : IRepository<Playlist>
{ {
Playlist? GetPlaylist(PlaylistSong playlistSong);
//void AddPlaylist();
//void RemovePlaylist(Playlist playlist);
//event EventHandler<PlaylistAddedEventArgs> PlaylistAdded;
//event EventHandler<PlaylistRemovedEventArgs> PlaylistRemoved;
} }

View File

@@ -5,15 +5,48 @@ namespace Harmonia.Core.Playlists;
public class Playlist public class Playlist
{ {
private readonly List<PlaylistSong> _songs = [];
private readonly List<GroupOption> _groupOptions = [];
private readonly List<SortOption> _sortOptions = [];
public string UID { get; init; } = Guid.NewGuid().ToString(); public string UID { get; init; } = Guid.NewGuid().ToString();
public string? Name { get; set; } public string? Name { get; private set; }
public List<PlaylistSong> Songs { get; init; } = []; // TODO: Change to "private init" once deserialization is fixed public IReadOnlyList<PlaylistSong> Songs => _songs;
public List<GroupOption> GroupOptions { get; set; } = []; public IReadOnlyList<GroupOption> GroupOptions => _groupOptions;
public List<SortOption> SortOptions { get; set; } = []; public IReadOnlyList<SortOption> SortOptions => _sortOptions;
public bool IsLocked { get; set; } public bool IsLocked { get; private set; }
public event EventHandler<PlaylistUpdatedEventArgs>? PlaylistUpdated; public event EventHandler<PlaylistUpdatedEventArgs>? PlaylistUpdated;
public Playlist()
{
}
public Playlist(string? name)
{
Name = name;
}
internal static Playlist Restore(string uid, string? name, IEnumerable<PlaylistSong> songs, IEnumerable<GroupOption> groupOptions, IEnumerable<SortOption> sortOptions, bool isLocked)
{
Playlist playlist = new(name)
{
UID = uid,
IsLocked = isLocked
};
playlist._songs.AddRange(songs);
playlist._groupOptions.AddRange(groupOptions);
playlist._sortOptions.AddRange(sortOptions);
return playlist;
}
public int IndexOf(PlaylistSong playlistSong)
{
return _songs.IndexOf(playlistSong);
}
public void SetName(string name) public void SetName(string name)
{ {
if (string.Equals(Name, name, StringComparison.Ordinal)) if (string.Equals(Name, name, StringComparison.Ordinal))
@@ -88,9 +121,9 @@ public class Playlist
if (playlistSongs.Length == 0) if (playlistSongs.Length == 0)
return; return;
int insertIndex = index ?? Songs.Count; int insertIndex = index ?? _songs.Count;
Songs.InsertRange(insertIndex, playlistSongs); _songs.InsertRange(insertIndex, playlistSongs);
PlaylistUpdatedEventArgs eventArgs = new() PlaylistUpdatedEventArgs eventArgs = new()
{ {
@@ -105,7 +138,7 @@ public class Playlist
public void MoveSong(PlaylistSong playlistSong, int newIndex) public void MoveSong(PlaylistSong playlistSong, int newIndex)
{ {
int currentIndex = Songs.IndexOf(playlistSong); int currentIndex = _songs.IndexOf(playlistSong);
MoveSong(currentIndex, newIndex); MoveSong(currentIndex, newIndex);
} }
@@ -118,10 +151,10 @@ public class Playlist
if (oldIndex == newIndex) if (oldIndex == newIndex)
return; return;
PlaylistSong playlistSong = Songs[oldIndex]; PlaylistSong playlistSong = _songs[oldIndex];
Songs.Remove(playlistSong); _songs.Remove(playlistSong);
Songs.Insert(newIndex, playlistSong); _songs.Insert(newIndex, playlistSong);
PlaylistUpdatedEventArgs eventArgs = new() PlaylistUpdatedEventArgs eventArgs = new()
{ {
@@ -141,8 +174,8 @@ public class Playlist
return; return;
Dictionary<int, PlaylistSong> oldPlaylistSongs = playlistSongs Dictionary<int, PlaylistSong> oldPlaylistSongs = playlistSongs
.OrderBy(Songs.IndexOf) .OrderBy(_songs.IndexOf)
.ToDictionary(Songs.IndexOf, playlistSong => playlistSong); .ToDictionary(_songs.IndexOf, playlistSong => playlistSong);
Song[] songs = [.. playlistSongs.Select(playlistSong => playlistSong.Song)]; Song[] songs = [.. playlistSongs.Select(playlistSong => playlistSong.Song)];
Song[] sortedSongs = [.. songs.SortBy(sortOptions)]; Song[] sortedSongs = [.. songs.SortBy(sortOptions)];
@@ -158,8 +191,8 @@ public class Playlist
if (newPlaylistSong == playlistSong) if (newPlaylistSong == playlistSong)
continue; continue;
Songs.RemoveAt(index); _songs.RemoveAt(index);
Songs.Insert(index, newPlaylistSong); _songs.Insert(index, newPlaylistSong);
} }
PlaylistUpdatedEventArgs eventArgs = new() PlaylistUpdatedEventArgs eventArgs = new()
@@ -178,11 +211,11 @@ public class Playlist
if (IsLocked) if (IsLocked)
return; return;
int[] originalIndexes = [.. playlistSongs.Select(playlistSong => Songs.IndexOf(playlistSong))]; int[] originalIndexes = [.. playlistSongs.Select(playlistSong => _songs.IndexOf(playlistSong))];
PlaylistSong[] shuffledSongs = [.. playlistSongs.Shuffle()]; PlaylistSong[] shuffledSongs = [.. playlistSongs.Shuffle()];
for (int i = 0; i < originalIndexes.Length; i++) for (int i = 0; i < originalIndexes.Length; i++)
Songs[originalIndexes[i]] = shuffledSongs[i]; _songs[originalIndexes[i]] = shuffledSongs[i];
PlaylistUpdatedEventArgs eventArgs = new() PlaylistUpdatedEventArgs eventArgs = new()
{ {
@@ -200,11 +233,11 @@ public class Playlist
if (IsLocked) if (IsLocked)
return; return;
int[] originalIndexes = [.. playlistSongs.Select(playlistSong => Songs.IndexOf(playlistSong))]; int[] originalIndexes = [.. playlistSongs.Select(playlistSong => _songs.IndexOf(playlistSong))];
PlaylistSong[] reversedSongs = [.. originalIndexes.Select(i => Songs[i]).Reverse()]; PlaylistSong[] reversedSongs = [.. originalIndexes.Select(i => _songs[i]).Reverse()];
for (int i = 0; i < originalIndexes.Length; i++) for (int i = 0; i < originalIndexes.Length; i++)
Songs[originalIndexes[i]] = reversedSongs[i]; _songs[originalIndexes[i]] = reversedSongs[i];
PlaylistUpdatedEventArgs eventArgs = new() PlaylistUpdatedEventArgs eventArgs = new()
{ {
@@ -224,7 +257,7 @@ public class Playlist
public void RemoveSongs(int index, int count) public void RemoveSongs(int index, int count)
{ {
PlaylistSong[] playlistSongs = [.. Songs.GetRange(index, count)]; PlaylistSong[] playlistSongs = [.. _songs.GetRange(index, count)];
RemoveSongs(playlistSongs); RemoveSongs(playlistSongs);
} }
@@ -243,7 +276,7 @@ public class Playlist
foreach (PlaylistSong playlistSong in playlistSongs) foreach (PlaylistSong playlistSong in playlistSongs)
{ {
if (Songs.Remove(playlistSong)) if (_songs.Remove(playlistSong))
{ {
removedSongs.Add(playlistSong); removedSongs.Add(playlistSong);
} }

View File

@@ -0,0 +1,60 @@
using Harmonia.Core.Models;
namespace Harmonia.Core.Playlists;
internal sealed record PlaylistDto
{
public string UID { get; init; } = Guid.NewGuid().ToString();
public string? Name { get; init; }
public List<PlaylistSongDto> Songs { get; init; } = [];
public List<GroupOption> GroupOptions { get; init; } = [];
public List<SortOption> SortOptions { get; init; } = [];
public bool IsLocked { get; init; }
public static PlaylistDto FromPlaylist(Playlist playlist)
{
return new PlaylistDto
{
UID = playlist.UID,
Name = playlist.Name,
Songs = [.. playlist.Songs.Select(PlaylistSongDto.FromPlaylistSong)],
GroupOptions = [.. playlist.GroupOptions],
SortOptions = [.. playlist.SortOptions],
IsLocked = playlist.IsLocked
};
}
public Playlist ToPlaylist()
{
return Playlist.Restore(
UID,
Name,
Songs.Select(song => song.ToPlaylistSong()),
GroupOptions,
SortOptions,
IsLocked);
}
}
internal sealed record PlaylistSongDto
{
public string UID { get; init; } = Guid.NewGuid().ToString();
public required Song Song { get; init; }
public static PlaylistSongDto FromPlaylistSong(PlaylistSong playlistSong)
{
return new PlaylistSongDto
{
UID = playlistSong.UID,
Song = playlistSong.Song
};
}
public PlaylistSong ToPlaylistSong()
{
return new PlaylistSong(Song)
{
UID = UID
};
}
}

View File

@@ -4,6 +4,7 @@ public class PlaylistManager : IPlaylistManager
{ {
private readonly IPlaylistRepository playlistRepository; private readonly IPlaylistRepository playlistRepository;
private readonly List<Playlist> _playlists = []; private readonly List<Playlist> _playlists = [];
private readonly Dictionary<string, Playlist> _playlistsBySongUid = [];
public IReadOnlyList<Playlist> Playlists => _playlists; public IReadOnlyList<Playlist> Playlists => _playlists;
@@ -37,6 +38,11 @@ public class PlaylistManager : IPlaylistManager
foreach (Playlist playlist in _playlists) foreach (Playlist playlist in _playlists)
{ {
playlist.PlaylistUpdated += OnPlaylistUpdated; playlist.PlaylistUpdated += OnPlaylistUpdated;
foreach (PlaylistSong song in playlist.Songs)
{
_playlistsBySongUid[song.UID] = playlist;
}
} }
CurrentPlaylist = _playlists.Count > 0 ? _playlists[0] : await AddPlaylistAsync(); CurrentPlaylist = _playlists.Count > 0 ? _playlists[0] : await AddPlaylistAsync();
@@ -44,14 +50,16 @@ public class PlaylistManager : IPlaylistManager
public async Task<Playlist> AddPlaylistAsync() public async Task<Playlist> AddPlaylistAsync()
{ {
Playlist playlist = new() Playlist playlist = new("New Playlist");
{
Name = "New Playlist"
};
playlist.PlaylistUpdated += OnPlaylistUpdated; playlist.PlaylistUpdated += OnPlaylistUpdated;
_playlists.Add(playlist); _playlists.Add(playlist);
foreach (PlaylistSong song in playlist.Songs)
{
_playlistsBySongUid[song.UID] = playlist;
}
await playlistRepository.SaveAsync(playlist); await playlistRepository.SaveAsync(playlist);
PlaylistAdded?.Invoke(this, new(playlist)); PlaylistAdded?.Invoke(this, new(playlist));
@@ -64,14 +72,19 @@ public class PlaylistManager : IPlaylistManager
playlist.PlaylistUpdated -= OnPlaylistUpdated; playlist.PlaylistUpdated -= OnPlaylistUpdated;
_playlists.Remove(playlist); _playlists.Remove(playlist);
foreach (PlaylistSong song in playlist.Songs)
{
_playlistsBySongUid.Remove(song.UID);
}
playlistRepository.Delete(playlist); playlistRepository.Delete(playlist);
PlaylistRemoved?.Invoke(this, new(playlist)); PlaylistRemoved?.Invoke(this, new(playlist));
} }
public Playlist? GetPlaylist(PlaylistSong playlistSong) public Playlist? FindPlaylistContaining(PlaylistSong playlistSong)
{ {
return _playlists.FirstOrDefault(playlist => playlist.Songs.Any(song => song.UID == playlistSong.UID)); return _playlistsBySongUid.GetValueOrDefault(playlistSong.UID);
} }
private async void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e) private async void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e)
@@ -79,6 +92,22 @@ public class PlaylistManager : IPlaylistManager
if (sender is not Playlist playlist) if (sender is not Playlist playlist)
return; return;
switch (e.Action)
{
case PlaylistUpdateAction.Add:
foreach (PlaylistSong song in e.Songs)
{
_playlistsBySongUid[song.UID] = playlist;
}
break;
case PlaylistUpdateAction.Remove:
foreach (PlaylistSong song in e.Songs)
{
_playlistsBySongUid.Remove(song.UID);
}
break;
}
await playlistRepository.SaveAsync(playlist); await playlistRepository.SaveAsync(playlist);
} }
} }

View File

@@ -1,14 +1,32 @@
using Harmonia.Core.Data; using Harmonia.Core.Data;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Harmonia.Core.Playlists; namespace Harmonia.Core.Playlists;
public class PlaylistRepository : JsonFileRepository<Playlist>, IPlaylistRepository public class PlaylistRepository : JsonFileRepository<Playlist>, IPlaylistRepository
{ {
private static readonly JsonSerializerOptions _options = new()
{
WriteIndented = true,
IgnoreReadOnlyProperties = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
protected override string DirectoryName => Path.Combine("Playlists"); protected override string DirectoryName => Path.Combine("Playlists");
public Playlist? GetPlaylist(PlaylistSong playlistSong) protected override async Task<Playlist> DeserializeAsync(Stream stream)
{ {
return Get().FirstOrDefault(playlist => playlist.Songs.Contains(playlistSong)); PlaylistDto dto = await JsonSerializer.DeserializeAsync<PlaylistDto>(stream, _options) ?? new();
return dto.ToPlaylist();
}
protected override Task<string> SerializeAsync(Playlist playlist)
{
PlaylistDto dto = PlaylistDto.FromPlaylist(playlist);
return Task.FromResult(JsonSerializer.Serialize(dto, _options));
} }
protected override string GetNewFileName() protected override string GetNewFileName()

View File

@@ -21,7 +21,7 @@ internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistManager playli
if (Playlist == null || PlayingSong == null) if (Playlist == null || PlayingSong == null)
return -1; return -1;
return Playlist.Songs.IndexOf(PlayingSong); return Playlist.IndexOf(PlayingSong);
} }
} }
@@ -54,10 +54,7 @@ public class AudioPlayerTests
new Song() { FileName = "Song7.mp3" } new Song() { FileName = "Song7.mp3" }
]; ];
Playlist playlist = new() Playlist playlist = new("Playlist1");
{
Name = "Playlist1"
};
playlist.AddSongs(songs); playlist.AddSongs(songs);
@@ -65,7 +62,7 @@ public class AudioPlayerTests
_playlistManager = Substitute.For<IPlaylistManager>(); _playlistManager = Substitute.For<IPlaylistManager>();
_playlistManager.Playlists.Returns([playlist]); _playlistManager.Playlists.Returns([playlist]);
_playlistManager.GetPlaylist(Arg.Any<PlaylistSong>()).Returns(playlist); _playlistManager.FindPlaylistContaining(Arg.Any<PlaylistSong>()).Returns(playlist);
_audioPlayer = new TestAudioPlayer(_audioEngine, _playlistManager); _audioPlayer = new TestAudioPlayer(_audioEngine, _playlistManager);
_audioPlayer.SetPlaylist(playlist); _audioPlayer.SetPlaylist(playlist);

View File

@@ -114,7 +114,7 @@ public class PlaylistTests
playlist.AddSongs(songs); playlist.AddSongs(songs);
playlist.IsLocked = true; playlist.Lock();
Song song = new() { FileName = "Song4.mp3" }; Song song = new() { FileName = "Song4.mp3" };
playlist.AddSong(song); playlist.AddSong(song);

View File

@@ -433,7 +433,7 @@ public class PlaylistViewModel : ViewModelBase
if (Playlist == null || SelectedPlaylistSongs.Count == 0) if (Playlist == null || SelectedPlaylistSongs.Count == 0)
return; return;
int selectedPlaylistSongIndex = Playlist.Songs.IndexOf(SelectedPlaylistSongs[0]); int selectedPlaylistSongIndex = Playlist.IndexOf(SelectedPlaylistSongs[0]);
if (selectedPlaylistSongIndex == -1) if (selectedPlaylistSongIndex == -1)
return; return;

View File

@@ -611,7 +611,7 @@ public partial class PlaylistDetailViewModel : ViewModelBase
if (Playlist == null || SelectedPlaylistSongs.Count == 0) if (Playlist == null || SelectedPlaylistSongs.Count == 0)
return; return;
int selectedPlaylistSongIndex = Playlist.Songs.IndexOf(SelectedPlaylistSongs[0]); int selectedPlaylistSongIndex = Playlist.IndexOf(SelectedPlaylistSongs[0]);
if (selectedPlaylistSongIndex == -1) if (selectedPlaylistSongIndex == -1)
return; return;

View File

@@ -1,4 +1,4 @@
using CommunityToolkit.Mvvm.ComponentModel; using Harmonia.Core.Player;
using Harmonia.Core.Playlists; using Harmonia.Core.Playlists;
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -9,6 +9,7 @@ namespace Harmonia.WinUI.ViewModels;
public partial class PlaylistsViewModel : ViewModelBase public partial class PlaylistsViewModel : ViewModelBase
{ {
private readonly IPlaylistManager _playlistManager; private readonly IPlaylistManager _playlistManager;
private readonly IAudioPlayer _audioPlayer;
private ObservableCollection<PlaylistItemViewModel> _playlists = []; private ObservableCollection<PlaylistItemViewModel> _playlists = [];
public ObservableCollection<PlaylistItemViewModel> Playlists public ObservableCollection<PlaylistItemViewModel> Playlists
@@ -39,13 +40,29 @@ public partial class PlaylistsViewModel : ViewModelBase
} }
} }
public PlaylistsViewModel(IPlaylistManager playlistManager) private PlaylistItemViewModel? _activePlaylist = null;
public PlaylistItemViewModel? ActivePlaylist
{
get
{
return _activePlaylist;
}
set
{
SetProperty(ref _activePlaylist, value);
}
}
public PlaylistsViewModel(IPlaylistManager playlistManager, IAudioPlayer audioPlayer)
{ {
_playlistManager = playlistManager; _playlistManager = playlistManager;
_playlistManager.PlaylistAdded += OnPlaylistAdded; _playlistManager.PlaylistAdded += OnPlaylistAdded;
_playlistManager.PlaylistRemoved += OnPlaylistRemoved; _playlistManager.PlaylistRemoved += OnPlaylistRemoved;
_playlistManager.CurrentPlaylistChanged += OnCurrentPlaylistChanged; _playlistManager.CurrentPlaylistChanged += OnCurrentPlaylistChanged;
_audioPlayer = audioPlayer;
_audioPlayer.PlayingSongChanged += OnPlayingSongChanged;
_playlists = new ObservableCollection<PlaylistItemViewModel>(_playlistManager.Playlists.Select(p => new PlaylistItemViewModel(p))); _playlists = new ObservableCollection<PlaylistItemViewModel>(_playlistManager.Playlists.Select(p => new PlaylistItemViewModel(p)));
_selectedPlaylist = _playlists.FirstOrDefault(pv => pv.Playlist == _playlistManager.CurrentPlaylist); _selectedPlaylist = _playlists.FirstOrDefault(pv => pv.Playlist == _playlistManager.CurrentPlaylist);
} }
@@ -70,4 +87,9 @@ public partial class PlaylistsViewModel : ViewModelBase
Playlists.Remove(playlistView); Playlists.Remove(playlistView);
} }
} }
private void OnPlayingSongChanged(object? sender, EventArgs e)
{
ActivePlaylist = Playlists.FirstOrDefault(pv => pv.Playlist == _audioPlayer.Playlist);
}
} }

View File

@@ -12,8 +12,34 @@
d:DataContext="{d:DesignInstance Type=vm:PlaylistsViewModel, IsDesignTimeCreatable=True}" d:DataContext="{d:DesignInstance Type=vm:PlaylistsViewModel, IsDesignTimeCreatable=True}"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<SolidColorBrush x:Key="PlaylistItemIconBrush" Color="#dddddd"/>
<SolidColorBrush x:Key="PlaylistItemTitleBrush" Color="#dddddd"/>
<SolidColorBrush x:Key="PlaylistItemSubtitleBrush" Color="#aaaaaa"/> <SolidColorBrush x:Key="PlaylistItemSubtitleBrush" Color="#aaaaaa"/>
<!-- Playlist Icon Path -->
<Style x:Key="PlaylistIconPath" TargetType="PathIcon">
<Setter Property="Foreground" Value="{StaticResource PlaylistItemIconBrush}"/>
</Style>
<Style x:Key="SelectedPlaylistIconPath" TargetType="PathIcon" BasedOn="{StaticResource PlaylistIconPath}">
<Setter Property="Foreground" Value="{StaticResource AccentTextFillColorPrimaryBrush}"/>
</Style>
<!-- Playlist Title Text Block -->
<Style x:Key="PlaylistTitleTextBlock" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource PlaylistItemTitleBrush}"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontWeight" Value="Medium"/>
<Setter Property="LineStackingStrategy" Value="BlockLineHeight"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
<Setter Property="LineHeight" Value="0"/>
</Style>
<Style x:Key="SelectedPlaylistTitleTextBlock" TargetType="TextBlock" BasedOn="{StaticResource PlaylistTitleTextBlock}">
<Setter Property="Foreground" Value="{StaticResource AccentTextFillColorPrimaryBrush}"/>
</Style>
<!-- Playlist Item Subtitle Text Block --> <!-- Playlist Item Subtitle Text Block -->
<Style x:Key="PlaylistSubtitleTextBlock" TargetType="TextBlock"> <Style x:Key="PlaylistSubtitleTextBlock" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource PlaylistItemSubtitleBrush}"/> <Setter Property="Foreground" Value="{StaticResource PlaylistItemSubtitleBrush}"/>
@@ -23,26 +49,31 @@
<Setter Property="LineHeight" Value="0"/> <Setter Property="LineHeight" Value="0"/>
</Style> </Style>
<Style x:Key="SelectedPlaylistSubtitleTextBlock" TargetType="TextBlock" BasedOn="{StaticResource PlaylistSubtitleTextBlock}">
<Setter Property="Foreground" Value="{StaticResource AccentTextFillColorSecondaryBrush}"/>
</Style>
<DataTemplate x:Key="PlaylistTemplate" x:DataType="vm:PlaylistItemViewModel"> <DataTemplate x:Key="PlaylistTemplate" x:DataType="vm:PlaylistItemViewModel">
<Grid> <Grid x:Name="PlaylistListViewItem">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="200" /> <ColumnDefinition Width="200" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center"> <StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
<Viewbox Height="24" Width="24"> <Viewbox Height="24" Width="24">
<PathIcon Data="{StaticResource MusicNoteList}" /> <PathIcon x:Name="MusicNoteListPathIcon" Data="{StaticResource MusicNoteList}" />
</Viewbox> </Viewbox>
<TextBlock Text="{x:Bind Name, Mode=OneWay}" FontSize="16" VerticalAlignment="Center" /> <TextBlock x:Name="PlaylistTitleTextBlock" Text="{x:Bind Name, Mode=OneWay}" Style="{StaticResource PlaylistTitleTextBlock}" />
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Spacing="8"> <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Spacing="8">
<TextBlock Text="{x:Bind Summary, Mode=OneWay}" Style="{StaticResource PlaylistSubtitleTextBlock}" /> <TextBlock x:Name="PlaylistSubtitleTextBlock" Text="{x:Bind Summary, Mode=OneWay}" Style="{StaticResource PlaylistSubtitleTextBlock}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</UserControl.Resources> </UserControl.Resources>
<Grid Margin="10"> <Grid Margin="10">
<ListView <ListView
Name="PlaylistsListView"
ItemsSource="{Binding Playlists, Mode=OneWay}" ItemsSource="{Binding Playlists, Mode=OneWay}"
SelectedItem="{Binding SelectedPlaylist, Mode=TwoWay}" SelectedItem="{Binding SelectedPlaylist, Mode=TwoWay}"
ItemTemplate="{StaticResource PlaylistTemplate}"> ItemTemplate="{StaticResource PlaylistTemplate}">

View File

@@ -1,12 +1,99 @@
using CommunityToolkit.WinUI;
using Harmonia.WinUI.ViewModels;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls;
using System.ComponentModel;
namespace Harmonia.WinUI.Views; namespace Harmonia.WinUI.Views;
public sealed partial class PlaylistsView : UserControl public sealed partial class PlaylistsView : UserControl
{ {
private readonly PlaylistsViewModel _viewModel;
public PlaylistsView() public PlaylistsView()
{ {
InitializeComponent(); InitializeComponent();
_viewModel = (PlaylistsViewModel)DataContext;
_viewModel.PropertyChanging += OnViewModelPropertyChanging;
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
}
private void OnViewModelPropertyChanging(object? sender, PropertyChangingEventArgs e)
{
switch (e.PropertyName)
{
case nameof(_viewModel.ActivePlaylist):
UpdateListViewItemStyle(PlaylistItemStyle.Normal);
break;
} }
} }
private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(_viewModel.ActivePlaylist):
UpdateListViewItemStyle(PlaylistItemStyle.Selected);
break;
}
}
private void UpdateListViewItemStyle(PlaylistItemStyle style)
{
ListViewItem listViewItem = (ListViewItem)PlaylistsListView.ContainerFromItem(_viewModel.ActivePlaylist);
if (listViewItem is null)
return;
FrameworkElement? frameworkElement = TryGetFrameworkElement(listViewItem, "PlaylistListViewItem");
if (frameworkElement == null)
return;
string playlistItemIconBrushName = style == PlaylistItemStyle.Selected ? "SelectedPlaylistIconPath" : "PlaylistIconPath";
string playlistItemTitleBrushName = style == PlaylistItemStyle.Selected ? "SelectedPlaylistTitleTextBlock" : "PlaylistTitleTextBlock";
string playlistItemSubtitleBrushName = style == PlaylistItemStyle.Selected ? "SelectedPlaylistSubtitleTextBlock" : "PlaylistSubtitleTextBlock";
UpdateElementStyle(frameworkElement, "MusicNoteListPathIcon", playlistItemIconBrushName);
UpdateElementStyle(frameworkElement, "PlaylistTitleTextBlock", playlistItemTitleBrushName);
UpdateElementStyle(frameworkElement, "PlaylistSubtitleTextBlock", playlistItemSubtitleBrushName);
}
private static FrameworkElement? TryGetFrameworkElement(ListViewItem listViewItem, string elementName)
{
if (listViewItem.Name == elementName)
{
return listViewItem;
}
else
{
if (listViewItem.FindDescendant(elementName) is not FrameworkElement frameworkElement)
return null;
return frameworkElement;
}
}
private void UpdateElementStyle(FrameworkElement dependencyObject, string elementName, string resourceName)
{
if (dependencyObject.FindDescendant(elementName) is not FrameworkElement frameworkElement)
return;
Resources.TryGetValue(resourceName, out object? resource);
if (resource == null)
Application.Current.Resources.TryGetValue(resourceName, out resource);
if (resource is not Style style)
return;
frameworkElement.Style = style;
}
}
public enum PlaylistItemStyle
{
Normal,
Selected
}