diff --git a/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs b/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs index 5ef033898..7f2d38919 100644 --- a/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs +++ b/src/TrackerCouncil.Smz3.Data/Options/GeneralOptions.cs @@ -47,10 +47,14 @@ public class GeneralOptions : INotifyPropertyChanged [Range(0.0, 1.0)] public float TrackerConfidenceSassThreshold { get; set; } = 0.92f; - public byte[] TrackerBGColor { get; set; } = { 0xFF, 0x21, 0x21, 0x21 }; + public byte[] TrackerBGColor { get; set; } = [0xFF, 0x21, 0x21, 0x21]; public bool TrackerShadows { get; set; } = true; + public byte[] TrackerSpeechBGColor { get; set; } = [0xFF, 0x48, 0x3D, 0x8B]; + + public bool TrackerSpeechEnableBounce { get; set; } = true; + [YamlIgnore] public int LaunchButton { get; set; } = (int)LaunchButtonOptions.PlayAndTrack; @@ -172,6 +176,11 @@ public string? TwitchId /// public bool DisplayMsuTrackWindow { get; set; } + /// + /// If the tracker speech window should open by default + /// + public bool DisplayTrackerSpeechWindow { get; set; } + /// /// Check for new releases on GitHub on startup /// diff --git a/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowTrackerOptions.cs b/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowTrackerOptions.cs index c5b91dd0b..274c9a5b2 100644 --- a/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowTrackerOptions.cs +++ b/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowTrackerOptions.cs @@ -18,6 +18,12 @@ public class OptionsWindowTrackerOptions [DynamicFormFieldCheckBox(checkBoxText: "Render shadows", alignment: DynamicFormAlignment.Right)] public bool TrackerShadows { get; set; } = true; + [DynamicFormFieldColorPicker(label: "Tracker speech window color:")] + public byte[] TrackerSpeechBGColor { get; set; } = [0xFF, 0x48, 0x3D, 0x8B]; + + [DynamicFormFieldCheckBox(checkBoxText: "Enable speech bounce animation", alignment: DynamicFormAlignment.Right)] + public bool TrackerSpeechEnableBounce { get; set; } = true; + [DynamicFormFieldSlider(minimumValue: 0, maximumValue:100, decimalPlaces:1, incrementAmount:.1, suffix:"%", label: "Tracker recognition threshold:", platforms: DynamicFormPlatform.Windows)] public float TrackerRecognitionThreshold { get; set; } diff --git a/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowViewModel.cs b/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowViewModel.cs index 1a8eb7686..111abdf56 100644 --- a/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowViewModel.cs +++ b/src/TrackerCouncil.Smz3.Data/ViewModels/OptionsWindowViewModel.cs @@ -34,6 +34,8 @@ public OptionsWindowViewModel(GeneralOptions options, Dictionary TrackerOptions.TrackerBGColor = options.TrackerBGColor; TrackerOptions.TrackerShadows = options.TrackerShadows; + TrackerOptions.TrackerSpeechBGColor = options.TrackerSpeechBGColor; + TrackerOptions.TrackerSpeechEnableBounce = options.TrackerSpeechEnableBounce; TrackerOptions.TrackerRecognitionThreshold = options.TrackerRecognitionThreshold * 100; TrackerOptions.TrackerConfidenceThreshold = options.TrackerConfidenceThreshold * 100; TrackerOptions.TrackerConfidenceSassThreshold = options.TrackerConfidenceSassThreshold * 100; @@ -84,6 +86,8 @@ public void UpdateOptions(GeneralOptions options) options.TrackerBGColor = TrackerOptions.TrackerBGColor; options.TrackerShadows = TrackerOptions.TrackerShadows; + options.TrackerSpeechBGColor = TrackerOptions.TrackerSpeechBGColor; + options.TrackerSpeechEnableBounce = TrackerOptions.TrackerSpeechEnableBounce; options.TrackerRecognitionThreshold = TrackerOptions.TrackerRecognitionThreshold / 100; options.TrackerConfidenceThreshold = TrackerOptions.TrackerConfidenceThreshold / 100; options.TrackerConfidenceSassThreshold = TrackerOptions.TrackerConfidenceSassThreshold / 100; diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs b/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs index 0b45e4888..4b9afbcae 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/ICommunicator.cs @@ -1,4 +1,5 @@ using System; +using System.Speech.Synthesis; namespace TrackerCouncil.Smz3.Tracking.Services; @@ -69,10 +70,20 @@ public void SlowDown() { } /// public bool IsSpeaking { get; } + /// + /// Event for when the communicator has started speaking + /// + public event EventHandler SpeakStarted; + /// /// Event for when the communicator has finished speaking /// public event EventHandler SpeakCompleted; + /// + /// Event for when the communicator has reached a new viseme + /// + public event EventHandler VisemeReached; + } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/IUIService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/IUIService.cs index 0b8421650..e2c37caa5 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/IUIService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/IUIService.cs @@ -74,4 +74,12 @@ public interface IUIService /// The base path of the desired sprite /// The full path of the sprite or null if it's not found public string? GetSpritePath(string category, string imageFileName, out string? profilePath, string? basePath = null); + + /// + /// Gets the images for tracker talking + /// + /// The selected profile + /// The base path of the folder used + /// A dictionary of all of the available tracker speech images + public Dictionary GetTrackerSpeechSprites(out string? profilePath, string? basePath = null); } diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs b/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs index 313a55d21..7a91fc73d 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/TextToSpeechCommunicator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Speech.Synthesis; using Microsoft.Extensions.Logging; using TrackerCouncil.Smz3.Data.Options; @@ -34,6 +35,7 @@ public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor, I if (IsSpeaking) return; _startSpeakingTime = DateTime.Now; IsSpeaking = true; + SpeakStarted?.Invoke(this, EventArgs.Empty); }; _tts.SpeakCompleted += (sender, args) => @@ -44,6 +46,12 @@ public TextToSpeechCommunicator(TrackerOptionsAccessor trackerOptionsAccessor, I SpeakCompleted?.Invoke(this, new SpeakCompletedEventArgs(duration)); }; + _tts.VisemeReached += (sender, args) => + { + if (!OperatingSystem.IsWindows()) return; + VisemeReached?.Invoke(this, args); + }; + _canSpeak = trackerOptionsAccessor.Options?.VoiceFrequency != Shared.Enums.TrackerVoiceFrequency.Disabled; } @@ -143,8 +151,12 @@ public void SlowDown() public bool IsSpeaking { get; private set; } + public event EventHandler? SpeakStarted; + public event EventHandler? SpeakCompleted; + public event EventHandler? VisemeReached; + /// public void Dispose() { diff --git a/src/TrackerCouncil.Smz3.Tracking/Services/UIService.cs b/src/TrackerCouncil.Smz3.Tracking/Services/UIService.cs index 3dc8d0400..5b83f6928 100644 --- a/src/TrackerCouncil.Smz3.Tracking/Services/UIService.cs +++ b/src/TrackerCouncil.Smz3.Tracking/Services/UIService.cs @@ -165,4 +165,80 @@ UIConfig uiConfig profilePath = null; return null; } + + /// + /// Gets the images for tracker talking + /// + /// The selected profile + /// The base path of the folder used + /// A dictionary of all of the available tracker speech images + public Dictionary GetTrackerSpeechSprites(out string? profilePath, string? basePath = null) + { + var toReturn = new Dictionary(); + + foreach (var idleSprite in GetCategorySprites("Tracker", out profilePath, basePath).Where(x => x.EndsWith("_idle.png"))) + { + var file = new FileInfo(idleSprite); + + var reaction = file.Name.Replace("_idle.png", "").ToLower(); + var talkSprite = idleSprite.Replace("_idle.png", "_talk.png"); + + if (File.Exists(talkSprite)) + { + toReturn.Add(reaction, new TrackerSpeechImages() + { + ReactionName = reaction, + IdleImage = idleSprite, + TalkingImage = talkSprite, + }); + } + } + + return toReturn; + } + + /// + /// Returns all of the sprites for a category + /// + /// The category of sprite + /// The path of the selected profile + /// The base path of the desired sprite + /// The full path of the sprite or null if it's not found + public List GetCategorySprites(string category, out string? profilePath, string? basePath = null) + { + var toReturn = new List(); + + if (!string.IsNullOrEmpty(basePath)) + { + var path = Path.Combine(basePath, category); + if (Directory.Exists(path)) + { + foreach (var spritePath in Directory.EnumerateFiles(path, "*.png")) + { + toReturn.Add(spritePath); + } + profilePath = basePath; + return toReturn; + } + } + else + { + foreach (var profile in _iconPaths) + { + var path = Path.Combine(profile, category); + if (Directory.Exists(path)) + { + foreach (var spritePath in Directory.EnumerateFiles(path, "*.png", new EnumerationOptions() { MatchCasing = MatchCasing.CaseInsensitive })) + { + toReturn.Add(spritePath); + } + profilePath = profile; + return toReturn; + } + } + } + + profilePath = null; + return toReturn; + } } diff --git a/src/TrackerCouncil.Smz3.Tracking/TrackerSpeechImages.cs b/src/TrackerCouncil.Smz3.Tracking/TrackerSpeechImages.cs new file mode 100644 index 000000000..ed9eba3b2 --- /dev/null +++ b/src/TrackerCouncil.Smz3.Tracking/TrackerSpeechImages.cs @@ -0,0 +1,8 @@ +namespace TrackerCouncil.Smz3.Tracking; + +public class TrackerSpeechImages +{ + public required string ReactionName { get; set; } + public required string IdleImage { get; set; } + public required string TalkingImage { get; set; } +} diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs new file mode 100644 index 000000000..d5dad3ae9 --- /dev/null +++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerSpeechWindowService.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Media; +using Avalonia.Threading; +using AvaloniaControls.ControlServices; +using TrackerCouncil.Smz3.Data.Options; +using TrackerCouncil.Smz3.Tracking; +using TrackerCouncil.Smz3.Tracking.Services; +using TrackerCouncil.Smz3.UI.ViewModels; + +namespace TrackerCouncil.Smz3.UI.Services; + +public class TrackerSpeechWindowService(ICommunicator communicator, IUIService uiService, OptionsFactory optionsFactory) : ControlService +{ + TrackerSpeechWindowViewModel _model = new(); + + private DispatcherTimer _dispatcherTimer = new() + { + Interval = TimeSpan.FromSeconds(1.0 / 60), + }; + + private TrackerSpeechImages? _currentSpeechImages; + private Dictionary _availableSpeechImages = []; + private int _tickCount; + private readonly int _maxTicks = 12; + private readonly double _bounceHeight = 6; + private int _prevViseme; + private bool _enableBounce; + + public TrackerSpeechWindowViewModel GetViewModel() + { + _availableSpeechImages = uiService.GetTrackerSpeechSprites(out _); + SetSpeechImages("default"); + + var options = optionsFactory.Create(); + var bytes = options.GeneralOptions.TrackerSpeechBGColor; + _enableBounce = options.GeneralOptions.TrackerSpeechEnableBounce; + _model.Background = new SolidColorBrush(Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3])); + + if (_currentSpeechImages == null) + { + return new TrackerSpeechWindowViewModel(); + } + + _model.TrackerImage = _currentSpeechImages.IdleImage; + + if (_enableBounce) + { + _model.AnimationMargin = new Thickness(0, 0, 0, -1 * _bounceHeight); + + _dispatcherTimer.Tick += (sender, args) => + { + _tickCount++; + var fraction = Math.Clamp(1.0 * _tickCount / _maxTicks, 0, 1); + + if (fraction < 0.5) + { + _model.AnimationMargin = new Thickness(0, 0, 0, -1 * _bounceHeight + fraction * 2 * _bounceHeight); + } + else + { + _model.AnimationMargin = new Thickness(0, 0, 0, (fraction - 0.5) * 2 * -1 * _bounceHeight); + } + + if (fraction >= 1) + { + _dispatcherTimer.Stop(); + } + }; + } + + SaveOpenStatus(true); + + communicator.SpeakCompleted += Communicator_SpeakCompleted; + communicator.VisemeReached += Communicator_VisemeReached; + return _model; + } + + public void StopTimer() + { + _dispatcherTimer.Stop(); + } + + public void SaveOpenStatus(bool isOpen) + { + var options = optionsFactory.Create(); + if (options.GeneralOptions.DisplayTrackerSpeechWindow == isOpen) + { + return; + } + options.GeneralOptions.DisplayTrackerSpeechWindow = isOpen; + options.Save(); + } + + private void Communicator_VisemeReached(object? sender, System.Speech.Synthesis.VisemeReachedEventArgs e) + { + if (!OperatingSystem.IsWindows()) return; + + if (e.Viseme == 0) + { + _model.TrackerImage = _currentSpeechImages?.IdleImage; + } + else + { + if (_enableBounce && _prevViseme == 0 && !_dispatcherTimer.IsEnabled) + { + _tickCount = 0; + _dispatcherTimer.Start(); + } + _model.TrackerImage = _currentSpeechImages?.TalkingImage; + } + + _prevViseme = e.Viseme; + } + + private void Communicator_SpeakCompleted(object? sender, SpeakCompletedEventArgs e) + { + _model.TrackerImage = _currentSpeechImages?.IdleImage; + _prevViseme = 0; + } + + private void SetSpeechImages(string reaction) + { + if (_availableSpeechImages.TryGetValue(reaction.ToLower(), out var requestedSpeechImage)) + { + _currentSpeechImages = requestedSpeechImage; + } + else if (_availableSpeechImages.TryGetValue("default", out var defaultSpeechImage)) + { + _currentSpeechImages = defaultSpeechImage; + } + else + { + _currentSpeechImages = _availableSpeechImages.Values.FirstOrDefault(); + } + } + + public string GetBackgroundHex() + { + var color = _model.Background.Color; + return "#" + BitConverter.ToString([color.R, color.G, color.B]).Replace("-", string.Empty); + } +} + diff --git a/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs b/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs index 26e775cef..f5997327d 100644 --- a/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs +++ b/src/TrackerCouncil.Smz3.UI/Services/TrackerWindowService.cs @@ -60,6 +60,7 @@ public TrackerWindowViewModel GetViewModel(TrackerWindow parent) var bytes = Options.GeneralOptions.TrackerBGColor; _model.Background = new SolidColorBrush(Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3])); _model.OpenTrackWindow = Options.GeneralOptions.DisplayMsuTrackWindow; + _model.OpenSpeechWindow = Options.GeneralOptions.DisplayTrackerSpeechWindow; _model.AddShadows = Options.GeneralOptions.TrackerShadows; _model.DisplayTimer = Options.GeneralOptions.TrackerTimerEnabled; @@ -520,6 +521,13 @@ public void OpenTrackerHelpWindow() _trackerHelpWindow.Closed += (_, _) => _trackerHelpWindow = null; } + public TrackerSpeechWindow OpenTrackerSpeechWindow() + { + var window = serviceProvider.GetRequiredService(); + window.Show(_window); + return window; + } + private TrackerWindowPanelViewModel? GetPanelViewModel(UIGridLocation? gridLocation) { return gridLocation?.Type switch diff --git a/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj b/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj index 17260e3ef..5d12887c7 100644 --- a/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj +++ b/src/TrackerCouncil.Smz3.UI/TrackerCouncil.Smz3.UI.csproj @@ -6,7 +6,7 @@ true app.manifest true - 9.8.3 + 9.8.4 SMZ3CasRandomizer Assets\smz3.ico $(MSBuildProjectName.Replace(" ", "_")) diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerSpeechWindowViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerSpeechWindowViewModel.cs new file mode 100644 index 000000000..140bd440c --- /dev/null +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerSpeechWindowViewModel.cs @@ -0,0 +1,12 @@ +using Avalonia; +using Avalonia.Media; +using ReactiveUI.Fody.Helpers; + +namespace TrackerCouncil.Smz3.UI.ViewModels; + +public class TrackerSpeechWindowViewModel : ViewModelBase +{ + [Reactive] public string? TrackerImage { get; set; } + [Reactive] public Thickness AnimationMargin { get; set; } = new(0, 0, 0, 0); + [Reactive] public SolidColorBrush Background { get; set; } = new(new Color(0xFF, 0x48, 0x3D, 0x8B)); +} diff --git a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowViewModel.cs b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowViewModel.cs index c5c9cca0d..ca7e74bec 100644 --- a/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowViewModel.cs +++ b/src/TrackerCouncil.Smz3.UI/ViewModels/TrackerWindowViewModel.cs @@ -84,6 +84,8 @@ public class TrackerWindowViewModel : ViewModelBase public bool OpenTrackWindow { get; set; } + public bool OpenSpeechWindow { get; set; } + public UILayout? PrevLayout { get; set; } public UILayout? CurrentLayout { get; set; } diff --git a/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml b/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml new file mode 100644 index 000000000..e11d643c6 --- /dev/null +++ b/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml.cs new file mode 100644 index 000000000..3a8a1ea5a --- /dev/null +++ b/src/TrackerCouncil.Smz3.UI/Views/TrackerSpeechWindow.axaml.cs @@ -0,0 +1,55 @@ +using System.IO; +using Avalonia.Controls; +using Avalonia.Interactivity; +using AvaloniaControls.Controls; +using AvaloniaControls.Extensions; +using TrackerCouncil.Smz3.UI.Services; +using TrackerCouncil.Smz3.UI.ViewModels; + +namespace TrackerCouncil.Smz3.UI; + +public partial class TrackerSpeechWindow : RestorableWindow +{ + private readonly TrackerSpeechWindowService? _service; + private bool _isShuttingDown; + + public TrackerSpeechWindow() + { + InitializeComponent(); + DataContext = new TrackerSpeechWindowViewModel(); + } + + public TrackerSpeechWindow(TrackerSpeechWindowService service) + { + _service = service; + InitializeComponent(); + DataContext = service.GetViewModel(); + } + + public void Close(bool isShuttingDown) + { + _isShuttingDown = isShuttingDown; + Close(); + } + + protected override void OnClosing(WindowClosingEventArgs e) + { + _service?.StopTimer(); + if (!_isShuttingDown && e.CloseReason != WindowCloseReason.OwnerWindowClosing && e.CloseReason != WindowCloseReason.ApplicationShutdown && e.CloseReason != WindowCloseReason.OSShutdown) + { + _service?.SaveOpenStatus(false); + } + base.OnClosing(e); + } + + protected override string RestoreFilePath => Path.Combine(Directories.AppDataFolder, "Windows", "speech-window.json"); + + protected override int DefaultWidth => 250; + + protected override int DefaultHeight => 250; + + private async void MenuItem_OnClick(object? sender, RoutedEventArgs e) + { + await this.SetClipboardAsync(_service?.GetBackgroundHex()); + } +} diff --git a/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml b/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml index 34d069a0d..667866160 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml +++ b/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml @@ -39,6 +39,9 @@ + diff --git a/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml.cs b/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml.cs index 663443d2c..baf63c6a3 100644 --- a/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml.cs +++ b/src/TrackerCouncil.Smz3.UI/Views/TrackerWindow.axaml.cs @@ -26,13 +26,18 @@ public partial class TrackerWindow : RestorableWindow private GeneratedRom? _generatedRom; private readonly TrackerWindowViewModel _model; private CurrentTrackWindow? _currentTrackWindow; + private TrackerSpeechWindow? _currentTrackerSpeechWindow; private Dictionary _layoutImages = new(); private MainWindow? _parentWindow; public TrackerWindow() { InitializeComponent(); - DataContext = _model = new TrackerWindowViewModel(); + DataContext = _model = new TrackerWindowViewModel() + { + Panels = [ new TrackerWindowPanelViewModel() ], + Layouts = [ new UILayout() ] + }; _parentWindow = MessageWindow.GlobalParentWindow as MainWindow; } @@ -82,6 +87,17 @@ private void OpenCurrentTrackWindow() _currentTrackWindow.Closed += (_, _) => _currentTrackWindow = null; } + private void OpenTrackerSpeechWindow() + { + if (_currentTrackerSpeechWindow != null || _service == null) + { + return; + } + + _currentTrackerSpeechWindow = _service.OpenTrackerSpeechWindow(); + _currentTrackerSpeechWindow.Closed += (_, _) => _currentTrackerSpeechWindow = null; + } + private void CreateLayout() { MainPanel.Children.Clear(); @@ -156,6 +172,10 @@ private void Control_OnLoaded(object? sender, RoutedEventArgs e) { OpenCurrentTrackWindow(); } + if (_model.OpenSpeechWindow) + { + _service?.OpenTrackerSpeechWindow(); + } _service?.OpenTrackerLocationsWindow(); _service?.OpenTrackerMapWindow(); }, DispatcherPriority.Background); @@ -208,6 +228,9 @@ private async void Window_OnClosing(object? sender, WindowClosingEventArgs e) { if (_service == null) return; + _currentTrackWindow?.Close(true); + _currentTrackerSpeechWindow?.Close(true); + if (!_finishedShutdown) { e.Cancel = true; @@ -215,8 +238,6 @@ private async void Window_OnClosing(object? sender, WindowClosingEventArgs e) await _service.Shutdown(); } - _currentTrackWindow?.Close(true); - _parentWindow?.Reload(); _parentWindow?.Show(); } @@ -275,6 +296,11 @@ private void TrackerHelpMenuItem_OnClick(object? sender, RoutedEventArgs e) _service?.OpenTrackerHelpWindow(); } + private void TrackerSpeechWindowMenuItem_OnClick(object? sender, RoutedEventArgs e) + { + _service?.OpenTrackerSpeechWindow(); + } + private void SpeechRecognition_OnPointerPressed(object? sender, PointerPressedEventArgs e) { var point = e.GetCurrentPoint(sender as Control);