Skip to content

Commit

Permalink
Made it possible to set a custom gamepad mapping via the settings (#2486
Browse files Browse the repository at this point in the history
)
  • Loading branch information
breiler authored Mar 15, 2024
1 parent 7ae52de commit 6a63d63
Show file tree
Hide file tree
Showing 18 changed files with 961 additions and 165 deletions.
6 changes: 6 additions & 0 deletions ugs-core/src/resources/MessagesBundle_en_US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ mainWindow.swing.arrowMovementEnabled = Enable Keyboard Movement
mainWindow.swing.baudLabel = Baud\:
mainWindow.swing.browseButton = Browse
mainWindow.swing.cancelButton = Cancel
mainWindow.swing.okButton = OK
mainWindow.swing.commandLabel = Command\:
mainWindow.swing.connectionPanel = Connection
mainWindow.swing.controlContextTabbedPane.commands = Commands
Expand Down Expand Up @@ -711,6 +712,11 @@ platform.plugin.joystick.action.analogFeed = Analog feed override
platform.plugin.joystick.action.analogSpindle = Analog spindle override
platform.plugin.joystick.reverseAxis = Reverse
platform.plugin.joystick.axisThreshold = Zero threshold (%)
platform.plugin.joystick.axisThreshold.description = You will need to increase the zero threshold if the analog controls are constantly reporting movement when in idle position.
platform.plugin.joystick.customMappings = Custom mappings
platform.plugin.joystick.customMappings.title = Change custom controller mappings
platform.plugin.joystick.connectedTo = Connected to
platform.plugin.joystick.notConnected = Joystick not connected
platform.plugin.cloud.s3Id = AWS Access Key ID
platform.plugin.cloud.s3Secret = AWS Secret Access Key
platform.plugin.cloud.open = Open cloud file
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
Copyright 2024 Will Winder
This file is part of Universal Gcode Sender (UGS).
UGS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
UGS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with UGS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.willwinder.ugs.nbp.joystick;

import com.willwinder.ugs.nbp.joystick.service.JoystickService;
import com.willwinder.universalgcodesender.i18n.Localization;
import net.miginfocom.swing.MigLayout;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;

