Allow configuring two pads together (the existing behavior), or selecting a single pad from a dropdown to configure them separately.

master
Glenn Maynard 7 years ago
parent 9ed01c29ba
commit 913f740113
  1. 12
      smx-config/CurrentSMXDevice.cs
  2. 59
      smx-config/Helpers.cs
  3. 24
      smx-config/MainWindow.xaml
  4. 149
      smx-config/MainWindow.xaml.cs
  5. 59
      smx-config/SMX.cs
  6. 48
      smx-config/Widgets.cs

@ -31,10 +31,6 @@ namespace smx_config
// Data for each of two controllers: // Data for each of two controllers:
public LoadFromConfigDelegateArgsPerController[] controller; public LoadFromConfigDelegateArgsPerController[] controller;
// For convenience, this is the index in controller of the first connected controller.
// If no controllers are connected, this is 0.
public int FirstController;
// The control that changed the configuration (passed to FireConfigurationChanged). // The control that changed the configuration (passed to FireConfigurationChanged).
public object source; public object source;
}; };
@ -134,7 +130,6 @@ namespace smx_config
public LoadFromConfigDelegateArgs GetState() public LoadFromConfigDelegateArgs GetState()
{ {
LoadFromConfigDelegateArgs args = new LoadFromConfigDelegateArgs(); LoadFromConfigDelegateArgs args = new LoadFromConfigDelegateArgs();
args.FirstController = -1;
args.controller = new LoadFromConfigDelegateArgsPerController[2]; args.controller = new LoadFromConfigDelegateArgsPerController[2];
for(int pad = 0; pad < 2; ++pad) for(int pad = 0; pad < 2; ++pad)
@ -151,15 +146,8 @@ namespace smx_config
SMX.SMX.GetConfig(pad, out controller.config); SMX.SMX.GetConfig(pad, out controller.config);
SMX.SMX.GetTestData(pad, out controller.test_data); SMX.SMX.GetTestData(pad, out controller.test_data);
args.controller[pad] = controller; args.controller[pad] = controller;
// If this is the first connected controller, set FirstController.
if(controller.info.connected && args.FirstController == -1)
args.FirstController = pad;
} }
if(args.FirstController == -1)
args.FirstController = 0;
return args; return args;
} }

