Skip to content

Commit cab93dd

Browse files
mherboldclaude
andcommitted
Add pinned quick controls, graph descriptions, and vibration test buttons
FFB graph settings can now be pinned as quick controls shown above the editor block, graphs carry a description (localized for the built-ins), and a new show-node-graph switch replaces the old simple mode. Also: test buttons for all vibration nodes (enabled on track only), a draggable column split for the editor block, role colors for source/generator/output nodes, and darker preview status tints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d149859 commit cab93dd

50 files changed

Lines changed: 1572 additions & 242 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MarvinsAIRARefactored/Components/RacingWheel.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -623,19 +623,19 @@ public void ProcessTelemetryFrame()
623623

624624
if ( engine.CurbProtectionActive )
625625
{
626-
clearColor = 0xFFFFFF00;
626+
clearColor = 0xFF606000;
627627
}
628628

629629
if ( engine.CrashProtectionActive )
630630
{
631-
clearColor = 0xFFFF5B2E;
631+
clearColor = 0xFF40260C;
632632
}
633633

634634
for ( var i = 0; i < Simulator.SamplesPerFrame360Hz; i++ )
635635
{
636636
if ( MathF.Abs( _burstOutputTorque[ i ] ) >= 0.99f )
637637
{
638-
clearColor = 0xFFFF0000;
638+
clearColor = 0xFF600000;
639639

640640
break;
641641
}
@@ -1244,17 +1244,17 @@ public void Tick( App app )
12441244

12451245
if ( _previewEngine.CurbProtectionActive )
12461246
{
1247-
clearColor = 0xFFFFFF00;
1247+
clearColor = 0xFF606000;
12481248
}
12491249

12501250
if ( _previewEngine.CrashProtectionActive )
12511251
{
1252-
clearColor = 0xFFFF5B2E;
1252+
clearColor = 0xFF40260C;
12531253
}
12541254

12551255
if ( previewIsOutput && ( MathF.Abs( outputValue ) >= 0.99f ) )
12561256
{
1257-
clearColor = 0xFFFF0000;
1257+
clearColor = 0xFF600000;
12581258
}
12591259

12601260
_algorithmPreviewGraphBase.SetClearColor( clearColor );
@@ -1279,6 +1279,11 @@ public void Tick( App app )
12791279
_racingWheelPage.Record_MairaMappableButton.Disabled = !app.Simulator.IsOnTrack;
12801280
_racingWheelPage.Record_MairaMappableButton.Blink = app.RecordingManager.IsRecording;
12811281

1282+
// generator (vibration) tests shake the physical wheel, which needs live FFB — gate their test
1283+
// buttons on the same on-track state as the record button
1284+
1285+
DataContext.DataContext.Instance.RacingWheelGraphViewModel.NotifyIsOnTrackChanged( app.Simulator.IsOnTrack );
1286+
12821287
// suspend racing wheel force feedback if iracing ffb is enabled or we are calibrating
12831288

12841289
SuspendForceFeedback = !app.Simulator.IsConnected || ( app.Simulator.SteeringFFBEnabled && !settings.RacingWheelAlwaysEnableFFB ) || app.SteeringEffects.IsCalibrating;

MarvinsAIRARefactored/Controls/FFBGraphEditor.xaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,19 @@
136136
<Setter Property="BorderBrush"
137137
Value="{DynamicResource Brush.Accent.BlueDark}" />
138138
</DataTrigger>
139+
<!-- telemetry source nodes green, the output node purple — same tone
140+
family as the generator blue, so no role shouts over the others;
141+
ordinary chain modules keep the gray default -->
142+
<DataTrigger Binding="{Binding IsSource}"
143+
Value="True">
144+
<Setter Property="BorderBrush"
145+
Value="{DynamicResource Brush.Accent.GreenDark}" />
146+
</DataTrigger>
147+
<DataTrigger Binding="{Binding IsOutput}"
148+
Value="True">
149+
<Setter Property="BorderBrush"
150+
Value="{DynamicResource Brush.Accent.PurpleDark}" />
151+
</DataTrigger>
139152
<!-- preview lock (right-click) renders below selection: when the same
140153
node is both, the selection accent wins (last matching trigger) -->
141154
<DataTrigger Binding="{Binding IsPreviewLocked}"
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Globalization;
2+
using System.Windows;
3+
using System.Windows.Data;
4+
5+
namespace MarvinsAIRARefactored.Converters;
6+
7+
// Turns a 0..1 split fraction into a star GridLength so two grid columns can share a settings-driven ratio;
8+
// pass ConverterParameter=Complement to get the other side's share (1 - fraction).
9+
[ValueConversion( typeof( double ), typeof( GridLength ) )]
10+
public class FractionToStarGridLengthConverter : IValueConverter
11+
{
12+
public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
13+
{
14+
var fraction = (double) value;
15+
16+
if ( parameter is string text && ( text == "Complement" ) )
17+
{
18+
fraction = 1.0 - fraction;
19+
}
20+
21+
return new GridLength( fraction, GridUnitType.Star );
22+
}
23+
24+
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
25+
{
26+
return DependencyProperty.UnsetValue;
27+
}
28+
}

MarvinsAIRARefactored/DataContext/Settings.cs

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,24 @@ public void ApplyImportedGraphValues( string existingGraphName, FFBGraph importe
643643
WriteImportedModuleValues( imported, localGraph.GraphId, moduleIdMap, FindContextSettings( baselineContext ).RacingWheelFFBGraphModuleValues );
644644
}
645645

646+
// The description and pinned quick controls are part of the author's intent for the graph, so an update
647+
// adopts them too (pins are remapped onto the local nodes the same way the values were).
648+
localGraph.Description = imported.Description;
649+
650+
foreach ( var importedModule in imported.Modules )
651+
{
652+
if ( moduleIdMap.TryGetValue( importedModule.ModuleId, out var localModuleId ) )
653+
{
654+
var localModule = localGraph.Modules.Find( module => module.ModuleId == localModuleId );
655+
656+
if ( localModule != null )
657+
{
658+
localModule.PinnedSettings.Clear();
659+
localModule.PinnedSettings.AddRange( importedModule.PinnedSettings );
660+
}
661+
}
662+
}
663+
646664
// Reflect the change live only when it lands on the context we are currently driving. Selecting the matched
647665
// graph (when it isn't already selected) loads this context's values and rebuilds the engine + editor; if it
648666
// is already selected, reload this context's values directly. A baseline-only write while a different context
@@ -661,6 +679,12 @@ public void ApplyImportedGraphValues( string existingGraphName, FFBGraph importe
661679
}
662680
}
663681

