using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Threading; using RedBookPlayer.GUI.ViewModels; namespace RedBookPlayer.GUI { public class PlayerView : UserControl { /// /// Read-only access to the view model /// public PlayerViewModel PlayerViewModel => DataContext as PlayerViewModel; /// /// Set of images representing the digits for the UI /// private Image[] _digits; /// /// Initialize the UI based on the default theme /// public PlayerView() : this(null, null) { } /// /// Initialize the UI based on the default theme with an existing view model /// /// XAML data representing the theme, null for default /// Existing PlayerViewModel to load in instead of creating a new one public PlayerView(PlayerViewModel playerViewModel) : this(null, playerViewModel) { } /// /// Initialize the UI based on the currently selected theme /// /// XAML data representing the theme, null for default /// Existing PlayerViewModel to load in instead of creating a new one public PlayerView(string xaml, PlayerViewModel playerViewModel) { if(playerViewModel != null) DataContext = playerViewModel; else DataContext = new PlayerViewModel(); PlayerViewModel.PropertyChanged += PlayerViewModelStateChanged; LoadTheme(xaml); InitializeDigits(); } #region Helpers /// /// Load an image from the path /// /// Path to the image to load public async Task LoadImage(string path) { return await Dispatcher.UIThread.InvokeAsync(() => { PlayerViewModel.Init(path, App.Settings.GenerateMissingTOC, App.Settings.PlayHiddenTracks, App.Settings.PlayDataTracks, App.Settings.AutoPlay, App.Settings.Volume); if (PlayerViewModel.Initialized) MainWindow.Instance.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last(); return PlayerViewModel.Initialized; }); } /// /// Update the view model with new settings /// public void UpdateViewModel() { PlayerViewModel.SetLoadDataTracks(App.Settings.PlayDataTracks); PlayerViewModel.SetLoadHiddenTracks(App.Settings.PlayHiddenTracks); } /// /// Generate the digit string to be interpreted by the frontend /// /// String representing the digits for the frontend private string GenerateDigitString() { // If the disc isn't initialized, return all '-' characters if(PlayerViewModel?.Initialized != true) return string.Empty.PadLeft(20, '-'); int usableTrackNumber = PlayerViewModel.CurrentTrackNumber; if(usableTrackNumber < 0) usableTrackNumber = 0; else if(usableTrackNumber > 99) usableTrackNumber = 99; // Otherwise, take the current time into account ulong sectorTime = GetCurrentSectorTime(); int[] numbers = new int[] { usableTrackNumber, PlayerViewModel.CurrentTrackIndex, (int)(sectorTime / (75 * 60)), (int)(sectorTime / 75 % 60), (int)(sectorTime % 75), PlayerViewModel.TotalTracks, PlayerViewModel.TotalIndexes, (int)(PlayerViewModel.TotalTime / (75 * 60)), (int)(PlayerViewModel.TotalTime / 75 % 60), (int)(PlayerViewModel.TotalTime % 75), }; return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2))); } /// /// Load the png image for a given character based on the theme /// /// Character to load the image for /// Bitmap representing the loaded image private Bitmap GetBitmap(char character) { try { if(App.Settings.SelectedTheme == "default") { IAssetLoader assets = AvaloniaLocator.Current.GetService(); return new Bitmap(assets.Open(new Uri($"avares://RedBookPlayer/Assets/{character}.png"))); } else { string themeDirectory = $"{Directory.GetCurrentDirectory()}/themes/{App.Settings.SelectedTheme}"; using FileStream stream = File.Open($"{themeDirectory}/{character}.png", FileMode.Open); return new Bitmap(stream); } } catch { return null; } } /// /// Get current sector time, accounting for offsets /// /// ulong representing the current sector time private ulong GetCurrentSectorTime() { ulong sectorTime = PlayerViewModel.CurrentSector; if(PlayerViewModel.SectionStartSector != 0) sectorTime -= PlayerViewModel.SectionStartSector; else if (PlayerViewModel.CurrentTrackNumber > 0) sectorTime += PlayerViewModel.TimeOffset; return sectorTime; } /// /// Generate a path selection dialog box /// /// User-selected path, if possible private async Task GetPath() { var dialog = new OpenFileDialog { AllowMultiple = false }; List knownExtensions = new Aaru.DiscImages.AaruFormat().KnownExtensions.ToList(); dialog.Filters.Add(new FileDialogFilter() { Name = "Aaru Image Format (*" + string.Join(", *", knownExtensions) + ")", Extensions = knownExtensions.ConvertAll(e => e.TrimStart('.')) }); return (await dialog.ShowAsync((Window)Parent.Parent))?.FirstOrDefault(); } /// /// Initialize the displayed digits array /// private void InitializeDigits() { _digits = new Image[] { this.FindControl("TrackDigit1"), this.FindControl("TrackDigit2"), this.FindControl("IndexDigit1"), this.FindControl("IndexDigit2"), this.FindControl("TimeDigit1"), this.FindControl("TimeDigit2"), this.FindControl("TimeDigit3"), this.FindControl("TimeDigit4"), this.FindControl("TimeDigit5"), this.FindControl("TimeDigit6"), this.FindControl("TotalTracksDigit1"), this.FindControl("TotalTracksDigit2"), this.FindControl("TotalIndexesDigit1"), this.FindControl("TotalIndexesDigit2"), this.FindControl("TotalTimeDigit1"), this.FindControl("TotalTimeDigit2"), this.FindControl("TotalTimeDigit3"), this.FindControl("TotalTimeDigit4"), this.FindControl("TotalTimeDigit5"), this.FindControl("TotalTimeDigit6"), }; } /// /// Load the theme from a XAML, if possible /// /// XAML data representing the theme, null for default private void LoadTheme(string xaml) { try { if(xaml != null) new AvaloniaXamlLoader().Load(xaml, null, this); else AvaloniaXamlLoader.Load(this); } catch { AvaloniaXamlLoader.Load(this); } } /// /// Update the UI from the view-model /// private void PlayerViewModelStateChanged(object sender, PropertyChangedEventArgs e) { Dispatcher.UIThread.InvokeAsync(() => { string digitString = GenerateDigitString(); for(int i = 0; i < _digits.Length; i++) { Bitmap digitImage = GetBitmap(digitString[i]); if(_digits[i] != null && digitImage != null) _digits[i].Source = digitImage; } }); } #endregion #region Event Handlers public async void LoadButton_Click(object sender, RoutedEventArgs e) { string path = await GetPath(); if (path == null) return; await LoadImage(path); } #endregion } }