|
16 | 16 |
|
17 | 17 | namespace DS4MapperTest.ViewModels |
18 | 18 | { |
19 | | - public class ButtonActionEditViewModel : INotifyPropertyChanged |
| 19 | + public class ButtonActionEditViewModel : INotifyPropertyChanged, ICalibrationPanelViewModel |
20 | 20 | { |
21 | 21 | public event PropertyChangedEventHandler PropertyChanged; |
22 | 22 | public enum ActionComboBoxTypes |
@@ -422,9 +422,11 @@ public GameCalibPreset SelectedCameraTurnPreset |
422 | 422 | _applyingCameraTurnPreset = true; |
423 | 423 | if (IsCountsMode) |
424 | 424 | { |
425 | | - // Counts is this mode's fixed master: keep it as-is and let sensitivity |
426 | | - // move to whatever value reproduces the preset's RWC at that Counts. |
427 | | - if (CameraTurnCounts360 > 0.0) CameraTurnInGameSens = next.RWC * 360.0 / CameraTurnCounts360; |
| 425 | + // A preset only ever names an RWC, and In-Game Sensitivity is the player's |
| 426 | + // own game setting, so it stays exactly as they had it in either mode. |
| 427 | + // From Counts mode that means moving Counts to whatever reproduces the |
| 428 | + // preset's RWC at that sensitivity. |
| 429 | + if (CameraTurnInGameSens > 0.0) CameraTurnCounts360 = next.RWC * 360.0 / CameraTurnInGameSens; |
428 | 430 | } |
429 | 431 | else |
430 | 432 | { |
@@ -529,6 +531,11 @@ public double MasterCalibrationValue |
529 | 531 | get => IsCountsMode ? CameraTurnCounts360 : CameraTurnRWC; |
530 | 532 | set |
531 | 533 | { |
| 534 | + // Same reason CameraTurnInGameSens and the preset check it: HandyControl's |
| 535 | + // NumericUpDown fires ValueChanged(Minimum) while it initialises, before the |
| 536 | + // binding has handed it the real number. Unguarded, that init write pushed a |
| 537 | + // zeroed RWC/Counts straight into the profile the moment this panel appeared. |
| 538 | + if (!_cameraTurnReady) return; |
532 | 539 | if (IsCountsMode) CameraTurnCounts360 = value; |
533 | 540 | else CameraTurnRWC = value; |
534 | 541 | } |
@@ -564,6 +571,78 @@ private void RaiseCalibModePropertyChanges() |
564 | 571 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DerivedValue))); |
565 | 572 | } |
566 | 573 |
|
| 574 | + // Reference counted for the same reason as GyroCalibrationViewModel's copy: the |
| 575 | + // Angle Calibration panel attaches while it is on screen and detaches when it goes, |
| 576 | + // so this editor never holds a subscription to a profile that outlives it. |
| 577 | + private int _calibPanelAttachCount = 0; |
| 578 | + |
| 579 | + public void AttachProfileCalibEvents() |
| 580 | + { |
| 581 | + _calibPanelAttachCount++; |
| 582 | + if (_calibPanelAttachCount == 1) |
| 583 | + { |
| 584 | + mapper.ActionProfile.CalibModeChanged += ActionProfile_CalibModeChanged; |
| 585 | + mapper.ActionProfile.CalibPresetNameChanged += ActionProfile_CalibPresetNameChanged; |
| 586 | + mapper.ActionProfile.CalibRwcChanged += ActionProfile_CalibValuesChanged; |
| 587 | + mapper.ActionProfile.CalibInGameSensChanged += ActionProfile_CalibValuesChanged; |
| 588 | + mapper.ActionProfile.CalibCountsChanged += ActionProfile_CalibValuesChanged; |
| 589 | + } |
| 590 | + |
| 591 | + // Calibration is one profile-wide setting, so whatever another panel left in the |
| 592 | + // profile is the current truth. BeginPanelInit reloads it and holds the fields |
| 593 | + // read-only until the freshly built control has finished initialising. |
| 594 | + BeginPanelInit(); |
| 595 | + } |
| 596 | + |
| 597 | + // Reloads the profile-wide calibration and keeps this panel's fields from writing |
| 598 | + // back until the control settles; see ICalibrationPanelViewModel.BeginPanelInit. |
| 599 | + public void BeginPanelInit() |
| 600 | + { |
| 601 | + _cameraTurnReady = false; |
| 602 | + LoadCameraTurnCalibFromProfile(updateSelectedSlot: true); |
| 603 | + RaiseCameraTurnCalibPropertiesChanged(); |
| 604 | + System.Windows.Application.Current.Dispatcher.BeginInvoke( |
| 605 | + System.Windows.Threading.DispatcherPriority.Background, |
| 606 | + new Action(() => |
| 607 | + { |
| 608 | + LoadCameraTurnCalibFromProfile(updateSelectedSlot: true); |
| 609 | + RaiseCameraTurnCalibPropertiesChanged(); |
| 610 | + System.Windows.Application.Current.Dispatcher.BeginInvoke( |
| 611 | + System.Windows.Threading.DispatcherPriority.ApplicationIdle, |
| 612 | + new Action(() => |
| 613 | + { |
| 614 | + LoadCameraTurnCalibFromProfile(updateSelectedSlot: true); |
| 615 | + _cameraTurnReady = true; |
| 616 | + RaiseCameraTurnCalibPropertiesChanged(); |
| 617 | + })); |
| 618 | + })); |
| 619 | + } |
| 620 | + |
| 621 | + public void DetachProfileCalibEvents() |
| 622 | + { |
| 623 | + if (_calibPanelAttachCount == 0) return; |
| 624 | + _calibPanelAttachCount--; |
| 625 | + if (_calibPanelAttachCount > 0) return; |
| 626 | + |
| 627 | + mapper.ActionProfile.CalibModeChanged -= ActionProfile_CalibModeChanged; |
| 628 | + mapper.ActionProfile.CalibPresetNameChanged -= ActionProfile_CalibPresetNameChanged; |
| 629 | + mapper.ActionProfile.CalibRwcChanged -= ActionProfile_CalibValuesChanged; |
| 630 | + mapper.ActionProfile.CalibInGameSensChanged -= ActionProfile_CalibValuesChanged; |
| 631 | + mapper.ActionProfile.CalibCountsChanged -= ActionProfile_CalibValuesChanged; |
| 632 | + } |
| 633 | + |
| 634 | + // A gyro, stick or touchpad panel changed the profile-wide calibration. Take those |
| 635 | + // values as they are: this editor shows the same setting, and its cached copy would |
| 636 | + // otherwise be written back over them the next time anything here is edited. |
| 637 | + // Skipped while this ViewModel is the one writing the profile, since its own |
| 638 | + // multi-field write leaves the profile briefly inconsistent between steps. |
| 639 | + private void ActionProfile_CalibValuesChanged(object sender, EventArgs e) |
| 640 | + { |
| 641 | + if (_syncingProfileCalib) return; |
| 642 | + LoadCameraTurnCalibFromProfile(updateSelectedSlot: true); |
| 643 | + RaiseCameraTurnCalibPropertiesChanged(); |
| 644 | + } |
| 645 | + |
567 | 646 | private void ActionProfile_CalibModeChanged(object sender, EventArgs e) |
568 | 647 | { |
569 | 648 | RaiseCalibModePropertyChanges(); |
@@ -602,24 +681,7 @@ public bool ShowCameraTurnOptions |
602 | 681 | showCameraTurnOptions = value; |
603 | 682 | if (value) |
604 | 683 | { |
605 | | - _cameraTurnReady = false; |
606 | | - LoadCameraTurnCalibFromProfile(updateSelectedSlot: true); |
607 | | - RaiseCameraTurnCalibPropertiesChanged(); |
608 | | - System.Windows.Application.Current.Dispatcher.BeginInvoke( |
609 | | - System.Windows.Threading.DispatcherPriority.Background, |
610 | | - new Action(() => |
611 | | - { |
612 | | - LoadCameraTurnCalibFromProfile(updateSelectedSlot: true); |
613 | | - RaiseCameraTurnCalibPropertiesChanged(); |
614 | | - System.Windows.Application.Current.Dispatcher.BeginInvoke( |
615 | | - System.Windows.Threading.DispatcherPriority.ApplicationIdle, |
616 | | - new Action(() => |
617 | | - { |
618 | | - LoadCameraTurnCalibFromProfile(updateSelectedSlot: true); |
619 | | - _cameraTurnReady = true; |
620 | | - RaiseCameraTurnCalibPropertiesChanged(); |
621 | | - })); |
622 | | - })); |
| 684 | + BeginPanelInit(); |
623 | 685 | } |
624 | 686 | ShowCameraTurnOptionsChanged?.Invoke(this, EventArgs.Empty); |
625 | 687 | } |
@@ -730,8 +792,9 @@ public ButtonActionEditViewModel(Mapper mapper, ButtonAction currentAction, Acti |
730 | 792 | CameraTurnRWC = cameraTurnCalculatedRWC; |
731 | 793 | }); |
732 | 794 |
|
733 | | - mapper.ActionProfile.CalibModeChanged += ActionProfile_CalibModeChanged; |
734 | | - mapper.ActionProfile.CalibPresetNameChanged += ActionProfile_CalibPresetNameChanged; |
| 795 | + // The calibration subscriptions belong to the Angle Calibration panel's own |
| 796 | + // Loaded/Unloaded (see AttachProfileCalibEvents), so this editor follows the |
| 797 | + // profile-wide values exactly while that panel is on screen. |
735 | 798 | mapper.ActionProfile.OutputGamepadSettings.OutputGamepadChanged += OutputGamepadSettings_OutputGamepadChanged; |
736 | 799 |
|
737 | 800 | SetupEvents(); |
@@ -1863,20 +1926,39 @@ private void UpdateCameraTurnPresetFromCurrentRwc() |
1863 | 1926 | mapper.ActionProfile.CalibPresetName = matchedName; |
1864 | 1927 | } |
1865 | 1928 |
|
| 1929 | + private bool _syncingProfileCalib = false; |
| 1930 | + |
1866 | 1931 | private void SyncCalibFromCameraTurnToProfile() |
1867 | 1932 | { |
1868 | 1933 | double counts = cameraTurnCounts360; |
1869 | 1934 | double inGameSens = cameraTurnInGameSens; |
1870 | 1935 | double rwc = inGameSens > 0.0 ? inGameSens * counts / 360.0 : 0.0; |
1871 | | - mapper.ActionProfile.CalibCounts = counts; |
1872 | | - mapper.ActionProfile.CalibInGameSens = inGameSens; |
1873 | | - mapper.ActionProfile.CalibRwc = rwc; |
| 1936 | + // Guards ActionProfile_CalibValuesChanged against this instance's own writes: |
| 1937 | + // the three profile fields are written one at a time, so reloading from the |
| 1938 | + // profile between them would pull back a half-updated calibration. |
| 1939 | + _syncingProfileCalib = true; |
| 1940 | + try |
| 1941 | + { |
| 1942 | + mapper.ActionProfile.CalibCounts = counts; |
| 1943 | + mapper.ActionProfile.CalibInGameSens = inGameSens; |
| 1944 | + mapper.ActionProfile.CalibRwc = rwc; |
| 1945 | + } |
| 1946 | + finally { _syncingProfileCalib = false; } |
1874 | 1947 | mapper.ProcessMappingChangeAction(() => |
1875 | 1948 | { |
1876 | 1949 | foreach (var set in mapper.ActionProfile.ActionSets) |
1877 | 1950 | foreach (var layer in set.ActionLayers) |
1878 | 1951 | foreach (var mapAction in layer.normalActionDict.Values) |
1879 | 1952 | { |
| 1953 | + // Gyro Mouse reads the same profile calibration as the flick stick |
| 1954 | + // and camera turn outputs, so an edit made from this panel has to |
| 1955 | + // reach its live params too; leaving it out kept gyro aiming on the |
| 1956 | + // pre-edit calibration until the profile was reloaded. |
| 1957 | + if (mapAction is GyroMouse gyroMouse) |
| 1958 | + { |
| 1959 | + gyroMouse.mouseParams.realWorldCalibration = rwc; |
| 1960 | + gyroMouse.mouseParams.inGameSens = inGameSens; |
| 1961 | + } |
1880 | 1962 | if (mapAction is ButtonAction ba) |
1881 | 1963 | foreach (var func in ba.ActionFuncs) |
1882 | 1964 | foreach (var data in func.OutputActions) |
|
0 commit comments