@ -7,6 +7,65 @@ using SMXJSON;
namespace smx_config namespace smx_config
{ {
// Track whether we're configuring one pad or both at once.
static class ActivePad
{
public enum SelectedPad {
P1,
P2,
Both,
};
// The actual pad selection. This defaults to both, and doesn't change if
// only one pad is selected. We don't actually show "both" in the dropdown
// unless two pads are connected, but the underlying setting remains.
public static SelectedPad selectedPad = SelectedPad.Both;
// A shortcut for when a LoadFromConfigDelegateArgs isn't available:
public static IEnumerable<Tuple<int, SMX.SMXConfig>> ActivePads()
{
return ActivePads(CurrentSMXDevice.singleton.GetState());
}
// Yield each connected pad which is currently active for configuration.
public static IEnumerable<Tuple<int, SMX.SMXConfig>> ActivePads(LoadFromConfigDelegateArgs args)
{
bool Pad1Connected = args.controller[0].info.connected;
bool Pad2Connected = args.controller[1].info.connected;
// If both pads are connected and a single pad is selected, ignore the deselected pad.
if(Pad1Connected && Pad2Connected)
{
if(selectedPad == SelectedPad.P1)
Pad2Connected = false;
if(selectedPad == SelectedPad.P2)
Pad1Connected = false;
}
if(Pad1Connected)
yield return Tuple.Create(0, args.controller[0].config);
if(Pad2Connected)
yield return Tuple.Create(1, args.controller[1].config);
}
// We know the selected pads are synced if there are two active, and when refreshing a
// UI we just want one of them to set the UI to. For convenience, return the first one.
public static SMX.SMXConfig GetFirstActivePadConfig(LoadFromConfigDelegateArgs args)
{
foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePads(args))
return activePad.Item2;
// There aren't any pads connected. Just return a dummy config, since the UI
// isn't visible.
return new SMX.SMXConfig();
}
public static SMX.SMXConfig GetFirstActivePadConfig()
{
return GetFirstActivePadConfig(CurrentSMXDevice.singleton.GetState());
}
}
static class Helpers static class Helpers
{ {
// Return true if we're in debug mode. // Return true if we're in debug mode.

@ -632,16 +632,7 @@ Use if the platform is too sensitive.</clr:String>
</Window.Resources> </Window.Resources>
<Grid> <Grid Background="#DDD">
<!-- A list of which pads are connected, overlapping the tab bar in the top right. -->
<Grid x:Name="ConnectedPads" Background="#DDD">
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Right" Orientation="Horizontal">
<Label Content="Connected:" FontSize="10"/>
<Label x:Name="P1Connected" Style="{StaticResource EnabledIcon}" Content="P1" Margin="1,0,4,0" FontSize="10"/>
<Label x:Name="P2Connected" Style="{StaticResource EnabledIcon}" Content="P2" Margin="0,0,4,0" FontSize="10"/>
</StackPanel>
</Grid>
<Grid x:Name="Searching" Visibility="Hidden" Background="#DDD"> <Grid x:Name="Searching" Visibility="Hidden" Background="#DDD">
<Label Content="Searching for controller..." <Label Content="Searching for controller..."
HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="33.333"/> HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="33.333"/>
@ -804,5 +795,18 @@ Input will be disabled from deselected panels.</TextBlock>
</TabItem> </TabItem>
</TabControl> </TabControl>
<!-- A list of which pads are connected, overlapping the tab bar in the top right. -->
<StackPanel x:Name="ConnectedPads" VerticalAlignment="Top" HorizontalAlignment="Right" Orientation="Horizontal">
<Label Content="Connected:" FontSize="10"/>
<ComboBox x:Name="ConnectedPadList" SelectionChanged="ConnectedPadList_SelectionChanged">
<ComboBoxItem x:Name="ConnectedPadList_P1">Player 1</ComboBoxItem>
<ComboBoxItem x:Name="ConnectedPadList_P2">Player 2</ComboBoxItem>
<ComboBoxItem x:Name="ConnectedPadList_Both">Both</ComboBoxItem>
</ComboBox>
<Label x:Name="P1Connected" Style="{StaticResource EnabledIcon}" Content="P1" Margin="1,0,4,0" FontSize="10"/>
<Label x:Name="P2Connected" Style="{StaticResource EnabledIcon}" Content="P2" Margin="0,0,4,0" FontSize="10"/>
</StackPanel>
</Grid> </Grid>
</Window> </Window>

@ -1,5 +1,8 @@
using System; using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows; using System.Windows;
using System.Windows.Controls;
namespace smx_config namespace smx_config
{ {
@ -44,6 +47,7 @@ namespace smx_config
if(!SMX.SMX.GetConfig(SelectedPad, out copyFromConfig)) if(!SMX.SMX.GetConfig(SelectedPad, out copyFromConfig))
return; return;
// Don't use ActivePad.ActivePads here, since the lights UI handles multiple pads on its own.
for(int pad = 0; pad < 2; ++pad) for(int pad = 0; pad < 2; ++pad)
{ {
SMX.SMXConfig config; SMX.SMXConfig config;
@ -70,28 +74,148 @@ namespace smx_config
Main.Visibility = EitherControllerConnected? Visibility.Visible:Visibility.Hidden; Main.Visibility = EitherControllerConnected? Visibility.Visible:Visibility.Hidden;
Searching.Visibility = EitherControllerConnected? Visibility.Hidden:Visibility.Visible; Searching.Visibility = EitherControllerConnected? Visibility.Hidden:Visibility.Visible;
ConnectedPads.Visibility = EitherControllerConnected? Visibility.Visible:Visibility.Hidden; ConnectedPads.Visibility = EitherControllerConnected? Visibility.Visible:Visibility.Hidden;
P1Connected.IsEnabled = args.controller[0].info.connected;
P2Connected.IsEnabled = args.controller[1].info.connected;
PanelColorP1.Visibility = args.controller[0].info.connected? Visibility.Visible:Visibility.Collapsed; PanelColorP1.Visibility = args.controller[0].info.connected? Visibility.Visible:Visibility.Collapsed;
PanelColorP2.Visibility = args.controller[1].info.connected? Visibility.Visible:Visibility.Collapsed; PanelColorP2.Visibility = args.controller[1].info.connected? Visibility.Visible:Visibility.Collapsed;
RefreshConnectedPadList(args);
// If a second controller has connected and we're on Both, see if we need to prompt
// to sync configs. We only actually need to do this if a controller just connected.
if(args.ConfigurationChanged)
CheckConfiguringBothPads(args);
}
private void ConnectedPadList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBoxItem selection = ConnectedPadList.SelectedItem as ComboBoxItem;
ActivePad.SelectedPad newSelection;
if(selection == ConnectedPadList_P1)
newSelection = ActivePad.SelectedPad.P1;
else if(selection == ConnectedPadList_P2)
newSelection = ActivePad.SelectedPad.P2;
else
newSelection = ActivePad.SelectedPad.Both;
if(ActivePad.selectedPad == newSelection)
return;
ActivePad.selectedPad = newSelection;
// Before firing and updating UI, run CheckConfiguringBothPads to see if we should
// sync the config and/or change the selection again.
CheckConfiguringBothPads(CurrentSMXDevice.singleton.GetState());
CurrentSMXDevice.singleton.FireConfigurationChanged(null);
}
// If the user selects "Both", or connects a second pad while on "Both", both pads need
// to have the same configuration, since we're configuring them together. Check if the
// configuration is out of sync, and ask the user before syncing them up so we don't
// clobber P2's configuration if this wasn't intended.
//
// If the user cancels, change the pad selection to P1 so we don't clobber P2.
private void CheckConfiguringBothPads(LoadFromConfigDelegateArgs args)
{
// Check if we're actually in "Both" mode with two controllers connected. If not,
// we don't have to do anything.
bool Pad1Connected = args.controller[0].info.connected;
bool Pad2Connected = args.controller[1].info.connected;
if(ActivePad.selectedPad != ActivePad.SelectedPad.Both || !Pad1Connected || !Pad2Connected)
return;
// If the two pads have the same configuration, there's nothing to do.
SMX.SMXConfig config1 = args.controller[0].config;
SMX.SMXConfig config2 = args.controller[1].config;
if(ConfigurationsSynced(config1, config2))
return;
string messageBoxText = "The two pads have different settings. Do you want to " +
"match P2 settings to P1 and configure both pads together? (This won't affect panel colors.)";
MessageBoxResult result = MessageBox.Show(messageBoxText, "StepManiaX", MessageBoxButton.YesNo, MessageBoxImage.None);
if(result == MessageBoxResult.Yes)
{
SyncP2FromP1(config1, config2);
return;
}
else
{
// Switch to P1.
ActivePad.selectedPad = ActivePad.SelectedPad.P1;
RefreshConnectedPadList(CurrentSMXDevice.singleton.GetState());
}
}
// Return true if the two pads have the same configuration, so we can configure them together
// without clobbering separate configurations.
bool ConfigurationsSynced(SMX.SMXConfig config1, SMX.SMXConfig config2)
{
if(!Enumerable.SequenceEqual(config1.GetLowThresholds(), config2.GetLowThresholds()))
return false;
if(!Enumerable.SequenceEqual(config1.GetHighThresholds(), config2.GetHighThresholds()))
return false;
if(!Enumerable.SequenceEqual(config1.enabledSensors, config2.enabledSensors))
return false;
return true;
} }
// Copy the P2 pad configuration to P1.
//
// This only copies settings that we actually configure, and it doesn't copy pad
// colors, which is separate from pad selection.
void SyncP2FromP1(SMX.SMXConfig config1, SMX.SMXConfig config2)
{
// Copy P1's configuration to P2.
Array.Copy(config1.enabledSensors, config2.enabledSensors, config1.enabledSensors.Count());
config2.SetLowThresholds(config1.GetLowThresholds());
config2.SetHighThresholds(config1.GetHighThresholds());
SMX.SMX.SetConfig(1, config2);
CurrentSMXDevice.singleton.FireConfigurationChanged(null);
}
// Refresh which items are visible in the connected pads list, and which item is displayed as selected.
private void RefreshConnectedPadList(LoadFromConfigDelegateArgs args)
{
bool TwoControllersConnected = args.controller[0].info.connected && args.controller[1].info.connected;
// Only show the dropdown if two controllers are connected.
ConnectedPadList.Visibility = TwoControllersConnected? Visibility.Visible:Visibility.Collapsed;
// Only show the P1/P2 text if only one controller is connected, since it takes the place
// of the dropdown.
P1Connected.Visibility = (!TwoControllersConnected && args.controller[0].info.connected)? Visibility.Visible:Visibility.Collapsed;
P2Connected.Visibility = (!TwoControllersConnected && args.controller[1].info.connected)? Visibility.Visible:Visibility.Collapsed;
if(!TwoControllersConnected)
return;
// Set the current selection.
ActivePad.SelectedPad selectedPad = ActivePad.selectedPad;
switch(ActivePad.selectedPad)
{
case ActivePad.SelectedPad.P1: ConnectedPadList.SelectedItem = ConnectedPadList_P1; break;
case ActivePad.SelectedPad.P2: ConnectedPadList.SelectedItem = ConnectedPadList_P2; break;
case ActivePad.SelectedPad.Both: ConnectedPadList.SelectedItem = ConnectedPadList_Both; break;
}
}
private void FactoryReset_Click(object sender, RoutedEventArgs e) private void FactoryReset_Click(object sender, RoutedEventArgs e)
{ {
for(int pad = 0; pad < 2; ++pad) foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
{
int pad = activePad.Item1;
SMX.SMX.FactoryReset(pad); SMX.SMX.FactoryReset(pad);
}
CurrentSMXDevice.singleton.FireConfigurationChanged(null); CurrentSMXDevice.singleton.FireConfigurationChanged(null);
} }
private void ExportSettings(object sender, RoutedEventArgs e) private void ExportSettings(object sender, RoutedEventArgs e)
{ {
// Save the current thresholds on the first available pad as a preset. // Save the current thresholds on the first available pad as a preset.
for(int pad = 0; pad < 2; ++pad) foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
{ {
SMX.SMXConfig config; int pad = activePad.Item1;
if(!SMX.SMX.GetConfig(pad, out config)) SMX.SMXConfig config = activePad.Item2;
continue;
string json = SMXHelpers.ExportSettingsToJSON(config); string json = SMXHelpers.ExportSettingsToJSON(config);
Microsoft.Win32.SaveFileDialog dialog = new Microsoft.Win32.SaveFileDialog(); Microsoft.Win32.SaveFileDialog dialog = new Microsoft.Win32.SaveFileDialog();
@ -120,12 +244,11 @@ namespace smx_config
string json = Helpers.ReadFile(dialog.FileName); string json = Helpers.ReadFile(dialog.FileName);
// Apply settings from the file to all connected pads. // Apply settings from the file to all active pads.
for(int pad = 0; pad < 2; ++pad) foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
{ {
SMX.SMXConfig config; int pad = activePad.Item1;
if(!SMX.SMX.GetConfig(pad, out config)) SMX.SMXConfig config = activePad.Item2;
continue;
SMXHelpers.ImportSettingsFromJSON(json, ref config); SMXHelpers.ImportSettingsFromJSON(json, ref config);
SMX.SMX.SetConfig(pad, config); SMX.SMX.SetConfig(pad, config);

@ -93,6 +93,65 @@ namespace SMX
if(panels[7]) enabledSensors[3] |= 0x0F; if(panels[7]) enabledSensors[3] |= 0x0F;
if(panels[8]) enabledSensors[4] |= 0xF0; if(panels[8]) enabledSensors[4] |= 0xF0;
} }
// The layout of this structure (and the underlying C struct) matches the firmware configuration
// data. This is a bit inconvenient for the panel thresholds which aren't contiguous, so these
// helpers just convert them to and from arrays.
public Byte[] GetLowThresholds()
{
return new Byte[] {
panelThreshold0Low,
panelThreshold1Low,
panelThreshold2Low,
panelThreshold3Low,
panelThreshold4Low,
panelThreshold5Low,
panelThreshold6Low,
panelThreshold7Low,
panelThreshold8Low,
};
}
public Byte[] GetHighThresholds()
{
return new Byte[] {
panelThreshold0High,
panelThreshold1High,
panelThreshold2High,
panelThreshold3High,
panelThreshold4High,
panelThreshold5High,
panelThreshold6High,
panelThreshold7High,
panelThreshold8High,
};
}
public void SetLowThresholds(Byte[] values)
{
panelThreshold0Low = values[0];
panelThreshold1Low = values[1];
panelThreshold2Low = values[2];
panelThreshold3Low = values[3];
panelThreshold4Low = values[4];
panelThreshold5Low = values[5];
panelThreshold6Low = values[6];
panelThreshold7Low = values[7];
panelThreshold8Low = values[8];
}
public void SetHighThresholds(Byte[] values)
{
panelThreshold0High = values[0];
panelThreshold1High = values[1];
panelThreshold2High = values[2];
panelThreshold3High = values[3];
panelThreshold4High = values[4];
panelThreshold5High = values[5];
panelThreshold6High = values[6];
panelThreshold7High = values[7];
panelThreshold8High = values[8];
}
}; };
public struct SMXSensorTestModeData public struct SMXSensorTestModeData

@ -35,7 +35,7 @@ namespace smx_config
base.OnApplyTemplate(); base.OnApplyTemplate();
onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) {
LoadUIFromConfig(args.controller[args.FirstController].config); LoadUIFromConfig(ActivePad.GetFirstActivePadConfig(args));
}); });
} }
@ -65,12 +65,10 @@ namespace smx_config
// Stop forcing advanced mode on, and sync the thresholds so we exit advanced mode. // Stop forcing advanced mode on, and sync the thresholds so we exit advanced mode.
ForcedOn = false; ForcedOn = false;
for(int pad = 0; pad < 2; ++pad) foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
{ {
SMX.SMXConfig config; int pad = activePad.Item1;
if(!SMX.SMX.GetConfig(pad, out config)) SMX.SMXConfig config = activePad.Item2;
continue;
ConfigPresets.SyncUnifiedThresholds(ref config); ConfigPresets.SyncUnifiedThresholds(ref config);
SMX.SMX.SetConfig(pad, config); SMX.SMX.SetConfig(pad, config);
} }
@ -83,8 +81,7 @@ namespace smx_config
} }
// Refresh the UI. // Refresh the UI.
LoadFromConfigDelegateArgs args = CurrentSMXDevice.singleton.GetState(); LoadUIFromConfig(ActivePad.GetFirstActivePadConfig());
LoadUIFromConfig(args.controller[args.FirstController].config);
} }
} }
@ -137,7 +134,7 @@ namespace smx_config
slider.ValueChanged += delegate(DoubleSlider slider) { SaveToConfig(); }; slider.ValueChanged += delegate(DoubleSlider slider) { SaveToConfig(); };
onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) {
LoadUIFromConfig(args.controller[args.FirstController].config); LoadUIFromConfig(ActivePad.GetFirstActivePadConfig(args));
}); });
} }
@ -193,11 +190,10 @@ namespace smx_config
return; return;
// Apply the change and save it to the devices. // Apply the change and save it to the devices.
for(int pad = 0; pad < 2; ++pad) foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
{ {
SMX.SMXConfig config; int pad = activePad.Item1;
if(!SMX.SMX.GetConfig(pad, out config)) SMX.SMXConfig config = activePad.Item2;
continue;
SetValueToConfig(ref config); SetValueToConfig(ref config);
SMX.SMX.SetConfig(pad, config); SMX.SMX.SetConfig(pad, config);
@ -233,8 +229,7 @@ namespace smx_config
void RefreshVisibility() void RefreshVisibility()
{ {
LoadFromConfigDelegateArgs args = CurrentSMXDevice.singleton.GetState(); SMX.SMXConfig config = ActivePad.GetFirstActivePadConfig();
SMX.SMXConfig config = args.controller[args.FirstController].config;
this.Visibility = ShouldBeDisplayed(config)? Visibility.Visible:Visibility.Collapsed; this.Visibility = ShouldBeDisplayed(config)? Visibility.Visible:Visibility.Collapsed;
} }
@ -299,18 +294,22 @@ namespace smx_config
button.Click += delegate(object sender, RoutedEventArgs e) { Select(); }; button.Click += delegate(object sender, RoutedEventArgs e) { Select(); };
onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) {
string CurrentPreset = ConfigPresets.GetPreset(args.controller[args.FirstController].config); foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
{
SMX.SMXConfig config = activePad.Item2;
string CurrentPreset = ConfigPresets.GetPreset(config);
Selected = CurrentPreset == Type; Selected = CurrentPreset == Type;
break;
}
}); });
} }
private void Select() private void Select()
{ {
for(int pad = 0; pad < 2; ++pad) foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
{ {
SMX.SMXConfig config; int pad = activePad.Item1;
if(!SMX.SMX.GetConfig(pad, out config)) SMX.SMXConfig config = activePad.Item2;
continue;
ConfigPresets.SetPreset(Type, ref config); ConfigPresets.SetPreset(Type, ref config);
Console.WriteLine("PresetButton::Select (" + Type + "): " + Console.WriteLine("PresetButton::Select (" + Type + "): " +
@ -724,7 +723,7 @@ namespace smx_config
button.Click += EnabledPanelButtonClicked; button.Click += EnabledPanelButtonClicked;
onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) { onConfigChange = new OnConfigChange(this, delegate (LoadFromConfigDelegateArgs args) {
LoadUIFromConfig(args.controller[args.FirstController].config); LoadUIFromConfig(ActivePad.GetFirstActivePadConfig(args));
}); });
} }
@ -768,11 +767,10 @@ namespace smx_config
Console.WriteLine("Clicked " + button); Console.WriteLine("Clicked " + button);
// Set the enabled sensor mask on both pads to the state of the UI. // Set the enabled sensor mask on both pads to the state of the UI.
for(int pad = 0; pad < 2; ++pad) foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
{ {
SMX.SMXConfig config; int pad = activePad.Item1;
if(!SMX.SMX.GetConfig(pad, out config)) SMX.SMXConfig config = activePad.Item2;
continue;
// This could be done algorithmically, but this is clearer. // This could be done algorithmically, but this is clearer.
int[] PanelButtonToSensorIndex = { int[] PanelButtonToSensorIndex = {

Loading…
Cancel
Save