Split common and GUI code into separate projects
7
RedBookPlayer.GUI/App.xaml
Normal file
@@ -0,0 +1,7 @@
|
||||
<Application xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="RedBookPlayer.App">
|
||||
<Application.Styles>
|
||||
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml" />
|
||||
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml" />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
42
RedBookPlayer.GUI/App.xaml.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using RedBookPlayer.GUI;
|
||||
|
||||
namespace RedBookPlayer
|
||||
{
|
||||
public class App : Application
|
||||
{
|
||||
public static Settings Settings;
|
||||
|
||||
static App() =>
|
||||
Directory.SetCurrentDirectory(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += (e, f) =>
|
||||
{
|
||||
Console.WriteLine(((Exception)f.ExceptionObject).ToString());
|
||||
};
|
||||
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if(ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow();
|
||||
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
||||
|
||||
Settings = Settings.Load("settings.json");
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
RedBookPlayer.GUI/Assets/-.png
Normal file
|
After Width: | Height: | Size: 357 B |
BIN
RedBookPlayer.GUI/Assets/0.png
Normal file
|
After Width: | Height: | Size: 658 B |
BIN
RedBookPlayer.GUI/Assets/1.png
Normal file
|
After Width: | Height: | Size: 414 B |
BIN
RedBookPlayer.GUI/Assets/2.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
RedBookPlayer.GUI/Assets/3.png
Normal file
|
After Width: | Height: | Size: 595 B |
BIN
RedBookPlayer.GUI/Assets/4.png
Normal file
|
After Width: | Height: | Size: 550 B |
BIN
RedBookPlayer.GUI/Assets/5.png
Normal file
|
After Width: | Height: | Size: 601 B |
BIN
RedBookPlayer.GUI/Assets/6.png
Normal file
|
After Width: | Height: | Size: 656 B |
BIN
RedBookPlayer.GUI/Assets/7.png
Normal file
|
After Width: | Height: | Size: 547 B |
BIN
RedBookPlayer.GUI/Assets/8.png
Normal file
|
After Width: | Height: | Size: 693 B |
BIN
RedBookPlayer.GUI/Assets/9.png
Normal file
|
After Width: | Height: | Size: 648 B |
BIN
RedBookPlayer.GUI/Assets/colon.png
Normal file
|
After Width: | Height: | Size: 407 B |
7
RedBookPlayer.GUI/MainWindow.xaml
Normal file
@@ -0,0 +1,7 @@
|
||||
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
|
||||
x:Class="RedBookPlayer.GUI.MainWindow" Title="RedBookPlayer" SizeToContent="WidthAndHeight"
|
||||
DragDrop.AllowDrop="True">
|
||||
<ContentControl Name="Content" />
|
||||
</Window>
|
||||
229
RedBookPlayer.GUI/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace RedBookPlayer.GUI
|
||||
{
|
||||
public class MainWindow : Window
|
||||
{
|
||||
public static MainWindow Instance;
|
||||
public ContentControl ContentControl;
|
||||
public Window settingsWindow;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
Instance = this;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a custom theme to the player
|
||||
/// </summary>
|
||||
/// <param name="theme">Path to the theme under the themes directory</param>
|
||||
public static void ApplyTheme(string theme)
|
||||
{
|
||||
// If no theme path is provided, we can ignore
|
||||
if(string.IsNullOrWhiteSpace(theme))
|
||||
return;
|
||||
|
||||
// If the theme name is "default", we assume the internal theme is used
|
||||
if(theme.Equals("default", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
Instance.ContentControl.Content = new PlayerView();
|
||||
}
|
||||
else
|
||||
{
|
||||
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}/");
|
||||
Instance.ContentControl.Content = new PlayerView(xaml);
|
||||
}
|
||||
catch(XmlException ex)
|
||||
{
|
||||
Console.WriteLine($"Error: invalid theme XAML ({ex.Message}), reverting to default");
|
||||
Instance.ContentControl.Content = new PlayerView();
|
||||
}
|
||||
}
|
||||
|
||||
Instance.Width = ((PlayerView)Instance.ContentControl.Content).Width;
|
||||
Instance.Height = ((PlayerView)Instance.ContentControl.Content).Height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the main window
|
||||
/// </summary>
|
||||
void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
ContentControl = this.FindControl<ContentControl>("Content");
|
||||
ContentControl.Content = new PlayerView();
|
||||
|
||||
Instance.MaxWidth = ((PlayerView)Instance.ContentControl.Content).Width;
|
||||
Instance.MaxHeight = ((PlayerView)Instance.ContentControl.Content).Height;
|
||||
|
||||
ContentControl.Content = new PlayerView();
|
||||
|
||||
CanResize = false;
|
||||
|
||||
KeyDown += OnKeyDown;
|
||||
|
||||
Closing += (s, e) =>
|
||||
{
|
||||
settingsWindow?.Close();
|
||||
settingsWindow = null;
|
||||
};
|
||||
|
||||
Closing += (e, f) =>
|
||||
{
|
||||
((PlayerView)ContentControl.Content).StopButton_Click(this, null);
|
||||
};
|
||||
|
||||
AddHandler(DragDrop.DropEvent, MainWindow_Drop);
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
public async void MainWindow_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
PlayerView playerView = ContentControl.Content as PlayerView;
|
||||
if(playerView == null)
|
||||
return;
|
||||
|
||||
IEnumerable<string> fileNames = e.Data.GetFileNames();
|
||||
foreach(string filename in fileNames)
|
||||
{
|
||||
bool loaded = await playerView.LoadImage(filename);
|
||||
if(loaded)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
PlayerView playerView = ContentControl.Content as PlayerView;
|
||||
|
||||
// Open settings window
|
||||
if(e.Key == App.Settings.OpenSettingsKey)
|
||||
{
|
||||
settingsWindow = new SettingsWindow(App.Settings);
|
||||
settingsWindow.Closed += OnSettingsClosed;
|
||||
settingsWindow.Show();
|
||||
}
|
||||
|
||||
// Load image
|
||||
else if (e.Key == App.Settings.LoadImageKey)
|
||||
{
|
||||
playerView?.LoadButton_Click(this, null);
|
||||
}
|
||||
|
||||
// Toggle playback
|
||||
else if(e.Key == App.Settings.TogglePlaybackKey || e.Key == Key.MediaPlayPause)
|
||||
{
|
||||
playerView?.PlayPauseButton_Click(this, null);
|
||||
}
|
||||
|
||||
// Stop playback
|
||||
else if(e.Key == App.Settings.StopPlaybackKey || e.Key == Key.MediaStop)
|
||||
{
|
||||
playerView?.StopButton_Click(this, null);
|
||||
}
|
||||
|
||||
// Next Track
|
||||
else if(e.Key == App.Settings.NextTrackKey || e.Key == Key.MediaNextTrack)
|
||||
{
|
||||
playerView?.NextTrackButton_Click(this, null);
|
||||
}
|
||||
|
||||
// Previous Track
|
||||
else if(e.Key == App.Settings.PreviousTrackKey || e.Key == Key.MediaPreviousTrack)
|
||||
{
|
||||
playerView?.PreviousTrackButton_Click(this, null);
|
||||
}
|
||||
|
||||
// Next Index
|
||||
else if(e.Key == App.Settings.NextIndexKey)
|
||||
{
|
||||
playerView?.NextIndexButton_Click(this, null);
|
||||
}
|
||||
|
||||
// Previous Index
|
||||
else if(e.Key == App.Settings.PreviousIndexKey)
|
||||
{
|
||||
playerView?.PreviousIndexButton_Click(this, null);
|
||||
}
|
||||
|
||||
// Fast Foward
|
||||
else if(e.Key == App.Settings.FastForwardPlaybackKey)
|
||||
{
|
||||
playerView?.FastForwardButton_Click(this, null);
|
||||
}
|
||||
|
||||
// Rewind
|
||||
else if(e.Key == App.Settings.RewindPlaybackKey)
|
||||
{
|
||||
playerView?.RewindButton_Click(this, null);
|
||||
}
|
||||
|
||||
// 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?.PlayerViewModel?.Volume != null)
|
||||
playerView.PlayerViewModel.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?.PlayerViewModel?.Volume != null)
|
||||
playerView.PlayerViewModel.Volume -= decrement;
|
||||
}
|
||||
|
||||
// Mute Toggle
|
||||
else if(e.Key == App.Settings.ToggleMuteKey || e.Key == Key.VolumeMute)
|
||||
{
|
||||
playerView?.MuteToggleButton_Click(this, null);
|
||||
}
|
||||
|
||||
// Emphasis Toggle
|
||||
else if(e.Key == App.Settings.ToggleDeEmphasisKey)
|
||||
{
|
||||
playerView?.EnableDisableDeEmphasisButton_Click(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSettingsClosed(object sender, EventArgs e)
|
||||
{
|
||||
PlayerView playerView = ContentControl.Content as PlayerView;
|
||||
playerView?.UpdateViewModel();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
103
RedBookPlayer.GUI/PlayerView.xaml
Normal file
@@ -0,0 +1,103 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
|
||||
x:Class="RedBookPlayer.GUI.PlayerView" Width="900" Height="400">
|
||||
<StackPanel Margin="16" VerticalAlignment="Center">
|
||||
<Button Click="LoadButton_Click" Margin="32,0,32,16">Load</Button>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
|
||||
<Button Click="PlayButton_Click" Width="100" Margin="0,0,16,0">Play</Button>
|
||||
<Button Click="PauseButton_Click" Width="100" Margin="0,0,16,0">Pause</Button>
|
||||
<Button Click="StopButton_Click" Width="100" Margin="0,0,16,0">Stop</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
|
||||
<Button Click="PreviousTrackButton_Click" Width="100" Margin="0,0,16,0">Previous Track</Button>
|
||||
<Button Click="NextTrackButton_Click" Width="100" Margin="0,0,16,0">Next Track</Button>
|
||||
<Button Click="PreviousIndexButton_Click" Width="100" Margin="0,0,16,0">Previous Index</Button>
|
||||
<Button Click="NextIndexButton_Click" Width="100" Margin="0,0,16,0">Next Index</Button>
|
||||
<RepeatButton Click="RewindButton_Click" Width="100" Margin="0,0,16,0">Rewind</RepeatButton>
|
||||
<RepeatButton Click="FastForwardButton_Click" Width="100">Fast Forward</RepeatButton>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
|
||||
<StackPanel Margin="0,0,32,0">
|
||||
<TextBlock Margin="0,0,0,4">TRACK</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Image Name="TrackDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="TrackDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,32,0">
|
||||
<TextBlock Margin="0,0,0,4">INDEX</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Image Name="IndexDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="IndexDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Margin="0,0,0,4">TIME</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Image Name="TimeDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="TimeDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Width="11" Height="51" Source="/Assets/colon.png" />
|
||||
<Image Name="TimeDigit3" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="TimeDigit4" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Width="11" Height="51" Source="/Assets/colon.png" />
|
||||
<Image Name="TimeDigit5" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="TimeDigit6" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
|
||||
<StackPanel Margin="0,0,32,0">
|
||||
<TextBlock Margin="0,0,0,4">TRACKS</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Image Name="TotalTracksDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="TotalTracksDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,32,0">
|
||||
<TextBlock Margin="0,0,0,4">INDEXES</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Image Name="TotalIndexesDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="TotalIndexesDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock Margin="0,0,0,4">TOTAL</TextBlock>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Image Name="TotalTimeDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="TotalTimeDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Width="11" Height="51" Source="/Assets/colon.png" />
|
||||
<Image Name="TotalTimeDigit3" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="TotalTimeDigit4" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Width="11" Height="51" Source="/Assets/colon.png" />
|
||||
<Image Name="TotalTimeDigit5" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
<Image Name="TotalTimeDigit6" Width="42" Height="51" Source="/Assets/-.png" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
|
||||
<Button Click="EnableDeEmphasisButton_Click" IsVisible="{Binding !ApplyDeEmphasis}" Width="200"
|
||||
Margin="0,0,16,0">
|
||||
Enable De-Emphasis
|
||||
</Button>
|
||||
<Button Click="DisableDeEmphasisButton_Click" IsVisible="{Binding ApplyDeEmphasis}" Width="200"
|
||||
Margin="0,0,16,0">
|
||||
Disable De-Emphasis
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding IsDataTrack}">AUDIO</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" IsVisible="{Binding !IsDataTrack}">AUDIO</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !IsDataTrack}">DATA</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" IsVisible="{Binding IsDataTrack}">DATA</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !TrackHasEmphasis}">EMPHASIS</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" IsVisible="{Binding TrackHasEmphasis}">EMPHASIS</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !CopyAllowed}">COPY</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" IsVisible="{Binding CopyAllowed}">COPY</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !QuadChannel}">4CH</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" IsVisible="{Binding QuadChannel}">4CH</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !HiddenTrack}">HIDDEN</TextBlock>
|
||||
<TextBlock Margin="0,0,16,0" IsVisible="{Binding HiddenTrack}">HIDDEN</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
287
RedBookPlayer.GUI/PlayerView.xaml.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
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;
|
||||
|
||||
namespace RedBookPlayer.GUI
|
||||
{
|
||||
public class PlayerView : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Read-only access to the view model
|
||||
/// </summary>
|
||||
public PlayerViewModel PlayerViewModel => DataContext as PlayerViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Set of images representing the digits for the UI
|
||||
/// </summary>
|
||||
private Image[] _digits;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the UI based on the default theme
|
||||
/// </summary>
|
||||
public PlayerView() : this(null) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the UI based on the currently selected theme
|
||||
/// </summary>
|
||||
/// <param name="xaml">XAML data representing the theme, null for default</param>
|
||||
public PlayerView(string xaml)
|
||||
{
|
||||
DataContext = new PlayerViewModel();
|
||||
PlayerViewModel.PropertyChanged += PlayerViewModelStateChanged;
|
||||
|
||||
LoadTheme(xaml);
|
||||
InitializeDigits();
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Load an image from the path
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the image to load</param>
|
||||
public async Task<bool> LoadImage(string path)
|
||||
{
|
||||
return await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
PlayerViewModel.Init(path, App.Settings.GenerateMissingTOC, App.Settings.PlayDataTracks, App.Settings.AutoPlay, App.Settings.Volume);
|
||||
if (PlayerViewModel.Initialized)
|
||||
MainWindow.Instance.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last();
|
||||
|
||||
return PlayerViewModel.Initialized;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the view model with new settings
|
||||
/// </summary>
|
||||
public void UpdateViewModel() => PlayerViewModel.SetLoadDataTracks(App.Settings.PlayDataTracks);
|
||||
|
||||
/// <summary>
|
||||
/// Generate the digit string to be interpreted by the frontend
|
||||
/// </summary>
|
||||
/// <returns>String representing the digits for the frontend</returns>
|
||||
private string GenerateDigitString()
|
||||
{
|
||||
// If the disc isn't initialized, return all '-' characters
|
||||
if(PlayerViewModel?.Initialized != true)
|
||||
return string.Empty.PadLeft(20, '-');
|
||||
|
||||
// Otherwise, take the current time into account
|
||||
ulong sectorTime = GetCurrentSectorTime();
|
||||
|
||||
int[] numbers = new int[]
|
||||
{
|
||||
PlayerViewModel.CurrentTrackNumber + 1,
|
||||
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)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load the png image for a given character based on the theme
|
||||
/// </summary>
|
||||
/// <param name="character">Character to load the image for</param>
|
||||
/// <returns>Bitmap representing the loaded image</returns>
|
||||
private Bitmap GetBitmap(char character)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current sector time, accounting for offsets
|
||||
/// </summary>
|
||||
/// <returns>ulong representing the current sector time</returns>
|
||||
private ulong GetCurrentSectorTime()
|
||||
{
|
||||
ulong sectorTime = PlayerViewModel.CurrentSector;
|
||||
if(PlayerViewModel.SectionStartSector != 0)
|
||||
sectorTime -= PlayerViewModel.SectionStartSector;
|
||||
else
|
||||
sectorTime += PlayerViewModel.TimeOffset;
|
||||
|
||||
return sectorTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a path selection dialog box
|
||||
/// </summary>
|
||||
/// <returns>User-selected path, if possible</returns>
|
||||
private async Task<string> GetPath()
|
||||
{
|
||||
var dialog = new OpenFileDialog { AllowMultiple = false };
|
||||
List<string> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the displayed digits array
|
||||
/// </summary>
|
||||
private void InitializeDigits()
|
||||
{
|
||||
_digits = new Image[]
|
||||
{
|
||||
this.FindControl<Image>("TrackDigit1"),
|
||||
this.FindControl<Image>("TrackDigit2"),
|
||||
|
||||
this.FindControl<Image>("IndexDigit1"),
|
||||
this.FindControl<Image>("IndexDigit2"),
|
||||
|
||||
this.FindControl<Image>("TimeDigit1"),
|
||||
this.FindControl<Image>("TimeDigit2"),
|
||||
this.FindControl<Image>("TimeDigit3"),
|
||||
this.FindControl<Image>("TimeDigit4"),
|
||||
this.FindControl<Image>("TimeDigit5"),
|
||||
this.FindControl<Image>("TimeDigit6"),
|
||||
|
||||
this.FindControl<Image>("TotalTracksDigit1"),
|
||||
this.FindControl<Image>("TotalTracksDigit2"),
|
||||
|
||||
this.FindControl<Image>("TotalIndexesDigit1"),
|
||||
this.FindControl<Image>("TotalIndexesDigit2"),
|
||||
|
||||
this.FindControl<Image>("TotalTimeDigit1"),
|
||||
this.FindControl<Image>("TotalTimeDigit2"),
|
||||
this.FindControl<Image>("TotalTimeDigit3"),
|
||||
this.FindControl<Image>("TotalTimeDigit4"),
|
||||
this.FindControl<Image>("TotalTimeDigit5"),
|
||||
this.FindControl<Image>("TotalTimeDigit6"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(xaml != null)
|
||||
new AvaloniaXamlLoader().Load(xaml, null, this);
|
||||
else
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
catch
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the UI from the view-model
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
public void PlayButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Play();
|
||||
|
||||
public void PauseButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Pause();
|
||||
|
||||
public void PlayPauseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if(PlayerViewModel.Playing == true)
|
||||
PlayerViewModel.Pause();
|
||||
else
|
||||
PlayerViewModel.Play();
|
||||
}
|
||||
|
||||
public void StopButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Stop();
|
||||
|
||||
public void NextTrackButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.NextTrack();
|
||||
|
||||
public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.PreviousTrack(App.Settings.AllowSkipHiddenTrack);
|
||||
|
||||
public void NextIndexButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.NextIndex(App.Settings.IndexButtonChangeTrack);
|
||||
|
||||
public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.PreviousIndex(App.Settings.IndexButtonChangeTrack, App.Settings.AllowSkipHiddenTrack);
|
||||
|
||||
public void FastForwardButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.FastForward();
|
||||
|
||||
public void RewindButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Rewind();
|
||||
|
||||
public void VolumeUpButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Volume++;
|
||||
|
||||
public void VolumeDownButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Volume--;
|
||||
|
||||
public void MuteToggleButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.ToggleMute();
|
||||
|
||||
public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.SetDeEmphasis(true);
|
||||
|
||||
public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.SetDeEmphasis(false);
|
||||
|
||||
public void EnableDisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.SetDeEmphasis(!PlayerViewModel.ApplyDeEmphasis);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
317
RedBookPlayer.GUI/PlayerViewModel.cs
Normal file
@@ -0,0 +1,317 @@
|
||||
using System.ComponentModel;
|
||||
using ReactiveUI;
|
||||
using RedBookPlayer.Common.Hardware;
|
||||
|
||||
namespace RedBookPlayer.GUI
|
||||
{
|
||||
public class PlayerViewModel : ReactiveObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Player representing the internal state
|
||||
/// </summary>
|
||||
private Player _player;
|
||||
|
||||
/// <summary>
|
||||
/// Last volume for mute toggling
|
||||
/// </summary>
|
||||
private int? _lastVolume = null;
|
||||
|
||||
#region Player Passthrough
|
||||
|
||||
#region OpticalDisc Passthrough
|
||||
|
||||
/// <summary>
|
||||
/// Current track number
|
||||
/// </summary>
|
||||
public int CurrentTrackNumber
|
||||
{
|
||||
get => _currentTrackNumber;
|
||||
private set => this.RaiseAndSetIfChanged(ref _currentTrackNumber, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current track index
|
||||
/// </summary>
|
||||
public ushort CurrentTrackIndex
|
||||
{
|
||||
get => _currentTrackIndex;
|
||||
private set => this.RaiseAndSetIfChanged(ref _currentTrackIndex, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current sector number
|
||||
/// </summary>
|
||||
public ulong CurrentSector
|
||||
{
|
||||
get => _currentSector;
|
||||
private set => this.RaiseAndSetIfChanged(ref _currentSector, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the sector starting the section
|
||||
/// </summary>
|
||||
public ulong SectionStartSector
|
||||
{
|
||||
get => _sectionStartSector;
|
||||
protected set => this.RaiseAndSetIfChanged(ref _sectionStartSector, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents if the disc has a hidden track
|
||||
/// </summary>
|
||||
public bool HiddenTrack
|
||||
{
|
||||
get => _hasHiddenTrack;
|
||||
private set => this.RaiseAndSetIfChanged(ref _hasHiddenTrack, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the 4CH flag [CompactDisc only]
|
||||
/// </summary>
|
||||
public bool QuadChannel
|
||||
{
|
||||
get => _quadChannel;
|
||||
private set => this.RaiseAndSetIfChanged(ref _quadChannel, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the DATA flag [CompactDisc only]
|
||||
/// </summary>
|
||||
public bool IsDataTrack
|
||||
{
|
||||
get => _isDataTrack;
|
||||
private set => this.RaiseAndSetIfChanged(ref _isDataTrack, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the DCP flag [CompactDisc only]
|
||||
/// </summary>
|
||||
public bool CopyAllowed
|
||||
{
|
||||
get => _copyAllowed;
|
||||
private set => this.RaiseAndSetIfChanged(ref _copyAllowed, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the PRE flag [CompactDisc only]
|
||||
/// </summary>
|
||||
public bool TrackHasEmphasis
|
||||
{
|
||||
get => _trackHasEmphasis;
|
||||
private set => this.RaiseAndSetIfChanged(ref _trackHasEmphasis, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the total tracks on the disc
|
||||
/// </summary>
|
||||
public int TotalTracks => _player.TotalTracks;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the total indices on the disc
|
||||
/// </summary>
|
||||
public int TotalIndexes => _player.TotalIndexes;
|
||||
|
||||
/// <summary>
|
||||
/// Total sectors in the image
|
||||
/// </summary>
|
||||
public ulong TotalSectors => _player.TotalSectors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the time adjustment offset for the disc
|
||||
/// </summary>
|
||||
public ulong TimeOffset => _player.TimeOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the total playing time for the disc
|
||||
/// </summary>
|
||||
public ulong TotalTime => _player.TotalTime;
|
||||
|
||||
private int _currentTrackNumber;
|
||||
private ushort _currentTrackIndex;
|
||||
private ulong _currentSector;
|
||||
private ulong _sectionStartSector;
|
||||
|
||||
private bool _hasHiddenTrack;
|
||||
private bool _quadChannel;
|
||||
private bool _isDataTrack;
|
||||
private bool _copyAllowed;
|
||||
private bool _trackHasEmphasis;
|
||||
|
||||
#endregion
|
||||
|
||||
#region SoundOutput Passthrough
|
||||
|
||||
/// <summary>
|
||||
/// Indicate if the model is ready to be used
|
||||
/// </summary>
|
||||
public bool Initialized => _player?.Initialized ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Indicate if the output is playing
|
||||
/// </summary>
|
||||
public bool? Playing
|
||||
{
|
||||
get => _playing;
|
||||
private set => this.RaiseAndSetIfChanged(ref _playing, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if de-emphasis should be applied
|
||||
/// </summary>
|
||||
public bool ApplyDeEmphasis
|
||||
{
|
||||
get => _applyDeEmphasis;
|
||||
private set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current playback volume
|
||||
/// </summary>
|
||||
public int Volume
|
||||
{
|
||||
get => _volume;
|
||||
set => this.RaiseAndSetIfChanged(ref _volume, value);
|
||||
}
|
||||
|
||||
private bool? _playing;
|
||||
private bool _applyDeEmphasis;
|
||||
private int _volume;
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
/// <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="loadDataTracks">Load data tracks for playback [CompactDisc only]</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 loadDataTracks, bool autoPlay, int defaultVolume)
|
||||
{
|
||||
// Stop current playback, if necessary
|
||||
if(Playing != null) Playing = null;
|
||||
|
||||
// Create and attempt to initialize new Player
|
||||
_player = new Player(path, generateMissingToc, loadDataTracks, autoPlay, defaultVolume);
|
||||
if(Initialized)
|
||||
{
|
||||
_player.PropertyChanged += PlayerStateChanged;
|
||||
PlayerStateChanged(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
#region Playback
|
||||
|
||||
/// <summary>
|
||||
/// Begin playback
|
||||
/// </summary>
|
||||
public void Play() => _player?.Play();
|
||||
|
||||
/// <summary>
|
||||
/// Pause current playback
|
||||
/// </summary>
|
||||
public void Pause() => _player?.Pause();
|
||||
|
||||
/// <summary>
|
||||
/// Stop current playback
|
||||
/// </summary>
|
||||
public void Stop() => _player?.Stop();
|
||||
|
||||
/// <summary>
|
||||
/// Move to the next playable track
|
||||
/// </summary>
|
||||
public void NextTrack() => _player?.NextTrack();
|
||||
|
||||
/// <summary>
|
||||
/// Move to the previous playable track
|
||||
/// </summary>
|
||||
/// <param name="playHiddenTrack">True to play the hidden track, if it exists</param>
|
||||
public void PreviousTrack(bool playHiddenTrack) => _player?.PreviousTrack(playHiddenTrack);
|
||||
|
||||
/// <summary>
|
||||
/// Move to the next index
|
||||
/// </summary>
|
||||
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
|
||||
public void NextIndex(bool changeTrack) => _player?.NextIndex(changeTrack);
|
||||
|
||||
/// <summary>
|
||||
/// Move to the previous index
|
||||
/// </summary>
|
||||
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
|
||||
/// <param name="playHiddenTrack">True to play the hidden track, if it exists</param>
|
||||
public void PreviousIndex(bool changeTrack, bool playHiddenTrack) => _player?.PreviousIndex(changeTrack, playHiddenTrack);
|
||||
|
||||
/// <summary>
|
||||
/// Fast-forward playback by 75 sectors, if possible
|
||||
/// </summary>
|
||||
public void FastForward() => _player?.FastForward();
|
||||
|
||||
/// <summary>
|
||||
/// Rewind playback by 75 sectors, if possible
|
||||
/// </summary>
|
||||
public void Rewind() => _player?.Rewind();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Set de-emphasis status
|
||||
/// </summary>
|
||||
/// <param name="apply"></param>
|
||||
public void SetDeEmphasis(bool apply) => _player?.SetDeEmphasis(apply);
|
||||
|
||||
/// <summary>
|
||||
/// Set the value for loading data tracks [CompactDisc only]
|
||||
/// </summary>
|
||||
/// <param name="load">True to enable loading data tracks, false otherwise</param>
|
||||
public void SetLoadDataTracks(bool load) => _player?.SetLoadDataTracks(load);
|
||||
|
||||
/// <summary>
|
||||
/// Temporarily mute playback
|
||||
/// </summary>
|
||||
public void ToggleMute()
|
||||
{
|
||||
if(_lastVolume == null)
|
||||
{
|
||||
_lastVolume = Volume;
|
||||
Volume = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Volume = _lastVolume.Value;
|
||||
_lastVolume = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the view-model from the Player
|
||||
/// </summary>
|
||||
private void PlayerStateChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if(_player?.Initialized != true)
|
||||
return;
|
||||
|
||||
CurrentTrackNumber = _player.CurrentTrackNumber;
|
||||
CurrentTrackIndex = _player.CurrentTrackIndex;
|
||||
CurrentSector = _player.CurrentSector;
|
||||
SectionStartSector = _player.SectionStartSector;
|
||||
|
||||
HiddenTrack = _player.HiddenTrack;
|
||||
|
||||
QuadChannel = _player.QuadChannel;
|
||||
IsDataTrack = _player.IsDataTrack;
|
||||
CopyAllowed = _player.CopyAllowed;
|
||||
TrackHasEmphasis = _player.TrackHasEmphasis;
|
||||
|
||||
Playing = _player.Playing;
|
||||
ApplyDeEmphasis = _player.ApplyDeEmphasis;
|
||||
Volume = _player.Volume;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
29
RedBookPlayer.GUI/Program.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
#if WindowsDebug
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
using Avalonia;
|
||||
using Avalonia.Logging.Serilog;
|
||||
|
||||
namespace RedBookPlayer.GUI
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
#if WindowsDebug
|
||||
AllocConsole();
|
||||
#endif
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
#if WindowsDebug
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
static extern bool AllocConsole();
|
||||
#endif
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<App>().UsePlatformDetect().LogToDebug();
|
||||
}
|
||||
}
|
||||
31
RedBookPlayer.GUI/RedBookPlayer.GUI.csproj
Normal file
@@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(RuntimeIdentifier)|$(Configuration)' == 'win-x64|Debug'">
|
||||
<DefineConstants>WindowsDebug</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="**\*.xaml.cs">
|
||||
<DependentUpon>%(Filename)</DependentUpon>
|
||||
</Compile>
|
||||
<AvaloniaResource Include="**\*.xaml" Exclude="bin\**;obj\**">
|
||||
<SubType>Designer</SubType>
|
||||
</AvaloniaResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.9.12" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.9.12" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.12" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RedBookPlayer.Common\RedBookPlayer.Common.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
193
RedBookPlayer.GUI/Settings.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Avalonia.Input;
|
||||
using RedBookPlayer.GUI;
|
||||
|
||||
namespace RedBookPlayer.GUI
|
||||
{
|
||||
public class Settings
|
||||
{
|
||||
#region Player Settings
|
||||
|
||||
/// <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 the index 0 of track 1 is treated like a hidden track
|
||||
/// </summary>
|
||||
public bool AllowSkipHiddenTrack { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if data tracks should be played like old, non-compliant players
|
||||
/// </summary>
|
||||
public bool PlayDataTracks { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Generate a TOC if the disc is missing one
|
||||
/// </summary>
|
||||
public bool GenerateMissingTOC { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the default playback volume
|
||||
/// </summary>
|
||||
public int Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if(value > 100)
|
||||
_volume = 100;
|
||||
else if(value < 0)
|
||||
_volume = 0;
|
||||
else
|
||||
_volume = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the currently selected theme
|
||||
/// </summary>
|
||||
public string SelectedTheme { get; set; } = "default";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Key Mappings
|
||||
|
||||
/// <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 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
|
||||
|
||||
/// <summary>
|
||||
/// Path to the settings file
|
||||
/// </summary>
|
||||
private string _filePath;
|
||||
|
||||
/// <summary>
|
||||
/// Internal value for the volume
|
||||
/// </summary>
|
||||
private int _volume = 100;
|
||||
|
||||
public Settings() {}
|
||||
|
||||
public Settings(string filePath) => _filePath = filePath;
|
||||
|
||||
/// <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 Settings Load(string filePath)
|
||||
{
|
||||
if(File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Settings settings = JsonSerializer.Deserialize<Settings>(File.ReadAllText(filePath));
|
||||
settings._filePath = filePath;
|
||||
|
||||
MainWindow.ApplyTheme(settings.SelectedTheme);
|
||||
|
||||
return settings;
|
||||
}
|
||||
catch(JsonException)
|
||||
{
|
||||
Console.WriteLine("Couldn't parse settings, reverting to default");
|
||||
|
||||
return new Settings(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return new Settings(filePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save settings to a file
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
string json = JsonSerializer.Serialize(this, options);
|
||||
File.WriteAllText(_filePath, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
122
RedBookPlayer.GUI/SettingsWindow.xaml
Normal file
@@ -0,0 +1,122 @@
|
||||
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800"
|
||||
d:DesignHeight="450" x:Class="RedBookPlayer.GUI.SettingsWindow" Title="Settings" SizeToContent="WidthAndHeight">
|
||||
<StackPanel>
|
||||
<TabControl>
|
||||
<TabItem Header="UI Settings">
|
||||
<DockPanel Margin="16">
|
||||
<TextBlock DockPanel.Dock="Top" Margin="0,0,0,4">Themes</TextBlock>
|
||||
<StackPanel DockPanel.Dock="Bottom">
|
||||
<WrapPanel Margin="0,0,0,16">
|
||||
<CheckBox IsChecked="{Binding AutoPlay}" Margin="0,0,8,0" />
|
||||
<TextBlock VerticalAlignment="Center">Auto-play CD on load</TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel Margin="0,0,0,16">
|
||||
<CheckBox IsChecked="{Binding IndexButtonChangeTrack}" Margin="0,0,8,0" />
|
||||
<TextBlock VerticalAlignment="Center">Index navigation can change track</TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel Margin="0,0,0,16">
|
||||
<CheckBox IsChecked="{Binding AllowSkipHiddenTrack}" Margin="0,0,8,0" />
|
||||
<TextBlock VerticalAlignment="Center">Treat index 0 of track 1 as track 0 (hidden track)</TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel Margin="0,0,0,16">
|
||||
<CheckBox IsChecked="{Binding PlayDataTracks}" Margin="0,0,8,0"/>
|
||||
<TextBlock VerticalAlignment="Center">Play data tracks like old, non-compliant players</TextBlock>
|
||||
</WrapPanel>
|
||||
<WrapPanel Margin="0,0,0,16">
|
||||
<CheckBox IsChecked="{Binding GenerateMissingTOC}" Margin="0,0,8,0"/>
|
||||
<TextBlock VerticalAlignment="Center">Generate a TOC if the disc is missing one</TextBlock>
|
||||
</WrapPanel>
|
||||
<DockPanel Margin="0,0,0,16">
|
||||
<TextBlock VerticalAlignment="Center" Margin="0,0,8,0" DockPanel.Dock="Left">Default Volume</TextBlock>
|
||||
<TextBlock VerticalAlignment="Center" DockPanel.Dock="Right" Text="%" />
|
||||
<TextBlock VerticalAlignment="Center" Margin="8,0,0,0" DockPanel.Dock="Right" Text="{Binding Volume}"
|
||||
Name="VolumeLabel" />
|
||||
<Slider Minimum="0" Maximum="100" SmallChange="1" LargeChange="10" Value="{Binding Volume}"
|
||||
Name="VolumeSlider" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
<ListBox Name="ThemeList" SelectionMode="Single" Margin="0,0,0,16" />
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
<TabItem Header="Keyboard Bindings">
|
||||
<Grid Margin="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Load Image-->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Width="120">Load Image</TextBlock>
|
||||
<ComboBox Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Name="LoadImageKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Toggle Play/Pause -->
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Width="120">Toggle Play/Pause</TextBlock>
|
||||
<ComboBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Right" Name="TogglePlaybackKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Stop Playback-->
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Width="120">Stop Playback</TextBlock>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Name="StopPlaybackKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Next Track -->
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Width="120">Next Track</TextBlock>
|
||||
<ComboBox Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right" Name="NextTrackKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Previous Track -->
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Width="120">Previous Track</TextBlock>
|
||||
<ComboBox Grid.Row="4" Grid.Column="1" HorizontalAlignment="Right" Name="PreviousTrackKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Next Index -->
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Width="120">Next Index</TextBlock>
|
||||
<ComboBox Grid.Row="5" Grid.Column="1" HorizontalAlignment="Right" Name="NextIndexKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Previous Index -->
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Width="120">Previous Index</TextBlock>
|
||||
<ComboBox Grid.Row="6" Grid.Column="1" HorizontalAlignment="Right" Name="PreviousIndexKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Fast Forward -->
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Width="120">Fast-Forward</TextBlock>
|
||||
<ComboBox Grid.Row="7" Grid.Column="1" HorizontalAlignment="Right" Name="FastForwardPlaybackKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Rewind -->
|
||||
<TextBlock Grid.Row="8" Grid.Column="0" Width="120">Rewind</TextBlock>
|
||||
<ComboBox Grid.Row="8" Grid.Column="1" HorizontalAlignment="Right" Name="RewindPlaybackKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Volume Up -->
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Width="120">Volume Up</TextBlock>
|
||||
<ComboBox Grid.Row="9" Grid.Column="1" HorizontalAlignment="Right" Name="VolumeUpKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Volume Down -->
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Width="120">Volume Down</TextBlock>
|
||||
<ComboBox Grid.Row="10" Grid.Column="1" HorizontalAlignment="Right" Name="VolumeDownKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- Mute Toggle -->
|
||||
<TextBlock Grid.Row="11" Grid.Column="0" Width="120">Toggle Mute</TextBlock>
|
||||
<ComboBox Grid.Row="11" Grid.Column="1" HorizontalAlignment="Right" Name="ToggleMuteKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
|
||||
<!-- De-Emphasis Toggle -->
|
||||
<TextBlock Grid.Row="12" Grid.Column="0" Width="120">Toggle De-Emphasis</TextBlock>
|
||||
<ComboBox Grid.Row="12" Grid.Column="1" HorizontalAlignment="Right" Name="ToggleDeEmphasisKeyBind" Margin="8,0,0,0" Width="120"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
<Button Name="ApplyButton">Apply</Button>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
186
RedBookPlayer.GUI/SettingsWindow.xaml.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace RedBookPlayer.GUI
|
||||
{
|
||||
public class SettingsWindow : Window
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private string _selectedTheme;
|
||||
private ListBox _themeList;
|
||||
|
||||
public SettingsWindow() {}
|
||||
|
||||
public SettingsWindow(Settings settings)
|
||||
{
|
||||
DataContext = _settings = settings;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void ThemeList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (e.AddedItems.Count == 0)
|
||||
return;
|
||||
|
||||
_selectedTheme = (string)e.AddedItems[0];
|
||||
}
|
||||
|
||||
public void ApplySettings(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_selectedTheme))
|
||||
{
|
||||
_settings.SelectedTheme = _selectedTheme;
|
||||
MainWindow.ApplyTheme(_selectedTheme);
|
||||
}
|
||||
|
||||
SaveKeyboardList();
|
||||
_settings.Save();
|
||||
}
|
||||
|
||||
public void UpdateView() => this.FindControl<TextBlock>("VolumeLabel").Text = _settings.Volume.ToString();
|
||||
|
||||
void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
PopulateThemes();
|
||||
PopulateKeyboardList();
|
||||
|
||||
this.FindControl<Button>("ApplyButton").Click += ApplySettings;
|
||||
this.FindControl<Slider>("VolumeSlider").PropertyChanged += (s, e) => UpdateView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate the list of themes
|
||||
/// </summary>
|
||||
private void PopulateThemes()
|
||||
{
|
||||
// Get a reference to the theme list
|
||||
_themeList = this.FindControl<ListBox>("ThemeList");
|
||||
_themeList.SelectionChanged += ThemeList_SelectionChanged;
|
||||
|
||||
// Create a list of all found themes
|
||||
List<string> items = new List<string>();
|
||||
items.Add("default");
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
_themeList.Items = items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate all of the keyboard bindings
|
||||
/// </summary>
|
||||
private void PopulateKeyboardList()
|
||||
{
|
||||
// Access all of the combo boxes
|
||||
ComboBox loadImageKeyBind = this.FindControl<ComboBox>("LoadImageKeyBind");
|
||||
ComboBox togglePlaybackKeyBind = this.FindControl<ComboBox>("TogglePlaybackKeyBind");
|
||||
ComboBox stopPlaybackKeyBind = this.FindControl<ComboBox>("StopPlaybackKeyBind");
|
||||
ComboBox nextTrackKeyBind = this.FindControl<ComboBox>("NextTrackKeyBind");
|
||||
ComboBox previousTrackKeyBind = this.FindControl<ComboBox>("PreviousTrackKeyBind");
|
||||
ComboBox nextIndexKeyBind = this.FindControl<ComboBox>("NextIndexKeyBind");
|
||||
ComboBox previousIndexKeyBind = this.FindControl<ComboBox>("PreviousIndexKeyBind");
|
||||
ComboBox fastForwardPlaybackKeyBind = this.FindControl<ComboBox>("FastForwardPlaybackKeyBind");
|
||||
ComboBox rewindPlaybackKeyBind = this.FindControl<ComboBox>("RewindPlaybackKeyBind");
|
||||
ComboBox volumeUpKeyBind = this.FindControl<ComboBox>("VolumeUpKeyBind");
|
||||
ComboBox volumeDownKeyBind = this.FindControl<ComboBox>("VolumeDownKeyBind");
|
||||
ComboBox toggleMuteKeyBind = this.FindControl<ComboBox>("ToggleMuteKeyBind");
|
||||
ComboBox toggleDeEmphasisKeyBind = this.FindControl<ComboBox>("ToggleDeEmphasisKeyBind");
|
||||
|
||||
// Assign the list of values to all of them
|
||||
Array keyboardList = GenerateKeyboardList();
|
||||
loadImageKeyBind.Items = keyboardList;
|
||||
togglePlaybackKeyBind.Items = keyboardList;
|
||||
stopPlaybackKeyBind.Items = keyboardList;
|
||||
nextTrackKeyBind.Items = keyboardList;
|
||||
previousTrackKeyBind.Items = keyboardList;
|
||||
nextIndexKeyBind.Items = keyboardList;
|
||||
previousIndexKeyBind.Items = keyboardList;
|
||||
fastForwardPlaybackKeyBind.Items = keyboardList;
|
||||
rewindPlaybackKeyBind.Items = keyboardList;
|
||||
volumeUpKeyBind.Items = keyboardList;
|
||||
volumeDownKeyBind.Items = keyboardList;
|
||||
toggleMuteKeyBind.Items = keyboardList;
|
||||
toggleDeEmphasisKeyBind.Items = keyboardList;
|
||||
|
||||
// Set all of the currently selected items
|
||||
loadImageKeyBind.SelectedItem = _settings.LoadImageKey;
|
||||
togglePlaybackKeyBind.SelectedItem = _settings.TogglePlaybackKey;
|
||||
stopPlaybackKeyBind.SelectedItem = _settings.StopPlaybackKey;
|
||||
nextTrackKeyBind.SelectedItem = _settings.NextTrackKey;
|
||||
previousTrackKeyBind.SelectedItem = _settings.PreviousTrackKey;
|
||||
nextIndexKeyBind.SelectedItem = _settings.NextIndexKey;
|
||||
previousIndexKeyBind.SelectedItem = _settings.PreviousIndexKey;
|
||||
fastForwardPlaybackKeyBind.SelectedItem = _settings.FastForwardPlaybackKey;
|
||||
rewindPlaybackKeyBind.SelectedItem = _settings.RewindPlaybackKey;
|
||||
volumeUpKeyBind.SelectedItem = _settings.VolumeUpKey;
|
||||
volumeDownKeyBind.SelectedItem = _settings.VolumeDownKey;
|
||||
toggleMuteKeyBind.SelectedItem = _settings.ToggleMuteKey;
|
||||
toggleDeEmphasisKeyBind.SelectedItem = _settings.ToggleDeEmphasisKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save back all values from keyboard bindings
|
||||
/// </summary>
|
||||
private void SaveKeyboardList()
|
||||
{
|
||||
// Access all of the combo boxes
|
||||
ComboBox loadImageKeyBind = this.FindControl<ComboBox>("LoadImageKeyBind");
|
||||
ComboBox togglePlaybackKeyBind = this.FindControl<ComboBox>("TogglePlaybackKeyBind");
|
||||
ComboBox stopPlaybackKeyBind = this.FindControl<ComboBox>("StopPlaybackKeyBind");
|
||||
ComboBox nextTrackKeyBind = this.FindControl<ComboBox>("NextTrackKeyBind");
|
||||
ComboBox previousTrackKeyBind = this.FindControl<ComboBox>("PreviousTrackKeyBind");
|
||||
ComboBox nextIndexKeyBind = this.FindControl<ComboBox>("NextIndexKeyBind");
|
||||
ComboBox previousIndexKeyBind = this.FindControl<ComboBox>("PreviousIndexKeyBind");
|
||||
ComboBox fastForwardPlaybackKeyBind = this.FindControl<ComboBox>("FastForwardPlaybackKeyBind");
|
||||
ComboBox rewindPlaybackKeyBind = this.FindControl<ComboBox>("RewindPlaybackKeyBind");
|
||||
ComboBox volumeUpKeyBind = this.FindControl<ComboBox>("VolumeUpKeyBind");
|
||||
ComboBox volumeDownKeyBind = this.FindControl<ComboBox>("VolumeDownKeyBind");
|
||||
ComboBox toggleMuteKeyBind = this.FindControl<ComboBox>("ToggleMuteKeyBind");
|
||||
ComboBox toggleDeEmphasisKeyBind = this.FindControl<ComboBox>("ToggleDeEmphasisKeyBind");
|
||||
|
||||
// Set all of the currently selected items
|
||||
_settings.LoadImageKey = (Key)loadImageKeyBind.SelectedItem;
|
||||
_settings.TogglePlaybackKey = (Key)togglePlaybackKeyBind.SelectedItem;
|
||||
_settings.StopPlaybackKey = (Key)stopPlaybackKeyBind.SelectedItem;
|
||||
_settings.NextTrackKey = (Key)nextTrackKeyBind.SelectedItem;
|
||||
_settings.PreviousTrackKey = (Key)previousTrackKeyBind.SelectedItem;
|
||||
_settings.NextIndexKey = (Key)nextIndexKeyBind.SelectedItem;
|
||||
_settings.PreviousIndexKey = (Key)previousIndexKeyBind.SelectedItem;
|
||||
_settings.FastForwardPlaybackKey = (Key)fastForwardPlaybackKeyBind.SelectedItem;
|
||||
_settings.RewindPlaybackKey = (Key)rewindPlaybackKeyBind.SelectedItem;
|
||||
_settings.VolumeUpKey = (Key)volumeUpKeyBind.SelectedItem;
|
||||
_settings.VolumeDownKey = (Key)volumeDownKeyBind.SelectedItem;
|
||||
_settings.ToggleMuteKey = (Key)toggleMuteKeyBind.SelectedItem;
|
||||
_settings.ToggleDeEmphasisKey = (Key)toggleDeEmphasisKeyBind.SelectedItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a list of keyboard keys for mapping
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Array GenerateKeyboardList()
|
||||
{
|
||||
return Enum.GetValues(typeof(Key));
|
||||
}
|
||||
}
|
||||
}
|
||||
6
RedBookPlayer.GUI/nuget.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="AvaloniaCI" value="https://www.myget.org/F/avalonia-ci/api/v2" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||