summaryrefslogtreecommitdiff
path: root/src/setup
diff options
context:
space:
mode:
authorSimon Howard2014-05-16 01:12:36 -0400
committerSimon Howard2014-05-16 01:12:36 -0400
commitbbc098c3ee39ed3167d020275bf1df039df8fd41 (patch)
tree8691bfae37e7f878d6f2157d3e252920b08186f9 /src/setup
parent4025b46a5c2e96168e18673733216f984ba1deae (diff)
downloadchocolate-doom-bbc098c3ee39ed3167d020275bf1df039df8fd41.tar.gz
chocolate-doom-bbc098c3ee39ed3167d020275bf1df039df8fd41.tar.bz2
chocolate-doom-bbc098c3ee39ed3167d020275bf1df039df8fd41.zip
setup: Factor out axis configuration to widget.
Move code for configuring joystick axes into a separate widget, and add axis widgets to the configuration window for all possible movement.
Diffstat (limited to 'src/setup')
-rw-r--r--src/setup/Makefile.am1
-rw-r--r--src/setup/joystick.c410
-rw-r--r--src/setup/txt_joyaxis.c527
-rw-r--r--src/setup/txt_joyaxis.h90
4 files changed, 672 insertions, 356 deletions
diff --git a/src/setup/Makefile.am b/src/setup/Makefile.am
index 27de0add..1a1e1ccb 100644
--- a/src/setup/Makefile.am
+++ b/src/setup/Makefile.am
@@ -18,6 +18,7 @@ SOURCE_FILES = \
multiplayer.c multiplayer.h \
sound.c sound.h \
execute.c execute.h \
+ txt_joyaxis.c txt_joyaxis.h \
txt_joybinput.c txt_joybinput.h \
txt_keyinput.c txt_keyinput.h \
txt_mouseinput.c txt_mouseinput.h
diff --git a/src/setup/joystick.c b/src/setup/joystick.c
index 1b2a00f3..41bde51c 100644
--- a/src/setup/joystick.c
+++ b/src/setup/joystick.c
@@ -25,19 +25,9 @@
#include "execute.h"
#include "joystick.h"
#include "mode.h"
+#include "txt_joyaxis.h"
#include "txt_joybinput.h"
-typedef enum
-{
- CALIBRATE_CENTER,
- CALIBRATE_LEFT,
- CALIBRATE_UP,
-
- // These are only used when defining button axes:
- CALIBRATE_RIGHT,
- CALIBRATE_DOWN,
-} calibration_stage_t;
-
typedef struct
{
char *name; // Config file name
@@ -69,15 +59,6 @@ int joystick_index = -1;
static int calibrate_button = -1;
-// "Bad" joystick axes. Sometimes an axis can be stuck or "bad". An
-// example I found is that if you unplug the nunchuck extension from
-// a Wii remote, the axes from the nunchuck can be stuck at one of
-// the maximum values. These have to be ignored, so when we ask the
-// user to center the joystick, we look for bad axes that are not
-// close to zero.
-
-static boolean *bad_axis = NULL;
-
// Which joystick axis to use for horizontal movement, and whether to
// invert the direction:
@@ -101,14 +82,14 @@ int joystick_physical_buttons[NUM_VIRTUAL_BUTTONS] = {
};
static txt_button_t *joystick_button;
+static txt_joystick_axis_t *x_axis_widget;
+static txt_joystick_axis_t *y_axis_widget;
//
// Calibration
//
static txt_window_t *calibration_window;
-static txt_label_t *calibration_label;
-static calibration_stage_t calibrate_stage;
static SDL_Joystick **all_joysticks = NULL;
// Known controllers.
@@ -468,262 +449,13 @@ static void CloseAllJoysticks(void)
all_joysticks = NULL;
}
-static void SetCalibrationLabel(void)
-{
- char *message = "???";
-
- switch (calibrate_stage)
- {
- case CALIBRATE_CENTER:
- message = "Move the D-pad or joystick to the\n"
- "center, and press a button.";
- break;
- case CALIBRATE_UP:
- message = "Push the D-pad or joystick up,\n"
- "and press the button.";
- break;
- case CALIBRATE_LEFT:
- message = "Push the D-pad or joystick to the\n"
- "left, and press the button.";
- break;
- case CALIBRATE_DOWN:
- message = "Push the D-pad or joystick down,\n"
- "and press the button.";
- break;
- case CALIBRATE_RIGHT:
- message = "Push the D-pad or joystick to the\n"
- "right, and press the button.";
- break;
- }
-
- TXT_SetLabel(calibration_label, message);
-}
-
-// Search all axes on joystick being configured; find a button that is
-// pressed (other than the calibrate button). Returns the button number.
-
-static int FindPressedAxisButton(void)
-{
- SDL_Joystick *joystick;
- int i;
-
- joystick = all_joysticks[joystick_index];
-
- for (i = 0; i < SDL_JoystickNumButtons(joystick); ++i)
- {
- if (i == calibrate_button)
- {
- continue;
- }
-
- if (SDL_JoystickGetButton(joystick, i))
- {
- return i;
- }
- }
-
- return -1;
-}
-
-// Look for a hat that isn't centered. Returns the encoded hat axis.
-
-static int FindUncenteredHat(int *axis_invert)
-{
- SDL_Joystick *joystick;
- int i, hatval;
-
- joystick = all_joysticks[joystick_index];
-
- for (i = 0; i < SDL_JoystickNumHats(joystick); ++i)
- {
- hatval = SDL_JoystickGetHat(joystick, i);
-
- switch (hatval)
- {
- case SDL_HAT_LEFT:
- case SDL_HAT_RIGHT:
- *axis_invert = hatval != SDL_HAT_LEFT;
- return CREATE_HAT_AXIS(i, HAT_AXIS_HORIZONTAL);
-
- case SDL_HAT_UP:
- case SDL_HAT_DOWN:
- *axis_invert = hatval != SDL_HAT_UP;
- return CREATE_HAT_AXIS(i, HAT_AXIS_VERTICAL);
-
- // If the hat is centered, or is not pointing in a
- // definite direction, then ignore it. We don't accept
- // the hat being pointed to the upper-left for example,
- // because it's ambiguous.
- case SDL_HAT_CENTERED:
- default:
- break;
- }
- }
-
- // None found.
- return -1;
-}
-
-static boolean CalibrateAxis(int *axis_index, int *axis_invert)
-{
- SDL_Joystick *joystick;
- int best_axis;
- int best_value;
- int best_invert;
- Sint16 axis_value;
- int i;
-
- joystick = all_joysticks[joystick_index];
-
- // Check all axes to find which axis has the largest value. We test
- // for one axis at a time, so eg. when we prompt to push the joystick
- // left, whichever axis has the largest value is the left axis.
-
- best_axis = 0;
- best_value = 0;
- best_invert = 0;
-
- for (i=0; i<SDL_JoystickNumAxes(joystick); ++i)
- {
- if (bad_axis[i])
- {
- continue;
- }
-
- axis_value = SDL_JoystickGetAxis(joystick, i);
-
- if (abs(axis_value) > best_value)
- {
- best_value = abs(axis_value);
- best_invert = axis_value > 0;
- best_axis = i;
- }
- }
-
- // Did we find one axis that had a significant value?
-
- if (best_value > 32768 / 4)
- {
- // Save the best values we have found
-
- *axis_index = best_axis;
- *axis_invert = best_invert;
- return true;
- }
-
- // Otherwise, maybe this is a "button axis", like the PS3 SIXAXIS
- // controller that exposes the D-pad as four individual buttons.
- // Search for a button.
-
- i = FindPressedAxisButton();
-
- if (i >= 0)
- {
- *axis_index = CREATE_BUTTON_AXIS(i, 0);
- *axis_invert = 0;
- return true;
- }
-
- // Maybe it's a D-pad that is presented as a hat. This sounds weird
- // but gamepads like this really do exist; an example is the
- // Nyko AIRFLO Ex.
-
- i = FindUncenteredHat(axis_invert);
-
- if (i >= 0)
- {
- *axis_index = i;
- return true;
- }
-
- // User pressed the button without pushing the joystick anywhere.
- return false;
-}
-
-static boolean SetButtonAxisPositive(int *axis_index)
+static void CalibrateXAxis(void)
{
- int button;
-
- button = FindPressedAxisButton();
-
- if (button >= 0)
- {
- *axis_index |= CREATE_BUTTON_AXIS(0, button);
- return true;
- }
-
- return false;
-}
-
-static int NextCalibrateStage(void)
-{
- switch (calibrate_stage)
- {
- case CALIBRATE_CENTER:
- return CALIBRATE_LEFT;
-
- // After pushing to the left, there are two possibilities:
- // either it is a button axis, in which case we need to find
- // the other button, or we can just move on to the next axis.
- case CALIBRATE_LEFT:
- if (IS_BUTTON_AXIS(joystick_x_axis))
- {
- return CALIBRATE_RIGHT;
- }
- else
- {
- return CALIBRATE_UP;
- }
-
- case CALIBRATE_RIGHT:
- return CALIBRATE_UP;
-
- case CALIBRATE_UP:
- if (IS_BUTTON_AXIS(joystick_y_axis))
- {
- return CALIBRATE_DOWN;
- }
- else
- {
- // Finished.
- return CALIBRATE_CENTER;
- }
-
- case CALIBRATE_DOWN:
- // Finished.
- return CALIBRATE_CENTER;
- }
-}
-
-static void IdentifyBadAxes(void)
-{
- SDL_Joystick *joystick;
- int i, val;
-
- free(bad_axis);
-
- joystick = all_joysticks[joystick_index];
- bad_axis = calloc(SDL_JoystickNumAxes(joystick), sizeof(boolean));
-
- // Look for uncentered axes.
-
- for (i = 0; i < SDL_JoystickNumAxes(joystick); ++i)
- {
- val = SDL_JoystickGetAxis(joystick, i);
-
- bad_axis[i] = abs(val) > (32768 / 5);
-
- if (bad_axis[i])
- {
- printf("Ignoring uncentered joystick axis #%i: %i\n", i, val);
- }
- }
+ TXT_ConfigureJoystickAxis(x_axis_widget, calibrate_button, NULL);
}
static int CalibrationEventCallback(SDL_Event *event, void *user_data)
{
- boolean advance;
-
if (event->type != SDL_JOYBUTTONDOWN)
{
return 0;
@@ -732,75 +464,28 @@ static int CalibrationEventCallback(SDL_Event *event, void *user_data)
// At this point, we have a button press.
// In the first "center" stage, we're just trying to work out which
// joystick is being configured and which button the user is pressing.
- if (calibrate_stage == CALIBRATE_CENTER)
- {
- joystick_index = event->jbutton.which;
- calibrate_button = event->jbutton.button;
- IdentifyBadAxes();
+ joystick_index = event->jbutton.which;
+ calibrate_button = event->jbutton.button;
- // If the joystick is a known one, auto-load default
- // config for it.
- if (IsKnownJoystick(joystick_index))
- {
- LoadKnownConfiguration();
- usejoystick = 1;
- TXT_CloseWindow(calibration_window);
- }
- else
- {
- // Advance to next stage.
- calibrate_stage = CALIBRATE_LEFT;
- SetCalibrationLabel();
- }
-
- return 1;
+ // If the joystick is a known one, auto-load default
+ // config for it. Otherwise, proceed with calibration.
+ if (IsKnownJoystick(joystick_index))
+ {
+ LoadKnownConfiguration();
+ usejoystick = 1;
+ TXT_CloseWindow(calibration_window);
}
-
- // In subsequent stages, the user is asked to push in a specific
- // direction and press the button. They must push the same button
- // as they did before; this is necessary to support button axes.
- if (event->jbutton.which == joystick_index
- && event->jbutton.button == calibrate_button)
+ else
{
- switch (calibrate_stage)
- {
- default:
- case CALIBRATE_LEFT:
- advance = CalibrateAxis(&joystick_x_axis, &joystick_x_invert);
- break;
-
- case CALIBRATE_RIGHT:
- advance = SetButtonAxisPositive(&joystick_x_axis);
- break;
-
- case CALIBRATE_UP:
- advance = CalibrateAxis(&joystick_y_axis, &joystick_y_invert);
- break;
-
- case CALIBRATE_DOWN:
- advance = SetButtonAxisPositive(&joystick_y_axis);
- break;
- }
-
- // Advance to the next calibration stage?
+ TXT_CloseWindow(calibration_window);
- if (advance)
- {
- calibrate_stage = NextCalibrateStage();
- SetCalibrationLabel();
-
- // Finished?
- if (calibrate_stage == CALIBRATE_CENTER)
- {
- usejoystick = 1;
- TXT_CloseWindow(calibration_window);
- }
-
- return 1;
- }
+ // Calibrate joystick axes: Y axis first, then X axis once
+ // completed.
+ TXT_ConfigureJoystickAxis(y_axis_widget, calibrate_button,
+ CalibrateXAxis);
}
- return 0;
+ return 1;
}
static void NoJoystick(void)
@@ -823,8 +508,6 @@ static void CalibrateWindowClosed(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused)
static void CalibrateJoystick(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
{
- calibrate_stage = CALIBRATE_CENTER;
-
// Try to open all available joysticks. If none are opened successfully,
// bomb out with an error.
@@ -837,10 +520,9 @@ static void CalibrateJoystick(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
calibration_window = TXT_NewWindow("Gamepad/Joystick calibration");
TXT_AddWidgets(calibration_window,
- TXT_NewLabel("Please follow the following instructions\n"
- "in order to calibrate your controller."),
TXT_NewStrut(0, 1),
- calibration_label = TXT_NewLabel("zzz"),
+ TXT_NewLabel("Center the D-pad or joystick,\n"
+ "and press a button."),
TXT_NewStrut(0, 1),
NULL);
@@ -849,7 +531,6 @@ static void CalibrateJoystick(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
TXT_NewWindowAbortAction(calibration_window));
TXT_SetWindowAction(calibration_window, TXT_HORIZ_RIGHT, NULL);
- TXT_SetWidgetAlign(calibration_label, TXT_HORIZ_CENTER);
TXT_SDL_SetEventCallback(CalibrationEventCallback, NULL);
TXT_SignalConnect(calibration_window, "closed", CalibrateWindowClosed, NULL);
@@ -857,9 +538,6 @@ static void CalibrateJoystick(TXT_UNCAST_ARG(widget), TXT_UNCAST_ARG(unused))
// Start calibration
joystick_index = -1;
- calibrate_stage = CALIBRATE_CENTER;
-
- SetCalibrationLabel();
}
//
@@ -888,10 +566,10 @@ static void AddJoystickControl(txt_table_t *table, char *label, int *var)
void ConfigJoystick(void)
{
txt_window_t *window;
- txt_table_t *button_table;
+ txt_table_t *button_table, *axis_table;
txt_table_t *joystick_table;
- if (!joystick_initted)
+ if (!joystick_initted)
{
joystick_initted = SDL_Init(SDL_INIT_JOYSTICK) >= 0;
}
@@ -901,8 +579,27 @@ void ConfigJoystick(void)
TXT_AddWidgets(window,
TXT_NewCheckBox("Enable gamepad/joystick", &usejoystick),
joystick_table = TXT_NewTable(2),
+ TXT_NewSeparator("Axes"),
+ axis_table = TXT_NewTable(2),
TXT_NewSeparator("Buttons"),
- button_table = TXT_NewTable(2),
+ button_table = TXT_NewTable(4),
+ NULL);
+
+ TXT_SetColumnWidths(axis_table, 20, 15);
+
+ TXT_AddWidgets(axis_table,
+ TXT_NewLabel("Forward/backward"),
+ y_axis_widget = TXT_NewJoystickAxis(&joystick_y_axis,
+ &joystick_y_invert,
+ JOYSTICK_AXIS_VERTICAL),
+ TXT_NewLabel("Turn left/right"),
+ x_axis_widget = TXT_NewJoystickAxis(&joystick_x_axis,
+ &joystick_x_invert,
+ JOYSTICK_AXIS_HORIZONTAL),
+ TXT_NewLabel("Strafe left/right"),
+ TXT_NewJoystickAxis(&joystick_strafe_axis,
+ &joystick_strafe_invert,
+ JOYSTICK_AXIS_HORIZONTAL),
NULL);
TXT_SetColumnWidths(joystick_table, 20, 15);
@@ -912,10 +609,18 @@ void ConfigJoystick(void)
joystick_button = TXT_NewButton("zzzz"),
NULL);
- TXT_SetColumnWidths(button_table, 20, 15);
+ TXT_SetColumnWidths(button_table, 16, 12, 14, 11);
AddJoystickControl(button_table, "Fire/Attack", &joybfire);
+ AddJoystickControl(button_table, "Strafe Left", &joybstrafeleft);
+
AddJoystickControl(button_table, "Use", &joybuse);
+ AddJoystickControl(button_table, "Strafe Right", &joybstraferight);
+
+ AddJoystickControl(button_table, "Previous weapon", &joybprevweapon);
+ AddJoystickControl(button_table, "Strafe", &joybstrafe);
+
+ AddJoystickControl(button_table, "Next weapon", &joybnextweapon);
// High values of joybspeed are used to activate the "always run mode"
// trick in Vanilla Doom. If this has been enabled, not only is the
@@ -926,13 +631,6 @@ void ConfigJoystick(void)
AddJoystickControl(button_table, "Speed", &joybspeed);
}
- AddJoystickControl(button_table, "Strafe", &joybstrafe);
-
- AddJoystickControl(button_table, "Strafe Left", &joybstrafeleft);
- AddJoystickControl(button_table, "Strafe Right", &joybstraferight);
- AddJoystickControl(button_table, "Previous weapon", &joybprevweapon);
- AddJoystickControl(button_table, "Next weapon", &joybnextweapon);
-
if (gamemission == hexen || gamemission == strife)
{
AddJoystickControl(button_table, "Jump", &joybjump);
diff --git a/src/setup/txt_joyaxis.c b/src/setup/txt_joyaxis.c
new file mode 100644
index 00000000..927c0473
--- /dev/null
+++ b/src/setup/txt_joyaxis.c
@@ -0,0 +1,527 @@
+//
+// Copyright(C) 2014 Simon Howard
+//
+// This program 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 2
+// of the License, or (at your option) any later version.
+//
+// This program 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.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "SDL.h"
+
+#include "joystick.h"
+#include "i_joystick.h"
+#include "i_system.h"
+#include "m_controls.h"
+#include "m_misc.h"
+
+#include "textscreen.h"
+#include "txt_gui.h"
+#include "txt_io.h"
+#include "txt_joyaxis.h"
+
+#define JOYSTICK_AXIS_WIDTH 24
+
+static char *CalibrationLabel(txt_joystick_axis_t *joystick_axis)
+{
+ switch (joystick_axis->config_stage)
+ {
+ case CONFIG_CENTER:
+ return "Center the D-pad or joystick,\n"
+ "and press a button.";
+
+ case CONFIG_STAGE1:
+ if (joystick_axis->dir == JOYSTICK_AXIS_VERTICAL)
+ {
+ return "Push the D-pad or joystick up,\n"
+ "and press the button.";
+ }
+ else
+ {
+ return "Push the D-pad or joystick to the\n"
+ "left, and press the button.";
+ }
+
+ case CONFIG_STAGE2:
+ if (joystick_axis->dir == JOYSTICK_AXIS_VERTICAL)
+ {
+ return "Push the D-pad or joystick down,\n"
+ "and press the button.";
+ }
+ else
+ {
+ return "Push the D-pad or joystick to the\n"
+ "right, and press the button.";
+ }
+ }
+}
+
+static void SetCalibrationLabel(txt_joystick_axis_t *joystick_axis)
+{
+ TXT_SetLabel(joystick_axis->config_label, CalibrationLabel(joystick_axis));
+}
+
+// Search all axes on joystick being configured; find a button that is
+// pressed (other than the calibrate button). Returns the button number.
+
+static int FindPressedAxisButton(txt_joystick_axis_t *joystick_axis)
+{
+ int i;
+
+ for (i = 0; i < SDL_JoystickNumButtons(joystick_axis->joystick); ++i)
+ {
+ if (i == joystick_axis->config_button)
+ {
+ continue;
+ }
+
+ if (SDL_JoystickGetButton(joystick_axis->joystick, i))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+// Look for a hat that isn't centered. Returns the encoded hat axis.
+
+static int FindUncenteredHat(SDL_Joystick *joystick, int *axis_invert)
+{
+ int i, hatval;
+
+ for (i = 0; i < SDL_JoystickNumHats(joystick); ++i)
+ {
+ hatval = SDL_JoystickGetHat(joystick, i);
+
+ switch (hatval)
+ {
+ case SDL_HAT_LEFT:
+ case SDL_HAT_RIGHT:
+ *axis_invert = hatval != SDL_HAT_LEFT;
+ return CREATE_HAT_AXIS(i, HAT_AXIS_HORIZONTAL);
+
+ case SDL_HAT_UP:
+ case SDL_HAT_DOWN:
+ *axis_invert = hatval != SDL_HAT_UP;
+ return CREATE_HAT_AXIS(i, HAT_AXIS_VERTICAL);
+
+ // If the hat is centered, or is not pointing in a
+ // definite direction, then ignore it. We don't accept
+ // the hat being pointed to the upper-left for example,
+ // because it's ambiguous.
+ case SDL_HAT_CENTERED:
+ default:
+ break;
+ }
+ }
+
+ // None found.
+ return -1;
+}
+
+static boolean CalibrateAxis(txt_joystick_axis_t *joystick_axis)
+{
+ int best_axis;
+ int best_value;
+ int best_invert;
+ Sint16 axis_value;
+ int i;
+
+ // Check all axes to find which axis has the largest value. We test
+ // for one axis at a time, so eg. when we prompt to push the joystick
+ // left, whichever axis has the largest value is the left axis.
+
+ best_axis = 0;
+ best_value = 0;
+ best_invert = 0;
+
+ for (i = 0; i < SDL_JoystickNumAxes(joystick_axis->joystick); ++i)
+ {
+ //if (bad_axis[i])
+ //{
+ // continue;
+ //}
+
+ axis_value = SDL_JoystickGetAxis(joystick_axis->joystick, i);
+
+ if (abs(axis_value) > best_value)
+ {
+ best_value = abs(axis_value);
+ best_invert = axis_value > 0;
+ best_axis = i;
+ }
+ }
+
+ // Did we find one axis that had a significant value?
+
+ if (best_value > 32768 / 4)
+ {
+ // Save the best values we have found
+
+ *joystick_axis->axis = best_axis;
+ *joystick_axis->invert = best_invert;
+ return true;
+ }
+
+ // Otherwise, maybe this is a "button axis", like the PS3 SIXAXIS
+ // controller that exposes the D-pad as four individual buttons.
+ // Search for a button.
+
+ i = FindPressedAxisButton(joystick_axis);
+
+ if (i >= 0)
+ {
+ *joystick_axis->axis = CREATE_BUTTON_AXIS(i, 0);
+ *joystick_axis->invert = 0;
+ return true;
+ }
+
+ // Maybe it's a D-pad that is presented as a hat. This sounds weird
+ // but gamepads like this really do exist; an example is the
+ // Nyko AIRFLO Ex.
+
+ i = FindUncenteredHat(joystick_axis->joystick, joystick_axis->invert);
+
+ if (i >= 0)
+ {
+ *joystick_axis->axis = i;
+ return true;
+ }
+
+ // User pressed the button without pushing the joystick anywhere.
+ return false;
+}
+
+static boolean SetButtonAxisPositive(txt_joystick_axis_t *joystick_axis)
+{
+ int button;
+
+ button = FindPressedAxisButton(joystick_axis);
+
+ if (button >= 0)
+ {
+ *joystick_axis->axis |= CREATE_BUTTON_AXIS(0, button);
+ return true;
+ }
+
+ return false;
+}
+
+static void IdentifyBadAxes(txt_joystick_axis_t *joystick_axis)
+{
+ int i, val;
+
+ free(joystick_axis->bad_axis);
+
+ joystick_axis->bad_axis
+ = calloc(SDL_JoystickNumAxes(joystick_axis->joystick),
+ sizeof(boolean));
+
+ // Look for uncentered axes.
+
+ for (i = 0; i < SDL_JoystickNumAxes(joystick_axis->joystick); ++i)
+ {
+ val = SDL_JoystickGetAxis(joystick_axis->joystick, i);
+
+ joystick_axis->bad_axis[i] = abs(val) > (32768 / 5);
+
+ if (joystick_axis->bad_axis[i])
+ {
+ printf("Ignoring uncentered joystick axis #%i: %i\n", i, val);
+ }
+ }
+}
+
+static int NextCalibrateStage(txt_joystick_axis_t *joystick_axis)
+{
+ switch (joystick_axis->config_stage)
+ {
+ case CONFIG_CENTER:
+ return CONFIG_STAGE1;
+
+ // After pushing to the left, there are two possibilities:
+ // either it is a button axis, in which case we need to find
+ // the other button, or we can just move on to the next axis.
+ case CONFIG_STAGE1:
+ if (IS_BUTTON_AXIS(*joystick_axis->axis))
+ {
+ return CONFIG_STAGE2;
+ }
+ else
+ {
+ return CONFIG_CENTER;
+ }
+
+ case CONFIG_STAGE2:
+ return CONFIG_CENTER;
+ }
+}
+
+static int EventCallback(SDL_Event *event, TXT_UNCAST_ARG(joystick_axis))
+{
+ TXT_CAST_ARG(txt_joystick_axis_t, joystick_axis);
+ boolean advance;
+
+ if (event->type != SDL_JOYBUTTONDOWN)
+ {
+ return 0;
+ }
+
+ // At this point, we have a button press.
+ // In the first "center" stage, we're just trying to work out which
+ // joystick is being configured and which button the user is pressing.
+ if (joystick_axis->config_stage == CONFIG_CENTER)
+ {
+ joystick_index = event->jbutton.which;
+ joystick_axis->config_button = event->jbutton.button;
+ IdentifyBadAxes(joystick_axis);
+
+ // Advance to next stage.
+ joystick_axis->config_stage = CONFIG_STAGE1;
+ SetCalibrationLabel(joystick_axis);
+
+ return 1;
+ }
+
+ // In subsequent stages, the user is asked to push in a specific
+ // direction and press the button. They must push the same button
+ // as they did before; this is necessary to support button axes.
+ if (event->jbutton.which == joystick_index
+ && event->jbutton.button == joystick_axis->config_button)
+ {
+ switch (joystick_axis->config_stage)
+ {
+ default:
+ case CONFIG_STAGE1:
+ advance = CalibrateAxis(joystick_axis);
+ break;
+
+ case CONFIG_STAGE2:
+ advance = SetButtonAxisPositive(joystick_axis);
+ break;
+ }
+
+ // Advance to the next calibration stage?
+
+ if (advance)
+ {
+ joystick_axis->config_stage = NextCalibrateStage(joystick_axis);
+ SetCalibrationLabel(joystick_axis);
+
+ // Finished?
+ if (joystick_axis->config_stage == CONFIG_CENTER)
+ {
+ TXT_CloseWindow(joystick_axis->config_window);
+
+ if (joystick_axis->callback != NULL)
+ {
+ joystick_axis->callback();
+ }
+ }
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void CalibrateWindowClosed(TXT_UNCAST_ARG(widget),
+ TXT_UNCAST_ARG(joystick_axis))
+{
+ TXT_CAST_ARG(txt_joystick_axis_t, joystick_axis);
+
+ free(joystick_axis->bad_axis);
+ joystick_axis->bad_axis = NULL;
+
+ SDL_JoystickClose(joystick_axis->joystick);
+ SDL_JoystickEventState(SDL_DISABLE);
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ TXT_SDL_SetEventCallback(NULL, NULL);
+}
+
+void TXT_ConfigureJoystickAxis(txt_joystick_axis_t *joystick_axis,
+ int using_button,
+ txt_joystick_axis_callback_t callback)
+{
+ // Open the joystick first.
+ if (SDL_Init(SDL_INIT_JOYSTICK) < 0)
+ {
+ return;
+ }
+
+ joystick_axis->joystick = SDL_JoystickOpen(joystick_index);
+ if (joystick_axis->joystick == NULL)
+ {
+ // TODO: OpenErrorWindow();
+ return;
+ }
+
+ SDL_JoystickEventState(SDL_ENABLE);
+
+ // Build the prompt window.
+
+ joystick_axis->config_window
+ = TXT_NewWindow("Gamepad/Joystick calibration");
+ TXT_AddWidgets(joystick_axis->config_window,
+ TXT_NewStrut(0, 1),
+ joystick_axis->config_label = TXT_NewLabel(""),
+ TXT_NewStrut(0, 1),
+ NULL);
+
+ TXT_SetWindowAction(joystick_axis->config_window, TXT_HORIZ_LEFT, NULL);
+ TXT_SetWindowAction(joystick_axis->config_window, TXT_HORIZ_CENTER,
+ TXT_NewWindowAbortAction(joystick_axis->config_window));
+ TXT_SetWindowAction(joystick_axis->config_window, TXT_HORIZ_RIGHT, NULL);
+ TXT_SetWidgetAlign(joystick_axis->config_window, TXT_HORIZ_CENTER);
+
+ if (using_button >= 0)
+ {
+ joystick_axis->config_stage = CONFIG_STAGE1;
+ joystick_axis->config_button = using_button;
+ IdentifyBadAxes(joystick_axis);
+ }
+ else
+ {
+ joystick_axis->config_stage = CONFIG_CENTER;
+ }
+
+ SetCalibrationLabel(joystick_axis);
+
+ // Close the joystick and shut down joystick subsystem when the window
+ // is closed.
+ TXT_SignalConnect(joystick_axis->config_window, "closed",
+ CalibrateWindowClosed, joystick_axis);
+
+ TXT_SDL_SetEventCallback(EventCallback, joystick_axis);
+
+ // When successfully calibrated, invoke this callback:
+ joystick_axis->callback = callback;
+}
+
+static void TXT_JoystickAxisSizeCalc(TXT_UNCAST_ARG(joystick_axis))
+{
+ TXT_CAST_ARG(txt_joystick_axis_t, joystick_axis);
+
+ // All joystickinputs are the same size.
+
+ joystick_axis->widget.w = JOYSTICK_AXIS_WIDTH;
+ joystick_axis->widget.h = 1;
+}
+
+static void TXT_JoystickAxisDrawer(TXT_UNCAST_ARG(joystick_axis))
+{
+ TXT_CAST_ARG(txt_joystick_axis_t, joystick_axis);
+ char buf[JOYSTICK_AXIS_WIDTH + 1];
+ int i;
+
+ if (*joystick_axis->axis < 0)
+ {
+ M_StringCopy(buf, "(None)", sizeof(buf));
+ }
+ else if (IS_BUTTON_AXIS(*joystick_axis->axis))
+ {
+ int neg, pos;
+
+ neg = BUTTON_AXIS_NEG(*joystick_axis->axis);
+ pos = BUTTON_AXIS_POS(*joystick_axis->axis);
+ M_snprintf(buf, sizeof(buf), "BUTTONS #%i+#%i", neg, pos);
+ }
+ else if (IS_HAT_AXIS(*joystick_axis->axis))
+ {
+ int hat, dir;
+
+ hat = HAT_AXIS_HAT(*joystick_axis->axis);
+ dir = HAT_AXIS_DIRECTION(*joystick_axis->axis);
+
+ M_snprintf(buf, sizeof(buf), "HAT #%i (%s)", hat,
+ dir == HAT_AXIS_HORIZONTAL ? "horizontal" : "vertical");
+ }
+ else
+ {
+ M_snprintf(buf, sizeof(buf), "AXIS #%i", *joystick_axis->axis);
+ }
+
+ TXT_SetWidgetBG(joystick_axis);
+ TXT_FGColor(TXT_COLOR_BRIGHT_WHITE);
+
+ TXT_DrawString(buf);
+
+ for (i=strlen(buf); i<JOYSTICK_AXIS_WIDTH; ++i)
+ {
+ TXT_DrawString(" ");
+ }
+}
+
+static void TXT_JoystickAxisDestructor(TXT_UNCAST_ARG(joystick_axis))
+{
+}
+
+static int TXT_JoystickAxisKeyPress(TXT_UNCAST_ARG(joystick_axis), int key)
+{
+ TXT_CAST_ARG(txt_joystick_axis_t, joystick_axis);
+
+ if (key == KEY_ENTER)
+ {
+ TXT_ConfigureJoystickAxis(joystick_axis, -1, NULL);
+ return 1;
+ }
+
+ if (key == KEY_BACKSPACE || key == KEY_DEL)
+ {
+ *joystick_axis->axis = -1;
+ }
+
+ return 0;
+}
+
+static void TXT_JoystickAxisMousePress(TXT_UNCAST_ARG(widget),
+ int x, int y, int b)
+{
+ TXT_CAST_ARG(txt_joystick_axis_t, widget);
+
+ // Clicking is like pressing enter
+
+ if (b == TXT_MOUSE_LEFT)
+ {
+ TXT_JoystickAxisKeyPress(widget, KEY_ENTER);
+ }
+}
+
+txt_widget_class_t txt_joystick_axis_class =
+{
+ TXT_AlwaysSelectable,
+ TXT_JoystickAxisSizeCalc,
+ TXT_JoystickAxisDrawer,
+ TXT_JoystickAxisKeyPress,
+ TXT_JoystickAxisDestructor,
+ TXT_JoystickAxisMousePress,
+ NULL,
+};
+
+txt_joystick_axis_t *TXT_NewJoystickAxis(int *axis, int *invert,
+ txt_joystick_axis_direction_t dir)
+{
+ txt_joystick_axis_t *joystick_axis;
+
+ joystick_axis = malloc(sizeof(txt_joystick_axis_t));
+
+ TXT_InitWidget(joystick_axis, &txt_joystick_axis_class);
+ joystick_axis->axis = axis;
+ joystick_axis->invert = invert;
+ joystick_axis->dir = dir;
+ joystick_axis->bad_axis = NULL;
+
+ return joystick_axis;
+}
+
diff --git a/src/setup/txt_joyaxis.h b/src/setup/txt_joyaxis.h
new file mode 100644
index 00000000..1f41d0e8
--- /dev/null
+++ b/src/setup/txt_joyaxis.h
@@ -0,0 +1,90 @@
+//
+// Copyright(C) 2005-2014 Simon Howard
+//
+// This program 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 2
+// of the License, or (at your option) any later version.
+//
+// This program 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.
+//
+
+#ifndef TXT_JOY_AXIS_H
+#define TXT_JOY_AXIS_H
+
+typedef struct txt_joystick_axis_s txt_joystick_axis_t;
+
+typedef enum
+{
+ JOYSTICK_AXIS_HORIZONTAL,
+ JOYSTICK_AXIS_VERTICAL,
+} txt_joystick_axis_direction_t;
+
+typedef enum
+{
+ CONFIG_CENTER, // "Center the joystick and press a button..."
+ CONFIG_STAGE1, // "Top or left and press a button..."
+ CONFIG_STAGE2, // [Optional] "Bottom or right and press a button..."
+} txt_joystick_axis_stage_t;
+
+// Callback invoked when calibration is completed.
+typedef void (*txt_joystick_axis_callback_t)(void);
+
+#include "txt_widget.h"
+#include "txt_window.h"
+
+#include "SDL.h"
+
+//
+// A joystick axis.
+//
+
+struct txt_joystick_axis_s
+{
+ txt_widget_t widget;
+ int *axis, *invert;
+ txt_joystick_axis_direction_t dir;
+
+ // Only used when configuring:
+
+ // Configuration prompt window and label.
+ txt_window_t *config_window;
+ txt_label_t *config_label;
+
+ // SDL joystick handle for reading joystick state.
+ SDL_Joystick *joystick;
+
+ // "Bad" joystick axes. Sometimes an axis can be stuck or "bad". An
+ // example I found is that if you unplug the nunchuck extension from
+ // a Wii remote, the axes from the nunchuck can be stuck at one of
+ // the maximum values. These have to be ignored, so when we ask the
+ // user to center the joystick, we look for bad axes that are not
+ // close to zero.
+ boolean *bad_axis;
+
+ // Stage we have reached in configuring joystick axis.
+ txt_joystick_axis_stage_t config_stage;
+
+ // Button to press to advance to next stage.
+ int config_button;
+
+ // Callback invoked when the axis is calibrated.
+ txt_joystick_axis_callback_t callback;
+};
+
+txt_joystick_axis_t *TXT_NewJoystickAxis(int *axis, int *invert,
+ txt_joystick_axis_direction_t dir);
+
+// Configure a joystick axis widget.
+// axis: The axis widget to configure.
+// using_button: If non-negative, use this joystick button as the button
+// to expect from the user. Otherwise, ask.
+void TXT_ConfigureJoystickAxis(txt_joystick_axis_t *axis, int using_button,
+ txt_joystick_axis_callback_t callback);
+
+#endif /* #ifndef TXT_JOY_AXIS_H */
+
+