682+
// the adopted description/pins reach the editor VM when this graph is (or becomes) the selected one
683+
if ( RacingWheelSelectedFFBGraphName == existingGraphName )
684+
{
685+
RebuildGraphEditorViewModel();
686+
}
687+
664688
App.Instance!.SettingsFile.QueueForSerialization = true;
665689
}
666690

@@ -1824,6 +1848,26 @@ public bool RacingWheelFFBGraphSnapToGrid
18241848
}
18251849
}
18261850

1851+
// Shows/hides the top row of the editor block (the node graph and the module settings column). Off by
1852+
// default: basic users see just the description, the pinned quick controls, and the preview row — advanced
1853+
// users flip this on for full control of the node graph. Global (not per graph).
1854+
private bool _racingWheelShowNodeGraph = false;
1855+
1856+
public bool RacingWheelShowNodeGraph
1857+
{
1858+
get => _racingWheelShowNodeGraph;
1859+
1860+
set
1861+
{
1862+
if ( value != _racingWheelShowNodeGraph )
1863+
{
1864+
_racingWheelShowNodeGraph = value;
1865+
1866+
OnPropertyChanged();
1867+
}
1868+
}
1869+
}
1870+
18271871
// Node editor viewport height in pixels (global — resized by dragging the grab handle on the node graph /
18281872
// preview graph seam; the preview graph's height never changes). Clamped here so a hand-edited settings
18291873
// file cannot collapse or blow up the layout.
@@ -1849,6 +1893,32 @@ public double RacingWheelFFBGraphEditorHeight
18491893
}
18501894
}
18511895

1896+
// Fraction of the FFB graph block's width given to the left (node graph / preview) column, the rest going to
1897+
// the module settings / track map column — resized by dragging the grab handle on the vertical seam. Stored
1898+
// as a fraction rather than pixels so the split survives window resizes. Clamped here so a hand-edited
1899+
// settings file cannot collapse either column.
1900+
public const double MinFFBGraphEditorSplit = 0.4;
1901+
public const double MaxFFBGraphEditorSplit = 0.85;
1902+
1903+
private double _racingWheelFFBGraphEditorSplit = 2.0 / 3.0;
1904+
1905+
public double RacingWheelFFBGraphEditorSplit
1906+
{
1907+
get => _racingWheelFFBGraphEditorSplit;
1908+
1909+
set
1910+
{
1911+
value = Math.Clamp( value, MinFFBGraphEditorSplit, MaxFFBGraphEditorSplit );
1912+
1913+
if ( value != _racingWheelFFBGraphEditorSplit )
1914+
{
1915+
_racingWheelFFBGraphEditorSplit = value;
1916+
1917+
OnPropertyChanged();
1918+
}
1919+
}
1920+
}
1921+
18521922
// Content hashes of the built-in graph files last synced into this settings file, keyed "{GraphType}/{Name}".
18531923
// When a shipped file's hash differs, the stored built-in graph is refreshed at launch (see
18541924
// EnsureBuiltInFFBGraphsInitialized) — so built-in graphs updated during development reach users automatically.
@@ -4165,31 +4235,6 @@ public bool RacingWheelAlwaysEnableFFB
41654235

41664236
#endregion
41674237

4168-
#region Racing wheel - Simple mode
4169-
4170-
private bool _racingWheelSimpleModeEnabled = false;
4171-
4172-
public bool RacingWheelSimpleModeEnabled
4173-
{
4174-
get => _racingWheelSimpleModeEnabled;
4175-
4176-
set
4177-
{
4178-
if ( value != _racingWheelSimpleModeEnabled )
4179-
{
4180-
_racingWheelSimpleModeEnabled = value;
4181-
4182-
OnPropertyChanged();
4183-
}
4184-
4185-
var app = App.Instance!;
4186-
4187-
app.MainWindow.UpdateRacingWheelSimpleMode();
4188-
}
4189-
}
4190-
4191-
#endregion
4192-
41934238
#region Steering effects - Understeer enabled
41944239

41954240
private bool _steeringEffectsUndersteerEnabled = false;

MarvinsAIRARefactored/FFB/FFBGraph.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class FFBModuleData
1616
public string InputAModuleId { get; set; } = FFBGraph.Source360ModuleId;
1717
public string InputBModuleId { get; set; } = FFBGraph.Source360ModuleId;
1818
public SerializableDictionary<string, float> SettingValues { get; set; } = []; // bools 0/1, choices as index
19+
public List<string> PinnedSettings { get; set; } = []; // setting keys surfaced as the graph's quick controls above the editor
1920
public float NodeX { get; set; } = 0f; // node editor canvas position; 0,0 everywhere = needs auto-layout
2021
public float NodeY { get; set; } = 0f;
2122

@@ -43,6 +44,8 @@ public FFBModuleData Clone()
4344
clone.SettingValues[ pair.Key ] = pair.Value;
4445
}
4546

47+
clone.PinnedSettings.AddRange( PinnedSettings );
48+
4649
return clone;
4750
}
4851
}
@@ -74,6 +77,12 @@ public class FFBGraph
7477
// out of another's. Empty on legacy graphs/files - assigned lazily on load/import.
7578
public string GraphId { get; set; } = "";
7679
public string Name { get; set; } = "";
80+
81+
// Free-text description shown below the graph selector (rides export/import). For BUILT-IN graphs the UI
82+
// resolves a localization key derived from the graph name first (so shipped descriptions arrive translated)
83+
// and only falls back to this stored text — see FFBGraphViewModel.GraphDescription.
84+
public string Description { get; set; } = "";
85+
7786
public bool IsBuiltIn { get; set; } = false;
7887
public List<FFBModuleData> Modules { get; set; } = []; // dependency order, last = Output
7988

@@ -136,7 +145,7 @@ public static FFBGraph CreateEmpty( string name, bool isBuiltIn = false )
136145
/// <summary>Deep copy of the whole graph (used to seed a new graph from an existing one).</summary>
137146
public FFBGraph Clone()
138147
{
139-
var clone = new FFBGraph { GraphId = GraphId, Name = Name, IsBuiltIn = IsBuiltIn };
148+
var clone = new FFBGraph { GraphId = GraphId, Name = Name, Description = Description, IsBuiltIn = IsBuiltIn };
140149

141150
foreach ( var module in Modules )
142151
{

0 commit comments

Comments
 (0)