mirror of
https://github.com/aaru-dps/RedBookPlayer.git
synced 2025-12-16 19:24:41 +00:00
Port only MVVM work and related fixes
This commit is contained in:
159
RedBookPlayer.GUI/ViewModels/MainViewModel.cs
Normal file
159
RedBookPlayer.GUI/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using RedBookPlayer.GUI.Views;
|
||||
|
||||
namespace RedBookPlayer.GUI.ViewModels
|
||||
{
|
||||
public class MainViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Read-only access to the control
|
||||
/// </summary>
|
||||
public ContentControl ContentControl => App.MainWindow.FindControl<ContentControl>("Content");
|
||||
|
||||
/// <summary>
|
||||
/// Read-only access to the view
|
||||
/// </summary>
|
||||
public PlayerView PlayerView => ContentControl?.Content as PlayerView;
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Execute the result of a keypress
|
||||
/// </summary>
|
||||
public async void ExecuteKeyPress(object sender, KeyEventArgs e)
|
||||
{
|
||||
// Open settings window
|
||||
if(e.Key == App.Settings.OpenSettingsKey)
|
||||
{
|
||||
SettingsWindow settingsWindow = new SettingsWindow() { DataContext = App.Settings };
|
||||
settingsWindow.Closed += OnSettingsClosed;
|
||||
settingsWindow.ShowDialog(App.MainWindow);
|
||||
}
|
||||
|
||||
// Load image
|
||||
else if(e.Key == App.Settings.LoadImageKey)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecuteLoad();
|
||||
}
|
||||
|
||||
// Toggle playback
|
||||
else if(e.Key == App.Settings.TogglePlaybackKey || e.Key == Key.MediaPlayPause)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecuteTogglePlayPause();
|
||||
}
|
||||
|
||||
// Stop playback
|
||||
else if(e.Key == App.Settings.StopPlaybackKey || e.Key == Key.MediaStop)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecuteStop();
|
||||
}
|
||||
|
||||
// Next Track
|
||||
else if(e.Key == App.Settings.NextTrackKey || e.Key == Key.MediaNextTrack)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecuteNextTrack();
|
||||
}
|
||||
|
||||
// Previous Track
|
||||
else if(e.Key == App.Settings.PreviousTrackKey || e.Key == Key.MediaPreviousTrack)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecutePreviousTrack();
|
||||
}
|
||||
|
||||
// Next Index
|
||||
else if(e.Key == App.Settings.NextIndexKey)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecuteNextIndex();
|
||||
}
|
||||
|
||||
// Previous Index
|
||||
else if(e.Key == App.Settings.PreviousIndexKey)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecutePreviousIndex();
|
||||
}
|
||||
|
||||
// Fast Foward
|
||||
else if(e.Key == App.Settings.FastForwardPlaybackKey)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecuteFastForward();
|
||||
}
|
||||
|
||||
// Rewind
|
||||
else if(e.Key == App.Settings.RewindPlaybackKey)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecuteRewind();
|
||||
}
|
||||
|
||||
// Volume Up
|
||||
else if(e.Key == App.Settings.VolumeUpKey || e.Key == Key.VolumeUp)
|
||||
{
|
||||
int increment = 1;
|
||||
if(e.KeyModifiers.HasFlag(KeyModifiers.Control))
|
||||
increment *= 2;
|
||||
if(e.KeyModifiers.HasFlag(KeyModifiers.Shift))
|
||||
increment *= 5;
|
||||
|
||||
if(PlayerView?.ViewModel?.Volume != null)
|
||||
PlayerView.ViewModel.ExecuteSetVolume(PlayerView.ViewModel.Volume + increment);
|
||||
}
|
||||
|
||||
// Volume Down
|
||||
else if(e.Key == App.Settings.VolumeDownKey || e.Key == Key.VolumeDown)
|
||||
{
|
||||
int decrement = 1;
|
||||
if(e.KeyModifiers.HasFlag(KeyModifiers.Control))
|
||||
decrement *= 2;
|
||||
if(e.KeyModifiers.HasFlag(KeyModifiers.Shift))
|
||||
decrement *= 5;
|
||||
|
||||
if(PlayerView?.ViewModel?.Volume != null)
|
||||
PlayerView.ViewModel.ExecuteSetVolume(PlayerView.ViewModel.Volume - decrement);
|
||||
}
|
||||
|
||||
// Mute Toggle
|
||||
else if(e.Key == App.Settings.ToggleMuteKey || e.Key == Key.VolumeMute)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecuteToggleMute();
|
||||
}
|
||||
|
||||
// Emphasis Toggle
|
||||
else if(e.Key == App.Settings.ToggleDeEmphasisKey)
|
||||
{
|
||||
PlayerView?.ViewModel?.ExecuteToggleDeEmphasis();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the first valid drag-and-dropped disc image
|
||||
/// </summary>
|
||||
public async void ExecuteLoadDragDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
if(PlayerView?.ViewModel == null)
|
||||
return;
|
||||
|
||||
IEnumerable<string> fileNames = e.Data.GetFileNames();
|
||||
foreach(string filename in fileNames)
|
||||
{
|
||||
bool loaded = await PlayerView.ViewModel.LoadImage(filename);
|
||||
if(loaded)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop current playback
|
||||
/// </summary>
|
||||
public void ExecuteStop(object sender, CancelEventArgs e) => PlayerView?.ViewModel?.ExecuteStop();
|
||||
|
||||
/// <summary>
|
||||
/// Handle the settings window closing
|
||||
/// </summary>
|
||||
private void OnSettingsClosed(object sender, EventArgs e) => PlayerView?.ViewModel?.RefreshFromSettings();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,16 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using RedBookPlayer.GUI.Views;
|
||||
using RedBookPlayer.Models;
|
||||
using RedBookPlayer.Models.Discs;
|
||||
using RedBookPlayer.Models.Hardware;
|
||||
|
||||
namespace RedBookPlayer.GUI.ViewModels
|
||||
@@ -21,7 +24,7 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
/// <summary>
|
||||
/// Player representing the internal state
|
||||
/// </summary>
|
||||
private Player _player;
|
||||
private readonly Player _player;
|
||||
|
||||
/// <summary>
|
||||
/// Set of images representing the digits for the UI
|
||||
@@ -50,6 +53,15 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
private set => this.RaiseAndSetIfChanged(ref _currentTrackIndex, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current track session
|
||||
/// </summary>
|
||||
public ushort CurrentTrackSession
|
||||
{
|
||||
get => _currentTrackSession;
|
||||
private set => this.RaiseAndSetIfChanged(ref _currentTrackSession, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current sector number
|
||||
/// </summary>
|
||||
@@ -140,6 +152,7 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
|
||||
private int _currentTrackNumber;
|
||||
private ushort _currentTrackIndex;
|
||||
private ushort _currentTrackSession;
|
||||
private ulong _currentSector;
|
||||
private ulong _sectionStartSector;
|
||||
|
||||
@@ -156,15 +169,28 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
/// <summary>
|
||||
/// Indicate if the model is ready to be used
|
||||
/// </summary>
|
||||
public bool Initialized => _player?.Initialized ?? false;
|
||||
public bool Initialized
|
||||
{
|
||||
get => _initialized;
|
||||
private set => this.RaiseAndSetIfChanged(ref _initialized, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicate if the output is playing
|
||||
/// </summary>
|
||||
public bool? Playing
|
||||
public PlayerState PlayerState
|
||||
{
|
||||
get => _playing;
|
||||
private set => this.RaiseAndSetIfChanged(ref _playing, value);
|
||||
get => _playerState;
|
||||
private set => this.RaiseAndSetIfChanged(ref _playerState, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates how to handle playback of data tracks
|
||||
/// </summary>
|
||||
public DataPlayback DataPlayback
|
||||
{
|
||||
get => _dataPlayback;
|
||||
private set => this.RaiseAndSetIfChanged(ref _dataPlayback, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -185,7 +211,9 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
private set => this.RaiseAndSetIfChanged(ref _volume, value);
|
||||
}
|
||||
|
||||
private bool? _playing;
|
||||
private bool _initialized;
|
||||
private PlayerState _playerState;
|
||||
private DataPlayback _dataPlayback;
|
||||
private bool _applyDeEmphasis;
|
||||
private int _volume;
|
||||
|
||||
@@ -299,6 +327,7 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
/// </summary>
|
||||
public PlayerViewModel()
|
||||
{
|
||||
// Initialize commands
|
||||
LoadCommand = ReactiveCommand.Create(ExecuteLoad);
|
||||
|
||||
PlayCommand = ReactiveCommand.Create(ExecutePlay);
|
||||
@@ -319,25 +348,27 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
EnableDeEmphasisCommand = ReactiveCommand.Create(ExecuteEnableDeEmphasis);
|
||||
DisableDeEmphasisCommand = ReactiveCommand.Create(ExecuteDisableDeEmphasis);
|
||||
ToggleDeEmphasisCommand = ReactiveCommand.Create(ExecuteToggleDeEmphasis);
|
||||
|
||||
// Initialize Player
|
||||
_player = new Player(App.Settings.Volume);
|
||||
PlayerState = PlayerState.NoDisc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the view model with a given image path
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the disc image</param>
|
||||
/// <param name="generateMissingToc">Generate a TOC if the disc is missing one [CompactDisc only]</param>
|
||||
/// <param name="loadHiddenTracks">Load hidden tracks for playback [CompactDisc only]</param>
|
||||
/// <param name="loadDataTracks">Load data tracks for playback [CompactDisc only]</param>
|
||||
/// <param name="options">Options to pass to the optical disc factory</param>
|
||||
/// <param name="autoPlay">True if playback should begin immediately, false otherwise</param>
|
||||
/// <param name="defaultVolume">Default volume between 0 and 100 to use when starting playback</param>
|
||||
public void Init(string path, bool generateMissingToc, bool loadHiddenTracks, bool loadDataTracks, bool autoPlay, int defaultVolume)
|
||||
public void Init(string path, OpticalDiscOptions options, bool autoPlay)
|
||||
{
|
||||
// Stop current playback, if necessary
|
||||
if(Playing != null) ExecuteStop();
|
||||
if(PlayerState != PlayerState.NoDisc)
|
||||
ExecuteStop();
|
||||
|
||||
// Create and attempt to initialize new Player
|
||||
_player = new Player(path, generateMissingToc, loadHiddenTracks, loadDataTracks, autoPlay, defaultVolume);
|
||||
if(Initialized)
|
||||
// Attempt to initialize Player
|
||||
_player.Init(path, options, autoPlay);
|
||||
if(_player.Initialized)
|
||||
{
|
||||
_player.PropertyChanged += PlayerStateChanged;
|
||||
PlayerStateChanged(this, null);
|
||||
@@ -444,6 +475,46 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Apply a custom theme to the player
|
||||
/// </summary>
|
||||
/// <param name="theme">Path to the theme under the themes directory</param>
|
||||
public void ApplyTheme(string theme)
|
||||
{
|
||||
// If the PlayerView isn't set, don't do anything
|
||||
if(App.PlayerView == null)
|
||||
return;
|
||||
|
||||
// If no theme path is provided, we can ignore
|
||||
if(string.IsNullOrWhiteSpace(theme))
|
||||
return;
|
||||
|
||||
string themeDirectory = $"{Directory.GetCurrentDirectory()}/themes/{theme}";
|
||||
string xamlPath = $"{themeDirectory}/view.xaml";
|
||||
|
||||
if(!File.Exists(xamlPath))
|
||||
{
|
||||
Console.WriteLine("Warning: specified theme doesn't exist, reverting to default");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string xaml = File.ReadAllText(xamlPath);
|
||||
xaml = xaml.Replace("Source=\"", $"Source=\"file://{themeDirectory}/");
|
||||
LoadTheme(xaml);
|
||||
}
|
||||
catch(XmlException ex)
|
||||
{
|
||||
Console.WriteLine($"Error: invalid theme XAML ({ex.Message}), reverting to default");
|
||||
LoadTheme(null);
|
||||
}
|
||||
|
||||
App.MainWindow.Width = App.PlayerView.Width;
|
||||
App.MainWindow.Height = App.PlayerView.Height;
|
||||
InitializeDigits();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a disc image from a selection box
|
||||
/// </summary>
|
||||
@@ -461,35 +532,36 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
/// </summary>
|
||||
public void InitializeDigits()
|
||||
{
|
||||
PlayerView playerView = MainWindow.Instance.ContentControl.Content as PlayerView;
|
||||
if(App.PlayerView == null)
|
||||
return;
|
||||
|
||||
_digits = new Image[]
|
||||
{
|
||||
playerView.FindControl<Image>("TrackDigit1"),
|
||||
playerView.FindControl<Image>("TrackDigit2"),
|
||||
App.PlayerView.FindControl<Image>("TrackDigit1"),
|
||||
App.PlayerView.FindControl<Image>("TrackDigit2"),
|
||||
|
||||
playerView.FindControl<Image>("IndexDigit1"),
|
||||
playerView.FindControl<Image>("IndexDigit2"),
|
||||
App.PlayerView.FindControl<Image>("IndexDigit1"),
|
||||
App.PlayerView.FindControl<Image>("IndexDigit2"),
|
||||
|
||||
playerView.FindControl<Image>("TimeDigit1"),
|
||||
playerView.FindControl<Image>("TimeDigit2"),
|
||||
playerView.FindControl<Image>("TimeDigit3"),
|
||||
playerView.FindControl<Image>("TimeDigit4"),
|
||||
playerView.FindControl<Image>("TimeDigit5"),
|
||||
playerView.FindControl<Image>("TimeDigit6"),
|
||||
App.PlayerView.FindControl<Image>("TimeDigit1"),
|
||||
App.PlayerView.FindControl<Image>("TimeDigit2"),
|
||||
App.PlayerView.FindControl<Image>("TimeDigit3"),
|
||||
App.PlayerView.FindControl<Image>("TimeDigit4"),
|
||||
App.PlayerView.FindControl<Image>("TimeDigit5"),
|
||||
App.PlayerView.FindControl<Image>("TimeDigit6"),
|
||||
|
||||
playerView.FindControl<Image>("TotalTracksDigit1"),
|
||||
playerView.FindControl<Image>("TotalTracksDigit2"),
|
||||
App.PlayerView.FindControl<Image>("TotalTracksDigit1"),
|
||||
App.PlayerView.FindControl<Image>("TotalTracksDigit2"),
|
||||
|
||||
playerView.FindControl<Image>("TotalIndexesDigit1"),
|
||||
playerView.FindControl<Image>("TotalIndexesDigit2"),
|
||||
App.PlayerView.FindControl<Image>("TotalIndexesDigit1"),
|
||||
App.PlayerView.FindControl<Image>("TotalIndexesDigit2"),
|
||||
|
||||
playerView.FindControl<Image>("TotalTimeDigit1"),
|
||||
playerView.FindControl<Image>("TotalTimeDigit2"),
|
||||
playerView.FindControl<Image>("TotalTimeDigit3"),
|
||||
playerView.FindControl<Image>("TotalTimeDigit4"),
|
||||
playerView.FindControl<Image>("TotalTimeDigit5"),
|
||||
playerView.FindControl<Image>("TotalTimeDigit6"),
|
||||
App.PlayerView.FindControl<Image>("TotalTimeDigit1"),
|
||||
App.PlayerView.FindControl<Image>("TotalTimeDigit2"),
|
||||
App.PlayerView.FindControl<Image>("TotalTimeDigit3"),
|
||||
App.PlayerView.FindControl<Image>("TotalTimeDigit4"),
|
||||
App.PlayerView.FindControl<Image>("TotalTimeDigit5"),
|
||||
App.PlayerView.FindControl<Image>("TotalTimeDigit6"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -501,19 +573,39 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
{
|
||||
return await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
Init(path, App.Settings.GenerateMissingTOC, App.Settings.PlayHiddenTracks, App.Settings.PlayDataTracks, App.Settings.AutoPlay, App.Settings.Volume);
|
||||
OpticalDiscOptions options = new OpticalDiscOptions
|
||||
{
|
||||
DataPlayback = App.Settings.DataPlayback,
|
||||
GenerateMissingToc = App.Settings.GenerateMissingTOC,
|
||||
LoadHiddenTracks = App.Settings.PlayHiddenTracks,
|
||||
};
|
||||
|
||||
// Ensure the context and view model are set
|
||||
App.PlayerView.DataContext = this;
|
||||
App.PlayerView.ViewModel = this;
|
||||
|
||||
Init(path, options, App.Settings.AutoPlay);
|
||||
if(Initialized)
|
||||
MainWindow.Instance.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last();
|
||||
App.MainWindow.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last();
|
||||
|
||||
return Initialized;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the value for loading data tracks [CompactDisc only]
|
||||
/// Refresh the view model from the current settings
|
||||
/// </summary>
|
||||
/// <param name="load">True to enable loading data tracks, false otherwise</param>
|
||||
public void SetLoadDataTracks(bool load) => _player?.SetLoadDataTracks(load);
|
||||
public void RefreshFromSettings()
|
||||
{
|
||||
SetDataPlayback(App.Settings.DataPlayback);
|
||||
SetLoadHiddenTracks(App.Settings.PlayHiddenTracks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set data playback method [CompactDisc only]
|
||||
/// </summary>
|
||||
/// <param name="dataPlayback">New playback value</param>
|
||||
public void SetDataPlayback(DataPlayback dataPlayback) => _player?.SetDataPlayback(dataPlayback);
|
||||
|
||||
/// <summary>
|
||||
/// Set the value for loading hidden tracks [CompactDisc only]
|
||||
@@ -569,18 +661,9 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
if(App.Settings.SelectedTheme == "default")
|
||||
{
|
||||
IAssetLoader assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
|
||||
|
||||
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);
|
||||
}
|
||||
string themeDirectory = $"{Directory.GetCurrentDirectory()}/themes/{App.Settings.SelectedTheme}";
|
||||
using FileStream stream = File.Open($"{themeDirectory}/{character}.png", FileMode.Open);
|
||||
return new Bitmap(stream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -619,18 +702,56 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
Extensions = knownExtensions.ConvertAll(e => e.TrimStart('.'))
|
||||
});
|
||||
|
||||
return (await dialog.ShowAsync(MainWindow.Instance))?.FirstOrDefault();
|
||||
return (await dialog.ShowAsync(App.MainWindow))?.FirstOrDefault();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the theme from a XAML, if possible
|
||||
/// </summary>
|
||||
/// <param name="xaml">XAML data representing the theme, null for default</param>
|
||||
private void LoadTheme(string xaml)
|
||||
{
|
||||
// If the view is null, we can't load the theme
|
||||
if(App.PlayerView == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if(xaml != null)
|
||||
new AvaloniaXamlLoader().Load(xaml, null, App.PlayerView);
|
||||
else
|
||||
AvaloniaXamlLoader.Load(App.PlayerView);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine(ex);
|
||||
}
|
||||
|
||||
// Ensure the context and view model are set
|
||||
App.PlayerView.DataContext = this;
|
||||
App.PlayerView.ViewModel = this;
|
||||
UpdateDigits();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the view-model from the Player
|
||||
/// </summary>
|
||||
private void PlayerStateChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if(_player?.Initialized != true)
|
||||
if(_player == null)
|
||||
return;
|
||||
|
||||
if(!_player.Initialized)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
App.MainWindow.Title = "RedBookPlayer";
|
||||
});
|
||||
}
|
||||
|
||||
Initialized = _player.Initialized;
|
||||
|
||||
CurrentTrackNumber = _player.CurrentTrackNumber;
|
||||
CurrentTrackIndex = _player.CurrentTrackIndex;
|
||||
CurrentSector = _player.CurrentSector;
|
||||
@@ -643,7 +764,8 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
CopyAllowed = _player.CopyAllowed;
|
||||
TrackHasEmphasis = _player.TrackHasEmphasis;
|
||||
|
||||
Playing = _player.Playing;
|
||||
PlayerState = _player.PlayerState;
|
||||
DataPlayback = _player.DataPlayback;
|
||||
ApplyDeEmphasis = _player.ApplyDeEmphasis;
|
||||
Volume = _player.Volume;
|
||||
|
||||
@@ -655,6 +777,10 @@ namespace RedBookPlayer.GUI.ViewModels
|
||||
/// </summary>
|
||||
private void UpdateDigits()
|
||||
{
|
||||
// Ensure the digits
|
||||
if(_digits == null)
|
||||
InitializeDigits();
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
string digitString = GenerateDigitString() ?? string.Empty.PadLeft(20, '-');
|
||||
|
||||
284
RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs
Normal file
284
RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Avalonia.Input;
|
||||
using ReactiveUI;
|
||||
using RedBookPlayer.Models;
|
||||
|
||||
namespace RedBookPlayer.GUI.ViewModels
|
||||
{
|
||||
public class SettingsViewModel : ReactiveObject
|
||||
{
|
||||
#region Player Settings
|
||||
|
||||
/// <summary>
|
||||
/// List of all data playback values
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public List<DataPlayback> DataPlaybackValues => GenerateDataPlaybackList();
|
||||
|
||||
/// <summary>
|
||||
/// List of all themes
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public List<string> ThemeValues => GenerateThemeList();
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if discs should start playing on load
|
||||
/// </summary>
|
||||
public bool AutoPlay { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if an index change can trigger a track change
|
||||
/// </summary>
|
||||
public bool IndexButtonChangeTrack { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if hidden tracks should be played
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Hidden tracks can be one of the following:
|
||||
/// - TrackSequence == 0
|
||||
/// - Larget pregap of track 1 (> 150 sectors)
|
||||
/// </remarks>
|
||||
public bool PlayHiddenTracks { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Generate a TOC if the disc is missing one
|
||||
/// </summary>
|
||||
public bool GenerateMissingTOC { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates how to deal with data tracks
|
||||
/// </summary>
|
||||
public DataPlayback DataPlayback { get; set; } = DataPlayback.Skip;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the default playback volume
|
||||
/// </summary>
|
||||
public int Volume
|
||||
{
|
||||
get => _volume;
|
||||
private set
|
||||
{
|
||||
int tempValue;
|
||||
if(value > 100)
|
||||
tempValue = 100;
|
||||
else if(value < 0)
|
||||
tempValue = 0;
|
||||
else
|
||||
tempValue = value;
|
||||
|
||||
this.RaiseAndSetIfChanged(ref _volume, tempValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the currently selected theme
|
||||
/// </summary>
|
||||
public string SelectedTheme { get; set; } = "Default";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Key Mappings
|
||||
|
||||
/// <summary>
|
||||
/// List of all keyboard keys
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public List<Key> KeyboardList => GenerateKeyList();
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to open settings
|
||||
/// </summary>
|
||||
public Key OpenSettingsKey { get; set; } = Key.F1;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to load a new image
|
||||
/// </summary>
|
||||
public Key LoadImageKey { get; set; } = Key.F2;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to toggle play and pause
|
||||
/// </summary>
|
||||
public Key TogglePlaybackKey { get; set; } = Key.Space;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to stop playback
|
||||
/// </summary>
|
||||
public Key StopPlaybackKey { get; set; } = Key.Escape;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to eject the disc
|
||||
/// </summary>
|
||||
public Key EjectKey { get; set; } = Key.OemTilde;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to move to the next track
|
||||
/// </summary>
|
||||
public Key NextTrackKey { get; set; } = Key.Right;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to move to the previous track
|
||||
/// </summary>
|
||||
public Key PreviousTrackKey { get; set; } = Key.Left;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to move to the next index
|
||||
/// </summary>
|
||||
public Key NextIndexKey { get; set; } = Key.OemCloseBrackets;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to move to the previous index
|
||||
/// </summary>
|
||||
public Key PreviousIndexKey { get; set; } = Key.OemOpenBrackets;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to fast forward playback
|
||||
/// </summary>
|
||||
public Key FastForwardPlaybackKey { get; set; } = Key.OemPeriod;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to rewind playback
|
||||
/// </summary>
|
||||
public Key RewindPlaybackKey { get; set; } = Key.OemComma;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to raise volume
|
||||
/// </summary>
|
||||
public Key VolumeUpKey { get; set; } = Key.Add;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to lower volume
|
||||
/// </summary>
|
||||
public Key VolumeDownKey { get; set; } = Key.Subtract;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to toggle mute
|
||||
/// </summary>
|
||||
public Key ToggleMuteKey { get; set; } = Key.M;
|
||||
|
||||
/// <summary>
|
||||
/// Key assigned to toggle de-emphasis
|
||||
/// </summary>
|
||||
public Key ToggleDeEmphasisKey { get; set; } = Key.E;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
/// <summary>
|
||||
/// Command for applying settings
|
||||
/// </summary>
|
||||
public ReactiveCommand<Unit, Unit> ApplySettingsCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Path to the settings file
|
||||
/// </summary>
|
||||
private string _filePath;
|
||||
|
||||
/// <summary>
|
||||
/// Internal value for the volume
|
||||
/// </summary>
|
||||
private int _volume = 100;
|
||||
|
||||
public SettingsViewModel() : this(null) { }
|
||||
|
||||
public SettingsViewModel(string filePath)
|
||||
{
|
||||
_filePath = filePath;
|
||||
|
||||
ApplySettingsCommand = ReactiveCommand.Create(ExecuteApplySettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load settings from a file
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the settings JSON file</param>
|
||||
/// <returns>Settings derived from the input file, if possible</returns>
|
||||
public static SettingsViewModel Load(string filePath)
|
||||
{
|
||||
if(File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
SettingsViewModel settings = JsonSerializer.Deserialize<SettingsViewModel>(File.ReadAllText(filePath));
|
||||
settings._filePath = filePath;
|
||||
|
||||
return settings;
|
||||
}
|
||||
catch(JsonException)
|
||||
{
|
||||
Console.WriteLine("Couldn't parse settings, reverting to default");
|
||||
|
||||
return new SettingsViewModel(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return new SettingsViewModel(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply settings from the UI
|
||||
/// </summary>
|
||||
public void ExecuteApplySettings()
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(SelectedTheme))
|
||||
App.PlayerView?.ViewModel?.ApplyTheme(SelectedTheme);
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(this, options);
|
||||
File.WriteAllText(_filePath, json);
|
||||
}
|
||||
|
||||
#region Generation
|
||||
|
||||
/// <summary>
|
||||
/// Generate the list of DataPlayback values
|
||||
/// </summary>
|
||||
private List<DataPlayback> GenerateDataPlaybackList() => Enum.GetValues(typeof(DataPlayback)).Cast<DataPlayback>().ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Generate the list of Key values
|
||||
/// </summary>
|
||||
private List<Key> GenerateKeyList() => Enum.GetValues(typeof(Key)).Cast<Key>().ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Generate the list of valid themes
|
||||
/// </summary>
|
||||
private List<string> GenerateThemeList()
|
||||
{
|
||||
// Create a list of all found themes
|
||||
List<string> items = new List<string>();
|
||||
|
||||
// Ensure the theme directory exists
|
||||
if(!Directory.Exists("themes/"))
|
||||
Directory.CreateDirectory("themes/");
|
||||
|
||||
// Add all theme directories if they're valid
|
||||
foreach(string dir in Directory.EnumerateDirectories("themes/"))
|
||||
{
|
||||
string themeName = dir.Split('/')[1];
|
||||
|
||||
if(!File.Exists($"themes/{themeName}/view.xaml"))
|
||||
continue;
|
||||
|
||||
items.Add(themeName);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user