diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickSettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickSettingsMenu.java index caa66fbb..f5379a51 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickSettingsMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2JoystickSettingsMenu.java @@ -36,8 +36,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { public final static int JOYSTICK_AXIS_SENSITIVITY_INC_NUMCHOICES = (int) ((JOYSTICK_AXIS_SENSITIVITY_MAX - JOYSTICK_AXIS_SENSITIVITY_DEFAULT) / JOYSTICK_AXIS_SENSITIVITY_INC_STEP); // 12 public final static int JOYSTICK_AXIS_SENSITIVITY_NUM_CHOICES = JOYSTICK_AXIS_SENSITIVITY_DEC_NUMCHOICES + JOYSTICK_AXIS_SENSITIVITY_INC_NUMCHOICES; // 15 + 12 - public final static int TAPDELAY_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES; - public final static float TAPDELAY_SCALE = 0.5f; + public final static int TAPDELAY_NUM_CHOICES = (30 + 1); // 0-30 (30Frames == ~0.5sec) public Apple2JoystickSettingsMenu(Apple2Activity activity) { @@ -227,85 +226,6 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { }); } }, - JOYSTICK_CALIBRATE { - @Override - public final String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.joystick_calibrate); - } - - @Override - public final String getSummary(Apple2Activity activity) { - return activity.getResources().getString(R.string.joystick_calibrate_summary); - } - - @Override - public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - ArrayList viewStack = new ArrayList(); - { - int idx = 0; - while (true) { - Apple2MenuView apple2MenuView = activity.peekApple2View(idx); - if (apple2MenuView == null) { - break; - } - viewStack.add(apple2MenuView); - ++idx; - } - } - - Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK); - - // show this new view... - calibration.show(); - - // ...with nothing else underneath 'cept the emulator OpenGL layer - for (Apple2MenuView apple2MenuView : viewStack) { - activity.popApple2View(apple2MenuView); - } - } - }, - JOYSTICK_TAPDELAY { - @Override - public final String getTitle(Apple2Activity activity) { - return ""; - } - - @Override - public final String getSummary(Apple2Activity activity) { - return activity.getResources().getString(R.string.joystick_button_tapdelay_summary); - } - - @Override - public String getPrefKey() { - return "jsTapDelaySecs"; - } - - @Override - public Object getPrefDefault() { - return ((float) 8 / TAPDELAY_NUM_CHOICES * TAPDELAY_SCALE); // -> 0.2f - } - - @Override - public View getView(final Apple2Activity activity, View convertView) { - final IMenuEnum self = this; - return _sliderView(activity, this, TAPDELAY_NUM_CHOICES, new IPreferenceSlider() { - @Override - public void saveInt(int progress) { - Apple2Preferences.setJSONPref(self, ((float) progress / TAPDELAY_NUM_CHOICES * TAPDELAY_SCALE)); - } - - @Override - public int intValue() { - return (int) (Apple2Preferences.getFloatJSONPref(self) / TAPDELAY_SCALE * TAPDELAY_NUM_CHOICES); - } - - @Override - public void showValue(int progress, final TextView seekBarValue) { - seekBarValue.setText("" + (((float) progress / TAPDELAY_NUM_CHOICES) * TAPDELAY_SCALE)); - } - }); - } - }, JOYSTICK_ADVANCED { @Override public final String getTitle(Apple2Activity activity) { @@ -319,7 +239,7 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - new Apple2JoystickSettingsMenu.JoystickAdvanced(activity).show(); + new Apple2JoystickSettingsMenu.JoystickAdvanced(activity, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK).show(); } }; @@ -363,8 +283,16 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { private final static String TAG = "JoystickAdvanced"; - public JoystickAdvanced(Apple2Activity activity) { + private Apple2SettingsMenu.TouchDeviceVariant mVariant; + + public JoystickAdvanced(Apple2Activity activity, Apple2SettingsMenu.TouchDeviceVariant variant) { super(activity); + + if (!(variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK || variant == Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD)) { + throw new RuntimeException("You're doing it wrong"); + } + + mVariant = variant; } @Override @@ -387,10 +315,58 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { if (position < 0 || position >= SETTINGS.size) { throw new ArrayIndexOutOfBoundsException(); } - return position == SETTINGS.JOYSTICK_AXIS_ON_LEFT.ordinal(); + return position <= SETTINGS.JOYSTICK_AXIS_ON_LEFT.ordinal(); } protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum { + JOYSTICK_CALIBRATE { + @Override + public final String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.joystick_calibrate); + } + + @Override + public final String getSummary(Apple2Activity activity) { + return activity.getResources().getString(R.string.joystick_calibrate_summary); + } + + @Override + public String getPrefKey() { + return null; + } + + @Override + public Object getPrefDefault() { + return null; + } + + @Override + public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { + ArrayList viewStack = new ArrayList(); + { + int idx = 0; + while (true) { + Apple2MenuView apple2MenuView = activity.peekApple2View(idx); + if (apple2MenuView == null) { + break; + } + viewStack.add(apple2MenuView); + ++idx; + } + } + + JoystickAdvanced advancedMenu = (JoystickAdvanced)settingsMenu; + Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, advancedMenu.mVariant); + + // show this new view... + calibration.show(); + + // ...with nothing else underneath 'cept the emulator OpenGL layer + for (Apple2MenuView apple2MenuView : viewStack) { + activity.popApple2View(apple2MenuView); + } + } + }, JOYSTICK_VISIBILITY { @Override public final String getTitle(Apple2Activity activity) { @@ -461,6 +437,52 @@ public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu { return convertView; } }, + JOYSTICK_TAPDELAY { + @Override + public final String getTitle(Apple2Activity activity) { + return ""; + } + + @Override + public final String getSummary(Apple2Activity activity) { + return activity.getResources().getString(R.string.joystick_button_tapdelay_summary); + } + + @Override + public String getPrefKey() { + return "jsTapDelayFrames"; + } + + @Override + public Object getPrefDefault() { + return 12; // 12 * 16.688millis == ~0.2secs + } + + @Override + public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; + return _sliderView(activity, this, TAPDELAY_NUM_CHOICES, new IPreferenceSlider() { + @Override + public void saveInt(int progress) { + Apple2Preferences.setJSONPref(self, progress); + } + + @Override + public int intValue() { + return Apple2Preferences.getIntJSONPref(self); + } + + @Override + public void showValue(int progress, final TextView seekBarValue) { + float millis = progress * 16.688f; + String framesStr = activity.getResources().getString(R.string.string_frames); + String millisStr = activity.getResources().getString(R.string.string_millis); + String textSummary = "" + progress + " " + framesStr + " (" + millis + " " + millisStr + ")"; + seekBarValue.setText(textSummary); + } + }); + } + }, JOYSTICK_AXIS_SENSITIVITY { @Override public final String getTitle(Apple2Activity activity) { diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeyboardSettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeyboardSettingsMenu.java index ddad97a1..0b2e297a 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeyboardSettingsMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeyboardSettingsMenu.java @@ -46,7 +46,9 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { public final static int ICONTEXT_NONACTION = ICONTEXT_KBD_BEGIN + 0x0C; public final static int SCANCODE_A = 30; + public final static int SCANCODE_C = 46; public final static int SCANCODE_D = 32; + public final static int SCANCODE_E = 18; public final static int SCANCODE_F = 33; public final static int SCANCODE_H = 35; public final static int SCANCODE_I = 23; @@ -56,17 +58,23 @@ public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu { public final static int SCANCODE_M = 50; public final static int SCANCODE_N = 49; public final static int SCANCODE_O = 24; + public final static int SCANCODE_P = 25; + public final static int SCANCODE_Q = 16; + public final static int SCANCODE_S = 31; public final static int SCANCODE_U = 22; public final static int SCANCODE_W = 17; public final static int SCANCODE_X = 45; public final static int SCANCODE_Y = 21; public final static int SCANCODE_Z = 44; public final static int SCANCODE_SPACE = 57; + public final static int SCANCODE_SEMICOLON = 39; public final static int SCANCODE_UP = 103; public final static int SCANCODE_LEFT = 105; public final static int SCANCODE_RIGHT = 106; public final static int SCANCODE_DOWN = 108; public final static int SCANCODE_COMMA = 51; + public final static int SCANCODE_PERIOD = 52; + public final static int SCANCODE_SLASH = 53; public Apple2KeyboardSettingsMenu(Apple2Activity activity) { super(activity); diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadChooser.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadChooser.java index 094d3903..a8a32053 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadChooser.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadChooser.java @@ -35,7 +35,7 @@ public class Apple2KeypadChooser implements Apple2MenuView { private ArrayList mViewStack = null; private TextView mCurrentChoicePrompt = null; - private STATE_MACHINE mChooserState = STATE_MACHINE.CHOOSE_NORTHWEST; + private STATE_MACHINE mChooserState = STATE_MACHINE.CHOOSE_AXIS_NORTHWEST; private boolean mTouchMenuEnabled = false; private int mSavedTouchDevice = Apple2SettingsMenu.TouchDeviceVariant.NONE.ordinal(); @@ -68,24 +68,6 @@ public class Apple2KeypadChooser implements Apple2MenuView { Apple2Preferences.sync(mActivity, Apple2Preferences.PREF_DOMAIN_TOUCHSCREEN); mCurrentChoicePrompt.setText(getNextChoiceString() + asciiStr); - switch (mChooserState) { - case CHOOSE_TAP: - Apple2View.nativeOnTouch(MotionEvent.ACTION_DOWN, 1, 0, new float[]{400.f}, new float[]{400.f}); - Apple2View.nativeOnTouch(MotionEvent.ACTION_UP, 1, 0, new float[]{400.f}, new float[]{400.f}); - break; - case CHOOSE_SWIPEDOWN: - Apple2View.nativeOnTouch(MotionEvent.ACTION_DOWN, 1, 0, new float[]{400.f}, new float[]{400.f}); - Apple2View.nativeOnTouch(MotionEvent.ACTION_MOVE, 1, 0, new float[]{400.f}, new float[]{600.f}); - Apple2View.nativeOnTouch(MotionEvent.ACTION_UP, 1, 0, new float[]{400.f}, new float[]{600.f}); - break; - case CHOOSE_SWIPEUP: - Apple2View.nativeOnTouch(MotionEvent.ACTION_DOWN, 1, 0, new float[]{400.f}, new float[]{400.f}); - Apple2View.nativeOnTouch(MotionEvent.ACTION_MOVE, 1, 0, new float[]{400.f}, new float[]{200.f}); - Apple2View.nativeOnTouch(MotionEvent.ACTION_UP, 1, 0, new float[]{400.f}, new float[]{200.f}); - break; - default: - break; - } calibrationContinue(); } @@ -220,98 +202,134 @@ public class Apple2KeypadChooser implements Apple2MenuView { } private enum STATE_MACHINE { - CHOOSE_NORTHWEST { + CHOOSE_AXIS_NORTHWEST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_ul); } }, - CHOOSE_NORTH { + CHOOSE_AXIS_NORTH { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_up); } }, - CHOOSE_NORTHEAST { + CHOOSE_AXIS_NORTHEAST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_ur); } }, - CHOOSE_WEST { + CHOOSE_AXIS_WEST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_l); } }, - CHOOSE_CENTER { + CHOOSE_AXIS_CENTER { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_c); } }, - CHOOSE_EAST { + CHOOSE_AXIS_EAST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_r); } }, - CHOOSE_SOUTHWEST { + CHOOSE_AXIS_SOUTHWEST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_dl); } }, - CHOOSE_SOUTH { + CHOOSE_AXIS_SOUTH { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_dn); } }, - CHOOSE_SOUTHEAST { + CHOOSE_AXIS_SOUTHEAST { @Override public String getKeyName(Apple2Activity activity) { return activity.getResources().getString(R.string.keypad_key_axis_dr); } }, - CHOOSE_TAP { + CHOOSE_BUTT_NORTHWEST { @Override public String getKeyName(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_key_button_tap); + return activity.getResources().getString(R.string.keypad_key_axis_ul); } }, - CHOOSE_SWIPEUP { + CHOOSE_BUTT_NORTH { @Override public String getKeyName(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_key_button_swipeup); + return activity.getResources().getString(R.string.keypad_key_axis_up); } }, - CHOOSE_SWIPEDOWN { + CHOOSE_BUTT_NORTHEAST { @Override public String getKeyName(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_key_button_swipedown); + return activity.getResources().getString(R.string.keypad_key_axis_ur); + } + }, + CHOOSE_BUTT_WEST { + @Override + public String getKeyName(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_key_axis_l); + } + }, + CHOOSE_BUTT_CENTER { + @Override + public String getKeyName(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_key_axis_c); + } + }, + CHOOSE_BUTT_EAST { + @Override + public String getKeyName(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_key_axis_r); + } + }, + CHOOSE_BUTT_SOUTHWEST { + @Override + public String getKeyName(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_key_axis_dl); + } + }, + CHOOSE_BUTT_SOUTH { + @Override + public String getKeyName(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_key_axis_dn); + } + }, + CHOOSE_BUTT_SOUTHEAST { + @Override + public String getKeyName(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_key_axis_dr); } }; public static final int size = STATE_MACHINE.values().length; - private static ArrayList chars = null; - private static ArrayList scans = null; + private static ArrayList axisChars = new ArrayList(); + private static ArrayList axisScans = new ArrayList(); + private static ArrayList buttChars = new ArrayList(); + private static ArrayList buttScans = new ArrayList(); public void setKey(Apple2Activity activity, int ascii, int scancode) { int ord = ordinal(); - if (ord < CHOOSE_TAP.ordinal()) { - chars.set(ord, "" + ascii); - scans.set(ord, "" + scancode); - Apple2KeypadSettingsMenu.KeypadPreset.saveRosettes(chars, scans); - } else if (ord == CHOOSE_TAP.ordinal()) { - Apple2KeypadSettingsMenu.KeypadPreset.saveTouchDownKey(ascii, scancode); - } else if (ord == CHOOSE_SWIPEUP.ordinal()) { - Apple2KeypadSettingsMenu.KeypadPreset.saveSwipeNorthKey(ascii, scancode); - } else if (ord == CHOOSE_SWIPEDOWN.ordinal()) { - Apple2KeypadSettingsMenu.KeypadPreset.saveSwipeSouthKey(ascii, scancode); + int buttbegin = CHOOSE_BUTT_NORTHWEST.ordinal(); + if (ord < buttbegin) { + axisChars.set(ord, "" + ascii); + axisScans.set(ord, "" + scancode); + Apple2KeypadSettingsMenu.KeypadPreset.saveAxisRosettes(axisChars, axisScans); } else { - throw new RuntimeException(); + ord -= buttbegin; + buttChars.set(ord, "" + ascii); + buttScans.set(ord, "" + scancode); + Apple2KeypadSettingsMenu.KeypadPreset.saveButtRosettes(buttChars, buttScans); } Apple2Preferences.sync(activity, Apple2Preferences.PREF_DOMAIN_JOYSTICK); } @@ -319,12 +337,19 @@ public class Apple2KeypadChooser implements Apple2MenuView { public abstract String getKeyName(Apple2Activity activity); public void start() { + setupCharsAndScans(axisChars, axisScans, Apple2KeypadSettingsMenu.PREF_KPAD_AXIS_ROSETTE_CHAR_ARRAY, Apple2KeypadSettingsMenu.PREF_KPAD_AXIS_ROSETTE_SCAN_ARRAY); - JSONArray jsonChars = (JSONArray) Apple2Preferences.getJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, Apple2KeypadSettingsMenu.PREF_KPAD_ROSETTE_CHAR_ARRAY, null); - JSONArray jsonScans = (JSONArray) Apple2Preferences.getJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, Apple2KeypadSettingsMenu.PREF_KPAD_ROSETTE_SCAN_ARRAY, null); + setupCharsAndScans(buttChars, buttScans, Apple2KeypadSettingsMenu.PREF_KPAD_BUTT_ROSETTE_CHAR_ARRAY, Apple2KeypadSettingsMenu.PREF_KPAD_BUTT_ROSETTE_SCAN_ARRAY); + } + + private void setupCharsAndScans(final ArrayList chars, final ArrayList scans, final String charArrayPref, final String scanArrayPref) { + chars.clear(); + scans.clear(); + + JSONArray jsonChars = (JSONArray) Apple2Preferences.getJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, charArrayPref, null); + JSONArray jsonScans = (JSONArray) Apple2Preferences.getJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, scanArrayPref, null); if (jsonChars == null || jsonScans == null) { - Log.v(TAG, "Creating new keypad joystick JSON..."); jsonChars = new JSONArray(); jsonScans = new JSONArray(); for (int i = 0; i < Apple2KeypadSettingsMenu.ROSETTE_SIZE; i++) { @@ -341,8 +366,6 @@ public class Apple2KeypadChooser implements Apple2MenuView { throw new RuntimeException("jsonScans not expected length"); } - chars = new ArrayList(); - scans = new ArrayList(); try { for (int i = 0; i < len; i++) { Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, jsonChars.getInt(i), jsonScans.getInt(i)); @@ -350,6 +373,13 @@ public class Apple2KeypadChooser implements Apple2MenuView { } catch (JSONException e) { e.printStackTrace(); } + + if (chars.size() != Apple2KeypadSettingsMenu.ROSETTE_SIZE) { + throw new RuntimeException("rosette chars is not correct size"); + } + if (scans.size() != Apple2KeypadSettingsMenu.ROSETTE_SIZE) { + throw new RuntimeException("rosette chars is not correct size"); + } } public STATE_MACHINE next() { diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadSettingsMenu.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadSettingsMenu.java index f7aabbda..6022eb5a 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadSettingsMenu.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2KeypadSettingsMenu.java @@ -12,7 +12,9 @@ package org.deadc0de.apple2ix; import android.view.View; -import android.widget.TextView; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.Toast; import java.util.ArrayList; @@ -23,16 +25,10 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { private final static String TAG = "Apple2KeypadSettingsMenu"; - public final static int KEYREPEAT_NUM_CHOICES = Apple2Preferences.DECENT_AMOUNT_OF_CHOICES; - - public final static String PREF_KPAD_ROSETTE_CHAR_ARRAY = "kpAxisRosetteChars"; - public final static String PREF_KPAD_ROSETTE_SCAN_ARRAY = "kpAxisRosetteScancodes"; - public final static String PREF_KPAD_SWIPE_NORTH_CHAR = "kpSwipeNorthChar"; - public final static String PREF_KPAD_SWIPE_NORTH_SCAN = "kpSwipeNorthScancode"; - public final static String PREF_KPAD_SWIPE_SOUTH_CHAR = "kpSwipeSouthChar"; - public final static String PREF_KPAD_SWIPE_SOUTH_SCAN = "kpSwipeSouthScancode"; - public final static String PREF_KPAD_TOUCHDOWN_CHAR = "kpTouchDownChar"; - public final static String PREF_KPAD_TOUCHDOWN_SCAN = "kpTouchDownScancode"; + public final static String PREF_KPAD_AXIS_ROSETTE_CHAR_ARRAY = "kpAxisRosetteChars"; + public final static String PREF_KPAD_AXIS_ROSETTE_SCAN_ARRAY = "kpAxisRosetteScancodes"; + public final static String PREF_KPAD_BUTT_ROSETTE_CHAR_ARRAY = "kpButtRosetteChars"; + public final static String PREF_KPAD_BUTT_ROSETTE_SCAN_ARRAY = "kpButtRosetteScancodes"; public final static int ROSETTE_SIZE = 9; @@ -72,22 +68,39 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { @Override public void apply(Apple2Activity activity) { - ArrayList chars = new ArrayList(); - ArrayList scans = new ArrayList(); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_UP, Apple2KeyboardSettingsMenu.SCANCODE_UP); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_DOWN, Apple2KeyboardSettingsMenu.SCANCODE_DOWN); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveRosettes(chars, scans); + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_UP, Apple2KeyboardSettingsMenu.SCANCODE_UP); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_DOWN, Apple2KeyboardSettingsMenu.SCANCODE_DOWN); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveAxisRosettes(chars, scans); + } + + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveButtRosettes(chars, scans); + } } }, AZ_LEFT_RIGHT_SPACE { @@ -98,22 +111,39 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { @Override public void apply(Apple2Activity activity) { - ArrayList chars = new ArrayList(); - ArrayList scans = new ArrayList(); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'A', Apple2KeyboardSettingsMenu.SCANCODE_A); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'Z', Apple2KeyboardSettingsMenu.SCANCODE_Z); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveRosettes(chars, scans); + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'A', Apple2KeyboardSettingsMenu.SCANCODE_A); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'Z', Apple2KeyboardSettingsMenu.SCANCODE_Z); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveAxisRosettes(chars, scans); + } + + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveButtRosettes(chars, scans); + } } }, LEFT_RIGHT_SPACE { @@ -124,22 +154,81 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { @Override public void apply(Apple2Activity activity) { - ArrayList chars = new ArrayList(); - ArrayList scans = new ArrayList(); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveRosettes(chars, scans); + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); - saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); + saveAxisRosettes(chars, scans); + } + + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveButtRosettes(chars, scans); + } + } + }, + QAZ_LEFT_RIGHT_SPACE { + @Override + public String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_qaz_left_right_space); + } + + @Override + public void apply(Apple2Activity activity) { + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'A', Apple2KeyboardSettingsMenu.SCANCODE_A); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT, Apple2KeyboardSettingsMenu.SCANCODE_LEFT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT, Apple2KeyboardSettingsMenu.SCANCODE_RIGHT); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'Z', Apple2KeyboardSettingsMenu.SCANCODE_Z); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveAxisRosettes(chars, scans); + } + + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_Q); + addRosetteKey(chars, scans, 'Q', Apple2KeyboardSettingsMenu.SCANCODE_Q); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_Q); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveButtRosettes(chars, scans); + } } }, IJKM_SPACE { @@ -150,22 +239,39 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { @Override public void apply(Apple2Activity activity) { - ArrayList chars = new ArrayList(); - ArrayList scans = new ArrayList(); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'J', Apple2KeyboardSettingsMenu.SCANCODE_J); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'M', Apple2KeyboardSettingsMenu.SCANCODE_M); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveRosettes(chars, scans); + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'J', Apple2KeyboardSettingsMenu.SCANCODE_J); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'M', Apple2KeyboardSettingsMenu.SCANCODE_M); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveAxisRosettes(chars, scans); + } + + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveButtRosettes(chars, scans); + } } }, WADX_SPACE { @@ -176,49 +282,173 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { @Override public void apply(Apple2Activity activity) { - ArrayList chars = new ArrayList(); - ArrayList scans = new ArrayList(); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'W', Apple2KeyboardSettingsMenu.SCANCODE_W); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'A', Apple2KeyboardSettingsMenu.SCANCODE_A); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'D', Apple2KeyboardSettingsMenu.SCANCODE_D); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - addRosetteKey(chars, scans, 'X', Apple2KeyboardSettingsMenu.SCANCODE_X); - addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveRosettes(chars, scans); + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'W', Apple2KeyboardSettingsMenu.SCANCODE_W); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveTouchDownKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); - saveSwipeSouthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'A', Apple2KeyboardSettingsMenu.SCANCODE_A); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'D', Apple2KeyboardSettingsMenu.SCANCODE_D); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'X', Apple2KeyboardSettingsMenu.SCANCODE_X); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveAxisRosettes(chars, scans); + } + + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + saveButtRosettes(chars, scans); + } } }, - CRAZY_SEAFOX_KEYS { + LODERUNNER_KEYS { @Override public String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_preset_crazy_seafox); + return activity.getResources().getString(R.string.keypad_preset_loderunner); + } + + @Override + public String getToast(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_loderunner_toast); } @Override public void apply(Apple2Activity activity) { - // Heh, the entire purpose of the keypad-variant touch joystick is to make this possible ;-) - ArrayList chars = new ArrayList(); - ArrayList scans = new ArrayList(); - addRosetteKey(chars, scans, 'Y', Apple2KeyboardSettingsMenu.SCANCODE_Y); - addRosetteKey(chars, scans, 'U', Apple2KeyboardSettingsMenu.SCANCODE_U); - addRosetteKey(chars, scans, 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); - addRosetteKey(chars, scans, 'H', Apple2KeyboardSettingsMenu.SCANCODE_H); - addRosetteKey(chars, scans, 'J', Apple2KeyboardSettingsMenu.SCANCODE_J); - addRosetteKey(chars, scans, 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); - addRosetteKey(chars, scans, 'N', Apple2KeyboardSettingsMenu.SCANCODE_N); - addRosetteKey(chars, scans, 'M', Apple2KeyboardSettingsMenu.SCANCODE_M); - addRosetteKey(chars, scans, ',', Apple2KeyboardSettingsMenu.SCANCODE_COMMA); - saveRosettes(chars, scans); + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); - saveTouchDownKey('D', Apple2KeyboardSettingsMenu.SCANCODE_D); - saveSwipeSouthKey('F', Apple2KeyboardSettingsMenu.SCANCODE_F); - saveSwipeNorthKey(Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, 'J', Apple2KeyboardSettingsMenu.SCANCODE_J); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, 'L', Apple2KeyboardSettingsMenu.SCANCODE_L); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveAxisRosettes(chars, scans); + } + + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_U); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_O); + + addRosetteKey(chars, scans, 'U', Apple2KeyboardSettingsMenu.SCANCODE_U); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, 'O', Apple2KeyboardSettingsMenu.SCANCODE_O); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_U); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_O); + saveButtRosettes(chars, scans); + } + } + }, + ROBOTRON_KEYS { + @Override + public String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_robotron); + } + + @Override + public void apply(Apple2Activity activity) { + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, 'Q', Apple2KeyboardSettingsMenu.SCANCODE_Q); + addRosetteKey(chars, scans, 'W', Apple2KeyboardSettingsMenu.SCANCODE_W); + addRosetteKey(chars, scans, 'E', Apple2KeyboardSettingsMenu.SCANCODE_E); + + addRosetteKey(chars, scans, 'A', Apple2KeyboardSettingsMenu.SCANCODE_A); + addRosetteKey(chars, scans, 'S', Apple2KeyboardSettingsMenu.SCANCODE_S); + addRosetteKey(chars, scans, 'D', Apple2KeyboardSettingsMenu.SCANCODE_D); + + addRosetteKey(chars, scans, 'Z', Apple2KeyboardSettingsMenu.SCANCODE_Z); + addRosetteKey(chars, scans, 'X', Apple2KeyboardSettingsMenu.SCANCODE_X); + addRosetteKey(chars, scans, 'C', Apple2KeyboardSettingsMenu.SCANCODE_C); + saveAxisRosettes(chars, scans); + } + + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); + addRosetteKey(chars, scans, 'O', Apple2KeyboardSettingsMenu.SCANCODE_O); + addRosetteKey(chars, scans, 'P', Apple2KeyboardSettingsMenu.SCANCODE_P); + + addRosetteKey(chars, scans, 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); + addRosetteKey(chars, scans, 'L', Apple2KeyboardSettingsMenu.SCANCODE_L); + addRosetteKey(chars, scans, ';', Apple2KeyboardSettingsMenu.SCANCODE_SEMICOLON); + + addRosetteKey(chars, scans, ',', Apple2KeyboardSettingsMenu.SCANCODE_COMMA); + addRosetteKey(chars, scans, '.', Apple2KeyboardSettingsMenu.SCANCODE_PERIOD); + addRosetteKey(chars, scans, '/', Apple2KeyboardSettingsMenu.SCANCODE_SLASH); + saveButtRosettes(chars, scans); + } + } + }, + SEAFOX_KEYS { + @Override + public String getTitle(Apple2Activity activity) { + return activity.getResources().getString(R.string.keypad_preset_seafox); + } + + @Override + public void apply(Apple2Activity activity) { + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, 'Y', Apple2KeyboardSettingsMenu.SCANCODE_Y); + addRosetteKey(chars, scans, 'U', Apple2KeyboardSettingsMenu.SCANCODE_U); + addRosetteKey(chars, scans, 'I', Apple2KeyboardSettingsMenu.SCANCODE_I); + + addRosetteKey(chars, scans, 'H', Apple2KeyboardSettingsMenu.SCANCODE_H); + addRosetteKey(chars, scans, 'J', Apple2KeyboardSettingsMenu.SCANCODE_J); + addRosetteKey(chars, scans, 'K', Apple2KeyboardSettingsMenu.SCANCODE_K); + + addRosetteKey(chars, scans, 'N', Apple2KeyboardSettingsMenu.SCANCODE_N); + addRosetteKey(chars, scans, 'M', Apple2KeyboardSettingsMenu.SCANCODE_M); + addRosetteKey(chars, scans, ',', Apple2KeyboardSettingsMenu.SCANCODE_COMMA); + saveAxisRosettes(chars, scans); + } + + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_D); + addRosetteKey(chars, scans, 'D', Apple2KeyboardSettingsMenu.SCANCODE_D); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, Apple2KeyboardSettingsMenu.SCANCODE_F); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE, Apple2KeyboardSettingsMenu.SCANCODE_SPACE); + addRosetteKey(chars, scans, 'F', Apple2KeyboardSettingsMenu.SCANCODE_F); + + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + saveButtRosettes(chars, scans); + } } }; @@ -227,36 +457,36 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { scans.add("" + aScan); } - public static void saveRosettes(ArrayList chars, ArrayList scans) { + public static void saveAxisRosettes(ArrayList chars, ArrayList scans) { if (chars.size() != 9) { throw new RuntimeException("rosette chars is not correct size"); } if (scans.size() != 9) { throw new RuntimeException("rosette scans is not correct size"); } - Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_ROSETTE_CHAR_ARRAY, new JSONArray(chars)); - Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_ROSETTE_SCAN_ARRAY, new JSONArray(scans)); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_AXIS_ROSETTE_CHAR_ARRAY, new JSONArray(chars)); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_AXIS_ROSETTE_SCAN_ARRAY, new JSONArray(scans)); } - public static void saveTouchDownKey(int aChar, int aScan) { - Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_TOUCHDOWN_CHAR, aChar); - Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_TOUCHDOWN_SCAN, aScan); - } - - public static void saveSwipeSouthKey(int aChar, int aScan) { - Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_SWIPE_SOUTH_CHAR, aChar); - Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_SWIPE_SOUTH_SCAN, aScan); - } - - public static void saveSwipeNorthKey(int aChar, int aScan) { - Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_SWIPE_NORTH_CHAR, aChar); - Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_SWIPE_NORTH_SCAN, aScan); + public static void saveButtRosettes(ArrayList chars, ArrayList scans) { + if (chars.size() != 9) { + throw new RuntimeException("rosette chars is not correct size"); + } + if (scans.size() != 9) { + throw new RuntimeException("rosette scans is not correct size"); + } + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_BUTT_ROSETTE_CHAR_ARRAY, new JSONArray(chars)); + Apple2Preferences.setJSONPref(Apple2Preferences.PREF_DOMAIN_JOYSTICK, PREF_KPAD_BUTT_ROSETTE_SCAN_ARRAY, new JSONArray(scans)); } public abstract String getTitle(Apple2Activity activity); public abstract void apply(Apple2Activity activity); + public String getToast(Apple2Activity activity) { + return null; + } + public static final int size = KeypadPreset.values().length; public static String[] titles(Apple2Activity activity) { @@ -319,52 +549,55 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { keypadSettingsMenu.chooseKeys(activity); } else { KeypadPreset.values()[value - 1].apply(activity); + + String toast = KeypadPreset.values()[value - 1].getToast(activity); + if (toast != null) { + Toast.makeText(activity, toast, Toast.LENGTH_SHORT).show(); + } } } }); } }, - KEYPAD_CALIBRATE { + FAST_AUTOREPEAT { @Override public final String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_calibrate); + return activity.getResources().getString(R.string.keypad_autorepeat_fast); } @Override public final String getSummary(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_calibrate_summary); + return activity.getResources().getString(R.string.keypad_autorepeat_fast_summary); } @Override - public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - ArrayList viewStack = new ArrayList(); - { - int idx = 0; - while (true) { - Apple2MenuView apple2MenuView = activity.peekApple2View(idx); - if (apple2MenuView == null) { - break; - } - viewStack.add(apple2MenuView); - ++idx; + public String getPrefKey() { + return "kpFastAutoRepeat"; + } + + @Override + public Object getPrefDefault() { + return true; + } + + @Override + public View getView(final Apple2Activity activity, View convertView) { + final IMenuEnum self = this; + convertView = _basicView(activity, this, convertView); + CheckBox cb = _addCheckbox(activity, this, convertView, (boolean) Apple2Preferences.getJSONPref(this)); + cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + Apple2Preferences.setJSONPref(self, isChecked); } - } - - Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD); - - // show this new view... - calibration.show(); - - // ...with nothing else underneath 'cept the emulator OpenGL layer - for (Apple2MenuView apple2MenuView : viewStack) { - activity.popApple2View(apple2MenuView); - } + }); + return convertView; } }, - KEYPAD_ADVANCED { + JOYSTICK_ADVANCED { @Override public final String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.settings_advanced); + return activity.getResources().getString(R.string.settings_advanced_joystick); } @Override @@ -374,7 +607,7 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { @Override public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - new Apple2KeypadSettingsMenu.KeypadAdvanced(activity).show(); + new Apple2JoystickSettingsMenu.JoystickAdvanced(activity, Apple2SettingsMenu.TouchDeviceVariant.JOYSTICK_KEYPAD).show(); } }; @@ -441,133 +674,4 @@ public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu { activity.popApple2View(apple2MenuView); } } - - public static class KeypadAdvanced extends Apple2AbstractMenu { - - private final static String TAG = "KeypadAdvanced"; - - public KeypadAdvanced(Apple2Activity activity) { - super(activity); - } - - @Override - public final String[] allTitles() { - return SETTINGS.titles(mActivity); - } - - @Override - public final IMenuEnum[] allValues() { - return SETTINGS.values(); - } - - @Override - public final boolean areAllItemsEnabled() { - return false; - } - - @Override - public final boolean isEnabled(int position) { - if (position < 0 || position >= SETTINGS.size) { - throw new ArrayIndexOutOfBoundsException(); - } - return position == SETTINGS.JOYSTICK_ADVANCED.ordinal(); - } - - protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum { - KEYREPEAT_THRESHOLD { - @Override - public final String getTitle(Apple2Activity activity) { - return ""; - } - - @Override - public final String getSummary(Apple2Activity activity) { - return activity.getResources().getString(R.string.keypad_repeat_summary); - } - - @Override - public String getPrefKey() { - return "keyRepeatThresholdSecs"; - } - - @Override - public Object getPrefDefault() { - return (float) 4 / KEYREPEAT_NUM_CHOICES; - } - - @Override - public View getView(final Apple2Activity activity, View convertView) { - final IMenuEnum self = this; - return _sliderView(activity, this, KEYREPEAT_NUM_CHOICES, new IPreferenceSlider() { - @Override - public void saveInt(int progress) { - Apple2Preferences.setJSONPref(self, (float) progress / KEYREPEAT_NUM_CHOICES); - } - - @Override - public int intValue() { - return (int) (Apple2Preferences.getFloatJSONPref(self) * KEYREPEAT_NUM_CHOICES); - } - - @Override - public void showValue(int progress, final TextView seekBarValue) { - seekBarValue.setText("" + ((float) progress / KEYREPEAT_NUM_CHOICES)); - } - }); - } - }, - JOYSTICK_ADVANCED { - @Override - public final String getTitle(Apple2Activity activity) { - return activity.getResources().getString(R.string.settings_advanced_joystick); - } - - @Override - public final String getSummary(Apple2Activity activity) { - return activity.getResources().getString(R.string.settings_advanced_joystick_summary); - } - - @Override - public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - new Apple2JoystickSettingsMenu.JoystickAdvanced(activity).show(); - } - }; - - public static final int size = SETTINGS.values().length; - - @Override - public String getPrefDomain() { - return Apple2Preferences.PREF_DOMAIN_JOYSTICK; - } - - @Override - public String getPrefKey() { - return null; - } - - @Override - public Object getPrefDefault() { - return null; - } - - @Override - public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) { - } - - @Override - public View getView(Apple2Activity activity, View convertView) { - return _basicView(activity, this, convertView); - } - - public static String[] titles(Apple2Activity activity) { - String[] titles = new String[size]; - int i = 0; - for (SETTINGS setting : values()) { - titles[i++] = setting.getTitle(activity); - } - return titles; - } - } - } } - diff --git a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Preferences.java b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Preferences.java index cfe482e7..21dac187 100644 --- a/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Preferences.java +++ b/Android/app/src/main/java/org/deadc0de/apple2ix/Apple2Preferences.java @@ -158,6 +158,28 @@ public class Apple2Preferences { return (float) ((long) obj); } + private static int _convertToInt(Object obj) { + if (obj == null) { + return 0; + } + try { + return (int) obj; + } catch (ClassCastException e) { + Log.d(TAG, "could not cast object as int"); + } + try { + return (int) ((long) obj); + } catch (ClassCastException e) { + Log.d(TAG, "could not cast object as long"); + } + try { + return (int) ((float) obj); + } catch (ClassCastException e) { + Log.d(TAG, "could not cast object as float"); + } + return (int) ((double) obj); + } + public static float getFloatJSONPref(Apple2AbstractMenu.IMenuEnum menu) { return _convertToFloat(getJSONPref(menu)); } @@ -166,6 +188,14 @@ public class Apple2Preferences { return _convertToFloat(getJSONPref(domain, key, defaultVal)); } + public static int getIntJSONPref(Apple2AbstractMenu.IMenuEnum menu) { + return _convertToInt(getJSONPref(menu)); + } + + public static int getIntJSONPref(String domain, String key, Object defaultVal) { + return _convertToInt(getJSONPref(domain, key, defaultVal)); + } + public static boolean migrate(Apple2Activity activity) { int versionCode = (int) getJSONPref(PREF_DOMAIN_INTERFACE, PREF_EMULATOR_VERSION, 0); final boolean firstTime = (versionCode != BuildConfig.VERSION_CODE); @@ -185,6 +215,57 @@ public class Apple2Preferences { Apple2Utils.migrateToExternalStorage(activity); + if (versionCode < 24) { + // migrate tap delay from seconds to frames ... + float secs = getFloatJSONPref(PREF_DOMAIN_JOYSTICK, "jsTapDelaySecs", 9999f); + if (secs != 9999f) { + // UtAIIe 3-13 : "The duration of the television scan is 262 horizontal scans. This is [16.688 milliseconds]" + // recalculate this to a frames value between 0-30 inclusive ... + int framesDelay = Math.round(secs / 0.016688f); + if (framesDelay < 0) { + framesDelay = 0; + } else if (framesDelay > 30) { + framesDelay = 30; + } + setJSONPref(Apple2JoystickSettingsMenu.JoystickAdvanced.SETTINGS.JOYSTICK_TAPDELAY, framesDelay); + } + + // migrate individual keypad button actions to new button rosette actions ... + { + ArrayList chars = new ArrayList(); + ArrayList scans = new ArrayList(); + + int northChar = getIntJSONPref(PREF_DOMAIN_JOYSTICK, "kpSwipeNorthChar", Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION); + int northScan = getIntJSONPref(PREF_DOMAIN_JOYSTICK, "kpSwipeNorthScancode",-1 ); + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, northChar, northScan); + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + + int downChar = getIntJSONPref(PREF_DOMAIN_JOYSTICK, "kpTouchDownChar", Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION); + int downScan = getIntJSONPref(PREF_DOMAIN_JOYSTICK, "kpTouchDownScancode", -1); + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, downChar, downScan); + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + + int southChar = getIntJSONPref(PREF_DOMAIN_JOYSTICK, "kpSwipeSouthChar", Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION); + int southScan = getIntJSONPref(PREF_DOMAIN_JOYSTICK, "kpSwipeSouthScancode", -1); + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, southChar, southScan); + Apple2KeypadSettingsMenu.KeypadPreset.addRosetteKey(chars, scans, Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1); + + Apple2KeypadSettingsMenu.KeypadPreset.saveButtRosettes(chars, scans); + } + + JSONObject map = _prefDomain(PREF_DOMAIN_JOYSTICK); + map.remove("jsTapDelaySecs"); + map.remove("kpSwipeNorthChar"); + map.remove("kpSwipeNorthScancode"); + map.remove("kpSwipeSouthChar"); + map.remove("kpSwipeSouthScancode"); + map.remove("kpTouchDownChar"); + map.remove("kpTouchDownScancode"); + } + save(activity); return firstTime; } diff --git a/Android/app/src/main/res/values-de/strings.xml b/Android/app/src/main/res/values-de/strings.xml index 85023dc4..f5816afd 100644 --- a/Android/app/src/main/res/values-de/strings.xml +++ b/Android/app/src/main/res/values-de/strings.xml @@ -56,7 +56,6 @@ Keine Drücke den Feuerknopf Ausgewählter Feuerknopf - Joystickknopf-Zeitverzögerung in Sek. Aufwärts wischen zum feuern Feuerknopf zum feuern beim aufwärts wischen Abwärts wischen zum feuern @@ -97,8 +96,6 @@ Sichtbarkeit wenn deaktiviert Sichtbarkeit des Keyboard und Touch Menüs wenn deaktiviert Keypad Joystick - @string/joystick_calibrate - @string/joystick_calibrate Auswahl der Keypad Tasten… Auswahl der Achsen und Knopf Tasten Achse & Knöpfe @@ -117,14 +114,13 @@ antippen Nach oben wischen Nach unten wischen - Seafox Tasten ;-)… + Seafox Tasten… Auswahl einer Anpassung… - ↑,←,→,↓, tippe auf die Leerstaste - A,Z,←,→, tippe auf die Leertaste - I,J,K,M, tippe auf die Leertaste - ←,→, tippe auf die Leertaste - W,A,D,X, tippe auf die Leertaste - Tastenwiederholungsschwellwert in Sek. + (↑ ← → ↓), tippe auf die Leerstaste + (A Z ← →), tippe auf die Leerstaste + (I J K M), tippe auf die Leertaste + (← →), tippe auf die Leertaste + (W A D X), tippe auf die Leertaste Lade Disk-Image… Einlegen eines Disk ][ Image Emulator Einstellungen… @@ -203,5 +199,15 @@ View notes for this release Enable dual-touch Support two-thumb input + (A Z ← →), Leertaste, Q + Keypad touch down delay in secs + Allows immediate auto-repeat (avoids original 534-801 millis delay) + Keypad Immediate Autorepeat + Frames + Millis + Joystick touch down delay in video frames + Robotron Tasten… + Lode Runner Tasten… + Start game with Ctrl-K to activate keyboard diff --git a/Android/app/src/main/res/values-es/strings.xml b/Android/app/src/main/res/values-es/strings.xml index bf475eed..da71f848 100644 --- a/Android/app/src/main/res/values-es/strings.xml +++ b/Android/app/src/main/res/values-es/strings.xml @@ -56,7 +56,6 @@ Ninguno Toque para disparar Botón para disparar sobre toque abajo - Toque retardo del joystick en segundos Pase hacia arriba Botón para disparar sobre pase el dedo hacia arriba Pase hacia abajo @@ -95,8 +94,6 @@ Visibilidad cuando está inactivo Visibilidad del teclado y menú cuando está inactivo Joystick como teclado numérico - @string/joystick_calibrate - @string/joystick_calibrate Teclas del teclado numérico… Elegir las teclas del joystick del teclado numérico Ejes y botones @@ -115,14 +112,13 @@ Toque Desliza el dedo hacia arriba Desliza el dedo hacia abajo - Seafox keys ;-)… + Tecla para Seafox… Elija personalizado… - ↑,←,→,↓, pulse espaciadora - A,Z,←,→, pulse espaciadora - I,J,K,M, pulse espaciadora - ←,→, pulse espaciadora - W,A,D,X, pulse espaciadora - Umbral de repetición de teclas en segundos + (↑ ← → ↓), pulse barra espaciadora + (A Z ← →), pulse barra espaciadora + (I J K M), pulse barra espaciadora + (← →), pulse barra espaciadora + (W A D X), pulse barra espaciadora Insertar imagen de disco… Insertar imagen de "Disk ][" Configuración del emulador… @@ -203,5 +199,15 @@ View notes for this release Enable dual-touch Support two-thumb input + (A Z ← →), barra espaciadora, Q + Keypad touch down delay in secs + Allows immediate auto-repeat (avoids original 534-801 millis delay) + Keypad Immediate Autorepeat + Frames + Millis + Joystick touch down delay in video frames + Tecla para Robotron… + Lode Runner keys… + Start game with Ctrl-K to activate keyboard diff --git a/Android/app/src/main/res/values-fr/strings.xml b/Android/app/src/main/res/values-fr/strings.xml index 96b92568..b3edbd23 100644 --- a/Android/app/src/main/res/values-fr/strings.xml +++ b/Android/app/src/main/res/values-fr/strings.xml @@ -56,7 +56,6 @@ Rien Sélectionner l\'action Bouton à activer lors d\'une pression vers le bas - Délai de pression du bouton Joystick en secondes Lancement du swipe up Bouton à lancer sur swipe up Lancement du swipe down @@ -95,8 +94,6 @@ Visibilité quand inactif Clavier et menu tactile visible quand inactif Keypad Joystick - @string/joystick_calibrate - @string/joystick_calibrate Sélection des touches du keypad… Sélection des touches pour les axes et boutons Axis & boutons @@ -115,14 +112,13 @@ Presser Slider vers le haut Slider vers le bas - Touches Seafox ;-)… + Touches Seafox… Choisir customisation… - ↑,←,→,↓, pressez barre d\'espace - A,Z,←,→, pressez barre d\'espace - I,J,K,M, pressez barre d\'espace - ←,→, pressez barre d\'espace - W,A,D,X, pressez barre d\'espace - Répétition des touches en secs + (↑ ← → ↓), pressez barre d\'espace + (A Z ← →), pressez barre d\'espace + (I J K M), pressez barre d\'espace + (← →), pressez barre d\'espace + (W A D X), pressez barre d\'espace Chargement de l\'image disque… Insérer un fichier image (disque) ][ Paramètres de l\'émulateur… @@ -203,5 +199,15 @@ View notes for this release Enable dual-touch Support two-thumb input + (A Z ← →) barre d\'espace, Q + Keypad touch down delay in secs + Allows immediate auto-repeat (avoids original 534-801 millis delay) + Keypad Immediate Autorepeat + Frames + Millis + Joystick touch down delay in video frames + Touches Robotron… + Touches Lode Runner… + Start game with Ctrl-K to activate keyboard diff --git a/Android/app/src/main/res/values/strings.xml b/Android/app/src/main/res/values/strings.xml index 52d4fd0e..7a11a4a8 100644 --- a/Android/app/src/main/res/values/strings.xml +++ b/Android/app/src/main/res/values/strings.xml @@ -60,7 +60,7 @@ None Tap fire Button to fire on tap down - Joystick button tap delay in secs + Joystick touch down delay in video frames Swipe up fire Button to fire on swipe up Swipe down fire @@ -68,7 +68,7 @@ Joystick/keypad button switch threshold in pts (max: ⅓ screen height) Calibrate… Configure and test current settings - Configure joystick… + Configure touch joystick… Axis touch, buttons, etc Joystick/keypad axis on left Joystick/keypad axis on left (buttons on right) @@ -92,7 +92,7 @@ Choose alternative customized layout Enable key click Enables key click sound if available - Configure keyboard… + Configure touch keyboard… Transparency, lowercase, custom keys Keyboard glyphs scaled 2x (Makes keyboard appear less pixelated on large screens) @@ -103,13 +103,11 @@ Visibility when inactive Keyboard and touch menu visibility when inactive Keypad Joystick - @string/joystick_calibrate - @string/joystick_calibrate Choose keypad keys… Choose axis and button keys Axis & buttons Choose XXX Key: - Configure keypad joystick… + Configure touch keypad joystick… @string/joystick_configure_summary Center Down @@ -123,14 +121,14 @@ Tap Swipe Up Swipe Down - Seafox keys ;-)… + Seafox keys… Choose custom… - ↑,←,→,↓, tap spacebar - A,Z,←,→, tap spacebar - I,J,K,M, tap spacebar - ←,→, tap spacebar - W,A,D,X, tap spacebar - Key repeat threshold in secs + (↑ ← → ↓), tap spacebar + (A Z ← →), tap spacebar + (I J K M), tap spacebar + (← →), tap spacebar + (W A D X), tap spacebar + Keypad touch down delay in secs Load image or state file… Insert Disk ][ image or state file Emulator settings… @@ -203,5 +201,13 @@ View notes for this release Enable dual-touch Support two-thumb input + (A Z ← →), tap spacebar, Q up + Keypad Immediate Autorepeat + Allows immediate auto-repeat (avoids original 534-801 millis delay) + Frames + Millis + Robotron keys… + Lode Runner keys… + Start game with Ctrl-K to activate keyboard diff --git a/Android/assets/release_notes.txt b/Android/assets/release_notes.txt index 2177f0c1..88162429 100644 --- a/Android/assets/release_notes.txt +++ b/Android/assets/release_notes.txt @@ -1,16 +1,9 @@ -Apple2ix (a2ix) 2.0.0-Android Release Notes - -TL;DR : new video mode settings! +Apple2ix (a2ix) 2.0.1-Beta CHANGES: -- Implemented a more conformant video scanner. This improves emulation fidelity for programs that implement custom video modes (e.g., custom split screen between text & graphics). - -- New NTSC video display modes including "Color monitor", "Monochrome TV", and "Color TV" modes. Thanks to the AppleWin project and Bill Simms for these modes. Also support "Green screen" monochrome video. - -- New preference to enable/disable half-scanline video effects. - -- EXPERIMENTAL: New preference to enable/disable fast disk image loading. This may cause audio glitches or other instability. Use at your own risk! +- Improved two-thumb touch keyboard support. +- Improved response of touch keypad joystick. GENERAL INFO: diff --git a/src/joystick.h b/src/joystick.h index 92a4913b..3e2cdac5 100644 --- a/src/joystick.h +++ b/src/joystick.h @@ -70,16 +70,4 @@ void joydriver_setButton1Pressed(bool pressed); // backend joystick driver reset procedure extern void (*joydriver_resetJoystick)(void); -#if INTERFACE_TOUCH - -typedef enum touchjoy_button_type_t { - TOUCH_NONE = 0, - TOUCH_BUTTON1 = 1, - TOUCH_BUTTON2, - TOUCH_BOTH, - // --or-- an ASCII/fonttext value ... -} touchjoy_button_type_t; - -#endif // INTERFACE_TOUCH - #endif // whole file diff --git a/src/keys.c b/src/keys.c index 80ecc35d..a06f522c 100644 --- a/src/keys.c +++ b/src/keys.c @@ -515,7 +515,3 @@ static __attribute__((constructor)) void __init_keys(void) { prefs_registerListener(PREF_DOMAIN_KEYBOARD, &keys_prefsChanged); } -#if INTERFACE_TOUCH -void (*keydriver_keyboardReadCallback)(void) = NULL; -#endif - diff --git a/src/keys.h b/src/keys.h index 066c1705..9b97bfcf 100644 --- a/src/keys.h +++ b/src/keys.h @@ -152,10 +152,5 @@ void keys_handleInput(int scan_or_key, bool is_pressed, bool is_ascii); int keys_scancode2ASCII(int scancode, bool is_shifted, bool is_ctrl); uint8_t keys_apple2ASCII(uint8_t c, OUTPARM font_mode_t *mode); -#if INTERFACE_TOUCH -// keyboard read callback -extern void (*keydriver_keyboardReadCallback)(void); -#endif - #endif diff --git a/src/meta/log.h b/src/meta/log.h index d5439780..8fd76647 100644 --- a/src/meta/log.h +++ b/src/meta/log.h @@ -37,6 +37,17 @@ void log_outputString(const char * const str); #define _MYFILE_ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#define _SIMPLE_LOG(...) \ + do { \ + char *buf = NULL; \ + int ignored = asprintf(&buf, __VA_ARGS__); \ + (void)ignored; \ + \ + log_outputString(buf); \ + \ + free(buf); \ + } while (0) + #define _LOG(...) \ do { \ int _err = errno; \ diff --git a/src/prefs.h b/src/prefs.h index 7987267a..036771d9 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -52,11 +52,13 @@ #define PREF_AXIS_SENSITIVITY "axisSensitivity" #define PREF_JOY_SWIPE_NORTH_CHAR "jsSwipeNorthChar" #define PREF_JOY_SWIPE_SOUTH_CHAR "jsSwipeSouthChar" -#define PREF_JOY_TAP_DELAY "jsTapDelaySecs" +#define PREF_JOY_TAP_DELAY "jsTapDelayFrames" #define PREF_JOY_TOUCHDOWN_CHAR "jsTouchDownChar" -#define PREF_KPAD_REPEAT_THRESH "keyRepeatThresholdSecs" -#define PREF_KPAD_ROSETTE_CHAR_ARRAY "kpAxisRosetteChars" -#define PREF_KPAD_ROSETTE_SCAN_ARRAY "kpAxisRosetteScancodes" +#define PREF_KPAD_FAST_AUTOREPEAT "kpFastAutoRepeat" +#define PREF_KPAD_AXIS_ROSETTE_CHAR_ARRAY "kpAxisRosetteChars" +#define PREF_KPAD_AXIS_ROSETTE_SCAN_ARRAY "kpAxisRosetteScancodes" +#define PREF_KPAD_BUTT_ROSETTE_CHAR_ARRAY "kpButtRosetteChars" +#define PREF_KPAD_BUTT_ROSETTE_SCAN_ARRAY "kpButtRosetteScancodes" #define PREF_KPAD_SWIPE_NORTH_CHAR "kpSwipeNorthChar" #define PREF_KPAD_SWIPE_NORTH_SCAN "kpSwipeNorthScancode" #define PREF_KPAD_SWIPE_SOUTH_CHAR "kpSwipeSouthChar" diff --git a/src/video/gltouchjoy.c b/src/video/gltouchjoy.c index b4479716..72e4d015 100644 --- a/src/video/gltouchjoy.c +++ b/src/video/gltouchjoy.c @@ -31,19 +31,32 @@ #define AXIS_OBJ_W 0.15 #define AXIS_OBJ_H 0.2 -#define AXIS_OBJ_HALF_W (AXIS_OBJ_W/2.f) -#define AXIS_OBJ_HALF_H (AXIS_OBJ_H/2.f) #define BUTTON_OBJ_W 0.075 #define BUTTON_OBJ_H 0.1 -#define BUTTON_OBJ_HALF_W (BUTTON_OBJ_W/2.f) -#define BUTTON_OBJ_HALF_H (BUTTON_OBJ_H/2.f) #define BUTTON_SWITCH_THRESHOLD_DEFAULT 22 GLTouchJoyGlobals joyglobals = { 0 }; -GLTouchJoyAxes axes = { 0 }; -GLTouchJoyButtons buttons = { 0 }; + +typedef struct variant_params_s { + GLModel *model; // origin model/texture + GLModel *azimuthModel; // azimuth model + void (*setupButtonModel)(char); + + int centerX; + int centerY; + int trackingIndex; + GLfloat obj_w; + GLfloat obj_h; + struct timespec timingBegin; + + uint8_t activeChar; + bool modelDirty; +} variant_params_s; + +static variant_params_s axes = { 0 }; +static variant_params_s butt = { 0 }; static struct { GLTouchJoyVariant *joys; @@ -63,12 +76,10 @@ static struct { int axisYMax; // Button box - int buttonX; - int buttonXMax; - int buttonY; - int buttonYMax; - - // TODO FIXME : support 2-players! + int buttX; + int buttXMax; + int buttY; + int buttYMax; } touchport = { 0 }; #define AZIMUTH_CLASS(CLS, ...) \ @@ -195,12 +206,12 @@ static void *_azimuth_create_model(GLModel *parent) { return azimuthJoystick; } -static void _azimuth_render(void) { - if (!axes.azimuthModel) { +static void _azimuth_render(GLModel *azimuthModel) { + if (!azimuthModel) { return; } - GLModelJoystickAzimuth *azimuthJoystick = (GLModelJoystickAzimuth *)axes.azimuthModel->custom; + GLModelJoystickAzimuth *azimuthJoystick = (GLModelJoystickAzimuth *)azimuthModel->custom; // use azimuth (SolidColor) program glUseProgram(azimuthJoystick->program); @@ -235,32 +246,32 @@ static void _azimuth_render(void) { } // NOTE : assuming we should just upload new postion data every time ... - glBindBuffer(GL_ARRAY_BUFFER, axes.azimuthModel->posBufferName); - glBufferData(GL_ARRAY_BUFFER, axes.azimuthModel->positionArraySize, axes.azimuthModel->positions, GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, azimuthModel->posBufferName); + glBufferData(GL_ARRAY_BUFFER, azimuthModel->positionArraySize, azimuthModel->positions, GL_DYNAMIC_DRAW); // Bind our vertex array object #if USE_VAO - glBindVertexArray(axes.azimuthModel->vaoName); + glBindVertexArray(azimuthModel->vaoName); #else - glBindBuffer(GL_ARRAY_BUFFER, axes.azimuthModel->posBufferName); + glBindBuffer(GL_ARRAY_BUFFER, azimuthModel->posBufferName); - GLsizei posTypeSize = getGLTypeSize(axes.azimuthModel->positionType); + GLsizei posTypeSize = getGLTypeSize(azimuthModel->positionType); // Set up parmeters for position attribute in the VAO including, size, type, stride, and offset in the currenly // bound VAO This also attaches the position VBO to the VAO glVertexAttribPointer(POS_ATTRIB_IDX, // What attibute index will this array feed in the vertex shader (see buildProgram) - axes.azimuthModel->positionSize, // How many elements are there per position? - axes.azimuthModel->positionType, // What is the type of this data? + azimuthModel->positionSize, // How many elements are there per position? + azimuthModel->positionType, // What is the type of this data? GL_FALSE, // Do we want to normalize this data (0-1 range for fixed-pont types) - axes.azimuthModel->positionSize*posTypeSize, // What is the stride (i.e. bytes between positions)? + azimuthModel->positionSize*posTypeSize, // What is the stride (i.e. bytes between positions)? 0); // What is the offset in the VBO to the position data? glEnableVertexAttribArray(POS_ATTRIB_IDX); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, axes.azimuthModel->elementBufferName); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, azimuthModel->elementBufferName); #endif // draw it - glDrawElements(axes.azimuthModel->primType, axes.azimuthModel->numElements, axes.azimuthModel->elementType, 0); + glDrawElements(azimuthModel->primType, azimuthModel->numElements, azimuthModel->elementType, 0); // back to main framebuffer/quad program glUseProgram(mainShaderProgram); @@ -303,10 +314,15 @@ static void _setup_axis_hud(GLModel *parent) { const unsigned int row = (AXIS_TEMPLATE_COLS+1); - uint8_t *rosetteChars = variant.curr->rosetteChars(); - for (unsigned int i=0; itpl)+(row*i))[j] = rosetteChars[(i*ROSETTE_ROWS)+j]; + uint8_t *rosetteChars = (parent == axes.model) + ? variant.curr->axisRosetteChars() + : variant.curr->buttRosetteChars(); + + if (rosetteChars) { + for (unsigned int i=0; itpl)+(row*i))[j] = rosetteChars[(i*ROSETTE_ROWS)+j]; + } } } @@ -353,7 +369,7 @@ static void _setup_button_hud(GLModel *parent) { } const unsigned int row = (BUTTON_TEMPLATE_COLS+1); - ((hudElement->tpl)+(row*0))[0] = buttons.activeChar; + ((hudElement->tpl)+(row*0))[0] = butt.activeChar; glhud_setupDefault(parent); } @@ -368,10 +384,14 @@ static void *_create_button_hud(GLModel *parent) { return hudElement; } +static inline void _setup_button_object_nop(char newChar) { + (void)newChar; +} + static inline void _setup_button_object_with_char(char newChar) { - if (buttons.activeChar != newChar) { - buttons.activeChar = newChar; - _setup_button_hud(buttons.model); + if (butt.activeChar != newChar) { + butt.activeChar = newChar; + _setup_button_hud(butt.model); } } @@ -380,23 +400,13 @@ static inline void _setup_button_object_with_char(char newChar) { static inline void resetState() { LOG("..."); axes.trackingIndex = TRACKING_NONE; - buttons.trackingIndex = TRACKING_NONE; + butt.trackingIndex = TRACKING_NONE; variant.joys->resetState(); variant.kpad->resetState(); } -static void gltouchjoy_setup(void) { - LOG("..."); - - gltouchjoy_shutdown(); - - if (joyglobals.prefsChanged) { - gltouchjoy_applyPrefs(); - } - - // axis origin object - - axes.model = mdlCreateQuad((GLModelParams_s){ +static void _setup_axis_models(variant_params_s *params) { + params->model = mdlCreateQuad((GLModelParams_s){ .skew_x = -1.05, .skew_y = -1.0, .z = MODEL_DEPTH, @@ -410,20 +420,23 @@ static void gltouchjoy_setup(void) { .create = &_create_axis_hud, .destroy = &glhud_destroyDefault, }); - if (!axes.model) { + if (!params->model) { LOG("gltouchjoy not initializing axis"); return; } - if (!axes.model->custom) { + if (!params->model->custom) { LOG("gltouchjoy axes initialization problem"); return; } + _setup_axis_hud(params->model);// HACK : twice to ensure correct template ... + params->obj_w = AXIS_OBJ_W; + params->obj_h = AXIS_OBJ_H; // axis azimuth object bool azimuthError = true; do { - axes.azimuthModel = mdlCreateQuad((GLModelParams_s){ + params->azimuthModel = mdlCreateQuad((GLModelParams_s){ .skew_x = -1.05, .skew_y = -1.0, .z = MODEL_DEPTH, @@ -437,70 +450,87 @@ static void gltouchjoy_setup(void) { .create = &_azimuth_create_model, .destroy = &_azimuth_destroy_model, }); - if (!axes.azimuthModel) { + if (!params->azimuthModel) { LOG("gltouchjoy azimuth model initialization problem"); break; } - if (!axes.azimuthModel->custom) { + if (!params->azimuthModel->custom) { LOG("gltouchjoy azimuth custom model initialization problem"); break; } - azimuthError = false; } while (0); if (azimuthError) { - mdlDestroyModel(&axes.azimuthModel); + mdlDestroyModel(¶ms->azimuthModel); } +} + +static void gltouchjoy_setup(void) { + LOG("..."); + + gltouchjoy_shutdown(); + + if (joyglobals.prefsChanged) { + gltouchjoy_applyPrefs(); + } + + // axis origin object + _setup_axis_models(&axes); // button object long lVal = 0; if (variant.curr->variant() == TOUCH_DEVICE_JOYSTICK_KEYPAD) { - buttons.activeChar = prefs_parseLongValue(PREF_DOMAIN_JOYSTICK, PREF_KPAD_TOUCHDOWN_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_SPACE_VISUAL; + _setup_axis_models(&butt); + butt.setupButtonModel = &_setup_button_object_nop; } else { + butt.setupButtonModel = &_setup_button_object_with_char; if (!prefs_parseLongValue(PREF_DOMAIN_JOYSTICK, PREF_JOY_TOUCHDOWN_CHAR, &lVal, /*base:*/10)) { - buttons.activeChar = MOUSETEXT_OPENAPPLE; + butt.activeChar = MOUSETEXT_OPENAPPLE; } else { if (lVal == TOUCH_BUTTON2) { - buttons.activeChar = MOUSETEXT_CLOSEDAPPLE; + butt.activeChar = MOUSETEXT_CLOSEDAPPLE; } else if (lVal == TOUCH_BOTH) { - buttons.activeChar = '+'; + butt.activeChar = '+'; } else { - buttons.activeChar = MOUSETEXT_OPENAPPLE; + butt.activeChar = MOUSETEXT_OPENAPPLE; } } + + butt.azimuthModel = NULL; + butt.model = mdlCreateQuad((GLModelParams_s){ + .skew_x = 1.05-BUTTON_OBJ_W, + .skew_y = -1.0, + .z = MODEL_DEPTH, + .obj_w = BUTTON_OBJ_W, + .obj_h = BUTTON_OBJ_H, + .positionUsageHint = GL_DYNAMIC_DRAW, // positions can change + .tex_w = BUTTON_FB_WIDTH, + .tex_h = BUTTON_FB_HEIGHT, + .texcoordUsageHint = GL_DYNAMIC_DRAW, // so can texture + }, (GLCustom){ + .create = &_create_button_hud, + .destroy = &glhud_destroyDefault, + }); + if (!butt.model) { + LOG("gltouchjoy not initializing buttons"); + return; + } + if (!butt.model->custom) { + LOG("gltouchjoy buttons initialization problem"); + return; + } + butt.obj_w = BUTTON_OBJ_W; + butt.obj_h = BUTTON_OBJ_H; } - buttons.model = mdlCreateQuad((GLModelParams_s){ - .skew_x = 1.05-BUTTON_OBJ_W, - .skew_y = -1.0, - .z = MODEL_DEPTH, - .obj_w = BUTTON_OBJ_W, - .obj_h = BUTTON_OBJ_H, - .positionUsageHint = GL_DYNAMIC_DRAW, // positions can change - .tex_w = BUTTON_FB_WIDTH, - .tex_h = BUTTON_FB_HEIGHT, - .texcoordUsageHint = GL_DYNAMIC_DRAW, // so can texture - }, (GLCustom){ - .create = &_create_button_hud, - .destroy = &glhud_destroyDefault, - }); - if (!buttons.model) { - LOG("gltouchjoy not initializing buttons"); - return; - } - if (!buttons.model->custom) { - LOG("gltouchjoy buttons initialization problem"); - return; - } - - variant.joys->setup(&_setup_button_object_with_char); - variant.kpad->setup(&_setup_button_object_with_char); + variant.joys->setup(); + variant.kpad->setup(); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); axes.timingBegin = now; - buttons.timingBegin = now; + butt.timingBegin = now; joyglobals.isAvailable = true; @@ -524,7 +554,41 @@ static void gltouchjoy_shutdown(void) { mdlDestroyModel(&axes.model); mdlDestroyModel(&axes.azimuthModel); - mdlDestroyModel(&buttons.model); + mdlDestroyModel(&butt.model); + mdlDestroyModel(&butt.azimuthModel); +} + +static void _render_glmodels(variant_params_s *params, int textureActive, int textureId) { + float alpha = 1.f; + if (params->trackingIndex == TRACKING_NONE) { + alpha = glhud_getTimedVisibility(params->timingBegin, joyglobals.minAlpha, 1.0); + if (alpha < joyglobals.minAlpha) { + alpha = joyglobals.minAlpha; + } + } + if (alpha > 0.0) { + GLModel *model = params->model; + glUniform1f(alphaValue, alpha); + + glActiveTexture(textureActive); + glBindTexture(GL_TEXTURE_2D, model->textureName); + if (model->texDirty) { + model->texDirty = false; + _HACKAROUND_GLTEXIMAGE2D_PRE(textureActive, model->textureName); + glTexImage2D(GL_TEXTURE_2D, /*level*/0, TEX_FORMAT_INTERNAL, model->texWidth, model->texHeight, /*border*/0, TEX_FORMAT, TEX_TYPE, model->texPixels); + } + if (params->modelDirty) { + params->modelDirty = false; + glBindBuffer(GL_ARRAY_BUFFER, model->posBufferName); + glBufferData(GL_ARRAY_BUFFER, model->positionArraySize, model->positions, GL_DYNAMIC_DRAW); + } + glUniform1i(texSamplerLoc, textureId); + glhud_renderDefault(model); + } + + if (joyglobals.showAzimuth && params->trackingIndex != TRACKING_NONE) { + _azimuth_render(params->azimuthModel); + } } static void gltouchjoy_render(void) { @@ -544,63 +608,12 @@ static void gltouchjoy_render(void) { glViewport(0, 0, touchport.width, touchport.height); // NOTE : show these HUD elements beyond the A2 framebuffer dimensions // draw axis - float alpha = 1.f; - if (axes.trackingIndex == TRACKING_NONE) { - alpha = glhud_getTimedVisibility(axes.timingBegin, joyglobals.minAlpha, 1.0); - if (alpha < joyglobals.minAlpha) { - alpha = joyglobals.minAlpha; - } - } - if (alpha > 0.0) { - glUniform1f(alphaValue, alpha); - - glActiveTexture(TEXTURE_ACTIVE_TOUCHJOY_AXIS); - glBindTexture(GL_TEXTURE_2D, axes.model->textureName); - if (axes.model->texDirty) { - axes.model->texDirty = false; - _HACKAROUND_GLTEXIMAGE2D_PRE(TEXTURE_ACTIVE_TOUCHJOY_AXIS, axes.model->textureName); - glTexImage2D(GL_TEXTURE_2D, /*level*/0, TEX_FORMAT_INTERNAL, axes.model->texWidth, axes.model->texHeight, /*border*/0, TEX_FORMAT, TEX_TYPE, axes.model->texPixels); - } - if (axes.modelDirty) { - axes.modelDirty = false; - glBindBuffer(GL_ARRAY_BUFFER, axes.model->posBufferName); - glBufferData(GL_ARRAY_BUFFER, axes.model->positionArraySize, axes.model->positions, GL_DYNAMIC_DRAW); - } - glUniform1i(texSamplerLoc, TEXTURE_ID_TOUCHJOY_AXIS); - glhud_renderDefault(axes.model); - } - - if (joyglobals.showAzimuth && axes.trackingIndex != TRACKING_NONE) { - _azimuth_render(); - } + _render_glmodels(&axes, TEXTURE_ACTIVE_TOUCHJOY_AXIS, TEXTURE_ID_TOUCHJOY_AXIS); // draw button(s) - - alpha = 1.f; - if (buttons.trackingIndex == TRACKING_NONE) { - alpha = glhud_getTimedVisibility(buttons.timingBegin, joyglobals.minAlpha, 1.0); - if (alpha < joyglobals.minAlpha) { - alpha = joyglobals.minAlpha; - } - } - if (alpha > 0.0) { - glUniform1f(alphaValue, alpha); - - glActiveTexture(TEXTURE_ACTIVE_TOUCHJOY_BUTTON); - glBindTexture(GL_TEXTURE_2D, buttons.model->textureName); - if (buttons.model->texDirty) { - buttons.model->texDirty = false; - _HACKAROUND_GLTEXIMAGE2D_PRE(TEXTURE_ACTIVE_TOUCHJOY_BUTTON, buttons.model->textureName); - glTexImage2D(GL_TEXTURE_2D, /*level*/0, TEX_FORMAT_INTERNAL, buttons.model->texWidth, buttons.model->texHeight, /*border*/0, TEX_FORMAT, TEX_TYPE, buttons.model->texPixels); - } - if (buttons.modelDirty) { - buttons.modelDirty = false; - glBindBuffer(GL_ARRAY_BUFFER, buttons.model->posBufferName); - glBufferData(GL_ARRAY_BUFFER, buttons.model->positionArraySize, buttons.model->positions, GL_DYNAMIC_DRAW); - } - glUniform1i(texSamplerLoc, TEXTURE_ID_TOUCHJOY_BUTTON); - glhud_renderDefault(buttons.model); - } + char active = variant.curr->buttActiveChar(); + butt.setupButtonModel(active); + _render_glmodels(&butt, TEXTURE_ACTIVE_TOUCHJOY_BUTTON, TEXTURE_ID_TOUCHJOY_BUTTON); } static void gltouchjoy_reshape(int w, int h, bool landscape) { @@ -613,17 +626,17 @@ static void gltouchjoy_reshape(int w, int h, bool landscape) { touchport.axisY = 0; touchport.axisYMax = h; - touchport.buttonY = 0; - touchport.buttonYMax = h; + touchport.buttY = 0; + touchport.buttYMax = h; if (joyglobals.axisIsOnLeft) { touchport.axisX = 0; touchport.axisXMax = (w * joyglobals.screenDivider); - touchport.buttonX = (w * joyglobals.screenDivider); - touchport.buttonXMax = w; + touchport.buttX = (w * joyglobals.screenDivider); + touchport.buttXMax = w; } else { - touchport.buttonX = 0; - touchport.buttonXMax = (w * joyglobals.screenDivider); + touchport.buttX = 0; + touchport.buttXMax = (w * joyglobals.screenDivider); touchport.axisX = (w * joyglobals.screenDivider); touchport.axisXMax = w; } @@ -669,90 +682,50 @@ static inline void _reset_model_position(GLModel *model, float touchX, float tou } } -static inline void _axis_touch_down(interface_touch_event_t action, int x, int y) { - axes.centerX = x; - axes.centerY = y; - - _reset_model_position(axes.model, x, y, AXIS_OBJ_HALF_W, AXIS_OBJ_HALF_H, axes.azimuthModel); - axes.modelDirty = true; - - TOUCH_JOY_LOG("---TOUCH %sDOWN (axis index %d) center:(%d,%d) -> joy(0x%02X,0x%02X)", (action == TOUCH_DOWN ? "" : "POINTER "), axes.trackingIndex, axes.centerX, axes.centerY, joy_x, joy_y); - variant.curr->axisDown(); +static void _touch_down(variant_params_s *params, int pointer_idx, int x, int y, void(*downFn)(void)) { + params->trackingIndex = pointer_idx; + params->centerX = x; + params->centerY = y; + _reset_model_position(params->model, x, y, (params->obj_w/2.f), (params->obj_h/2.f), params->azimuthModel); + params->modelDirty = true; + downFn(); } -static inline void _button_touch_down(interface_touch_event_t action, int x, int y) { - buttons.centerX = x; - buttons.centerY = y; +static void _touch_move(variant_params_s *params, float *x_coords, float *y_coords, void (*moveFn)(int, int)) { + int x = (int)x_coords[params->trackingIndex]; + int y = (int)y_coords[params->trackingIndex]; - _reset_model_position(buttons.model, x, y, BUTTON_OBJ_HALF_W, BUTTON_OBJ_HALF_H, NULL); - buttons.modelDirty = true; - - TOUCH_JOY_LOG("---TOUCH %sDOWN (buttons index %d) center:(%d,%d) -> buttons(0x%02X,0x%02X)", (action == TOUCH_DOWN ? "" : "POINTER "), buttons.trackingIndex, buttons.centerX, buttons.centerY, joy_button0, joy_button1); - variant.curr->buttonDown(); -} - -static inline void _axis_move(int x, int y) { - - if (joyglobals.showAzimuth && axes.azimuthModel) { + if (joyglobals.showAzimuth && params->azimuthModel) { float centerX = 0.f; float centerY = 0.f; glhud_screenToModel(x, y, touchport.width, touchport.height, ¢erX, ¢erY); - GLfloat *quadAzimuth = (GLfloat *)axes.azimuthModel->positions; + GLfloat *quadAzimuth = (GLfloat *)params->azimuthModel->positions; quadAzimuth[4 +0] = centerX; quadAzimuth[4 +1] = centerY; }; - x -= axes.centerX; - y -= axes.centerY; - TOUCH_JOY_LOG("---TOUCH MOVE ...tracking axis:%d (%d,%d) -> joy(0x%02X,0x%02X)", axes.trackingIndex, x, y, joy_x, joy_y); - variant.curr->axisMove(x, y); + x -= params->centerX; + y -= params->centerY; + moveFn(x, y); } -static inline void _button_move(int x, int y) { - x -= buttons.centerX; - y -= buttons.centerY; - TOUCH_JOY_LOG("+++TOUCH MOVE ...tracking button:%d (%d,%d) -> buttons(0x%02X,0x%02X)", buttons.trackingIndex, x, y, joy_button0, joy_button1); - variant.curr->buttonMove(x, y); -} +static void _touch_up(variant_params_s *params, float *x_coords, float *y_coords, variant_params_s *altParams, void(*upFn)(int, int), const char *altTag) { -static inline void _axis_touch_up(interface_touch_event_t action, int x, int y) { -#if DEBUG_TOUCH_JOY - bool resetIndex = false; - if (buttons.trackingIndex > axes.trackingIndex) { - // TODO FIXME : is resetting the pointer index just an Android-ism? - resetIndex = true; - } - TOUCH_JOY_LOG("---TOUCH %sUP (axis went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset buttons index!)" : "")); -#endif - x -= axes.centerX; - y -= axes.centerY; - if (buttons.trackingIndex > axes.trackingIndex) { - TOUCH_JOY_LOG("!!! : DECREMENTING buttons.trackingIndex"); - --buttons.trackingIndex; - } - variant.curr->axisUp(x, y); - axes.trackingIndex = TRACKING_NONE; -} + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + params->timingBegin = now; -static inline void _button_touch_up(interface_touch_event_t action, int x, int y) { -#if DEBUG_TOUCH_JOY - bool resetIndex = false; - if (axes.trackingIndex > buttons.trackingIndex) { - // TODO FIXME : is resetting the pointer index just an Android-ism? - resetIndex = true; - } - TOUCH_JOY_LOG("---TOUCH %sUP (buttons went up)%s", (action == TOUCH_UP ? "" : "POINTER "), (resetIndex ? " (reset axis index!)" : "")); -#endif - x -= buttons.centerX; - y -= buttons.centerY; - if (axes.trackingIndex > buttons.trackingIndex) { - TOUCH_JOY_LOG("!!! : DECREMENTING axes.trackingIndex"); - --axes.trackingIndex; - } - variant.curr->buttonUp(x, y); - buttons.trackingIndex = TRACKING_NONE; -} + int x = (int)x_coords[params->trackingIndex] - params->centerX; + int y = (int)y_coords[params->trackingIndex] - params->centerY; + if (altParams->trackingIndex > params->trackingIndex) { + TOUCH_JOY_LOG("\t!!! : DECREMENTING %s.trackingIndex", altTag); + --altParams->trackingIndex; + } + + upFn(x, y); + params->trackingIndex = TRACKING_NONE; +} static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int pointer_count, int pointer_idx, float *x_coords, float *y_coords) { if (!joyglobals.isAvailable) { @@ -765,9 +738,6 @@ static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int point return 0x0LL; } - bool axisConsumed = false; - bool buttonConsumed = false; - switch (action) { case TOUCH_DOWN: case TOUCH_POINTER_DOWN: @@ -776,28 +746,24 @@ static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int point int x = (int)x_coords[pointer_idx]; int y = (int)y_coords[pointer_idx]; if (_is_point_on_axis_side(x, y)) { - if (pointer_idx == buttons.trackingIndex) { - TOUCH_JOY_LOG("!!! : INCREMENTING buttons.trackingIndex"); - ++buttons.trackingIndex; + if (pointer_idx == butt.trackingIndex) { + TOUCH_JOY_LOG("\t!!! : INCREMENTING buttons.trackingIndex"); + ++butt.trackingIndex; } if (axes.trackingIndex != TRACKING_NONE) { - TOUCH_JOY_LOG("!!! : IGNORING OTHER AXIS TOUCH DOWN %d", pointer_idx); + TOUCH_JOY_LOG("\t!!! : IGNORING OTHER AXIS TOUCH DOWN %d", pointer_idx); } else { - axisConsumed = true; - axes.trackingIndex = pointer_idx; - _axis_touch_down(action, x, y); + _touch_down(&axes, pointer_idx, x, y, variant.curr->axisDown); } } else { if (pointer_idx == axes.trackingIndex) { - TOUCH_JOY_LOG("!!! : INCREMENTING axes.trackingIndex"); + TOUCH_JOY_LOG("\t!!! : INCREMENTING axes.trackingIndex"); ++axes.trackingIndex; } - if (buttons.trackingIndex != TRACKING_NONE) { - TOUCH_JOY_LOG("!!! : IGNORING OTHER BUTTON TOUCH DOWN %d", pointer_idx); + if (butt.trackingIndex != TRACKING_NONE) { + TOUCH_JOY_LOG("\t!!! : IGNORING OTHER BUTTON TOUCH DOWN %d", pointer_idx); } else { - buttonConsumed = true; - buttons.trackingIndex = pointer_idx; - _button_touch_down(action, x, y); + _touch_down(&butt, pointer_idx, x, y, variant.curr->buttonDown); } } } @@ -806,42 +772,26 @@ static int64_t gltouchjoy_onTouchEvent(interface_touch_event_t action, int point case TOUCH_MOVE: TOUCH_JOY_LOG("------MOVE:"); if (axes.trackingIndex >= 0) { - axisConsumed = true; - int x = (int)x_coords[axes.trackingIndex]; - int y = (int)y_coords[axes.trackingIndex]; - _axis_move(x, y); + _touch_move(&axes, x_coords, y_coords, variant.curr->axisMove); } - if (buttons.trackingIndex >= 0) { - buttonConsumed = true; - int x = (int)x_coords[buttons.trackingIndex]; - int y = (int)y_coords[buttons.trackingIndex]; - _button_move(x, y); + if (butt.trackingIndex >= 0) { + _touch_move(&butt, x_coords, y_coords, variant.curr->buttonMove); } break; case TOUCH_UP: case TOUCH_POINTER_UP: - { - TOUCH_JOY_LOG("------UP:"); - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - if (pointer_idx == axes.trackingIndex) { - axes.timingBegin = now; - int x = (int)x_coords[axes.trackingIndex]; - int y = (int)y_coords[axes.trackingIndex]; - _axis_touch_up(action, x, y); - } else if (pointer_idx == buttons.trackingIndex) { - buttons.timingBegin = now; - int x = (int)x_coords[buttons.trackingIndex]; - int y = (int)y_coords[buttons.trackingIndex]; - _button_touch_up(action, x, y); + TOUCH_JOY_LOG("------UP:"); + if (pointer_idx == axes.trackingIndex) { + _touch_up(&axes, x_coords, y_coords, /*alt:*/&butt, variant.curr->axisUp, "buttons"); + } else if (pointer_idx == butt.trackingIndex) { + _touch_up(&butt, x_coords, y_coords, /*alt:*/&axes, variant.curr->buttonUp, "axis"); + } else { + if (pointer_count == 1) { + TOUCH_JOY_LOG("\t!!! : RESETTING TOUCH JOYSTICK STATE MACHINE"); + resetState(); } else { - if (pointer_count == 1) { - TOUCH_JOY_LOG("!!! : RESETTING TOUCH JOYSTICK STATE MACHINE"); - resetState(); - } else { - TOUCH_JOY_LOG("!!! : IGNORING OTHER TOUCH UP %d", pointer_idx); - } + TOUCH_JOY_LOG("\t!!! : IGNORING OTHER TOUCH UP %d", pointer_idx); } } break; @@ -866,20 +816,23 @@ static void _animation_showTouchJoystick(void) { return; } - int x = touchport.axisX + ((touchport.axisXMax - touchport.axisX)/2); - int y = touchport.axisY + ((touchport.axisYMax - touchport.axisY)/2); - _reset_model_position(axes.model, x, y, AXIS_OBJ_HALF_W, AXIS_OBJ_HALF_H, NULL); + int x; + int y; + + x = touchport.axisX + ((touchport.axisXMax - touchport.axisX)/2); + y = touchport.axisY + ((touchport.axisYMax - touchport.axisY)/2); + _reset_model_position(axes.model, x, y, (axes.obj_w/2.f), (axes.obj_h/2.f), NULL); axes.modelDirty = true; - x = touchport.buttonX + ((touchport.buttonXMax - touchport.buttonX)/2); - y = touchport.buttonY + ((touchport.buttonYMax - touchport.buttonY)/2); - _reset_model_position(buttons.model, x, y, BUTTON_OBJ_HALF_W, BUTTON_OBJ_HALF_H, NULL); - buttons.modelDirty = true; + x = touchport.buttX + ((touchport.buttXMax - touchport.buttX)/2); + y = touchport.buttY + ((touchport.buttYMax - touchport.buttY)/2); + _reset_model_position(butt.model, x, y, (butt.obj_w/2.f), (butt.obj_h/2.f), NULL); + butt.modelDirty = true; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); axes.timingBegin = now; - buttons.timingBegin = now; + butt.timingBegin = now; } static void _animation_hideTouchJoystick(void) { @@ -887,7 +840,7 @@ static void _animation_hideTouchJoystick(void) { return; } axes.timingBegin = (struct timespec){ 0 }; - buttons.timingBegin = (struct timespec){ 0 }; + butt.timingBegin = (struct timespec){ 0 }; } static void gltouchjoy_applyPrefs(void) { @@ -911,12 +864,19 @@ static void gltouchjoy_applyPrefs(void) { joyglobals.minAlpha = 0.0; } + joyglobals.tapDelayFrames = prefs_parseLongValue (PREF_DOMAIN_JOYSTICK, PREF_JOY_TAP_DELAY, &lVal, 10) ? lVal : BUTTON_TAP_DELAY_FRAMES_DEFAULT; + if (joyglobals.tapDelayFrames < 0) { + joyglobals.tapDelayFrames = 0; + } else if (joyglobals.tapDelayFrames > BUTTON_TAP_DELAY_FRAMES_MAX) { + joyglobals.tapDelayFrames = BUTTON_TAP_DELAY_FRAMES_MAX; + } + joyglobals.showControls = prefs_parseBoolValue (PREF_DOMAIN_JOYSTICK, PREF_SHOW_CONTROLS, &bVal) ? bVal : true; joyglobals.showAzimuth = true;//prefs_parseBoolValue (PREF_DOMAIN_JOYSTICK, PREF_SHOW_AZIMUTH, &bVal) ? bVal : true; joyglobals.switchThreshold = prefs_parseLongValue (PREF_DOMAIN_JOYSTICK, PREF_SWITCH_THRESHOLD, &lVal, 10) ? lVal : BUTTON_SWITCH_THRESHOLD_DEFAULT; joyglobals.screenDivider = prefs_parseFloatValue(PREF_DOMAIN_JOYSTICK, PREF_SCREEN_DIVISION, &fVal) ? fVal : 0.5f; joyglobals.axisIsOnLeft = prefs_parseBoolValue (PREF_DOMAIN_JOYSTICK, PREF_AXIS_ON_LEFT, &bVal) ? bVal : true; - axes.multiplier = prefs_parseFloatValue(PREF_DOMAIN_JOYSTICK, PREF_AXIS_SENSITIVITY, &fVal) ? fVal : 1.f; + joyglobals.axisMultiplier = prefs_parseFloatValue(PREF_DOMAIN_JOYSTICK, PREF_AXIS_SENSITIVITY, &fVal) ? fVal : 1.f; joyglobals.isCalibrating = prefs_parseBoolValue (PREF_DOMAIN_TOUCHSCREEN, PREF_CALIBRATING, &bVal) ? bVal : false; @@ -942,11 +902,12 @@ static void _init_gltouchjoy(void) { axes.centerX = 240; axes.centerY = 160; axes.trackingIndex = TRACKING_NONE; + axes.activeChar = ' '; - buttons.centerX = 240; - buttons.centerY = 160; - buttons.trackingIndex = TRACKING_NONE; - buttons.activeChar = MOUSETEXT_OPENAPPLE; + butt.centerX = 240; + butt.centerY = 160; + butt.trackingIndex = TRACKING_NONE; + butt.activeChar = ' '; joyglobals.prefsChanged = true; // force reload preferences/defaults diff --git a/src/video/gltouchjoy.h b/src/video/gltouchjoy.h index e25210cf..ed1fcd83 100644 --- a/src/video/gltouchjoy.h +++ b/src/video/gltouchjoy.h @@ -17,20 +17,79 @@ #define DEBUG_TOUCH_JOY 0 #if DEBUG_TOUCH_JOY -# define TOUCH_JOY_LOG(...) LOG(__VA_ARGS__) +# define TOUCH_JOY_LOG(...) _SIMPLE_LOG(__VA_ARGS__) #else # define TOUCH_JOY_LOG(...) #endif -#define DEBUG_TOUCH_JOY_GESTURE 0 -#if DEBUG_TOUCH_JOY_GESTURE -# define TOUCH_JOY_GESTURE_LOG(...) LOG(__VA_ARGS__) -#else -# define TOUCH_JOY_GESTURE_LOG(...) -#endif - #define ROSETTE_ROWS 3 #define ROSETTE_COLS 3 +#define ROSETTE_COUNT (ROSETTE_ROWS*ROSETTE_COLS) + +#define BUTTON_TAP_DELAY_FRAMES_DEFAULT 12L // 12 * 16.688 millis == ~0.2sec +#define BUTTON_TAP_DELAY_FRAMES_MAX 30L // 30 * 16.688 millis == ~0.5sec + +// TOUCH EVENT LIFECYCLE MAPPING TO APPLE ][ JOYSTICK BUTTONS OR KEYS +// ------------------------------------------------------------------ +// +// The touch screen event lifecycle (touch down, touch move, touch up) has a direct 1:1 correspondence to the Apple ][ +// joystick axis. Touch down zeros the joystick axis, touch move will convert the screen coordinates to x,y joystick +// axis value, touch up will re-zero the joystick axis. The only user configuration here is to normalize and scale +// between the touchscreen coordinates to the 0-255 Apple ][ joystick axes. +// +// Difficulty arises in the mapping between the touch screen events and which button/key should be fired. The gltouchjoy +// implementation (both traditional joystick and keypad joystick) allows tap/long-press and swipe gestures to fire +// different configured buttons/keys. +// +// BUTTON TAP DELAY RATIONALE +// -------------------------- +// +// A touch-down can be an ambiguous event ... does the user intend to tap/long-press or swipe/swipe-and-hold? Our +// mapping of the touch lifecycle to the joystick buttons/keys uses a user-configurable delay before firing the +// appropriate button/key. +// +// BUTTON TAP DELAY STATE MACHINE +// ------------------------------ +// +// - On touch-down, we need to delay for a certain amount of time (video frames) to determine the user's intent +// + This latches the end-of-video-frame callback (on CPU thread). The amount of frames to delay is a configurable +// preference -- (who are we to hardcode this determination?) +// * Users likely want a custom twitch response setting, (at smaller thresholds, running the risk of firing a +// tap when they intended just a swipe) +// * Some games may not make use of the second joystick button, and so may be more effectively played with only +// tapping (no swipes) and a `tapDelayFrames` configured to zero. (In which case the button down event is +// non-ambiguous) and should immediately fire the configured tap button. +// - Ambiguity about user's intent is also resolved the moment there is a touch-up or a touch-move beyond the `switchThreshold` +// + Touch-up will immediately fire the tap button (if not yet fired) +// + Touch-move beyond the configured `switchThreshold` will immediately fire the appropriate configured swipe button +// + Touch-move beneath the `switchThreshold` is considered ambiguous "jitter" +// - The end-of-video-frame callback on the CPU thread... +// + ...handles tap delay counting +// + ...handles continual fire of current button (during touch-and-hold, or swipe-and-hold) +// + ...will unregister itself after touch-up event +// +// SCENARIOS: +// - SINGLE TAP: +// + down ... (move < `switchThreshold`)* ... up ... [cbX] +// * up: fires configured tap button on UI thread, resets `tapDelayFrames` +// * cbX: after `tapDelayFrames`, resets joystick button state and unregisters itself from the e-o-f callback +// +// - SINGLE TOUCH-AND-HOLD: +// + down ... ([cb], move < `switchThreshold`)* ... up ... [cbX] +// + down ... (move < `switchThreshold`, [cb])* ... up ... [cbX] +// * [cb] fires configured tap button on CPU thread end-of-frame callback +// +// - down, move >= `switchThreshold`, up +// - down, (move >= `switchThreshold`, move back to < `switchThreshold`)+, up +// - down, ... cancel/reset + +typedef enum touchjoy_button_type_t { + TOUCH_NONE = 0, + TOUCH_BUTTON1 = 1, + TOUCH_BUTTON2, + TOUCH_BOTH, + // --or-- an ASCII/fonttext value ... +} touchjoy_button_type_t; enum { ROSETTE_NORTHWEST=0, @@ -57,51 +116,18 @@ typedef struct GLTouchJoyGlobals { float minAlphaWhenOwnsScreen; float minAlpha; float screenDivider; + float axisMultiplier; // multiplier for joystick axis bool axisIsOnLeft; - int switchThreshold; + long switchThreshold; // threshold over which a move is in one of the octants + long tapDelayFrames; // tap/swipe discrimination delay } GLTouchJoyGlobals; extern GLTouchJoyGlobals joyglobals; -// touch axis variables - -typedef struct GLTouchJoyAxes { - - // origin model/texture - GLModel *model; - bool modelDirty; - - // azimuth model - GLModel *azimuthModel; - - int centerX; - int centerY; - float multiplier; - int trackingIndex; - struct timespec timingBegin; - -} GLTouchJoyAxes; -extern GLTouchJoyAxes axes; - -// button object variables - -typedef struct GLTouchJoyButtons { - GLModel *model; - uint8_t activeChar; - bool modelDirty; - - int centerX; - int centerY; - int trackingIndex; - struct timespec timingBegin; - -} GLTouchJoyButtons; -extern GLTouchJoyButtons buttons; - typedef struct GLTouchJoyVariant { interface_device_t (*variant)(void); void (*resetState)(void); - void (*setup)(void (*buttonDrawCallback)(char newChar)); + void (*setup)(void); void (*shutdown)(void); void (*prefsChanged)(const char *domain); @@ -114,7 +140,9 @@ typedef struct GLTouchJoyVariant { void (*axisMove)(int dx, int dy); void (*axisUp)(int dx, int dy); - uint8_t *(*rosetteChars)(void); + uint8_t *(*axisRosetteChars)(void); + uint8_t *(*buttRosetteChars)(void); + uint8_t (*buttActiveChar)(void); } GLTouchJoyVariant; diff --git a/src/video/gltouchjoy_joy.c b/src/video/gltouchjoy_joy.c index b1500fa7..efb6982a 100644 --- a/src/video/gltouchjoy_joy.c +++ b/src/video/gltouchjoy_joy.c @@ -15,32 +15,26 @@ #error this is a touch interface module, possibly you mean to not compile this at all? #endif -#define BUTTON_TAP_DELAY_NANOS_DEFAULT (NANOSECONDS_PER_SECOND/20) // 0.2 secs -#define BUTTON_TAP_DELAY_NANOS_MIN (NANOSECONDS_PER_SECOND/10000) // 0.0001 secs - -typedef struct touch_event_s { - struct touch_event_s *next; - interface_touch_event_t event; - uint8_t currJoyButtonValue0; - uint8_t currJoyButtonValue1; - uint8_t currButtonDisplayChar; -} touch_event_s; - -static touch_event_s *touchEventQ = NULL; - static GLTouchJoyVariant joyVariant = { 0 }; static struct { - void (*buttonDrawCallback)(char newChar); + video_frame_callback_fn frameCallback; + volatile unsigned long spinlock; + unsigned long tapDelayFrameCount; - pthread_t tapDelayThreadId; - pthread_mutex_t tapDelayMutex; - pthread_cond_t tapDelayCond; - unsigned int tapDelayNanos; + interface_touch_event_t currEventType; touchjoy_button_type_t touchDownChar; touchjoy_button_type_t northChar; touchjoy_button_type_t southChar; + + uint8_t currJoyButtonValue0; + uint8_t currJoyButtonValue1; + uint8_t currButtonDisplayChar; + uint8_t lastButtonDisplayChar; + + bool buttonThresholdExceeded; + bool justTapConfigured; } joys = { 0 }; // ---------------------------------------------------------------------------- @@ -49,178 +43,44 @@ static interface_device_t touchjoy_variant(void) { return TOUCH_DEVICE_JOYSTICK; } +static inline void _reset_button_state(void) { + run_args.joy_button0 = 0; + run_args.joy_button1 = 0; +} + static inline void _reset_axis_state(void) { joy_x = HALF_JOY_RANGE; joy_y = HALF_JOY_RANGE; } -static inline void _reset_buttons_state(void) { - run_args.joy_button0 = 0x0; - run_args.joy_button1 = 0x0; -} - static void touchjoy_resetState(void) { + TOUCH_JOY_LOG("\t\tjoy resetState"); + _reset_axis_state(); - //_reset_buttons_state(); -- do not reset button state here, it may interfere with other code performing reboot/reset + //NOTE : do not reset button state here, it may interfere with other code performing reboot/reset + joys.spinlock = SPINLOCK_INIT; + joys.frameCallback = NULL; + joys.tapDelayFrameCount = 0; } -// ---------------------------------------------------------------------------- - -// Tap Delay Thread : implements a gesture recognizer that differentiates between a "long touch", "tap", and "swipe -// down/up" gestures. Necessarily delays processing of initial touch down event to make a proper determination. Also -// delays resetting joystick button state after touch up event to avoid resetting joystick button state too soon. -// -// * long touch and tap are interpreted as one (configurable) joystick button fire event -// * swipe up and swipe down are the other two (configurable) joystick button fire events -// - -static struct timespec *_tap_wait(void) { - static struct timespec wait = { 0 }; - clock_gettime(CLOCK_REALTIME, &wait); - wait = timespec_add(wait, joys.tapDelayNanos); - return &wait; -} - -static void *_button_tap_delayed_thread(void *dummyptr) { - TRACE_INTERFACE_MARK("_button_tap_delayed_thread ..."); - LOG(">>> [DELAYEDTAP] thread start ..."); - - pthread_mutex_lock(&joys.tapDelayMutex); - - int timedOut = ETIMEDOUT; - for (;;) { - if (UNLIKELY(emulator_isShuttingDown())) { - break; - } - - if (timedOut) { - // reset state and deep sleep waiting for touch down - _reset_buttons_state(); - TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] deep sleep ..."); - pthread_cond_wait(&joys.tapDelayCond, &joys.tapDelayMutex); - } else { - // delays reset of button state while remaining ready to process a touch down - TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] event looping ..."); - timedOut = pthread_cond_timedwait(&joys.tapDelayCond, &joys.tapDelayMutex, _tap_wait()); // wait and possibly consume event - assert((!timedOut || timedOut == ETIMEDOUT) && "should not fail any other way"); - if (timedOut) { - // reset state and go into deep sleep - continue; - } - _reset_buttons_state(); - } - - if (UNLIKELY(emulator_isShuttingDown())) { - break; - } - - TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] touch down ..."); - - touch_event_s *touchCurrEvent = NULL; - touch_event_s *touchPrevEvent = touchEventQ; - assert(touchPrevEvent && "should be a touch event ready to consume"); - touchEventQ = touchEventQ->next; - assert(touchPrevEvent->event == TOUCH_DOWN && "event queue head should be a touch down event"); - - for (;;) { - // delay processing of touch down to perform simple gesture recognition - timedOut = pthread_cond_timedwait(&joys.tapDelayCond, &joys.tapDelayMutex, _tap_wait()); - assert((!timedOut || timedOut == ETIMEDOUT) && "should not fail any other way"); - - if (UNLIKELY(emulator_isShuttingDown())) { - break; - } - - touchCurrEvent = touchEventQ; - if (!touchCurrEvent) { - assert(timedOut); - // touch-down-and-hold - TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] long touch ..."); - run_args.joy_button0 = touchPrevEvent->currJoyButtonValue0; - run_args.joy_button1 = touchPrevEvent->currJoyButtonValue1; - joys.buttonDrawCallback(touchPrevEvent->currButtonDisplayChar); - continue; - } - touchEventQ = touchEventQ->next; - - if (touchCurrEvent->event == TOUCH_MOVE) { - // dragging ... - TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] move ..."); - run_args.joy_button0 = touchCurrEvent->currJoyButtonValue0; - run_args.joy_button1 = touchCurrEvent->currJoyButtonValue1; - joys.buttonDrawCallback(touchCurrEvent->currButtonDisplayChar); - FREE(touchPrevEvent); - touchPrevEvent = touchCurrEvent; - } else if (touchCurrEvent->event == TOUCH_UP) { - // tap - TOUCH_JOY_GESTURE_LOG(">>> [DELAYEDTAP] touch up ..."); - run_args.joy_button0 = touchPrevEvent->currJoyButtonValue0; - run_args.joy_button1 = touchPrevEvent->currJoyButtonValue1; - joys.buttonDrawCallback(touchPrevEvent->currButtonDisplayChar); - timedOut = 0; - break; - } else if (touchCurrEvent->event == TOUCH_DOWN) { - LOG("WHOA : unexpected touch down, are you spamming the touchscreen?!"); - FREE(touchPrevEvent); - touchPrevEvent = touchCurrEvent; - continue; - } else { - __builtin_unreachable(); - } - } - - FREE(touchPrevEvent); - FREE(touchCurrEvent); - } - - // clear out event queue - touch_event_s *p = touchEventQ; - while (p) { - touch_event_s *dead = p; - p = p->next; - FREE(dead); - } - - pthread_mutex_unlock(&joys.tapDelayMutex); - - joys.tapDelayMutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; - joys.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER; - - LOG(">>> [DELAYEDTAP] thread exit ..."); - - return NULL; -} - -static void touchjoy_setup(void (*buttonDrawCallback)(char newChar)) { - joys.buttonDrawCallback = buttonDrawCallback; - if (joys.tapDelayThreadId == 0) { - int err = TEMP_FAILURE_RETRY(pthread_create(&joys.tapDelayThreadId, NULL, (void *)&_button_tap_delayed_thread, (void *)NULL)); - assert(!err); - } +static void touchjoy_setup(void) { + // ... } static void touchjoy_shutdown(void) { - if (joys.tapDelayThreadId && emulator_isShuttingDown()) { - pthread_mutex_lock(&joys.tapDelayMutex); - pthread_cond_signal(&joys.tapDelayCond); - pthread_mutex_unlock(&joys.tapDelayMutex); - pthread_join(joys.tapDelayThreadId, NULL); - joys.tapDelayThreadId = 0; - } + // ... } // ---------------------------------------------------------------------------- -// axis state +// axis state machine static void touchjoy_axisDown(void) { _reset_axis_state(); } static void touchjoy_axisMove(int x, int y) { - if (axes.multiplier != 1.f) { - x = (int) ((float)x * axes.multiplier); - y = (int) ((float)y * axes.multiplier); - } + x = (int) ((float)x * joyglobals.axisMultiplier); + y = (int) ((float)y * joyglobals.axisMultiplier); x += 0x80; y += 0x80; @@ -228,13 +88,13 @@ static void touchjoy_axisMove(int x, int y) { if (x < 0) { x = 0; } - if (x > 0xff) { + else if (x > 0xff) { x = 0xff; } if (y < 0) { y = 0; } - if (y > 0xff) { + else if (y > 0xff) { y = 0xff; } @@ -243,70 +103,165 @@ static void touchjoy_axisMove(int x, int y) { } static void touchjoy_axisUp(int x, int y) { + //x = (int) ((float)x * joyglobals.axisMultiplier); + //y = (int) ((float)y * joyglobals.axisMultiplier); + (void)x; + (void)y; _reset_axis_state(); } // ---------------------------------------------------------------------------- -// button state +// button state machine + +static void _fire_current_buttons(void) { + TOUCH_JOY_LOG("\t\t\tfire buttons 0:%02x 1:%02X char:%02x", joys.currJoyButtonValue0, joys.currJoyButtonValue1, joys.currButtonDisplayChar); + run_args.joy_button0 = joys.currJoyButtonValue0; + run_args.joy_button1 = joys.currJoyButtonValue1; + joys.lastButtonDisplayChar = joys.currButtonDisplayChar; +} + +static void _signal_tap_delay_event(interface_touch_event_t eventType, touchjoy_button_type_t theButtonChar) { + + joys.currEventType = eventType; -static void _signal_tap_delay_event(interface_touch_event_t type, touchjoy_button_type_t theButtonChar) { - touch_event_s *touchEvent = MALLOC(sizeof(*touchEvent)); - touchEvent->next = NULL; - touchEvent->event = type; if (theButtonChar == TOUCH_BUTTON1) { - touchEvent->currJoyButtonValue0 = 0x80; - touchEvent->currJoyButtonValue1 = 0; - touchEvent->currButtonDisplayChar = MOUSETEXT_OPENAPPLE; + joys.currJoyButtonValue0 = 0x80; + joys.currJoyButtonValue1 = 0; + joys.currButtonDisplayChar = MOUSETEXT_OPENAPPLE; } else if (theButtonChar == TOUCH_BUTTON2) { - touchEvent->currJoyButtonValue0 = 0; - touchEvent->currJoyButtonValue1 = 0x80; - touchEvent->currButtonDisplayChar = MOUSETEXT_CLOSEDAPPLE; + joys.currJoyButtonValue0 = 0; + joys.currJoyButtonValue1 = 0x80; + joys.currButtonDisplayChar = MOUSETEXT_CLOSEDAPPLE; } else if (theButtonChar == TOUCH_BOTH) { - touchEvent->currJoyButtonValue0 = 0x80; - touchEvent->currJoyButtonValue1 = 0x80; - touchEvent->currButtonDisplayChar = '+'; + joys.currJoyButtonValue0 = 0x80; + joys.currJoyButtonValue1 = 0x80; + joys.currButtonDisplayChar = '+'; } else { - touchEvent->currJoyButtonValue0 = 0; - touchEvent->currJoyButtonValue1 = 0; - touchEvent->currButtonDisplayChar = ' '; + joys.currJoyButtonValue0 = 0; + joys.currJoyButtonValue1 = 0; + //joys.currButtonDisplayChar = ' '; -- avoid truncating normal render cycle } +} - pthread_mutex_lock(&joys.tapDelayMutex); - touch_event_s *p0 = NULL; - touch_event_s *p = touchEventQ; - while (p) { - p0 = p; - p = p->next; - } - if (p0) { - p0->next = touchEvent; - } else { - touchEventQ = touchEvent; - } - pthread_cond_signal(&joys.tapDelayCond); - pthread_mutex_unlock(&joys.tapDelayMutex); +// End-of-video-frame callback (handling tap delay and auto-fire) +static void touchjoy_frameCallback(uint8_t textFlashCounter) { + (void)textFlashCounter; + + TOUCH_JOY_LOG("\t\t+++joy frameCallback"); + + // When activated, this is called every video frame -- ~16.688 millis + + ASSERT_ON_CPU_THREAD(); + + SPIN_LOCK_FULL(&joys.spinlock); + do { + ++joys.tapDelayFrameCount; + TOUCH_JOY_LOG("\t\t+++joy frameCallback acquire (%lu)", joys.tapDelayFrameCount); + if (joys.tapDelayFrameCount < joyglobals.tapDelayFrames) { + break; + } + + _fire_current_buttons(); + + if (joys.currEventType == TOUCH_UP) { + // unregister callback ... + joys.frameCallback = NULL; + TOUCH_JOY_LOG("\t\t+++joy callback done"); + } + } while (0); + + TOUCH_JOY_LOG("\t\t+++joy frameCallback release"); + SPIN_UNLOCK_FULL(&joys.spinlock); } static void touchjoy_buttonDown(void) { + TOUCH_JOY_LOG("\t\tjoy buttonDown"); + + SPIN_LOCK_FULL(&joys.spinlock); + TOUCH_JOY_LOG("\t\tjoy buttonDown acquire"); + + joys.buttonThresholdExceeded = false; + joys.tapDelayFrameCount = 0UL; + + _reset_button_state(); _signal_tap_delay_event(TOUCH_DOWN, joys.touchDownChar); + + if (joyglobals.tapDelayFrames == 0 || joys.justTapConfigured) { + // unambiguous intent : no tap delay or only the tap button is configured + _fire_current_buttons(); + } + + // hook into end-of-video-frame callback + joys.frameCallback = &touchjoy_frameCallback; + + TOUCH_JOY_LOG("\t\tjoy buttonDown release"); + SPIN_UNLOCK_FULL(&joys.spinlock); } -static void touchjoy_buttonMove(int dx, int dy) { - if ((dy < -joyglobals.switchThreshold) || (dy > joyglobals.switchThreshold)) { +static void _touchjoy_buttonMove(int dx, int dy) { - touchjoy_button_type_t theButtonChar = -1; - if (dy < 0) { - theButtonChar = joys.northChar; - } else { - theButtonChar = joys.southChar; + bool shouldFire = false; + touchjoy_button_type_t theButtonChar = joys.touchDownChar; + + int delta = abs(dy); + if (!joys.buttonThresholdExceeded) { + if (delta >= joyglobals.switchThreshold) { + // unambiguous intent : user swiped beyond threshold + joys.buttonThresholdExceeded = true; + shouldFire = true; + theButtonChar = (dy < 0) ? joys.northChar : joys.southChar; } + } else { + if (delta < joyglobals.switchThreshold) { + // 2019/04/20 NOTE: originally we did not re-zero back to touchDownChar ... this allowed a progression between + // southChar/northChar (or vice-versa) + // + // touchDownChar should be fired on a tap or long-press (once we swipe beyond the threshold, we should only switch + // between northChar and southChar) + + //shouldFire = true; + joys.buttonThresholdExceeded = false; + } + } + + if (shouldFire) { + // immediately fire current button(s) upon threshold [re]-change ... and remove delay for auto-fire _signal_tap_delay_event(TOUCH_MOVE, theButtonChar); + _fire_current_buttons(); } } +static void touchjoy_buttonMove(int dx, int dy) { + TOUCH_JOY_LOG("\t\tjoy buttonMove"); + + SPIN_LOCK_FULL(&joys.spinlock); + TOUCH_JOY_LOG("\t\tjoy buttonMove acquire"); + + _touchjoy_buttonMove(dx, dy); + + TOUCH_JOY_LOG("\t\tjoy buttonMove release"); + SPIN_UNLOCK_FULL(&joys.spinlock); +} + static void touchjoy_buttonUp(int dx, int dy) { - _signal_tap_delay_event(TOUCH_UP, ' '); + TOUCH_JOY_LOG("\t\tjoy buttonUp"); + + SPIN_LOCK_FULL(&joys.spinlock); + TOUCH_JOY_LOG("\t\tjoy buttonUp acquire"); + + bool ignored = false; + _touchjoy_buttonMove(dx, dy); + + _fire_current_buttons(); + + // force CPU thread callback into recount before unsetting button(s) state ... this allows time for CPU thread to consume current button(s) + joys.tapDelayFrameCount = 0UL; + + _signal_tap_delay_event(TOUCH_UP, TOUCH_NONE); + + TOUCH_JOY_LOG("\t\tjoy buttonUp release"); + SPIN_UNLOCK_FULL(&joys.spinlock); } static void touchjoy_prefsChanged(const char *domain) { @@ -318,31 +273,28 @@ static void touchjoy_prefsChanged(const char *domain) { joys.northChar = prefs_parseLongValue(domain, PREF_JOY_SWIPE_NORTH_CHAR, &lVal, /*base:*/10) ? lVal : TOUCH_BOTH; joys.southChar = prefs_parseLongValue(domain, PREF_JOY_SWIPE_SOUTH_CHAR, &lVal, /*base:*/10) ? lVal : TOUCH_BUTTON2; - float fVal = 0.f; - joys.tapDelayNanos = prefs_parseFloatValue(domain, PREF_JOY_TAP_DELAY, &fVal) ? (fVal * NANOSECONDS_PER_SECOND) : BUTTON_TAP_DELAY_NANOS_DEFAULT; - if (joys.tapDelayNanos < BUTTON_TAP_DELAY_NANOS_MIN) { - joys.tapDelayNanos = BUTTON_TAP_DELAY_NANOS_MIN; - } + joys.justTapConfigured = (joys.touchDownChar != TOUCH_NONE) && + (joys.northChar == TOUCH_NONE) && + (joys.southChar == TOUCH_NONE); } -static uint8_t *touchjoy_rosetteChars(void) { - static uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS] = { 0 }; - if (rosetteChars[0] == 0x0) { - rosetteChars[0] = ' '; - rosetteChars[1] = MOUSETEXT_UP; - rosetteChars[2] = ' '; - - rosetteChars[3] = MOUSETEXT_LEFT; - rosetteChars[4] = ICONTEXT_MENU_TOUCHJOY; - rosetteChars[5] = MOUSETEXT_RIGHT; - - rosetteChars[6] = ' '; - rosetteChars[7] = MOUSETEXT_DOWN; - rosetteChars[8] = ' '; - } +static uint8_t *touchjoy_axisRosetteChars(void) { + static uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS] = { + ' ', MOUSETEXT_UP, ' ', + MOUSETEXT_LEFT, ICONTEXT_MENU_TOUCHJOY, MOUSETEXT_RIGHT, + ' ', MOUSETEXT_DOWN, ' ', + }; return rosetteChars; } +static uint8_t *touchjoy_buttRosetteChars(void) { + return NULL; +} + +static uint8_t touchjoy_buttActiveChar(void) { + return joys.lastButtonDisplayChar; +} + // ---------------------------------------------------------------------------- static void _init_gltouchjoy_joy(void) { @@ -363,12 +315,13 @@ static void _init_gltouchjoy_joy(void) { joyVariant.axisMove = &touchjoy_axisMove; joyVariant.axisUp = &touchjoy_axisUp; - joyVariant.rosetteChars = &touchjoy_rosetteChars; - - joys.tapDelayMutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; - joys.tapDelayCond = (pthread_cond_t)PTHREAD_COND_INITIALIZER; + joyVariant.axisRosetteChars = &touchjoy_axisRosetteChars; + joyVariant.buttRosetteChars = &touchjoy_buttRosetteChars; + joyVariant.buttActiveChar = &touchjoy_buttActiveChar; gltouchjoy_registerVariant(TOUCH_DEVICE_JOYSTICK, &joyVariant); + + video_registerFrameCallback(&joys.frameCallback); } static __attribute__((constructor)) void __init_gltouchjoy_joy(void) { diff --git a/src/video/gltouchjoy_kpad.c b/src/video/gltouchjoy_kpad.c index a68595c3..d2f5f982 100644 --- a/src/video/gltouchjoy_kpad.c +++ b/src/video/gltouchjoy_kpad.c @@ -15,9 +15,6 @@ #error this is a touch interface module, possibly you mean to not compile this at all? #endif -#define KEY_REPEAT_THRESHOLD_NANOS (NANOSECONDS_PER_SECOND / 4) -#define CALLBACK_LOCK_VALUE (1000) - // WARNING : this does not match the rosette left-right-down layout, but what is returned by atan2f() typedef enum keypad_octant_t { OCTANT_WEST = 0, @@ -41,162 +38,580 @@ typedef enum keypad_octant_t { #define RADIANS_SOUTH (( 1.f * M_PI) / 2.f) #define RADIANS_SOUTHEAST (( 1.f * M_PI) / 4.f) -typedef enum keypad_fire_t { - REPEAT_AXIS = 0, - REPEAT_AXIS_ALT, - REPEAT_BUTTON, +typedef enum keypad_press_t { + REPEAT_IDX_A = 0, + REPEAT_IDX_B, MAX_REPEATING, -} keypad_fire_t; +} keypad_press_t; + +typedef struct subvariant_s { + // state variables + keypad_octant_t currentOctant; + keypad_press_t autoPressIdx; + unsigned long frameCount; + interface_touch_event_t currEventType; + unsigned int keyPressCount; + int scancodes[MAX_REPEATING]; + uint8_t strobeShifter; + + // semi-static configured rosette + long rosetteScans[ROSETTE_ROWS * ROSETTE_COLS]; + uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS]; +} subvariant_s; static struct { - keypad_octant_t axisCurrentOctant; + video_frame_callback_fn frameCallback; - uint8_t currButtonDisplayChar; - void (*buttonDrawCallback)(char newChar); + volatile unsigned long spinlock; - // index of repeating scancodes to fire - keypad_fire_t fireIdx; + subvariant_s axis; + subvariant_s butt; - volatile int axisLock; // modified by both CPU thread and touch handling thread - volatile int buttonLock; // modified by both CPU thread and touch handling thread - - int scancodes[MAX_REPEATING]; - struct timespec timingBegins[MAX_REPEATING]; - bool buttonBegan; - bool axisBegan; - int lastScancode; - - float repeatThresholdNanos; - - // axis rosette - uint8_t rosetteChars[ROSETTE_ROWS * ROSETTE_COLS]; - long rosetteScancodes[ROSETTE_ROWS * ROSETTE_COLS]; - - // touch/swipe buttons - touchjoy_button_type_t touchDownChar; - int touchDownScancode; - touchjoy_button_type_t northChar; - int northScancode; - touchjoy_button_type_t southChar; - int southScancode; + bool autostrobeDelay; // pref for autostrobe delay } kpad = { 0 }; static GLTouchJoyVariant kpadVariant = { 0 }; // ---------------------------------------------------------------------------- -// repeat key callback scheduling and unscheduling -// -// Assumptions : -// - All touch sources run on a single thread -// - callback itself runs on CPU thread -// unlock callback section -static inline void _callback_sourceUnlock(volatile int *source) { - int val = __sync_add_and_fetch(source, (int)(CALLBACK_LOCK_VALUE)); - assert(val >= 0 && "inconsistent lock state for callback detected"); - (void)val; +static inline void _reset_subvariant_state(subvariant_s *subvariant) { + subvariant->frameCount = 0; + subvariant->keyPressCount = 0; + subvariant->strobeShifter = 0; + subvariant->currentOctant = ORIGIN; + subvariant->autoPressIdx = REPEAT_IDX_A; + subvariant->scancodes[REPEAT_IDX_A] = -1; + subvariant->scancodes[REPEAT_IDX_B] = -1; + subvariant->currEventType = TOUCH_UP; } -// attempt to lock the critical section where we unschedule the function pointer callback -// there should be no outstanding touch sources being tracked -static inline bool _callback_sourceTryLock(volatile int *source) { - int val = __sync_sub_and_fetch(source, (int)(CALLBACK_LOCK_VALUE)); - if (val == -CALLBACK_LOCK_VALUE) { - return true; - } else { - _callback_sourceUnlock(source); - return false; - } +static inline void _advance_press_index(keypad_press_t *idx) { + *idx = (*idx == REPEAT_IDX_A) ? REPEAT_IDX_B : REPEAT_IDX_A; } -// touch source has ended -static inline void _touch_sourceEnd(volatile int *source) { - __sync_sub_and_fetch(source, 1); -} +static inline void _press_key(subvariant_s *subvariant, keypad_press_t pressIdx) { + int scancode = subvariant->scancodes[pressIdx]; -// touch source has begun -static inline void _touch_sourceBegin(volatile int *source) { - do { - int val = __sync_add_and_fetch(source, 1); - if (val >= 0) { - assert(val > 0 && "inconsistent lock state for touch source detected"); - return; +#if DEBUG_TOUCH_JOY + { + int c = keys_scancode2ASCII(scancode, /*is_shifted:*/true, /*is_ctrl:*/false); + if (c >= 0) { + TOUCH_JOY_LOG("\t\t\tPRESS KEY: %c", (uint8_t)c); } - // spin waiting on callback critical - _touch_sourceEnd(source); - } while (1); + } +#endif + + keys_handleInput(scancode, /*is_pressed:*/true, /*is_ascii:*/false); + keys_handleInput(scancode, /*is_pressed:*/false, /*is_ascii:*/false); + + _advance_press_index(&subvariant->autoPressIdx); + + ++(subvariant->keyPressCount); } -static void touchkpad_keyboardReadCallback(void) { +// No-op callback +static void touchkpad_nopCallback(uint8_t textFlashCounter) { + (void)textFlashCounter; +} + +// End-of-video-frame callback (handling tap delay and auto-strobe) +static void touchkpad_frameCallback(uint8_t textFlashCounter) { + + // When activated, this is called every video frame -- ~16.688 millis + ASSERT_ON_CPU_THREAD(); - // HACK FIXME TODO : - // - // There are a number of cases where the emulated software is reading the keyboard state but not using the value. - // This is quite noticeable in a number of games that take keyboard input. - // - // This indicates that we are incorrectly emulating the keyboard hardware. The proper fix for this touch keypad - // joystick will be to properly emulate the original hardware timing, using the existing facility to count 65c02 CPU - // machine cyles. -#warning FIXME TODO : implement proper keyboard repeat callback timing + SPIN_LOCK_FULL(&kpad.spinlock); + TOUCH_JOY_LOG("\t\t+++kpad frameCallback acquire %c%c%c%c %x %x", + (textFlashCounter & 0x8 ? '1' : '0'), + (textFlashCounter & 0x4 ? '1' : '0'), + (textFlashCounter & 0x2 ? '1' : '0'), + (textFlashCounter & 0x1 ? '1' : '0'), kpad.axis.strobeShifter, kpad.butt.strobeShifter); - if (kpad.lastScancode >= 0) { - keys_handleInput(kpad.lastScancode, /*is_pressed:*/false, /*is_ascii:*/false); - kpad.lastScancode = -1; + ++(kpad.axis.frameCount); + ++(kpad.butt.frameCount); + + subvariant_s *subvariant = ((textFlashCounter & 0x1) == 0x0) ? &kpad.axis : &kpad.butt; + int scancode = subvariant->scancodes[subvariant->autoPressIdx]; + if (scancode < 0) { + _advance_press_index(&subvariant->autoPressIdx); + scancode = subvariant->scancodes[subvariant->autoPressIdx]; } - struct timespec now = { 0 }; - clock_gettime(CLOCK_MONOTONIC, &now); + do { + if (kpad.autostrobeDelay) { + // perform autostrobe delay counting ... - int fired = -1; - for (unsigned int i=0; i= 0) { - struct timespec deltat = timespec_diff(kpad.timingBegins[kpad.fireIdx], now, NULL); - if (deltat.tv_sec || deltat.tv_nsec > kpad.repeatThresholdNanos) { - TOUCH_JOY_LOG("ACTIVE(%d,%d) REPEAT #%d/%lu/%lu: %d", kpad.axisLock, kpad.buttonLock, kpad.fireIdx, deltat.tv_sec, deltat.tv_nsec, scancode); - keys_handleInput(scancode, /*is_pressed:*/true, /*is_ascii:*/false); - kpad.lastScancode = scancode; - fired = kpad.fireIdx; + if (textFlashCounter == 0x8) { + // This occurs every 16 video frames ... textFlashCounter : 1000 + // UtAIIe 3-13 : "The duration of the television scan is 262 horizontal scans. This is [16.688 milliseconds]" + // UtAIIe 3-17 : "[bit3] of the flash counter is the clockpulse for generating the delay before auto repeat" + // UtAIIe 3-17 : "When a key is held for 534-801 millis, the AUTOSTRB starts to alternate at 15 Hz" + // NOTE : 16.688 * 16 == ~267 millis ... 267 * 2 == 534 millis, 267 * 3 == 801 millis + // Fastest scenario: + // key down 0@ 0x08 => 001 , t = 0ms + // 1@ 0x08 => 011 , t = 267ms + // 2@ 0x08 => 111 , t = 534ms + // + // Slowest scenario: + // key down 0@ 0x09 => 000 , t = 0ms + // 1@ 0x08 => 001 , t = 267ms + // 2@ 0x08 => 011 , t = 534ms + // 3@ 0x08 => 111 , t = 801ms + if (kpad.axis.frameCount >= joyglobals.tapDelayFrames) { + kpad.axis.strobeShifter = kpad.axis.strobeShifter << 1; + kpad.axis.strobeShifter |= 0x1; + kpad.axis.strobeShifter &= 0x3; + } + if (kpad.butt.frameCount >= joyglobals.tapDelayFrames) { + kpad.butt.strobeShifter = kpad.butt.strobeShifter << 1; + kpad.butt.strobeShifter |= 0x1; + kpad.butt.strobeShifter &= 0x3; + } + } + + bool notYetPressed = subvariant->keyPressCount == 0; + bool twoKeys2Press = (subvariant->scancodes[REPEAT_IDX_A] >= 0 && subvariant->scancodes[REPEAT_IDX_B] >= 0); + if (notYetPressed || twoKeys2Press) { + // do not apply autostrobe delay ... + } else if ((subvariant->strobeShifter & 0x3) != 0x3) { + // still waiting for the autostrobe delay ... + break; } } - ++kpad.fireIdx; - if (kpad.fireIdx >= MAX_REPEATING) { - kpad.fireIdx = 0; - } - if (fired >= 0) { + + if (subvariant->frameCount < joyglobals.tapDelayFrames) { break; } + + // AUTOSTROBE PRESS CONFIGURED KEY + // UtAIIe 3-17 : "[after the key press delay ...] the AUTOSTRB starts to alternate at 15 Hz" + // + // NOTE: Our implementation here is sometimes faster by 2x ... + // - We are simulating at least two simultaneous keypresses (one for the axis and one for the button side/subvariant) + // - If there are two keys to press for a particular side/subvariant, each one is pressed at 15 Hz + // - If there is only one key for the particular side/subvariant, it is pressed every two frames (7.5 Hz) + + if (scancode >= 0) { + int idx = subvariant->autoPressIdx; + _press_key(subvariant, idx); + + // some key was pressed ... + if (subvariant->currEventType == TOUCH_UP) { + TOUCH_JOY_LOG("\t\t+++resetting axis index %d ...", idx); + subvariant->scancodes[idx] = -1; + } + } else { + // no keys were pressed ... + } + + } while (0); + + if ((kpad.axis.currEventType == TOUCH_UP) && (kpad.butt.currEventType == TOUCH_UP)) { + // all touch up occurred -- unlatch the callback + TOUCH_JOY_LOG("\t\t+++kpad callback done"); + kpad.frameCallback = touchkpad_nopCallback; + kpad.axis.scancodes[REPEAT_IDX_A] = -1; + kpad.axis.scancodes[REPEAT_IDX_B] = -1; + kpad.butt.scancodes[REPEAT_IDX_A] = -1; + kpad.butt.scancodes[REPEAT_IDX_B] = -1; } - bool lockedAxis = _callback_sourceTryLock(&kpad.axisLock); - if (lockedAxis) { - if (fired == REPEAT_AXIS || fired == REPEAT_AXIS_ALT) { - TOUCH_JOY_LOG("RESETTING AXIS INDEX %d ...", fired); - kpad.scancodes[fired] = -1; + TOUCH_JOY_LOG("\t\t+++kpad frameCallback release"); + SPIN_UNLOCK_FULL(&kpad.spinlock); +} + +// ---------------------------------------------------------------------------- +// common subvariant touch lifecycle handling + +static void _subvariant_touchDown(subvariant_s *subvariant) { + + _reset_subvariant_state(subvariant); + subvariant->currEventType = TOUCH_DOWN; + + subvariant->scancodes[REPEAT_IDX_A] = subvariant->rosetteScans[ROSETTE_CENTER]; + subvariant->scancodes[REPEAT_IDX_B] = -1; + + if (joyglobals.tapDelayFrames == 0) { + // unambiguous intent : no tap delay + _press_key(subvariant, REPEAT_IDX_A); + } + + kpad.frameCallback = &touchkpad_frameCallback; +} + +static void _subvariant_touchMove(subvariant_s *subvariant, int dx, int dy, bool isTouchUp) { + + subvariant->currEventType = TOUCH_MOVE; + + int lastScancodes[MAX_REPEATING]; + lastScancodes[REPEAT_IDX_A] = subvariant->scancodes[REPEAT_IDX_A]; + lastScancodes[REPEAT_IDX_B] = subvariant->scancodes[REPEAT_IDX_B]; + + keypad_octant_t lastOctant = subvariant->currentOctant; + do { + int c = (int)sqrtf(dx * dx + dy * dy); + if (c < joyglobals.switchThreshold) { + if (lastOctant != ORIGIN) { + // unambiguous intent : user swiped beyond origin, then re-zeroed + subvariant->currentOctant = ORIGIN; + subvariant->scancodes[REPEAT_IDX_A] = subvariant->rosetteScans[ROSETTE_CENTER]; + subvariant->scancodes[REPEAT_IDX_B] = -1; + } + break; + } + + // unambiguous intent : user swiped beyond origin, determine octant of dx,dy + + const float radians = atan2f(dy, dx); // -180-180deg range + float radnorm = radians + M_PI; // 0-360deg range + if (UNLIKELY(radnorm < 0.f)) { + radnorm = 0.f; // clamp positive + } + float octant = radnorm + (M_PI/8.f); // rotate to correct octant (+45deg) + octant /= (M_PI/4.f); // divide to octant (/90deg) + subvariant->currentOctant = (keypad_octant_t) ((int)octant & 0x7); // integer modulo maps to enum + + if (subvariant->currentOctant == lastOctant) { + break; + } + + // CHANGED : moved to new octant, (and presumably scancode), so reset timings + + //TOUCH_JOY_LOG("\t\tradians:%f radnorm:%f octant:%f, currOctant:%d", radians, radnorm, octant, subvariant->currentOctant); + + // Current implementation NOTE : four cardinal directions are handled slightly different than the intercardinal + // ones. + // - The intercardinals might generate 2 scanscodes (for example north and west scancodes for a northwest axis) + // if there is not a specific scancode to handle it (e.g., the northwest scancode). + // - The cardinals will only ever generate one scancode (the cardinal in question if it's set, or the scancode + // of the adjacent intercardinal where the point lies). + + long scanA = -1; + long scanB = -1; + switch (subvariant->currentOctant) { + case OCTANT_NORTHWEST: + if (subvariant->rosetteScans[ROSETTE_NORTHWEST] >= 0) { + TOUCH_JOY_LOG("\t\tXY : NORTHWEST, (%ld)", subvariant->rosetteScans[ROSETTE_WEST]); + scanA = subvariant->rosetteScans[ROSETTE_NORTHWEST]; + } else { + TOUCH_JOY_LOG("\t\tXY : WEST (%ld) & NORTH (%ld)", subvariant->rosetteScans[ROSETTE_WEST], subvariant->rosetteScans[ROSETTE_NORTH]); + scanA = subvariant->rosetteScans[ROSETTE_WEST]; + scanB = subvariant->rosetteScans[ROSETTE_NORTH]; + } + break; + + case OCTANT_NORTH: + if (subvariant->rosetteScans[ROSETTE_NORTH] >= 0) { + TOUCH_JOY_LOG("\t\tY : NORTH (%ld)", subvariant->rosetteScans[ROSETTE_NORTH]); + scanA = subvariant->rosetteScans[ROSETTE_NORTH]; + } else if (radians < RADIANS_NORTH) { + TOUCH_JOY_LOG("\t\tXY : NORTHWEST (%ld)", subvariant->rosetteScans[ROSETTE_NORTHWEST]); + scanA = subvariant->rosetteScans[ROSETTE_NORTHWEST]; + } else { + TOUCH_JOY_LOG("\t\tXY : NORTHEAST (%ld)", subvariant->rosetteScans[ROSETTE_NORTHEAST]); + scanA = subvariant->rosetteScans[ROSETTE_NORTHEAST]; + } + break; + + case OCTANT_NORTHEAST: + if (subvariant->rosetteScans[ROSETTE_NORTHEAST] >= 0) { + TOUCH_JOY_LOG("\t\tXY : NORTHEAST (%ld)", subvariant->rosetteScans[ROSETTE_NORTHEAST]); + scanA = subvariant->rosetteScans[ROSETTE_NORTHEAST]; + } else { + TOUCH_JOY_LOG("\t\tXY : EAST (%ld) & NORTH (%ld)", subvariant->rosetteScans[ROSETTE_EAST], subvariant->rosetteScans[ROSETTE_NORTH]); + scanA = subvariant->rosetteScans[ROSETTE_EAST]; + scanB = subvariant->rosetteScans[ROSETTE_NORTH]; + } + break; + + case OCTANT_WEST: + if (subvariant->rosetteScans[ROSETTE_WEST] >= 0) { + TOUCH_JOY_LOG("\t\tY : WEST (%ld)", subvariant->rosetteScans[ROSETTE_WEST]); + scanA = subvariant->rosetteScans[ROSETTE_WEST]; + } else if (radians > RADIANS_WEST_NEG && radians < 0) { + TOUCH_JOY_LOG("\t\tXY : NORTHWEST (%ld)", subvariant->rosetteScans[ROSETTE_NORTHWEST]); + scanA = subvariant->rosetteScans[ROSETTE_NORTHWEST]; + } else { + TOUCH_JOY_LOG("\t\tXY : SOUTHWEST (%ld)", subvariant->rosetteScans[ROSETTE_SOUTHWEST]); + scanA = subvariant->rosetteScans[ROSETTE_SOUTHWEST]; + } + break; + + case OCTANT_EAST: + if (subvariant->rosetteScans[ROSETTE_EAST] >= 0) { + TOUCH_JOY_LOG("\t\tY : EAST (%ld)", subvariant->rosetteScans[ROSETTE_EAST]); + scanA = subvariant->rosetteScans[ROSETTE_EAST]; + } else if (radians < RADIANS_EAST) { + TOUCH_JOY_LOG("\t\tXY : NORTHEAST (%ld)", subvariant->rosetteScans[ROSETTE_NORTHEAST]); + scanA = subvariant->rosetteScans[ROSETTE_NORTHEAST]; + } else { + TOUCH_JOY_LOG("\t\tXY : SOUTHEAST (%ld)", subvariant->rosetteScans[ROSETTE_SOUTHEAST]); + scanA = subvariant->rosetteScans[ROSETTE_SOUTHEAST]; + } + break; + + case OCTANT_SOUTHWEST: + if (subvariant->rosetteScans[ROSETTE_SOUTHWEST] >= 0) { + TOUCH_JOY_LOG("\t\tXY : SOUTHWEST (%ld)", subvariant->rosetteScans[ROSETTE_SOUTHWEST]); + scanA = subvariant->rosetteScans[ROSETTE_SOUTHWEST]; + } else { + TOUCH_JOY_LOG("\t\tXY : WEST (%ld) & SOUTH (%ld)", subvariant->rosetteScans[ROSETTE_WEST], subvariant->rosetteScans[ROSETTE_SOUTH]); + scanA = subvariant->rosetteScans[ROSETTE_WEST]; + scanB = subvariant->rosetteScans[ROSETTE_SOUTH]; + } + break; + + case OCTANT_SOUTH: + if (subvariant->rosetteScans[ROSETTE_SOUTH] >= 0) { + TOUCH_JOY_LOG("\t\tY : SOUTH (%ld)", subvariant->rosetteScans[ROSETTE_SOUTH]); + scanA = subvariant->rosetteScans[ROSETTE_SOUTH]; + } else if (radians > RADIANS_SOUTH) { + TOUCH_JOY_LOG("\t\tXY : SOUTHWEST (%ld)", subvariant->rosetteScans[ROSETTE_SOUTHWEST]); + scanA = subvariant->rosetteScans[ROSETTE_SOUTHWEST]; + } else { + TOUCH_JOY_LOG("\t\tXY : SOUTHEAST (%ld)", subvariant->rosetteScans[ROSETTE_SOUTHEAST]); + scanA = subvariant->rosetteScans[ROSETTE_SOUTHEAST]; + } + break; + + case OCTANT_SOUTHEAST: + if (subvariant->rosetteScans[ROSETTE_SOUTHEAST] >= 0) { + TOUCH_JOY_LOG("\t\tXY : SOUTHEAST (%ld)", subvariant->rosetteScans[ROSETTE_SOUTHEAST]); + scanA = subvariant->rosetteScans[ROSETTE_SOUTHEAST]; + } else { + TOUCH_JOY_LOG("\t\tXY : EAST (%ld) & SOUTH (%ld)", subvariant->rosetteScans[ROSETTE_EAST], subvariant->rosetteScans[ROSETTE_SOUTH]); + scanA = subvariant->rosetteScans[ROSETTE_EAST]; + scanB = subvariant->rosetteScans[ROSETTE_SOUTH]; + } + break; + + default: + assert(false && "should not happen"); + break; + } + + if (scanA < 0) { + scanA = scanB; + scanB = -1; + } + subvariant->scancodes[REPEAT_IDX_A] = scanA; + subvariant->scancodes[REPEAT_IDX_B] = scanB; + } while (0); + + if (subvariant->currentOctant != lastOctant) { + // immediately press key upon threshold [re]-change ... and remove tap delay + keypad_press_t pressIdx = subvariant->autoPressIdx; + if (subvariant->scancodes[pressIdx] < 0) { + _advance_press_index(&subvariant->autoPressIdx); + pressIdx = subvariant->autoPressIdx; + } else { + _advance_press_index(&subvariant->autoPressIdx); + } + + _press_key(subvariant, pressIdx); + + // remove tap delay + subvariant->frameCount = joyglobals.tapDelayFrames; + + // reset strobeShifter if keys differ + if (lastScancodes[pressIdx] != subvariant->scancodes[pressIdx]) { + subvariant->strobeShifter = 0; + } + + if (isTouchUp) { + subvariant->scancodes[pressIdx] = -1; } } +} - bool lockedButton = _callback_sourceTryLock(&kpad.buttonLock); - if (lockedButton) { - if (fired == REPEAT_BUTTON) { - TOUCH_JOY_LOG("RESETTING BUTTON INDEX %d ...", fired); - kpad.scancodes[fired] = -1; +static void _subvariant_touchUp(subvariant_s *subvariant, int dx, int dy) { + + _subvariant_touchMove(subvariant, dx, dy, /*isTouchUp:*/true); + + subvariant->currEventType = TOUCH_UP; + + // remove tap delay ... + subvariant->frameCount = joyglobals.tapDelayFrames; + + if (subvariant->keyPressCount == 0) { + // nothing previously pressed, immediately press origin key + assert(subvariant->currentOctant == ORIGIN); + _press_key(subvariant, REPEAT_IDX_A); + subvariant->scancodes[REPEAT_IDX_A] = -1; + } +} + +// ---------------------------------------------------------------------------- +// axis key(s) state + +static void touchkpad_axisDown(void) { + + SPIN_LOCK_FULL(&kpad.spinlock); + TOUCH_JOY_LOG("\t\tkpad axisDown acquire"); + + _subvariant_touchDown(&kpad.axis); + + TOUCH_JOY_LOG("\t\tkpad axisDown release"); + SPIN_UNLOCK_FULL(&kpad.spinlock); +} + +static void touchkpad_axisMove(int dx, int dy) { + + SPIN_LOCK_FULL(&kpad.spinlock); + TOUCH_JOY_LOG("\t\tkpad axisMove acquire"); + + _subvariant_touchMove(&kpad.axis, dx, dy, /*isTouchUp:*/false); + + TOUCH_JOY_LOG("\t\tkpad axisMove release"); + SPIN_UNLOCK_FULL(&kpad.spinlock); +} + +static void touchkpad_axisUp(int dx, int dy) { + + SPIN_LOCK_FULL(&kpad.spinlock); + TOUCH_JOY_LOG("\t\tkpad axisUp acquire"); + + _subvariant_touchUp(&kpad.axis, dx, dy); + + TOUCH_JOY_LOG("\t\tkpad axisUp release"); + SPIN_UNLOCK_FULL(&kpad.spinlock); +} + +// ---------------------------------------------------------------------------- +// button key(s) state + +static void touchkpad_buttonDown(void) { + + SPIN_LOCK_FULL(&kpad.spinlock); + TOUCH_JOY_LOG("\t\tkpad buttonDown acquire"); + + _subvariant_touchDown(&kpad.butt); + + TOUCH_JOY_LOG("\t\tkpad buttonDown release"); + SPIN_UNLOCK_FULL(&kpad.spinlock); +} + +static void touchkpad_buttonMove(int dx, int dy) { + + SPIN_LOCK_FULL(&kpad.spinlock); + TOUCH_JOY_LOG("\t\tkpad buttonMove acquire"); + + _subvariant_touchMove(&kpad.butt, dx, dy, /*isTouchUp:*/false); + + TOUCH_JOY_LOG("\t\tkpad buttonMove release"); + SPIN_UNLOCK_FULL(&kpad.spinlock); +} + +static void touchkpad_buttonUp(int dx, int dy) { + + SPIN_LOCK_FULL(&kpad.spinlock); + TOUCH_JOY_LOG("\t\tkpad buttonUp acquire"); + + _subvariant_touchUp(&kpad.butt, dx, dy); + + TOUCH_JOY_LOG("\t\tkpad buttonUp release"); + SPIN_UNLOCK_FULL(&kpad.spinlock); +} + +// ---------------------------------------------------------------------------- +// prefs handling + +static void _subvariant_prefsChanged(subvariant_s *subvariant, const char *domain, const char *prefCharsKey, const char *prefScansKey) { + long lVal = 0; + + // ASCII : "kp{Axis,Butt}RosetteChars" : [ 121, 127, 130, 100, 101, 121, 132, 120, 99 ] + JSON_ref array = NULL; + do { + if (!prefs_copyJSONValue(domain, prefCharsKey, &array)) { + LOG("could not parse touch keypad rosette"); + break; } - } + long count = 0; + if (!json_arrayCount(array, &count)) { + LOG("rosette is not an array!"); + break; + } + if (count != ROSETTE_COUNT) { + LOG("rosette count unexpected : %lu!", count); + break; + } + for (unsigned long i=0; irosetteChars[i] = json_arrayParseLongValueAtIndex(array, i, &lVal, /*base:*/10) ? (uint8_t)lVal : ' '; + } + } while (0); - if (lockedButton && lockedAxis && fired < 0) { - TOUCH_JOY_LOG("REPEAT KEY CALLBACK DONE ..."); - keydriver_keyboardReadCallback = NULL; - } + json_destroy(&array); - if (lockedAxis) { - _callback_sourceUnlock(&kpad.axisLock); - } - if (lockedButton) { - _callback_sourceUnlock(&kpad.buttonLock); - } + // long : "kp{Axis,Butt}RosetteScans" : [ -1, 100, -1, 99, -1, 96, -1, 101, -1 ] + do { + if (!prefs_copyJSONValue(domain, prefScansKey, &array)) { + LOG("could not parse touch keypad rosette"); + break; + } + long count = 0; + if (!json_arrayCount(array, &count)) { + LOG("rosette is not an array!"); + break; + } + if (count != ROSETTE_COUNT) { + LOG("rosette count unexpected : %lu!", count); + break; + } + for (unsigned long i=0; irosetteScans[i] = json_arrayParseLongValueAtIndex(array, i, &lVal, /*base:*/10) ? lVal : -1; + } + } while (0); + + json_destroy(&array); +} + +static void touchkpad_prefsChanged(const char *domain) { + assert(video_isRenderThread()); + + long lVal = 0; + bool bVal = false; + + kpad.autostrobeDelay = !(prefs_parseBoolValue(domain, PREF_KPAD_FAST_AUTOREPEAT, &bVal) ? bVal : true); + + do { + const int rosetteChars[ROSETTE_ROWS*ROSETTE_COLS] = { + ICONTEXT_NONACTIONABLE, 'I', ICONTEXT_NONACTIONABLE, + 'J', ICONTEXT_NONACTIONABLE, 'K', + ICONTEXT_NONACTIONABLE, 'M', ICONTEXT_NONACTIONABLE, + }; + const int rosetteScans[ROSETTE_ROWS*ROSETTE_COLS] = { + -1, keys_ascii2Scancode('I'), -1, + keys_ascii2Scancode('J'), -1, keys_ascii2Scancode('K'), + -1, keys_ascii2Scancode('M'), -1, + }; + for (unsigned long i=0; i= 0) { - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_CENTER]; - kpad.scancodes[REPEAT_AXIS_ALT] = -1; - } +static uint8_t *touchkpad_axisRosetteChars(void) { + return kpad.axis.rosetteChars; } -static void touchkpad_axisMove(int dx, int dy) { - TOUCH_JOY_LOG("..."); - - if ((dx > -joyglobals.switchThreshold) && (dx < joyglobals.switchThreshold) && (dy > -joyglobals.switchThreshold) && (dy < joyglobals.switchThreshold)) { - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_CENTER]; - kpad.scancodes[REPEAT_AXIS_ALT] = -1; - return; - } - - if (dx == 0 && dy == 0) { - return; - } - - do { - - // determine the octant of this dx/dy - - keypad_octant_t axisLastOctant = kpad.axisCurrentOctant; - - const float radians = atan2f(dy, dx); // -180-180deg range - float radnorm = radians + M_PI; // 0-360deg range - if (UNLIKELY(radnorm < 0.f)) { - radnorm = 0.f; // clamp positive - } - float octant = radnorm + (M_PI/8.f); // rotate to correct octant (+45deg) - octant /= (M_PI/4.f); // divide to octant (/90deg) - kpad.axisCurrentOctant = (keypad_octant_t) ((int)octant & 0x7);// integer modulo maps to enum - - if (kpad.axisCurrentOctant == axisLastOctant) { - break; - } - - TOUCH_JOY_LOG("radians:%f radnorm:%f octant:%f, currOctant:%d", radians, radnorm, octant, kpad.axisCurrentOctant); - - // Current implementation NOTE : four cardinal directions are handled slightly different than the intercardinal - // ones. - // - The intercardinals might generate 2 scanscodes (for example north and west scancodes for a northwest axis) - // if there is not a specific scancode to handle it (e.g., the northwest scancode). - // - The cardinals will only ever generate one scancode (the cardinal in question if it's set, or the scancode - // of the adjacent intercardinal where the point lies). - kpad.scancodes[REPEAT_AXIS_ALT] = -1; - switch (kpad.axisCurrentOctant) { - case OCTANT_NORTHWEST: - if (kpad.rosetteScancodes[ROSETTE_NORTHWEST] >= 0) { - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHWEST]; - TOUCH_JOY_LOG("XY : NORTHWEST, (%d)", kpad.rosetteScancodes[ROSETTE_WEST]); - } else { - TOUCH_JOY_LOG("XY : WEST (%d) & NORTH (%d)", kpad.rosetteScancodes[ROSETTE_WEST], kpad.rosetteScancodes[ROSETTE_NORTH]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_WEST]; - kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_NORTH]; - } - break; - - case OCTANT_NORTH: - if (kpad.rosetteScancodes[ROSETTE_NORTH] >= 0) { - TOUCH_JOY_LOG("Y : NORTH (%d)", kpad.rosetteScancodes[ROSETTE_NORTH]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTH]; - } else if (radians < RADIANS_NORTH) { - TOUCH_JOY_LOG("XY : NORTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHWEST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHWEST]; - } else { - TOUCH_JOY_LOG("XY : NORTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHEAST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHEAST]; - } - break; - - case OCTANT_NORTHEAST: - if (kpad.rosetteScancodes[ROSETTE_NORTHEAST] >= 0) { - TOUCH_JOY_LOG("XY : NORTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHEAST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHEAST]; - } else { - TOUCH_JOY_LOG("XY : EAST (%d) & NORTH (%d)", kpad.rosetteScancodes[ROSETTE_EAST], kpad.rosetteScancodes[ROSETTE_NORTH]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_EAST]; - kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_NORTH]; - } - break; - - case OCTANT_WEST: - if (kpad.rosetteScancodes[ROSETTE_WEST] >= 0) { - TOUCH_JOY_LOG("Y : WEST (%d)", kpad.rosetteScancodes[ROSETTE_WEST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_WEST]; - } else if (radians > RADIANS_WEST_NEG && radians < 0) { - TOUCH_JOY_LOG("XY : NORTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHWEST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHWEST]; - } else { - TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHWEST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHWEST]; - } - break; - - case OCTANT_EAST: - if (kpad.rosetteScancodes[ROSETTE_EAST] >= 0) { - TOUCH_JOY_LOG("Y : EAST (%d)", kpad.rosetteScancodes[ROSETTE_EAST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_EAST]; - } else if (radians < RADIANS_EAST) { - TOUCH_JOY_LOG("XY : NORTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_NORTHEAST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_NORTHEAST]; - } else { - TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHEAST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHEAST]; - } - break; - - case OCTANT_SOUTHWEST: - if (kpad.rosetteScancodes[ROSETTE_SOUTHWEST] >= 0) { - TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHWEST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHWEST]; - } else { - TOUCH_JOY_LOG("XY : WEST (%d) & SOUTH (%d)", kpad.rosetteScancodes[ROSETTE_WEST], kpad.rosetteScancodes[ROSETTE_SOUTH]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_WEST]; - kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_SOUTH]; - } - break; - - case OCTANT_SOUTH: - if (kpad.rosetteScancodes[ROSETTE_SOUTH] >= 0) { - TOUCH_JOY_LOG("Y : SOUTH (%d)", kpad.rosetteScancodes[ROSETTE_SOUTH]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTH]; - } else if (radians > RADIANS_SOUTH) { - TOUCH_JOY_LOG("XY : SOUTHWEST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHWEST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHWEST]; - } else { - TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHEAST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHEAST]; - } - break; - - case OCTANT_SOUTHEAST: - if (kpad.rosetteScancodes[ROSETTE_SOUTHEAST] >= 0) { - TOUCH_JOY_LOG("XY : SOUTHEAST (%d)", kpad.rosetteScancodes[ROSETTE_SOUTHEAST]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_SOUTHEAST]; - } else { - TOUCH_JOY_LOG("XY : EAST (%d) & SOUTH (%d)", kpad.rosetteScancodes[ROSETTE_EAST], kpad.rosetteScancodes[ROSETTE_SOUTH]); - kpad.scancodes[REPEAT_AXIS] = kpad.rosetteScancodes[ROSETTE_EAST]; - kpad.scancodes[REPEAT_AXIS_ALT] = kpad.rosetteScancodes[ROSETTE_SOUTH]; - } - break; - - default: - assert(false && "should not happen"); - break; - } - - } while (0); +static uint8_t *touchkpad_buttRosetteChars(void) { + return kpad.butt.rosetteChars; } -static void touchkpad_axisUp(int dx, int dy) { - TOUCH_JOY_LOG("..."); - touchkpad_axisMove(dx, dy); - kpad.axisCurrentOctant = ORIGIN; - kpad.timingBegins[REPEAT_AXIS] = (struct timespec){ 0 }; - kpad.timingBegins[REPEAT_AXIS_ALT] = (struct timespec){ 0 }; - if (kpad.axisBegan) { - kpad.axisBegan = false; - _touch_sourceEnd(&kpad.axisLock); - } -} - -// ---------------------------------------------------------------------------- -// button key state - -static void _set_current_button_state(touchjoy_button_type_t theButtonChar, int theButtonScancode) { - TOUCH_JOY_LOG("..."); - if (theButtonChar >= 0) { - kpad.currButtonDisplayChar = theButtonChar; - kpad.scancodes[REPEAT_BUTTON] = theButtonScancode; - } else { - kpad.currButtonDisplayChar = ' '; - kpad.scancodes[REPEAT_BUTTON] = -1; - } - kpad.buttonDrawCallback(kpad.currButtonDisplayChar); -} - -static void touchkpad_buttonDown(void) { - if (!kpad.buttonBegan) { - kpad.buttonBegan = true; - _touch_sourceBegin(&kpad.buttonLock); - } - _set_current_button_state(kpad.touchDownChar, kpad.touchDownScancode); - if (kpad.scancodes[REPEAT_BUTTON] >= 0) { - TOUCH_JOY_LOG("->BUTT : %d/'%c'", kpad.scancodes[REPEAT_BUTTON], kpad.currButtonDisplayChar); - clock_gettime(CLOCK_MONOTONIC, &kpad.timingBegins[REPEAT_BUTTON]); - keydriver_keyboardReadCallback = &touchkpad_keyboardReadCallback; - } -} - -static void touchkpad_buttonMove(int dx, int dy) { - // Currently this is the same logic as the "regular" joystick variant ... but we could likely be more creative here - // in a future revision, like having a full octant of key possibilities ... (for example, playing Bolo with a friend - // on the same tablet, one driving, the other shooting and controlling the turret) - if ((dy < -joyglobals.switchThreshold) || (dy > joyglobals.switchThreshold)) { - touchjoy_button_type_t theButtonChar = -1; - int theButtonScancode = -1; - if (dy < 0) { - theButtonChar = kpad.northChar; - theButtonScancode = kpad.northScancode; - } else { - theButtonChar = kpad.southChar; - theButtonScancode = kpad.southScancode; - } - _set_current_button_state(theButtonChar, theButtonScancode); - } -} - -static void touchkpad_buttonUp(int dx, int dy) { - TOUCH_JOY_LOG("..."); - touchkpad_buttonMove(dx, dy); - kpad.timingBegins[REPEAT_BUTTON] = (struct timespec){ 0 }; - if (kpad.buttonBegan) { - kpad.buttonBegan = false; - _touch_sourceEnd(&kpad.buttonLock); - } -} - -static void touchkpad_prefsChanged(const char *domain) { - assert(video_isRenderThread()); - - float fVal = 0.f; - long lVal = 0; - - kpad.repeatThresholdNanos = prefs_parseFloatValue(domain, PREF_KPAD_REPEAT_THRESH, &fVal) ? fVal*NANOSECONDS_PER_SECOND : NANOSECONDS_PER_SECOND; - - kpad.touchDownChar = prefs_parseLongValue (domain, PREF_KPAD_TOUCHDOWN_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_SPACE_VISUAL; - kpad.touchDownScancode = prefs_parseLongValue (domain, PREF_KPAD_TOUCHDOWN_SCAN, &lVal, /*base:*/10) ? lVal : keys_ascii2Scancode(' '); - - kpad.southChar = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_SOUTH_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_NONACTIONABLE; - kpad.southScancode = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_SOUTH_SCAN, &lVal, /*base:*/10) ? lVal : -1; - - kpad.northChar = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_NORTH_CHAR, &lVal, /*base:*/10) ? lVal : ICONTEXT_NONACTIONABLE; - kpad.northScancode = prefs_parseLongValue (domain, PREF_KPAD_SWIPE_NORTH_SCAN, &lVal, /*base:*/10) ? lVal : -1; - - const unsigned long rosetteCount = ROSETTE_ROWS*ROSETTE_COLS; - - do { - const int rosetteChars[ROSETTE_ROWS*ROSETTE_COLS] = { - ICONTEXT_NONACTIONABLE, 'I', ICONTEXT_NONACTIONABLE, - 'J', ICONTEXT_NONACTIONABLE, 'K', - ICONTEXT_NONACTIONABLE, 'M', ICONTEXT_NONACTIONABLE, - }; - const int rosetteScans[ROSETTE_ROWS*ROSETTE_COLS] = { - -1, keys_ascii2Scancode('I'), -1, - keys_ascii2Scancode('J'), -1, keys_ascii2Scancode('K'), - -1, keys_ascii2Scancode('M'), -1, - }; - for (unsigned long i=0; i