diff --git a/Harmonia.WinUI/App.xaml b/Harmonia.WinUI/App.xaml new file mode 100644 index 0000000..7eeb327 --- /dev/null +++ b/Harmonia.WinUI/App.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/Harmonia.WinUI/App.xaml.cs b/Harmonia.WinUI/App.xaml.cs new file mode 100644 index 0000000..d6b7a73 --- /dev/null +++ b/Harmonia.WinUI/App.xaml.cs @@ -0,0 +1,50 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using Microsoft.UI.Xaml.Shapes; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.Foundation; +using Windows.Foundation.Collections; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace Harmonia.WinUI +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + /// + /// Invoked when the application is launched. + /// + /// Details about the launch request and process. + protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) + { + m_window = new MainWindow(); + m_window.Activate(); + } + + private Window? m_window; + } +} diff --git a/Harmonia.WinUI/Assets/LockScreenLogo.scale-200.png b/Harmonia.WinUI/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000..7440f0d Binary files /dev/null and b/Harmonia.WinUI/Assets/LockScreenLogo.scale-200.png differ diff --git a/Harmonia.WinUI/Assets/SplashScreen.scale-200.png b/Harmonia.WinUI/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000..32f486a Binary files /dev/null and b/Harmonia.WinUI/Assets/SplashScreen.scale-200.png differ diff --git a/Harmonia.WinUI/Assets/Square150x150Logo.scale-200.png b/Harmonia.WinUI/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000..53ee377 Binary files /dev/null and b/Harmonia.WinUI/Assets/Square150x150Logo.scale-200.png differ diff --git a/Harmonia.WinUI/Assets/Square44x44Logo.scale-200.png b/Harmonia.WinUI/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000..f713bba Binary files /dev/null and b/Harmonia.WinUI/Assets/Square44x44Logo.scale-200.png differ diff --git a/Harmonia.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/Harmonia.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000..dc9f5be Binary files /dev/null and b/Harmonia.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/Harmonia.WinUI/Assets/StoreLogo.png b/Harmonia.WinUI/Assets/StoreLogo.png new file mode 100644 index 0000000..a4586f2 Binary files /dev/null and b/Harmonia.WinUI/Assets/StoreLogo.png differ diff --git a/Harmonia.WinUI/Assets/Wide310x150Logo.scale-200.png b/Harmonia.WinUI/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000..8b4a5d0 Binary files /dev/null and b/Harmonia.WinUI/Assets/Wide310x150Logo.scale-200.png differ diff --git a/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs b/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs new file mode 100644 index 0000000..c92b011 --- /dev/null +++ b/Harmonia.WinUI/Caching/AudioBitmapImageCache.cs @@ -0,0 +1,92 @@ +using Harmonia.Core.Caching; +using Harmonia.Core.Imaging; +using Harmonia.Core.Models; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.UI.Xaml.Media.Imaging; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Harmonia.WinUI.Caching; + +public class AudioBitmapImageCache(IAudioImageExtractor audioImageExtractor) : MemoryCache, IAudioBitmapImageCache +{ + protected virtual int MaxImageWidthOrHeight => 1000; + + protected override MemoryCacheOptions Options => new() + { + SizeLimit = 40, + CompactionPercentage = 0.2, + }; + + protected override TimeSpan SlidingExpiration => TimeSpan.FromSeconds(600); + + protected override int MaxConcurrentRequests => 8; + + protected override object? GetKey(Song key) + { + if (string.IsNullOrWhiteSpace(key.ImageHash) == false) + { + return key.ImageHash; + } + else if (string.IsNullOrWhiteSpace(key.ImageName) == false) + { + return key.ImageName; + } + + return null; + } + + protected override async ValueTask FetchAsync(Song key, CancellationToken cancellationToken) + { + SongPictureInfo? songPictureInfo = await audioImageExtractor.ExtractImageAsync(key.FileName, cancellationToken); + + if (songPictureInfo == null) + return null; + + using MemoryStream stream = new(songPictureInfo.Data); + + BitmapImage bitmapImage = new(); + + await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream()); + + bitmapImage.DecodePixelWidth = GetDecodePixelWidth(bitmapImage); + bitmapImage.DecodePixelHeight = GetDecodePixelHeight(bitmapImage); + + return bitmapImage; + } + + private int GetDecodePixelWidth(BitmapImage bitmapImage) + { + int originalImageWidth = bitmapImage.PixelWidth; + int orignalImageHeight = bitmapImage.PixelHeight; + + if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight) + return 0; + + if (orignalImageHeight > originalImageWidth) + return 0; + + return MaxImageWidthOrHeight; + } + + private int GetDecodePixelHeight(BitmapImage bitmapImage) + { + int originalImageWidth = bitmapImage.PixelWidth; + int orignalImageHeight = bitmapImage.PixelHeight; + + if (originalImageWidth <= MaxImageWidthOrHeight && orignalImageHeight <= MaxImageWidthOrHeight) + return 0; + + if (originalImageWidth > orignalImageHeight) + return 0; + + return MaxImageWidthOrHeight; + } + + protected override long GetEntrySize(BitmapImage entry) + { + return entry.PixelWidth * entry.PixelHeight; + } +} \ No newline at end of file diff --git a/Harmonia.WinUI/Caching/IAudioBitmapImageCache.cs b/Harmonia.WinUI/Caching/IAudioBitmapImageCache.cs new file mode 100644 index 0000000..3f67447 --- /dev/null +++ b/Harmonia.WinUI/Caching/IAudioBitmapImageCache.cs @@ -0,0 +1,10 @@ +using Harmonia.Core.Caching; +using Harmonia.Core.Models; +using Microsoft.UI.Xaml.Media.Imaging; + +namespace Harmonia.WinUI.Caching; + +public interface IAudioBitmapImageCache : ICache +{ + +} \ No newline at end of file diff --git a/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs b/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs new file mode 100644 index 0000000..f211f34 --- /dev/null +++ b/Harmonia.WinUI/Converters/ArtistsToStringConverter.cs @@ -0,0 +1,26 @@ +using Microsoft.UI.Xaml.Data; +using System; +using System.Collections.Generic; + +namespace Harmonia.WinUI.Converters; + +public partial class ArtistsToStringConverter : IValueConverter +{ + public ArtistsToStringConverter() + { + + } + + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is not string[] artists) + return string.Empty; + + return string.Join(" / ", artists); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Harmonia.WinUI/Converters/NullVisibilityConverter.cs b/Harmonia.WinUI/Converters/NullVisibilityConverter.cs new file mode 100644 index 0000000..9b76c2e --- /dev/null +++ b/Harmonia.WinUI/Converters/NullVisibilityConverter.cs @@ -0,0 +1,27 @@ +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml; +using System; +using System.Collections; + +namespace Harmonia.WinUI.Converters; + +public sealed partial class NullVisibilityConverter : IValueConverter +{ + public NullVisibilityConverter() + { + + } + + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is not IList list) + return Visibility.Collapsed; + + return list.Count == 0 ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Harmonia.WinUI/Converters/RepeatStateConverter.cs b/Harmonia.WinUI/Converters/RepeatStateConverter.cs new file mode 100644 index 0000000..9f94d83 --- /dev/null +++ b/Harmonia.WinUI/Converters/RepeatStateConverter.cs @@ -0,0 +1,26 @@ +using Harmonia.Core.Player; +using Microsoft.UI.Xaml.Data; +using System; + +namespace Harmonia.WinUI.Converters; + +public sealed partial class RepeatStateConverter : IValueConverter +{ + public RepeatStateConverter() + { + + } + + public object? Convert(object value, Type targetType, object parameter, string language) + { + if (value is not RepeatState repeatState) + return null; + + return repeatState.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Harmonia.WinUI/Converters/SecondsToStringConverter.cs b/Harmonia.WinUI/Converters/SecondsToStringConverter.cs new file mode 100644 index 0000000..5796340 --- /dev/null +++ b/Harmonia.WinUI/Converters/SecondsToStringConverter.cs @@ -0,0 +1,30 @@ +using Microsoft.UI.Xaml.Data; +using System; + +namespace Harmonia.WinUI.Converters; + +public sealed partial class SecondsToStringConverter : IValueConverter +{ + public SecondsToStringConverter() + { + + } + + public object? Convert(object value, Type targetType, object parameter, string language) + { + if (value is not double doubleValue) + return null; + + TimeSpan timeSpan = TimeSpan.FromSeconds(doubleValue); + + if (timeSpan.Hours >= 1) + return timeSpan.ToString(@"%h\:mm\:ss"); + + return timeSpan.ToString(@"%m\:ss"); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return TimeSpan.Parse((string)value); + } +} \ No newline at end of file diff --git a/Harmonia.WinUI/Converters/SongTitleConverter.cs b/Harmonia.WinUI/Converters/SongTitleConverter.cs new file mode 100644 index 0000000..0c807d2 --- /dev/null +++ b/Harmonia.WinUI/Converters/SongTitleConverter.cs @@ -0,0 +1,26 @@ +using Harmonia.Core.Models; +using Microsoft.UI.Xaml.Data; +using System; + +namespace Harmonia.WinUI.Converters; + +public sealed partial class SongTitleConverter : IValueConverter +{ + public SongTitleConverter() + { + + } + + public object? Convert(object value, Type targetType, object parameter, string language) + { + if (value is not Song song) + return null; + + return string.IsNullOrWhiteSpace(song.Title) ? song.ShortFileName : song.Title; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Harmonia.WinUI/Converters/VolumeStateToIconConverter.cs b/Harmonia.WinUI/Converters/VolumeStateToIconConverter.cs new file mode 100644 index 0000000..cbcce10 --- /dev/null +++ b/Harmonia.WinUI/Converters/VolumeStateToIconConverter.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.UI.Xaml.Data; +using Harmonia.WinUI.Models; + +namespace Harmonia.WinUI.Converters; + +public sealed partial class VolumeStateConverter : IValueConverter +{ + public VolumeStateConverter() + { + + } + + public object? Convert(object value, Type targetType, object parameter, string language) + { + if (value is not VolumeState volumeState) + return null; + + return volumeState.ToString(); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Harmonia.WinUI/Harmonia.WinUI.csproj b/Harmonia.WinUI/Harmonia.WinUI.csproj new file mode 100644 index 0000000..89e81f2 --- /dev/null +++ b/Harmonia.WinUI/Harmonia.WinUI.csproj @@ -0,0 +1,64 @@ + + + WinExe + net9.0-windows10.0.19041.0 + 10.0.17763.0 + Harmonia.WinUI + app.manifest + x86;x64;ARM64 + win-x86;win-x64;win-arm64 + win-$(Platform).pubxml + true + true + None + true + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + False + True + False + True + + \ No newline at end of file diff --git a/Harmonia.WinUI/MainWindow.xaml b/Harmonia.WinUI/MainWindow.xaml new file mode 100644 index 0000000..3207319 --- /dev/null +++ b/Harmonia.WinUI/MainWindow.xaml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/Harmonia.WinUI/MainWindow.xaml.cs b/Harmonia.WinUI/MainWindow.xaml.cs new file mode 100644 index 0000000..a0ddcf0 --- /dev/null +++ b/Harmonia.WinUI/MainWindow.xaml.cs @@ -0,0 +1,36 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Navigation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace Harmonia.WinUI +{ + /// + /// An empty window that can be used on its own or navigated to within a Frame. + /// + public sealed partial class MainWindow : Window + { + public MainWindow() + { + this.InitializeComponent(); + } + + private void myButton_Click(object sender, RoutedEventArgs e) + { + myButton.Content = "Clicked"; + } + } +} diff --git a/Harmonia.WinUI/Models/VolumeState.cs b/Harmonia.WinUI/Models/VolumeState.cs new file mode 100644 index 0000000..a30102a --- /dev/null +++ b/Harmonia.WinUI/Models/VolumeState.cs @@ -0,0 +1,10 @@ +namespace Harmonia.WinUI.Models; + +public enum VolumeState +{ + Muted, + Off, + Low, + Medium, + High +} \ No newline at end of file diff --git a/Harmonia.WinUI/Package.appxmanifest b/Harmonia.WinUI/Package.appxmanifest new file mode 100644 index 0000000..e82a799 --- /dev/null +++ b/Harmonia.WinUI/Package.appxmanifest @@ -0,0 +1,51 @@ + + + + + + + + + + Harmonia.WinUI + Brian + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Harmonia.WinUI/Properties/launchSettings.json b/Harmonia.WinUI/Properties/launchSettings.json new file mode 100644 index 0000000..7c79af8 --- /dev/null +++ b/Harmonia.WinUI/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Harmonia.WinUI (Package)": { + "commandName": "MsixPackage" + }, + "Harmonia.WinUI (Unpackaged)": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/Harmonia.WinUI/app.manifest b/Harmonia.WinUI/app.manifest new file mode 100644 index 0000000..87a6138 --- /dev/null +++ b/Harmonia.WinUI/app.manifest @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + PerMonitorV2 + + + \ No newline at end of file diff --git a/Harmonia.sln b/Harmonia.sln index 1042818..295c649 100644 --- a/Harmonia.sln +++ b/Harmonia.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harmonia.UI", "Harmonia.UI\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harmonia.UI.Desktop", "Harmonia.UI.Desktop\Harmonia.UI.Desktop.csproj", "{61B86E6A-A7E9-4946-E488-40A7E12ED4FC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harmonia.WinUI", "Harmonia.WinUI\Harmonia.WinUI.csproj", "{5972FBF1-E81A-4A80-9153-13F45FA0E2AA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,12 @@ Global {61B86E6A-A7E9-4946-E488-40A7E12ED4FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {61B86E6A-A7E9-4946-E488-40A7E12ED4FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {61B86E6A-A7E9-4946-E488-40A7E12ED4FC}.Release|Any CPU.Build.0 = Release|Any CPU + {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Debug|Any CPU.ActiveCfg = Debug|x64 + {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Debug|Any CPU.Build.0 = Debug|x64 + {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Debug|Any CPU.Deploy.0 = Debug|x64 + {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Release|Any CPU.ActiveCfg = Release|x64 + {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Release|Any CPU.Build.0 = Release|x64 + {5972FBF1-E81A-4A80-9153-13F45FA0E2AA}.Release|Any CPU.Deploy.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE