diff --git a/GenshinLyreMidiPlayer.Data/GenshinLyreMidiPlayer.Data.csproj b/GenshinLyreMidiPlayer.Data/GenshinLyreMidiPlayer.Data.csproj index 8c1c738..0c34d7d 100644 --- a/GenshinLyreMidiPlayer.Data/GenshinLyreMidiPlayer.Data.csproj +++ b/GenshinLyreMidiPlayer.Data/GenshinLyreMidiPlayer.Data.csproj @@ -1,32 +1,32 @@  - - net5.0-windows10.0.19041.0 - latest - enable - + + net5.0-windows10.0.19041.0 + latest + enable + - - - - - - - + + + + + + + - - - True - Settings.settings - True - - + + + True + Settings.settings + True + + - - - Settings.Designer.cs - PublicSettingsSingleFileGenerator - - + + + Settings.Designer.cs + PublicSettingsSingleFileGenerator + + diff --git a/GenshinLyreMidiPlayer.Data/Notification/PlayTimerNotification.cs b/GenshinLyreMidiPlayer.Data/Notification/PlayTimerNotification.cs index 865d387..c68113b 100644 --- a/GenshinLyreMidiPlayer.Data/Notification/PlayTimerNotification.cs +++ b/GenshinLyreMidiPlayer.Data/Notification/PlayTimerNotification.cs @@ -1,6 +1,4 @@ namespace GenshinLyreMidiPlayer.Data.Notification { - public class PlayTimerNotification - { - } + public class PlayTimerNotification { } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.Data/Properties/Settings.cs b/GenshinLyreMidiPlayer.Data/Properties/Settings.cs index 18365a3..7e62c5f 100644 --- a/GenshinLyreMidiPlayer.Data/Properties/Settings.cs +++ b/GenshinLyreMidiPlayer.Data/Properties/Settings.cs @@ -10,6 +10,8 @@ namespace GenshinLyreMidiPlayer.Data.Properties // The SettingsSaving event is raised before the setting values are saved. public sealed partial class Settings { + protected override void OnPropertyChanged(object sender, PropertyChangedEventArgs e) => Save(); + protected override void OnSettingsLoaded(object sender, SettingsLoadedEventArgs e) { if (Default.UpgradeRequired) @@ -19,7 +21,5 @@ protected override void OnSettingsLoaded(object sender, SettingsLoadedEventArgs Default.Save(); } } - - protected override void OnPropertyChanged(object sender, PropertyChangedEventArgs e) => Save(); } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.Data/Properties/Settings.settings b/GenshinLyreMidiPlayer.Data/Properties/Settings.settings index 873fcfb..16f9919 100644 --- a/GenshinLyreMidiPlayer.Data/Properties/Settings.settings +++ b/GenshinLyreMidiPlayer.Data/Properties/Settings.settings @@ -1,62 +1,67 @@  - - - - - <?xml version="1.0" encoding="utf-16"?> -<SerializableConnectionString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> - <ConnectionString>history.db</ConnectionString> - <ProviderName /> -</SerializableConnectionString> - history.db - - - https://github.com/sabihoshi/GenshinLyreMidiPlayer/blob/main/LICENSE.md - - - https://github.com/sabihoshi/GenshinLyreMidiPlayer/blob/main/THIRD-PARTY-NOTICES.md - - - True - - - True - - - False - - - True - - - False - - - False - - - 0 - - - -1 - - - 0 - - - 3 - - - 0 - - - 0 - - - True - - - GenshinImpact.exe - - + + + + + <?xml version="1.0" encoding="utf-16"?> + <SerializableConnectionString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <ConnectionString>history.db</ConnectionString> + <ProviderName /> + </SerializableConnectionString> + + history.db + + + https://github.com/sabihoshi/GenshinLyreMidiPlayer/blob/main/LICENSE.md + + + + https://github.com/sabihoshi/GenshinLyreMidiPlayer/blob/main/THIRD-PARTY-NOTICES.md + + + + True + + + True + + + False + + + True + + + False + + + False + + + 0 + + + -1 + + + 0 + + + 3 + + + 0 + + + 0 + + + True + + + GenshinImpact.exe + + \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/Core/LyrePlayer.cs b/GenshinLyreMidiPlayer.WPF/Core/LyrePlayer.cs index 222053e..6bbaf1b 100644 --- a/GenshinLyreMidiPlayer.WPF/Core/LyrePlayer.cs +++ b/GenshinLyreMidiPlayer.WPF/Core/LyrePlayer.cs @@ -45,6 +45,20 @@ public enum Transpose 83 // B5 }; + public static bool TryGetKey(this Layout layout, int noteId, out VirtualKeyCode key) + { + var keys = GetLayout(layout); + return TryGetKey(keys, noteId, out key); + } + + public static bool TryGetKey(this IEnumerable keys, int noteId, out VirtualKeyCode key) + { + var keyIndex = LyreNotes.IndexOf(noteId); + key = keys.ElementAtOrDefault(keyIndex); + + return keyIndex != -1; + } + public static int TransposeNote(int noteId, Transpose direction = Transpose.Ignore) { @@ -69,9 +83,11 @@ public static int TransposeNote(int noteId, } } - public static void PlayNote(int noteId, Layout selectedLayout) + public static void InteractNote(int noteId, Layout selectedLayout, + Func action) { - InteractNote(noteId, selectedLayout, Input.Keyboard.KeyPress); + if (selectedLayout.TryGetKey(noteId, out var key)) + action.Invoke(key); } public static void NoteDown(int noteId, Layout selectedLayout) @@ -84,25 +100,9 @@ public static void NoteUp(int noteId, Layout selectedLayout) InteractNote(noteId, selectedLayout, Input.Keyboard.KeyUp); } - public static void InteractNote(int noteId, Layout selectedLayout, - Func action) - { - if (selectedLayout.TryGetKey(noteId, out var key)) - action.Invoke(key); - } - - public static bool TryGetKey(this Layout layout, int noteId, out VirtualKeyCode key) - { - var keys = GetLayout(layout); - return TryGetKey(keys, noteId, out key); - } - - public static bool TryGetKey(this IEnumerable keys, int noteId, out VirtualKeyCode key) + public static void PlayNote(int noteId, Layout selectedLayout) { - var keyIndex = LyreNotes.IndexOf(noteId); - key = keys.ElementAtOrDefault(keyIndex); - - return keyIndex != -1; + InteractNote(noteId, selectedLayout, Input.Keyboard.KeyPress); } } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/Core/WindowHelper.cs b/GenshinLyreMidiPlayer.WPF/Core/WindowHelper.cs index 68c9b63..0ba4a4c 100644 --- a/GenshinLyreMidiPlayer.WPF/Core/WindowHelper.cs +++ b/GenshinLyreMidiPlayer.WPF/Core/WindowHelper.cs @@ -17,18 +17,6 @@ public static class WindowHelper private static string GenshinProcessName => Path.GetFileNameWithoutExtension(Settings.Default.GenshinLocation)!; - private static IntPtr? FindWindowByProcessName(string processName) - { - var process = Process.GetProcessesByName(processName); - return process.FirstOrDefault(p => p.MainWindowHandle != IntPtr.Zero)?.MainWindowHandle; - } - - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] - private static extern IntPtr GetForegroundWindow(); - - [DllImport("user32.dll")] - private static extern void SwitchToThisWindow(IntPtr hWnd, bool fUnknown); - public static bool EnsureGameOnTop() { var genshinWindow = FindWindowByProcessName(GenshinProcessName); @@ -41,17 +29,29 @@ public static bool EnsureGameOnTop() return GetForegroundWindow().Equals(genshinWindow); } + public static bool IsGameFocused() + { + var genshinWindow = FindWindowByProcessName(GenshinProcessName); + return genshinWindow != null && + IsWindowFocused((IntPtr) genshinWindow); + } + private static bool IsWindowFocused(IntPtr windowPtr) { var hWnd = GetForegroundWindow(); return hWnd.Equals(windowPtr); } - public static bool IsGameFocused() + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + private static extern IntPtr GetForegroundWindow(); + + private static IntPtr? FindWindowByProcessName(string processName) { - var genshinWindow = FindWindowByProcessName(GenshinProcessName); - return genshinWindow != null && - IsWindowFocused((IntPtr) genshinWindow); + var process = Process.GetProcessesByName(processName); + return process.FirstOrDefault(p => p.MainWindowHandle != IntPtr.Zero)?.MainWindowHandle; } + + [DllImport("user32.dll")] + private static extern void SwitchToThisWindow(IntPtr hWnd, bool fUnknown); } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/FodyWeavers.xml b/GenshinLyreMidiPlayer.WPF/FodyWeavers.xml index d5abfed..22d54c1 100644 --- a/GenshinLyreMidiPlayer.WPF/FodyWeavers.xml +++ b/GenshinLyreMidiPlayer.WPF/FodyWeavers.xml @@ -1,3 +1,3 @@  - + \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj b/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj index 82849e0..2d08abe 100644 --- a/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj +++ b/GenshinLyreMidiPlayer.WPF/GenshinLyreMidiPlayer.WPF.csproj @@ -1,49 +1,49 @@ - - WinExe - net5.0-windows10.0.19041.0 - true - GenshinLyreMidiPlayer.WPF.App - app.manifest - 2.3.3 - item_windsong_lyre.ico - enable - https://github.com/sabihoshi/GenshinLyreMidiPlayer - sabihoshi - Genshin Lyre MIDI Player - - sabihoshi - A music player that plays MIDI files into Genshin Impact's Windsong Lyre. - latest - LICENSE.md - true - + + WinExe + net5.0-windows10.0.19041.0 + true + GenshinLyreMidiPlayer.WPF.App + app.manifest + 2.3.3 + item_windsong_lyre.ico + enable + https://github.com/sabihoshi/GenshinLyreMidiPlayer + sabihoshi + Genshin Lyre MIDI Player + + sabihoshi + A music player that plays MIDI files into Genshin Impact's Windsong Lyre. + latest + LICENSE.md + true + - - - - - - - - - + + + + + + + + + - - - True - - - + + + True + + + - - - + + + - - - - + + + + diff --git a/GenshinLyreMidiPlayer.WPF/ModernWPF/Animation/Animation.cs b/GenshinLyreMidiPlayer.WPF/ModernWPF/Animation/Animation.cs index 707fd64..dfd67b1 100644 --- a/GenshinLyreMidiPlayer.WPF/ModernWPF/Animation/Animation.cs +++ b/GenshinLyreMidiPlayer.WPF/ModernWPF/Animation/Animation.cs @@ -42,13 +42,6 @@ public void Stop() _element.InvalidateProperty(UIElement.RenderTransformOriginProperty); } - private void OnCurrentStateInvalidated(object? sender, EventArgs e) - { - if (sender is Clock clock) _currentState = clock.CurrentState; - } - - private void OnCompleted(object? sender, EventArgs e) { Completed?.Invoke(this, EventArgs.Empty); } - private BitmapCache GetBitmapCache() { #if NETCOREAPP || NET462 @@ -57,5 +50,12 @@ private BitmapCache GetBitmapCache() return _defaultBitmapCache; #endif } + + private void OnCompleted(object? sender, EventArgs e) { Completed?.Invoke(this, EventArgs.Empty); } + + private void OnCurrentStateInvalidated(object? sender, EventArgs e) + { + if (sender is Clock clock) _currentState = clock.CurrentState; + } } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/ModernWPF/Animation/Transition.cs b/GenshinLyreMidiPlayer.WPF/ModernWPF/Animation/Transition.cs index 8de268b..516c678 100644 --- a/GenshinLyreMidiPlayer.WPF/ModernWPF/Animation/Transition.cs +++ b/GenshinLyreMidiPlayer.WPF/ModernWPF/Animation/Transition.cs @@ -39,13 +39,6 @@ static Transition() DecelerateKeySpline.Freeze(); } - //protected virtual string GetNavigationStateCore(); - //protected virtual void SetNavigationStateCore(string navigationState); - - protected abstract Animation? GetEnterAnimation(FrameworkElement element, bool movingBackwards); - - protected abstract Animation? GetExitAnimation(FrameworkElement element, bool movingBackwards); - public Animation? GetEnterAnimation(object element, bool movingBackwards) => GetEnterAnimation( (FrameworkElement) element, movingBackwards); @@ -53,5 +46,12 @@ static Transition() public Animation? GetExitAnimation(object element, bool movingBackwards) => GetExitAnimation( (FrameworkElement) element, movingBackwards); + + //protected virtual string GetNavigationStateCore(); + //protected virtual void SetNavigationStateCore(string navigationState); + + protected abstract Animation? GetEnterAnimation(FrameworkElement element, bool movingBackwards); + + protected abstract Animation? GetExitAnimation(FrameworkElement element, bool movingBackwards); } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/ModernWPF/CaptionedObject.cs b/GenshinLyreMidiPlayer.WPF/ModernWPF/CaptionedObject.cs index 691c8c0..787a2ca 100644 --- a/GenshinLyreMidiPlayer.WPF/ModernWPF/CaptionedObject.cs +++ b/GenshinLyreMidiPlayer.WPF/ModernWPF/CaptionedObject.cs @@ -11,10 +11,10 @@ public CaptionedObject(T o, string? caption = null) Caption = caption; } - protected string? Caption { get; } - public T Object { get; } + protected string? Caption { get; } + public override string ToString() => Caption ?? base.ToString() ?? string.Empty; } diff --git a/GenshinLyreMidiPlayer.WPF/ViewModels/LyrePlayerViewModel.cs b/GenshinLyreMidiPlayer.WPF/ViewModels/LyrePlayerViewModel.cs index 30319b9..eb1c517 100644 --- a/GenshinLyreMidiPlayer.WPF/ViewModels/LyrePlayerViewModel.cs +++ b/GenshinLyreMidiPlayer.WPF/ViewModels/LyrePlayerViewModel.cs @@ -30,9 +30,9 @@ public class LyrePlayerViewModel : Screen, private static readonly Settings Settings = Settings.Default; private readonly IEventAggregator _events; private readonly MediaPlayer? _player; - private readonly SettingsPageViewModel _settings; private readonly OutputDevice? _speakers; private readonly PlaybackCurrentTimeWatcher _timeWatcher; + private readonly SettingsPageViewModel _settings; private bool _ignoreSliderChange; private InputDevice? _inputDevice; private TimeSpan _songPosition; @@ -122,26 +122,26 @@ public double SongPosition public MidiInput? SelectedMidiInput { get; set; } - private MusicDisplayProperties? Display => - _player?.SystemMediaTransportControls.DisplayUpdater.MusicProperties; - public Playback? Playback { get; private set; } public PlaylistViewModel Playlist { get; } + public string PlayPauseIcon => Playback?.IsRunning ?? false ? PauseIcon : PlayIcon; + + public TimeSpan CurrentTime => _songPosition; + + public TimeSpan MaximumTime => Playlist.OpenedFile?.Duration ?? TimeSpan.Zero; + + private MusicDisplayProperties? Display => + _player?.SystemMediaTransportControls.DisplayUpdater.MusicProperties; + private static string PauseIcon => "\xEDB4"; private static string PlayIcon => "\xF5B0"; - public string PlayPauseIcon => Playback?.IsRunning ?? false ? PauseIcon : PlayIcon; - private SystemMediaTransportControls? Controls => _player?.SystemMediaTransportControls; - public TimeSpan CurrentTime => _songPosition; - - public TimeSpan MaximumTime => Playlist.OpenedFile?.Duration ?? TimeSpan.Zero; - public async void Handle(MergeNotesNotification message) { if (!message.Merge) @@ -172,6 +172,93 @@ public async void Handle(PlayTimerNotification message) public async void Handle(SettingsPageViewModel message) { await InitializePlayback(); } + public async Task OpenFile() + { + await Playlist.OpenFile(); + UpdateButtons(); + } + + public async Task PlayPause() + { + if (Playback is null) + await InitializePlayback(); + + if (Playback!.IsRunning) + Playback.Stop(); + else + { + var time = new MetricTimeSpan(CurrentTime); + Playback.PlaybackStart = time; + Playback.MoveToTime(time); + + if (Settings.UseSpeakers) + Playback.Start(); + else + { + WindowHelper.EnsureGameOnTop(); + await Task.Delay(100); + + if (WindowHelper.IsGameFocused()) + { + Playback.PlaybackStart = Playback.GetCurrentTime(TimeSpanType.Midi); + Playback.Start(); + } + } + } + } + + public void CloseFile() + { + if (Playback != null) + { + _timeWatcher.RemovePlayback(Playback); + + Playback.Stop(); + Playback.Dispose(); + } + + MidiTracks.Clear(); + MoveSlider(TimeSpan.Zero); + + Playback = null; + Playlist.OpenedFile = null; + } + + public async void Next() + { + var next = Playlist.Next(); + if (next is null) + { + if (Playback is not null) + { + Playback.PlaybackStart = null; + Playback.MoveToStart(); + } + + MoveSlider(TimeSpan.Zero); + return; + } + + Handle(next); + + if (Playback is not null) + await PlayPause(); + } + + public void OnSelectedMidiInputChanged() + { + _inputDevice?.Dispose(); + + if (SelectedMidiInput?.DeviceName is not null + && SelectedMidiInput.DeviceName != "None") + { + _inputDevice = InputDevice.GetByName(SelectedMidiInput.DeviceName); + + _inputDevice!.EventReceived += OnNoteEvent; + _inputDevice!.StartEventsListening(); + } + } + public void OnSongPositionChanged() { NotifyOfPropertyChange(() => CurrentTime); @@ -191,18 +278,42 @@ public void OnSongPositionChanged() _ignoreSliderChange = false; } - public void OnSelectedMidiInputChanged() + public void OnSongTick(object? sender, PlaybackCurrentTimeChangedEventArgs e) { - _inputDevice?.Dispose(); + foreach (var playbackTime in e.Times) + { + TimeSpan time = (MetricTimeSpan) playbackTime.Time; + MoveSlider(time); - if (SelectedMidiInput?.DeviceName is not null - && SelectedMidiInput.DeviceName != "None") + UpdateButtons(); + } + } + + public void Previous() + { + if (CurrentTime > TimeSpan.FromSeconds(3)) { - _inputDevice = InputDevice.GetByName(SelectedMidiInput.DeviceName); + Playback?.Stop(); + Playback?.MoveToStart(); - _inputDevice!.EventReceived += OnNoteEvent; - _inputDevice!.StartEventsListening(); + MoveSlider(TimeSpan.Zero); + Playback?.Start(); + } + else + Playlist.Previous(); + } + + public void RefreshDevices() + { + MidiInputs.Clear(); + MidiInputs.Add(new("None")); + + foreach (var device in InputDevice.GetAll()) + { + MidiInputs.Add(new(device.Name)); } + + SelectedMidiInput = MidiInputs[0]; } public void UpdateButtons() @@ -242,38 +353,14 @@ public void UpdateButtons() } } - private void InitializeTracks() - { - if (Playlist.OpenedFile?.Midi is null) - return; - - MidiTracks.Clear(); - MidiTracks.AddRange(Playlist.OpenedFile - .Midi.GetTrackChunks() - .Select(t => new MidiTrack(_events, t))); - } - - public async Task OpenFile() - { - await Playlist.OpenFile(); - UpdateButtons(); - } - - public void CloseFile() + private int ApplyNoteSettings(int noteId) { - if (Playback != null) - { - _timeWatcher.RemovePlayback(Playback); - - Playback.Stop(); - Playback.Dispose(); - } + noteId -= Settings.KeyOffset; - MidiTracks.Clear(); - MoveSlider(TimeSpan.Zero); + if (Settings.TransposeNotes) + noteId = LyrePlayer.TransposeNote(noteId, _settings.Transpose ?? Ignore); - Playback = null; - Playlist.OpenedFile = null; + return noteId; } private async Task InitializePlayback() @@ -352,89 +439,21 @@ await Application.Current.Dispatcher.Invoke(async () => UpdateButtons(); } - private int ApplyNoteSettings(int noteId) - { - noteId -= Settings.KeyOffset; - - if (Settings.TransposeNotes) - noteId = LyrePlayer.TransposeNote(noteId, _settings.Transpose ?? Ignore); - - return noteId; - } - - public void Previous() - { - if (CurrentTime > TimeSpan.FromSeconds(3)) - { - Playback?.Stop(); - Playback?.MoveToStart(); - - MoveSlider(TimeSpan.Zero); - Playback?.Start(); - } - else - Playlist.Previous(); - } - - public async void Next() + private void InitializeTracks() { - var next = Playlist.Next(); - if (next is null) - { - if (Playback is not null) - { - Playback.PlaybackStart = null; - Playback.MoveToStart(); - } - - MoveSlider(TimeSpan.Zero); + if (Playlist.OpenedFile?.Midi is null) return; - } - - Handle(next); - - if (Playback is not null) - await PlayPause(); - } - - public async Task PlayPause() - { - if (Playback is null) - await InitializePlayback(); - - if (Playback!.IsRunning) - Playback.Stop(); - else - { - var time = new MetricTimeSpan(CurrentTime); - Playback.PlaybackStart = time; - Playback.MoveToTime(time); - - if (Settings.UseSpeakers) - Playback.Start(); - else - { - WindowHelper.EnsureGameOnTop(); - await Task.Delay(100); - if (WindowHelper.IsGameFocused()) - { - Playback.PlaybackStart = Playback.GetCurrentTime(TimeSpanType.Midi); - Playback.Start(); - } - } - } + MidiTracks.Clear(); + MidiTracks.AddRange(Playlist.OpenedFile + .Midi.GetTrackChunks() + .Select(t => new MidiTrack(_events, t))); } - public void OnSongTick(object? sender, PlaybackCurrentTimeChangedEventArgs e) + private void MoveSlider(TimeSpan value) { - foreach (var playbackTime in e.Times) - { - TimeSpan time = (MetricTimeSpan) playbackTime.Time; - MoveSlider(time); - - UpdateButtons(); - } + _ignoreSliderChange = true; + SongPosition = value.TotalSeconds; } private void OnNoteEvent(object? sender, MidiEventPlayedEventArgs e) @@ -485,24 +504,5 @@ private void PlayNote(NoteEvent noteEvent) break; } } - - private void MoveSlider(TimeSpan value) - { - _ignoreSliderChange = true; - SongPosition = value.TotalSeconds; - } - - public void RefreshDevices() - { - MidiInputs.Clear(); - MidiInputs.Add(new("None")); - - foreach (var device in InputDevice.GetAll()) - { - MidiInputs.Add(new(device.Name)); - } - - SelectedMidiInput = MidiInputs[0]; - } } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/ViewModels/MainWindowViewModel.cs b/GenshinLyreMidiPlayer.WPF/ViewModels/MainWindowViewModel.cs index 30ebbc2..7d73841 100644 --- a/GenshinLyreMidiPlayer.WPF/ViewModels/MainWindowViewModel.cs +++ b/GenshinLyreMidiPlayer.WPF/ViewModels/MainWindowViewModel.cs @@ -10,8 +10,8 @@ namespace GenshinLyreMidiPlayer.WPF.ViewModels { public class MainWindowViewModel : Conductor.StackNavigation { - private readonly Stack _history = new(); private readonly IContainer _ioc; + private readonly Stack _history = new(); private NavigationView _navView; public MainWindowViewModel(IContainer ioc, IEventAggregator events) @@ -74,13 +74,6 @@ private void AutoSuggestBoxOnTextChanged(AutoSuggestBox sender, AutoSuggestBoxTe } } - private void NavigateBack(NavigationView sender, NavigationViewBackRequestedEventArgs args) - { - _history.Pop(); - sender.SelectedItem = _history.Pop(); - sender.IsBackEnabled = _history.Count > 1; - } - private void Navigate(NavigationView sender, NavigationViewSelectionChangedEventArgs args) { if (args.IsSettingsSelected) @@ -97,5 +90,12 @@ void Activate(IScreen viewModel) _history.Push((NavigationViewItem) sender.SelectedItem); } } + + private void NavigateBack(NavigationView sender, NavigationViewBackRequestedEventArgs args) + { + _history.Pop(); + sender.SelectedItem = _history.Pop(); + sender.IsBackEnabled = _history.Count > 1; + } } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/ViewModels/PianoSheetViewModel.cs b/GenshinLyreMidiPlayer.WPF/ViewModels/PianoSheetViewModel.cs index 2b8979b..326c8f0 100644 --- a/GenshinLyreMidiPlayer.WPF/ViewModels/PianoSheetViewModel.cs +++ b/GenshinLyreMidiPlayer.WPF/ViewModels/PianoSheetViewModel.cs @@ -24,6 +24,13 @@ public PianoSheetViewModel(SettingsPageViewModel settingsPage, [OnChangedMethod(nameof(Update))] public char Delimiter { get; set; } = '.'; + [OnChangedMethod(nameof(Update))] + public KeyValuePair SelectedLayout + { + get => SettingsPage.SelectedLayout; + set => SettingsPage.SelectedLayout = value; + } + public PlaylistViewModel PlaylistView { get; } public SettingsPageViewModel SettingsPage { get; } @@ -51,15 +58,6 @@ public uint Shorten set => SetAndNotify(ref _shorten, Math.Max(value, 1)); } - [OnChangedMethod(nameof(Update))] - public KeyValuePair SelectedLayout - { - get => SettingsPage.SelectedLayout; - set => SettingsPage.SelectedLayout = value; - } - - protected override void OnActivate() { Update(); } - public void Update() { if (PlaylistView.OpenedFile is null) @@ -102,5 +100,7 @@ public void Update() Result = sb.ToString(); } + + protected override void OnActivate() { Update(); } } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/ViewModels/PlaylistViewModel.cs b/GenshinLyreMidiPlayer.WPF/ViewModels/PlaylistViewModel.cs index 625ea0b..371de9f 100644 --- a/GenshinLyreMidiPlayer.WPF/ViewModels/PlaylistViewModel.cs +++ b/GenshinLyreMidiPlayer.WPF/ViewModels/PlaylistViewModel.cs @@ -26,9 +26,10 @@ public enum LoopMode All } - private readonly IEventAggregator _events; private readonly IContainer _ioc; + private readonly IEventAggregator _events; + public PlaylistViewModel(IContainer ioc, IEventAggregator events) { _ioc = ioc; @@ -39,8 +40,6 @@ public PlaylistViewModel(IContainer ioc, IEventAggregator events) ? Tracks : new(Tracks.Where(t => t.Title.Contains(FilterText, StringComparison.OrdinalIgnoreCase))); - private BindableCollection ShuffledTracks { get; set; } = new(); - public BindableCollection Tracks { get; } = new(); public bool Shuffle { get; set; } @@ -68,29 +67,9 @@ public PlaylistViewModel(IContainer ioc, IEventAggregator events) LoopMode.All => "\xE8EE" }; - public void OnFilterTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs e) - { - FilterText = sender.Text; - } - - public void ToggleShuffle() - { - Shuffle = !Shuffle; - - if (Shuffle) - ShuffledTracks = new(Tracks.OrderBy(_ => Guid.NewGuid())); - - RefreshPlaylist(); - } - - public void ToggleLoop() - { - var loopState = (int) Loop; - var loopStates = Enum.GetValues(typeof(LoopMode)).Length; + private BindableCollection ShuffledTracks { get; set; } = new(); - var newState = (loopState + 1) % loopStates; - Loop = (LoopMode) newState; - } + public BindableCollection GetPlaylist() => Shuffle ? ShuffledTracks : Tracks; public MidiFile? Next() { @@ -112,7 +91,27 @@ public void ToggleLoop() return playlist.ElementAtOrDefault(next); } - public BindableCollection GetPlaylist() => Shuffle ? ShuffledTracks : Tracks; + public async Task AddFiles(IEnumerable files) + { + foreach (var file in files) + { + await AddFile(file); + } + + ShuffledTracks = new(Tracks.OrderBy(_ => Guid.NewGuid())); + RefreshPlaylist(); + await UpdateHistory(); + + var next = Next(); + if (OpenedFile is null && Tracks.Count > 0 && next is not null) + _events.Publish(Next()); + } + + public async Task ClearPlaylist() + { + Tracks.Clear(); + await UpdateHistory(); + } public async Task OpenFile() { @@ -128,35 +127,29 @@ public async Task OpenFile() await AddFiles(openFileDialog.FileNames); } - public async Task AddFiles(IEnumerable files) + public async Task UpdateHistory() { - foreach (var file in files) - { - await AddFile(file); - } + await using var db = _ioc.Get(); + db.History.RemoveRange(db.History); + db.History.AddRange(Tracks.Select(t => new History(t.Path))); + await db.SaveChangesAsync(); + } - ShuffledTracks = new(Tracks.OrderBy(_ => Guid.NewGuid())); - RefreshPlaylist(); - await UpdateHistory(); + public void OnFileChanged(object sender, EventArgs e) + { + if (SelectedFile is not null) + _events.Publish(SelectedFile); + } - var next = Next(); - if (OpenedFile is null && Tracks.Count > 0 && next is not null) - _events.Publish(Next()); + public void OnFilterTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs e) + { + FilterText = sender.Text; } - private async Task AddFile(string fileName, ReadingSettings? settings = null) + public void Previous() { - try - { - var file = new MidiFile(fileName, settings); - Tracks.Add(file); - } - catch (Exception e) - { - settings ??= new(); - if (await ExceptionHandler.TryHandleException(e, settings)) - await AddFile(fileName, settings); - } + History.Pop(); + _events.Publish(History.Pop()); } public void RemoveTrack() @@ -168,18 +161,38 @@ public void RemoveTrack() } } - public async Task ClearPlaylist() + public void ToggleLoop() { - Tracks.Clear(); - await UpdateHistory(); + var loopState = (int) Loop; + var loopStates = Enum.GetValues(typeof(LoopMode)).Length; + + var newState = (loopState + 1) % loopStates; + Loop = (LoopMode) newState; } - public async Task UpdateHistory() + public void ToggleShuffle() { - await using var db = _ioc.Get(); - db.History.RemoveRange(db.History); - db.History.AddRange(Tracks.Select(t => new History(t.Path))); - await db.SaveChangesAsync(); + Shuffle = !Shuffle; + + if (Shuffle) + ShuffledTracks = new(Tracks.OrderBy(_ => Guid.NewGuid())); + + RefreshPlaylist(); + } + + private async Task AddFile(string fileName, ReadingSettings? settings = null) + { + try + { + var file = new MidiFile(fileName, settings); + Tracks.Add(file); + } + catch (Exception e) + { + settings ??= new(); + if (await ExceptionHandler.TryHandleException(e, settings)) + await AddFile(fileName, settings); + } } private void RefreshPlaylist() @@ -190,17 +203,5 @@ private void RefreshPlaylist() file.Position = playlist.IndexOf(file); } } - - public void OnFileChanged(object sender, EventArgs e) - { - if (SelectedFile is not null) - _events.Publish(SelectedFile); - } - - public void Previous() - { - History.Pop(); - _events.Publish(History.Pop()); - } } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/ViewModels/SettingsPageViewModel.cs b/GenshinLyreMidiPlayer.WPF/ViewModels/SettingsPageViewModel.cs index 80322b7..5bccb7b 100644 --- a/GenshinLyreMidiPlayer.WPF/ViewModels/SettingsPageViewModel.cs +++ b/GenshinLyreMidiPlayer.WPF/ViewModels/SettingsPageViewModel.cs @@ -149,6 +149,12 @@ public int KeyOffset public MidiSpeed SelectedSpeed { get; set; } = MidiSpeeds[Settings.SelectedSpeed]; + public string GenshinLocation + { + get => Settings.GenshinLocation; + set => Settings.GenshinLocation = value; + } + public string Key => $"Key: {KeyOffsets[KeyOffset]}"; public string TimerText => CanChangeTime ? "Start" : "Stop"; @@ -161,12 +167,6 @@ public int KeyOffset public static Version ProgramVersion => Assembly.GetExecutingAssembly().GetName().Version!; - public string GenshinLocation - { - get => Settings.GenshinLocation; - set => Settings.GenshinLocation = value; - } - public bool TryGetLocation() { var locations = new[] @@ -190,32 +190,30 @@ public bool TryGetLocation() return locations.Any(TrySetLocation); } - private bool TrySetLocation(string? location) + public async Task CheckForUpdate() { - if (File.Exists(location) && location is not null) - { - Settings.GenshinLocation = location; - NotifyOfPropertyChange(() => Settings.GenshinLocation); - return true; - } + if (IsCheckingUpdate) + return; - return false; - } + UpdateString = "Checking for updates..."; + IsCheckingUpdate = true; - public async Task SetLocation() - { - var openFileDialog = new OpenFileDialog + try { - Filter = "Executable|*.exe|All files (*.*)|*.*", - InitialDirectory = WindowHelper.InstallLocation is null - ? string.Empty - : Path.Combine(WindowHelper.InstallLocation, "Genshin Impact Game") - }; - - var success = openFileDialog.ShowDialog() == true; - var set = TrySetLocation(openFileDialog.FileName); - - if (!(success && set)) await LocationMissing(); + LatestVersion = await GetLatestVersion(); + UpdateString = LatestVersion.Version > ProgramVersion + ? "(Update available!)" + : string.Empty; + } + catch (Exception) + { + UpdateString = "Failed to check updates"; + } + finally + { + IsCheckingUpdate = false; + NotifyOfPropertyChange(() => NeedsUpdate); + } } public async Task LocationMissing() @@ -245,6 +243,22 @@ public async Task LocationMissing() } } + public async Task SetLocation() + { + var openFileDialog = new OpenFileDialog + { + Filter = "Executable|*.exe|All files (*.*)|*.*", + InitialDirectory = WindowHelper.InstallLocation is null + ? string.Empty + : Path.Combine(WindowHelper.InstallLocation, "Genshin Impact Game") + }; + + var success = openFileDialog.ShowDialog() == true; + var set = TrySetLocation(openFileDialog.FileName); + + if (!(success && set)) await LocationMissing(); + } + public async Task StartStopTimer() { if (PlayTimerToken is not null) @@ -265,44 +279,6 @@ await Task.Delay(start, PlayTimerToken.Token) PlayTimerToken = null; } - public void SetTimeToNow() => DateTime = DateTime.Now; - - private void OnAutoCheckUpdatesChanged() - { - if (AutoCheckUpdates) - _ = CheckForUpdate(); - } - - private void OnIncludeBetaUpdatesChanged() => _ = CheckForUpdate(); - - private void OnKeyOffsetChanged() => Settings.Modify(s => s.KeyOffset = KeyOffset); - - public async Task CheckForUpdate() - { - if (IsCheckingUpdate) - return; - - UpdateString = "Checking for updates..."; - IsCheckingUpdate = true; - - try - { - LatestVersion = await GetLatestVersion(); - UpdateString = LatestVersion.Version > ProgramVersion - ? "(Update available!)" - : string.Empty; - } - catch (Exception) - { - UpdateString = "Failed to check updates"; - } - finally - { - IsCheckingUpdate = false; - NotifyOfPropertyChange(() => NeedsUpdate); - } - } - public async Task GetLatestVersion() { var client = new HttpClient(); @@ -321,12 +297,42 @@ public async Task GetLatestVersion() .First(v => !v.Draft && !v.Prerelease || IncludeBetaUpdates); } - private void OnSelectedLayoutIndexChanged() + public void OnThemeChanged() { - var layout = (int) SelectedLayout.Key; - Settings.Modify(s => s.SelectedLayout = layout); + var theme = (int?) ThemeManager.Current.ApplicationTheme ?? -1; + Settings.Modify(s => s.AppTheme = theme); } + public void SetTimeToNow() => DateTime = DateTime.Now; + + protected override void OnActivate() + { + if (AutoCheckUpdates) + _ = CheckForUpdate(); + } + + private bool TrySetLocation(string? location) + { + if (File.Exists(location) && location is not null) + { + Settings.GenshinLocation = location; + NotifyOfPropertyChange(() => Settings.GenshinLocation); + return true; + } + + return false; + } + + private void OnAutoCheckUpdatesChanged() + { + if (AutoCheckUpdates) + _ = CheckForUpdate(); + } + + private void OnIncludeBetaUpdatesChanged() => _ = CheckForUpdate(); + + private void OnKeyOffsetChanged() => Settings.Modify(s => s.KeyOffset = KeyOffset); + private void OnMergeMillisecondsChanged() { Settings.Modify(s => s.MergeMilliseconds = MergeMilliseconds); @@ -339,18 +345,12 @@ private void OnMergeNotesChanged() _events.Publish(new MergeNotesNotification(MergeNotes)); } - private void OnSelectedSpeedChanged() => _events.Publish(this); - - public void OnThemeChanged() + private void OnSelectedLayoutIndexChanged() { - var theme = (int?) ThemeManager.Current.ApplicationTheme ?? -1; - Settings.Modify(s => s.AppTheme = theme); + var layout = (int) SelectedLayout.Key; + Settings.Modify(s => s.SelectedLayout = layout); } - protected override void OnActivate() - { - if (AutoCheckUpdates) - _ = CheckForUpdate(); - } + private void OnSelectedSpeedChanged() => _events.Publish(this); } } \ No newline at end of file diff --git a/GenshinLyreMidiPlayer.WPF/Views/LyrePlayerView.xaml b/GenshinLyreMidiPlayer.WPF/Views/LyrePlayerView.xaml index 2660a01..f0217cb 100644 --- a/GenshinLyreMidiPlayer.WPF/Views/LyrePlayerView.xaml +++ b/GenshinLyreMidiPlayer.WPF/Views/LyrePlayerView.xaml @@ -98,7 +98,8 @@