/**
* @author Joacim Breiler
*/
public class CustomMappingsDialog extends JDialog {

private final transient JoystickService joystickService;
private JButton cancelButton;
private JButton okButton;
private JTextArea textArea;

public CustomMappingsDialog(Component parent, JoystickService joystickService) {
super(SwingUtilities.getWindowAncestor(parent), ModalityType.APPLICATION_MODAL);
this.joystickService = joystickService;
setTitle(Localization.getString("platform.plugin.joystick.customMappings.title"));
setPreferredSize(new Dimension(600, 300));
setLayout(new MigLayout("fill, insets 5", "", ""));
setResizable(true);

createComponents();
addEventListeners();

pack();
setLocationRelativeTo(SwingUtilities.getWindowAncestor(parent));
}

private void addEventListeners() {
cancelButton.addActionListener(event -> onCancel());
okButton.addActionListener(event -> onOk());
}

private void createComponents() {
setLayout(new BorderLayout());

textArea = new JTextArea(Settings.getCustomMapping());
add(new JScrollPane(textArea), BorderLayout.CENTER);

JPanel buttonPanel = new JPanel(new MigLayout("insets 5", "[center, grow]"));
cancelButton = new JButton(Localization.getString("mainWindow.swing.cancelButton"));
buttonPanel.add(cancelButton);

okButton = new JButton(Localization.getString("mainWindow.swing.okButton"));
buttonPanel.add(okButton);
add(buttonPanel, BorderLayout.SOUTH);
getRootPane().setDefaultButton(okButton);
}

private void onCancel() {
setVisible(false);
dispose();
}

private void onOk() {
Settings.setCustomMapping(textArea.getText());
joystickService.destroy();
joystickService.initialize();
setVisible(false);
dispose();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2020 Will Winder
Copyright 2020-2024 Will Winder
This file is part of Universal Gcode Sender (UGS).
Expand All @@ -23,16 +23,26 @@ This file is part of Universal Gcode Sender (UGS).
import com.willwinder.ugs.nbp.joystick.service.JoystickService;
import com.willwinder.ugs.nbp.joystick.service.JoystickServiceListener;
import com.willwinder.ugs.nbp.joystick.ui.BindActionButton;
import com.willwinder.ugs.nbp.joystick.ui.JoystickOptionsActivateRow;
import com.willwinder.ugs.nbp.joystick.ui.ReverseAxisCheckBox;
import com.willwinder.ugs.nbp.joystick.ui.StatusLabel;
import com.willwinder.ugs.nbp.lib.lookup.CentralLookup;
import com.willwinder.ugs.nbp.lib.options.AbstractOptionsPanel;
import com.willwinder.universalgcodesender.i18n.Localization;
import net.miginfocom.swing.MigLayout;

import javax.swing.*;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import java.awt.*;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
Expand All @@ -43,88 +53,91 @@ public class JoystickOptionsPanel extends AbstractOptionsPanel implements Joysti

private final transient JoystickService joystickService;
private final Map<JoystickControl, StatusLabel> statusLabelMap = new EnumMap<>(JoystickControl.class);
private JPanel panel;
private JCheckBox activeCheckbox;
private JPanel leftPanel;
private JPanel rightPanel;
private JSpinner thresholdSpinner;

JoystickOptionsPanel(JoystickOptionsPanelController controller) {
super(controller);
joystickService = CentralLookup.getDefault().lookup(JoystickService.class);
joystickService.addListener(this);

setLayout(new BorderLayout());
clear();
setLayout(new MigLayout("fill"));
removeAll();
}

@Override
public void load() {
if (panel != null) {
this.remove(panel);
}
joystickService.setActivateActionDispatcher(false);

panel = new JPanel(new MigLayout("inset 5"));

activeCheckbox = new JCheckBox(Localization.getString("platform.plugin.joystick.activate"), Settings.isActive());
panel.add(activeCheckbox, "wrap, spanx");

panel.add(new JSeparator(SwingConstants.HORIZONTAL), "wrap, spanx");
panel.add(new JLabel(Localization.getString("platform.plugin.joystick.buttonControls")), "wrap, spanx, hmin 24");

for (JoystickControl joystickControl : JoystickControl.getDigitalControls()) {
String name = Localization.getString(joystickControl.getLocalization());
StatusLabel label = new StatusLabel(name);
statusLabelMap.put(joystickControl, label);
panel.add(label, "wmin 100, hmin 24");
panel.add(new BindActionButton(joystickService, joystickControl), "wmin 150, hmin 24, wrap");
}
JoystickOptionsActivateRow activateRow;
removeAll();
activateRow = new JoystickOptionsActivateRow(joystickService);
activateRow.addActionListener(event -> {
leftPanel.setVisible(activateRow.isActive());
rightPanel.setVisible(activateRow.isActive());
});
add(activateRow, "growx, spanx, wrap, gapbottom 10");

createLeftPanel();
createRightPanel();
SwingUtilities.invokeLater(changer::changed);
}

panel.add(new JSeparator(SwingConstants.HORIZONTAL), "wrap, spanx");
panel.add(new JLabel(Localization.getString("platform.plugin.joystick.analogControls")), "wrap, spanx, hmin 24");
private void createRightPanel() {

panel.add(new JLabel(Localization.getString("platform.plugin.joystick.axisThreshold")), "wmin 100, hmin 24");
thresholdSpinner = new JSpinner(new SpinnerNumberModel(Settings.getAxisThreshold() * 100, 0, 100, 1));
thresholdSpinner.addChangeListener(this::onThresholdChange);
panel.add(thresholdSpinner, "wmin 150, hmin 24, wrap");
rightPanel = new JPanel(new MigLayout("fillx"));
rightPanel.setVisible(Settings.isActive());
rightPanel.setBorder(new CompoundBorder(new TitledBorder(Localization.getString("platform.plugin.joystick.analogControls")), new EmptyBorder(5, 5, 5, 5)));

for (JoystickControl joystickControl : JoystickControl.getAnalogControls()) {
String name = Localization.getString(joystickControl.getLocalization());
StatusLabel label = new StatusLabel(name);
statusLabelMap.put(joystickControl, label);
panel.add(label, "wmin 100, hmin 24");

rightPanel.add(label, "wmin 150, hmin 24");

JCheckBox reverseAxis = null;
String wrap = ", wrap";
if (REVERSIBLE_CONTROLS.contains(joystickControl)) {
reverseAxis = new ReverseAxisCheckBox(joystickService, joystickControl);
wrap = "";
}
panel.add(new BindActionButton(joystickService, joystickControl), "wmin 150, hmin 24" + wrap);
rightPanel.add(new BindActionButton(joystickService, joystickControl), "w 150:150:150, hmin 24" + wrap);
if (reverseAxis != null) {
panel.add(reverseAxis, "wrap");
rightPanel.add(reverseAxis, "wrap");
}
}

add(panel, BorderLayout.CENTER);
SwingUtilities.invokeLater(changer::changed);
rightPanel.add(new JSeparator(SwingConstants.HORIZONTAL), "hmin 24, gaptop 20, growx, wrap, spanx");
rightPanel.add(new JLabel("<html><body><p style='width: 320px;'>" + Localization.getString("platform.plugin.joystick.axisThreshold.description") + "</p></body></html>"), "spanx, grow, wrap, gapbottom 10");

rightPanel.add(new JLabel(Localization.getString("platform.plugin.joystick.axisThreshold")), "wmin 100, hmin 24");
thresholdSpinner = new JSpinner(new SpinnerNumberModel(Settings.getAxisThreshold() * 100, 0, 100, 1));
thresholdSpinner.addChangeListener(this::onThresholdChange);
rightPanel.add(thresholdSpinner, "wmin 150, hmin 24, wrap");

add(rightPanel, "grow");
}

private void createLeftPanel() {
leftPanel = new JPanel(new MigLayout("fillx"));
leftPanel.setVisible(Settings.isActive());
leftPanel.setBorder(new TitledBorder(Localization.getString("platform.plugin.joystick.buttonControls")));

for (JoystickControl joystickControl : JoystickControl.getDigitalControls()) {
String name = Localization.getString(joystickControl.getLocalization());
StatusLabel label = new StatusLabel(name);
statusLabelMap.put(joystickControl, label);
leftPanel.add(label, "wmin 150, hmin 24");
leftPanel.add(new BindActionButton(joystickService, joystickControl), "w 150:150:150, hmin 24, wrap");
}
add(leftPanel, "grow, gapright 10");
}

private void onThresholdChange(ChangeEvent changeEvent) {
Settings.setAxisThreshold(((Double)thresholdSpinner.getValue()).intValue() / 100f);
Settings.setAxisThreshold(((Double) thresholdSpinner.getValue()).intValue() / 100f);
}

@Override
public void store() {
joystickService.setActivateActionDispatcher(true);

if (activeCheckbox.isSelected()) {
joystickService.initialize();
} else {
joystickService.destroy();
}

Settings.setActive(activeCheckbox.isSelected());
}

@Override
Expand All @@ -141,14 +154,19 @@ public void cancel() {
public void onUpdate(JoystickState state) {
for (JoystickControl control : JoystickControl.getDigitalControls()) {
StatusLabel label = statusLabelMap.get(control);
boolean isPressed = state.getButton(control);
label.setActive(isPressed);
if (label != null) {
boolean isPressed = state.getButton(control);
label.setActive(isPressed);
}
}

for (JoystickControl control : JoystickControl.getAnalogControls()) {
StatusLabel label = statusLabelMap.get(control);
float value = state.getAxis(control);
label.setActive(value != 0);
if (label != null) {
label.setActive(value != 0);
label.setAnalogValue(value);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,18 @@ This file is part of Universal Gcode Sender (UGS).
public class Settings {
public static final String SETTINGS_ACTIVE = "active";
public static final String SETTINGS_VERSION = "version";
public static final String SETTINGS_CUSTOM_MAPPING = "customMapping";

private static Preferences preferences = NbPreferences.forModule(JoystickService.class);
public static final String DEFAULT_CUSTOM_MAPPING = "# Custom UGS controllers\n" +
"03000000412300003780000000000000,Arduino Micro,a:b0,b:b1,x:b2,y:b3,back:b4,guide:b5,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b9,rightshoulder:b10,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,-leftx:a0,+leftx:a1,-lefty:a2,+lefty:a3,-rightx:a4,+rightx:a5,-righty:a6,-righty:a7,lefttrigger:b11,righttrigger:b12,platform:Windows,\n" +
"03000000412300003e00000000000000,Arduino Due,platform:Windows,a:b10,b:b8,x:b9,y:b11,guide:b12,leftshoulder:b0,rightshoulder:b1,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0,\n" +
"03000000072000004512000000000000,JOYSTICK FZ,platform:Windows,guide:b0,leftstick:b4,rightstick:b1,leftshoulder:b2,rightshoulder:b3,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a5,righty:a4~,\n" +
"78696e70757401000000000000000000,XInput Controller,platform:Windows,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,guide:b10,leftshoulder:b4,rightshoulder:b5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,\n" +
"03000000786901006e70000000000000,XInput Controller,platform:Windows,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,\n" +
"030000006d04000015c2000000000000,Logitech Extreme 3D Pro M/N: J-UK17 P/N: 863225-1000,a:b11,b:b10,x:b8,y:b9,back:b6,guide:b7,start:b5,leftstick:b2,rightstick:-a1,leftshoulder:b4,rightshoulder:b0,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a2,platform:Windows,\n" +
"03000000790000000600000000000000,G-Shark GS-GP702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,\n";

private static final Preferences preferences = NbPreferences.forModule(JoystickService.class);

protected Settings() {
}
Expand Down Expand Up @@ -101,7 +111,7 @@ public static String getActionMapping(JoystickControl joystickControl) {
* Sets if the axis values should be inverted
*
* @param joystickControl the axis we wish change settings for
* @param reversed if the axis should be inverted
* @param reversed if the axis should be inverted
*/
public static void setReverseAxis(JoystickControl joystickControl, boolean reversed) {
preferences.putBoolean(joystickControl.name() + "_reverse", reversed);
Expand Down Expand Up @@ -135,4 +145,22 @@ public static float getAxisThreshold() {
public static void setAxisThreshold(float threshold) {
preferences.putFloat("axisThreshold", threshold);
}

/**
* Gets the custom mapping in the gamecontrollerdb-format (https://github.com/gabomdq/SDL_GameControllerDB)
*
* @return the custom mapping
*/
public static String getCustomMapping() {
return preferences.get(SETTINGS_CUSTOM_MAPPING, DEFAULT_CUSTOM_MAPPING);
}

/**
* Sets the custom mapping in the gamecontrollerdb-format (https://github.com/gabomdq/SDL_GameControllerDB)
*
* @param customMapping the custom mapping
*/
public static void setCustomMapping(String customMapping) {
preferences.put(SETTINGS_CUSTOM_MAPPING, customMapping);
}
}
Loading

0 comments on commit 6a63d63

Please sign in to comment.