public Split(SplitTimeSpan splitTimeSpan, Run owner) { if (owner == null) throw new ArgumentNullException(); Owner = owner; SplitInfo = splitTimeSpan; }
public Split(TimeSpan splitTime, Run owner, bool isPreciseSplit) : this(new SplitTimeSpan(splitTime, isPreciseSplit), owner) { }
public void SaveCurrentFile() { if (!CurrentFile.IsPathSet) { SaveCurrentFileAs(); return; } try { CurrentFile.MergeAndSave(CurrentRun); CurrentRun = new Run(CurrentFile.RunDefinition.Length); } catch (Exception) { MessageBox.Show(MainWindow, "Error saving file. Perhaps you don't have access to that folder.", "Error Saving", MessageBoxButton.OK, MessageBoxImage.Error); } }
public Split(TimeSpan splitTime, Run owner) : this(new SplitTimeSpan(splitTime, true), owner) { }
private void generateRunsFromDefinition() { PersonalBest = new Run(RunDefinition.Select(si => si.PersonalBestSplit).ToArray()); SumOfBest = new Run(RunDefinition.Select(si => si.SumOfBestSplit).ToArray()); }
//private KeyboardListener keyListener = new KeyboardListener(); public MainViewModel() { MainWindow = (View.MainWindow)Application.Current.MainWindow; if (Settings.Default.IsNewVersion) { Settings.Default.Upgrade(); var targetConfigPath = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath; //Attempt to upgrade display templates if (!string.IsNullOrWhiteSpace(Settings.Default.ConfigPath)) { //Forcefully upgrade old DisplayTemplates by copying the old settings file xml node over //I know that the Application Settings can be upgraded using the Upgrade method but I //can't seem to find how to do the same with configuration sections var previousVersion = XDocument.Load(Settings.Default.ConfigPath); var newVersion = XDocument.Load(targetConfigPath); var previousDisplayTemplates = previousVersion.Descendants("displayTemplates").FirstOrDefault(); if (previousDisplayTemplates != null) { newVersion.Descendants("configuration").First().AddFirst(new XElement(previousDisplayTemplates)); newVersion.Save(targetConfigPath); } } Settings.Default.ConfigPath = targetConfigPath; Settings.Default.IsNewVersion = false; Settings.Default.Save(); } LiveTimer = new Timer(30); SettingsViewModel = new SettingsViewModel(this); DisplaySettingsViewModel = new DisplayTemplatesViewModel(this); SplitManagementViewModel = new SplitManagementViewModel(); ResizeEnabled = false; ShowGoldSplits = true; MainWindow.Loaded += (sender, e) => { //HotKeyManager must be initialized after the window has loaded HotKeyManager = new HotKeyManager(MainWindow); HotKeyManager.KeyBoardHook(); var keyPressedObs = Observable.FromEventPattern<KeyboardHookEventHandler, KeyboardHookEventArgs>( h => HotKeyManager.KeyBoardKeyEvent += h, h => HotKeyManager.KeyBoardKeyEvent -= h) .Where(_ => !SettingsWindowOpen && CurrentFile != null && CurrentRun != null) .Publish().RefCount(); TimeSpan cooldown = TimeSpan.FromMilliseconds(Settings.Default.HotkeyCooldownTime); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.SplitKey).Cooldown(cooldown).SubscribeSafeLog(_ => CurrentRun.Split()); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.UnsplitKey).Cooldown(cooldown).SubscribeSafeLog(_ => CurrentRun.Unsplit()); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.SkipKey).Cooldown(cooldown).SubscribeSafeLog(_ => CurrentRun.SkipSplit()); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.PauseKey).Cooldown(cooldown).SubscribeSafeLog(_ => CurrentRun.Pause()); keyPressedObs.Where(ep => ep.EventArgs.Key == Settings.Default.ResetKey).Cooldown(cooldown).SubscribeSafeLog(_ => { //This call to CheckMergeSuggested is done like this in order to free up the key pressed event immediately. //Blocking the event with a popup causes application responsiveness issues. Observable.Return(System.Reactive.Unit.Default).ObserveOnDispatcher().SubscribeSafeLog(_2 => { CheckMergeSuggested(); CurrentRun = new Run(CurrentFile.RunDefinition.Length); }); }); }; CreateNewFileCommand = Command.Create(() => true, CreateNewFile); OpenFileCommand = Command.Create(() => true, OpenFile); ImportFromWsplitCommand = Command.Create(() => true, ImportFromWsplit); SaveSplits = Command.Create(() => true, SaveCurrentFile); SaveSplitsAs = Command.Create(() => true, SaveCurrentFileAs); AcceptResizeCommand = Command.Create(() => true, AcceptResize); CancelResizeCommand = Command.Create(() => true, CancelResize); //Set up observable which monitors changes in the current run var runChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => PropertyChanged += h, h => PropertyChanged -= h).Where(a => a.EventArgs.PropertyName == "CurrentRun") .Publish().RefCount(); //Set up observable which monitors changes in the current file var fileChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => PropertyChanged += h, h => PropertyChanged -= h).Where(a => a.EventArgs.PropertyName == "CurrentFile") .Publish().RefCount(); //Set up observable which monitors changes in the display template var displayTemplateObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => DisplaySettingsViewModel.PropertyChanged += h, h => DisplaySettingsViewModel.PropertyChanged -= h) .Where(a => a.EventArgs.PropertyName == "SelectedDisplayTemplate").Publish().RefCount(); //Set up an observable of an observable which will be used to monitor changes in run status var runStatusObs = runChangedObs.Select(_ => { if (CurrentRun == null) return Observable.Never<Unit>(); var startObs = Observable.FromEventPattern(h => CurrentRun.RunStatusChanged += h, h => CurrentRun.RunStatusChanged -= h).Select(_2 => Unit.Default); //Propagate the start and complete events but also fire an event immediately which will be used //to reset the timer as soon as the run changes return startObs.StartWith(Unit.Default); }); //Set up an observable of an observable which will be used to monitor changes in splits var splitObs = runChangedObs.Select(_ => { if (CurrentRun == null) return Observable.Never<EventPattern<SplitChange>>(); var splitChangedObs = Observable.FromEventPattern<SplitChange>(h => CurrentRun.SplitChanged += h, h => CurrentRun.SplitChanged -= h); return splitChangedObs.StartWith(new EventPattern<SplitChange>(this, new SplitChange(SplitChange.ActionEnum.Reset, null, -1))); }); //Set up an observable of an observable which will be used to monitor changes in display template var fileDisplayTemplateObs = fileChangedObs.Select(_ => { if (CurrentFile == null) return Observable.Never<Unit>(); var templateChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => CurrentFile.PropertyChanged += h, h => CurrentFile.PropertyChanged -= h).Where(a => a.EventArgs.PropertyName == "DisplayTemplate"); return templateChangedObs.Select(_2 => Unit.Default).StartWith(Unit.Default); }); //Set up an observable of an observable which will be used to monitor changes in window size var windowSizeObs = displayTemplateObs.Select(_ => Unit.Default).StartWith(Unit.Default).Select(_ => { if (DisplaySettingsViewModel == null || DisplaySettingsViewModel.SelectedDisplayTemplate == null) return Observable.Never<Unit>(); var selectedDisplay = DisplaySettingsViewModel.SelectedDisplayTemplate; var sizeChangedObs = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>( h => selectedDisplay.PropertyChanged += h, h => selectedDisplay.PropertyChanged -= h) .Where(a => a.EventArgs.PropertyName == "WindowWidth" || a.EventArgs.PropertyName == "WindowHeight"); return sizeChangedObs.Select(_2 => Unit.Default).StartWith(Unit.Default); }); //Monitor when run changes and changes state in order to control live timer runStatusObs.Switch().SubscribeSafeLog(_ => { if (CurrentRun == null || !CurrentRun.IsStarted) { IsCurrentRunStarted = false; LiveTimer.Clear(); } else if (CurrentRun.IsCompleted) { LiveTimer.Stop(CurrentRun.CompletedRunTime); } else if (CurrentRun.IsStarted) { if (CurrentRun.CurrentSplit == 0) { IsCurrentRunStarted = true; CurrentSplitRow = SplitRows[0]; } LiveTimer.Start(CurrentRun); } }); //Monitor when run changes and a split is detected. This is used to update the split view splitObs.Switch().SubscribeSafeLog(args => { switch(args.EventArgs.Action) { case SplitChange.ActionEnum.Removed: SplitRows[args.EventArgs.Index].CurrentRunSplit = null; PreviousSplitRow = args.EventArgs.Index > 0 ? SplitRows[args.EventArgs.Index - 1] : null; CurrentSplitRow = SplitRows[args.EventArgs.Index]; break; case SplitChange.ActionEnum.Added: SplitRows[args.EventArgs.Index].CurrentRunSplit = args.EventArgs.Item; PreviousSplitRow = SplitRows[args.EventArgs.Index]; CurrentSplitRow = SplitRows.Length > args.EventArgs.Index + 1 ? SplitRows[args.EventArgs.Index + 1] : null; break; case SplitChange.ActionEnum.Reset: var pbSplits = CurrentFile.PersonalBest.Splits; var goldSplits = CurrentFile.SumOfBest.Splits; var splitInfo = CurrentFile.RunDefinition; var merged = pbSplits.Zip(splitInfo, (pbs, si) => new { PbSplit = pbs, Info = si }).Zip(goldSplits, (a, gs) => new { PbSplit = a.PbSplit, GoldSplit = gs, Info = a.Info }); SplitRows = merged.Select(a => new SplitRowDisplay(a.Info.Name, a.PbSplit, a.GoldSplit)).ToArray(); PreviousSplitRow = null; CurrentSplitRow = null; break; } }); //Change current run when a new file is loaded fileChangedObs.SubscribeSafeLog(_ => { CurrentRun = new Run(CurrentFile.RunDefinition.Length); }); //Force window to resize correctly when a new template is loaded windowSizeObs.Switch().Throttle(TimeSpan.FromMilliseconds(50)).ObserveOnDispatcher().SubscribeSafeLog(_ => { if (DisplaySettingsViewModel == null || DisplaySettingsViewModel.SelectedDisplayTemplate == null) return; MainWindow.ForceChangeWindowSize(DisplaySettingsViewModel.SelectedDisplayTemplate.WindowHeight, DisplaySettingsViewModel.SelectedDisplayTemplate.WindowWidth); }); //Keep DisplayTemplateViewModel synchronized with these changes. fileDisplayTemplateObs.Switch().SubscribeSafeLog(_ => { if (CurrentFile == null || CurrentFile.DisplayTemplate == null) return; DisplaySettingsViewModel.SelectedDisplayTemplate = CurrentFile.DisplayTemplate; }); }
public bool IsPersonalBest(Run latestRun) { //Check if this run was better than personal best return latestRun.CompletedRunTime < PersonalBest.CompletedRunTime; }
public bool ContainsGoldSplit(Run latestRun) { //Check if any splits from the current run beat any gold split return latestRun.Splits.Zip(SumOfBest.Splits, (lr, sb) => new { Latest = lr, Best = sb }) .Any(a => Split.IsGoldSplit(a.Latest, a.Best)); }
public bool CheckMergeSuggested(Run latestRun) { return ContainsGoldSplit(latestRun) || IsPersonalBest(latestRun); }
public void MergeAndSave(Run latestRun) { var zippedSplits = latestRun.Splits.Zip(PersonalBest.Splits, (lr, pb) => new { Lr = lr, Pb = pb }).Zip(SumOfBest.Splits, (a, sb) => new { Lr = a.Lr, Pb = a.Pb, Sb = sb }).Zip(RunDefinition, (a, d) => new { Latest = a.Lr, PersonalBest = a.Pb, SumOfBest = a.Sb, Definition = d }).ToArray(); var isPersonalBest = IsPersonalBest(latestRun); var isGoldSplit = ContainsGoldSplit(latestRun); if (isPersonalBest || isGoldSplit) { if (isPersonalBest) PersonalBestDate = latestRun.StartTime; RunDefinition = zippedSplits.Select(a => { return new SplitInfo() { Name = a.Definition.Name, PersonalBestSplit = isPersonalBest ? a.Latest.SplitInfo : a.Definition.PersonalBestSplit, SumOfBestSplit = a.Latest != null && a.Latest.IsWellBounded && (a.Latest.Time < a.Definition.SumOfBestSplit.Time) ? a.Latest.SplitInfo : a.Definition.SumOfBestSplit }; }).ToArray(); generateRunsFromDefinition(); }; Save(); }
public void Start(Run run) { if (subscription != null) subscription.Dispose(); clearOverrideTimer(); subscription = frequencyObservable.Subscribe(_ => setTimer(run.TimeSinceStart)); }