[Scummvm-git-logs] scummvm master -> 7959c431aa03d45a3f50a16f963dc91c7f4a963e
antoniou79
noreply at scummvm.org
Mon Aug 7 14:40:44 UTC 2023
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
3bb60278d6 ANDROID: JANITORIAL: Fix formatting
95f1ca2847 ANDROID: Fix double tap and right click hold for direct mouse mode
7959c431aa ANDROID: Make virtual keyboard arrow keys similar to physical
Commit: 3bb60278d636426ea0b016d4513d4efe76cb5043
https://github.com/scummvm/scummvm/commit/3bb60278d636426ea0b016d4513d4efe76cb5043
Author: antoniou79 (a.antoniou79 at gmail.com)
Date: 2023-08-07T17:31:01+03:00
Commit Message:
ANDROID: JANITORIAL: Fix formatting
Changed paths:
backends/platform/android/org/scummvm/scummvm/CustomKeyboard.java
backends/platform/android/org/scummvm/scummvm/CustomKeyboardView.java
diff --git a/backends/platform/android/org/scummvm/scummvm/CustomKeyboard.java b/backends/platform/android/org/scummvm/scummvm/CustomKeyboard.java
index 7b246fec4bb..1b1d2b9fa67 100755
--- a/backends/platform/android/org/scummvm/scummvm/CustomKeyboard.java
+++ b/backends/platform/android/org/scummvm/scummvm/CustomKeyboard.java
@@ -57,762 +57,729 @@ import java.util.StringTokenizer;
*/
public class CustomKeyboard {
- static final String LOG_TAG = "CustomKeyboard";
-
- // Keyboard XML Tags
- private static final String TAG_KEYBOARD = "CustomKeyboard";
- private static final String TAG_ROW = "CustomRow";
- private static final String TAG_KEY = "CustomKey";
+ static final String LOG_TAG = "CustomKeyboard";
+
+ // Keyboard XML Tags
+ private static final String TAG_KEYBOARD = "CustomKeyboard";
+ private static final String TAG_ROW = "CustomRow";
+ private static final String TAG_KEY = "CustomKey";
- public static final int EDGE_LEFT = 0x01;
- public static final int EDGE_RIGHT = 0x02;
- public static final int EDGE_TOP = 0x04;
- public static final int EDGE_BOTTOM = 0x08;
-
- public static final int KEYCODE_SHIFT = -1;
- public static final int KEYCODE_MODE_CHANGE = -2;
- public static final int KEYCODE_CANCEL = -3;
- public static final int KEYCODE_DONE = -4;
- public static final int KEYCODE_DELETE = -5;
- public static final int KEYCODE_ALT = -6;
+ public static final int EDGE_LEFT = 0x01;
+ public static final int EDGE_RIGHT = 0x02;
+ public static final int EDGE_TOP = 0x04;
+ public static final int EDGE_BOTTOM = 0x08;
+
+ public static final int KEYCODE_SHIFT = -1;
+ public static final int KEYCODE_MODE_CHANGE = -2;
+ public static final int KEYCODE_CANCEL = -3;
+ public static final int KEYCODE_DONE = -4;
+ public static final int KEYCODE_DELETE = -5;
+ public static final int KEYCODE_ALT = -6;
- /** Keyboard label **/
- private CharSequence mLabel;
+ /** Keyboard label **/
+ private CharSequence mLabel;
- /** Horizontal gap default for all rows */
- private int mDefaultHorizontalGap;
-
- /** Default key width */
- private int mDefaultWidth;
-
- /** Default key height */
- private int mDefaultHeight;
-
- /** Default gap between rows */
- private int mDefaultVerticalGap;
-
- /** Is the keyboard in the shifted state */
- private boolean mShifted;
-
- /** Key instance for the shift key, if present */
- private CustomKey[] mShiftKeys = { null, null };
-
- /** Key index for the shift key, if present */
- private int[] mShiftKeyIndices = {-1, -1};
-
- /** Current key width, while loading the keyboard */
- private int mKeyWidth;
-
- /** Current key height, while loading the keyboard */
- private int mKeyHeight;
-
- /** Total height of the keyboard, including the padding and keys */
-// @UnsupportedAppUsage
- private int mTotalHeight;
-
- /**
- * Total width of the keyboard, including left side gaps and keys, but not any gaps on the
- * right side.
- */
-// @UnsupportedAppUsage
- private int mTotalWidth;
-
- /** List of keys in this keyboard */
- private List<CustomKey> mKeys;
-
- /** List of modifier keys such as Shift & Alt, if any */
-// @UnsupportedAppUsage
- private List<CustomKey> mModifierKeys;
-
- /** Width of the screen available to fit the keyboard */
- private int mDisplayWidth;
-
- /** Height of the screen */
- private int mDisplayHeight;
-
- /** Keyboard mode, or zero, if none. */
- private int mKeyboardMode;
-
- // Variables for pre-computing nearest keys.
-
- private static final int GRID_WIDTH = 10;
- private static final int GRID_HEIGHT = 5;
- private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
- private int mCellWidth;
- private int mCellHeight;
- private int[][] mGridNeighbors;
- private int mProximityThreshold;
- /** Number of key widths from current touch point to search for nearest keys. */
- private static float SEARCH_DISTANCE = 1.8f;
-
- private ArrayList<CustomRow> rows = new ArrayList<CustomRow>();
-
- /**
- * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
- * Some of the key size defaults can be overridden per row from what the {@link CustomKeyboard}
- * defines.
- */
- public static class CustomRow {
- /** Default width of a key in this row. */
- public int defaultWidth;
- /** Default height of a key in this row. */
- public int defaultHeight;
- /** Default horizontal gap between keys in this row. */
- public int defaultHorizontalGap;
- /** Vertical gap following this row. */
- public int verticalGap;
-
- ArrayList<CustomKey> mKeys = new ArrayList<>();
-
- /**
- * Edge flags for this row of keys. Possible values that can be assigned are
- * {@link CustomKeyboard#EDGE_TOP EDGE_TOP} and {@link CustomKeyboard#EDGE_BOTTOM EDGE_BOTTOM}
- */
- public int rowEdgeFlags;
-
- /** The keyboard mode for this row */
- public int mode;
-
- private CustomKeyboard parent;
-
- public CustomRow(CustomKeyboard parent) {
- this.parent = parent;
- }
-
- public CustomRow(Resources res, CustomKeyboard parent, XmlResourceParser parser) {
- this.parent = parent;
- TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.CustomKeyboard);
- defaultWidth = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_keyWidth,
- parent.mDisplayWidth, parent.mDefaultWidth);
- defaultHeight = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_keyHeight,
- parent.mDisplayHeight, parent.mDefaultHeight);
- defaultHorizontalGap = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_horizontalGap,
- parent.mDisplayWidth, parent.mDefaultHorizontalGap);
- verticalGap = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_verticalGap,
- parent.mDisplayHeight, parent.mDefaultVerticalGap);
- a.recycle();
-
- a = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.CustomKeyboard_CustomRow);
- rowEdgeFlags = a.getInt(R.styleable.CustomKeyboard_CustomRow_rowEdgeFlags, 0);
- mode = a.getResourceId(R.styleable.CustomKeyboard_CustomRow_keyboardMode,
- 0);
+ /** Horizontal gap default for all rows */
+ private int mDefaultHorizontalGap;
+
+ /** Default key width */
+ private int mDefaultWidth;
+
+ /** Default key height */
+ private int mDefaultHeight;
+
+ /** Default gap between rows */
+ private int mDefaultVerticalGap;
+
+ /** Is the keyboard in the shifted state */
+ private boolean mShifted;
+
+ /** Key instance for the shift key, if present */
+ private CustomKey[] mShiftKeys = { null, null };
+
+ /** Key index for the shift key, if present */
+ private int[] mShiftKeyIndices = {-1, -1};
+
+ /** Current key width, while loading the keyboard */
+ private int mKeyWidth;
+
+ /** Current key height, while loading the keyboard */
+ private int mKeyHeight;
+
+ /** Total height of the keyboard, including the padding and keys */
+// @UnsupportedAppUsage
+ private int mTotalHeight;
+
+ /**
+ * Total width of the keyboard, including left side gaps and keys, but not any gaps on the
+ * right side.
+ */
+// @UnsupportedAppUsage
+ private int mTotalWidth;
+
+ /** List of keys in this keyboard */
+ private List<CustomKey> mKeys;
+
+ /** List of modifier keys such as Shift & Alt, if any */
+// @UnsupportedAppUsage
+ private List<CustomKey> mModifierKeys;
+
+ /** Width of the screen available to fit the keyboard */
+ private int mDisplayWidth;
+
+ /** Height of the screen */
+ private int mDisplayHeight;
+
+ /** Keyboard mode, or zero, if none. */
+ private int mKeyboardMode;
+
+ // Variables for pre-computing nearest keys.
+
+ private static final int GRID_WIDTH = 10;
+ private static final int GRID_HEIGHT = 5;
+ private static final int GRID_SIZE = GRID_WIDTH * GRID_HEIGHT;
+ private int mCellWidth;
+ private int mCellHeight;
+ private int[][] mGridNeighbors;
+ private int mProximityThreshold;
+ /** Number of key widths from current touch point to search for nearest keys. */
+ private static float SEARCH_DISTANCE = 1.8f;
+
+ private ArrayList<CustomRow> rows = new ArrayList<CustomRow>();
+
+ /**
+ * Container for keys in the keyboard. All keys in a row are at the same Y-coordinate.
+ * Some of the key size defaults can be overridden per row from what the {@link CustomKeyboard}
+ * defines.
+ */
+ public static class CustomRow {
+ /** Default width of a key in this row. */
+ public int defaultWidth;
+ /** Default height of a key in this row. */
+ public int defaultHeight;
+ /** Default horizontal gap between keys in this row. */
+ public int defaultHorizontalGap;
+ /** Vertical gap following this row. */
+ public int verticalGap;
+
+ ArrayList<CustomKey> mKeys = new ArrayList<>();
+
+ /**
+ * Edge flags for this row of keys. Possible values that can be assigned are
+ * {@link CustomKeyboard#EDGE_TOP EDGE_TOP} and {@link CustomKeyboard#EDGE_BOTTOM EDGE_BOTTOM}
+ */
+ public int rowEdgeFlags;
+
+ /** The keyboard mode for this row */
+ public int mode;
+
+ private CustomKeyboard parent;
+
+ public CustomRow(CustomKeyboard parent) {
+ this.parent = parent;
+ }
+
+ public CustomRow(Resources res, CustomKeyboard parent, XmlResourceParser parser) {
+ this.parent = parent;
+ TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.CustomKeyboard);
+ defaultWidth = getDimensionOrFraction(a, R.styleable.CustomKeyboard_keyWidth, parent.mDisplayWidth, parent.mDefaultWidth);
+ defaultHeight = getDimensionOrFraction(a, R.styleable.CustomKeyboard_keyHeight, parent.mDisplayHeight, parent.mDefaultHeight);
+ defaultHorizontalGap = getDimensionOrFraction(a, R.styleable.CustomKeyboard_horizontalGap, parent.mDisplayWidth, parent.mDefaultHorizontalGap);
+ verticalGap = getDimensionOrFraction(a, R.styleable.CustomKeyboard_verticalGap, parent.mDisplayHeight, parent.mDefaultVerticalGap);
a.recycle();
- }
- }
-
- /**
- * Class for describing the position and characteristics of a single key in the keyboard.
- *
- */
- public static class CustomKey {
- /**
- * All the key codes (unicode or custom code) that this key could generate, zero'th
- * being the most important.
- */
- public int[] codes;
-
- /** Label to display */
- public CharSequence label;
-
- /** Icon to display instead of a label. Icon takes precedence over a label */
- public Drawable icon;
- /** Preview version of the icon, for the preview popup */
- public Drawable iconPreview;
- /** Width of the key, not including the gap */
- public int width;
- /** Height of the key, not including the gap */
- public int height;
- /** The horizontal gap before this key */
- public int gap;
- /** Whether this key is sticky, i.e., a toggle key */
- public boolean sticky;
- /** X coordinate of the key in the keyboard layout */
- public int x;
- /** Y coordinate of the key in the keyboard layout */
- public int y;
- /** The current pressed state of this key */
- public boolean pressed;
- /** If this is a sticky key, is it on? */
- public boolean on;
- /** Text to output when pressed. This can be multiple characters, like ".com" */
- public CharSequence text;
- /** Popup characters */
- public CharSequence popupCharacters;
-
- /**
- * Flags that specify the anchoring to edges of the keyboard for detecting touch events
- * that are just out of the boundary of the key. This is a bit mask of
- * {@link CustomKeyboard#EDGE_LEFT}, {@link CustomKeyboard#EDGE_RIGHT}, {@link CustomKeyboard#EDGE_TOP} and
- * {@link CustomKeyboard#EDGE_BOTTOM}.
- */
- public int edgeFlags;
- /** Whether this is a modifier key, such as Shift or Alt */
- public boolean modifier;
- /** The keyboard that this key belongs to */
- private CustomKeyboard keyboard;
- /**
- * If this key pops up a mini keyboard, this is the resource id for the XML layout for that
- * keyboard.
- */
- public int popupResId;
- /** Whether this key repeats itself when held down */
- public boolean repeatable;
-
-
- private final static int[] KEY_STATE_NORMAL_ON = {
- android.R.attr.state_checkable,
- android.R.attr.state_checked
- };
-
- private final static int[] KEY_STATE_PRESSED_ON = {
- android.R.attr.state_pressed,
- android.R.attr.state_checkable,
- android.R.attr.state_checked
- };
-
- private final static int[] KEY_STATE_NORMAL_OFF = {
- android.R.attr.state_checkable
- };
-
- private final static int[] KEY_STATE_PRESSED_OFF = {
- android.R.attr.state_pressed,
- android.R.attr.state_checkable
- };
-
- private final static int[] KEY_STATE_NORMAL = {
- };
-
- private final static int[] KEY_STATE_PRESSED = {
- android.R.attr.state_pressed
- };
-
- /** Create an empty key with no attributes. */
- public CustomKey(CustomRow parent) {
- keyboard = parent.parent;
- height = parent.defaultHeight;
- width = parent.defaultWidth;
- gap = parent.defaultHorizontalGap;
- edgeFlags = parent.rowEdgeFlags;
- }
-
- /** Create a key with the given top-left coordinate and extract its attributes from
- * the XML parser.
- * @param res resources associated with the caller's context
- * @param parent the row that this key belongs to. The row must already be attached to
- * a {@link CustomKeyboard}.
- * @param x the x coordinate of the top-left
- * @param y the y coordinate of the top-left
- * @param parser the XML parser containing the attributes for this key
- */
- public CustomKey(Resources res, CustomRow parent, int x, int y, XmlResourceParser parser) {
- this(parent);
-
- this.x = x;
- this.y = y;
-
- // obtainAttributes (AttributeSet set, int[] attrs)
+
+ a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.CustomKeyboard_CustomRow);
+ rowEdgeFlags = a.getInt(R.styleable.CustomKeyboard_CustomRow_rowEdgeFlags, 0);
+ mode = a.getResourceId(R.styleable.CustomKeyboard_CustomRow_keyboardMode, 0);
+ a.recycle();
+ }
+ } // end of: static class CustomRow
+
+ /**
+ * Class for describing the position and characteristics of a single key in the keyboard.
+ *
+ */
+ public static class CustomKey {
+ /**
+ * All the key codes (unicode or custom code) that this key could generate, zero'th
+ * being the most important.
+ */
+ public int[] codes;
+
+ /** Label to display */
+ public CharSequence label;
+
+ /** Icon to display instead of a label. Icon takes precedence over a label */
+ public Drawable icon;
+ /** Preview version of the icon, for the preview popup */
+ public Drawable iconPreview;
+ /** Width of the key, not including the gap */
+ public int width;
+ /** Height of the key, not including the gap */
+ public int height;
+ /** The horizontal gap before this key */
+ public int gap;
+ /** Whether this key is sticky, i.e., a toggle key */
+ public boolean sticky;
+ /** X coordinate of the key in the keyboard layout */
+ public int x;
+ /** Y coordinate of the key in the keyboard layout */
+ public int y;
+ /** The current pressed state of this key */
+ public boolean pressed;
+ /** If this is a sticky key, is it on? */
+ public boolean on;
+ /** Text to output when pressed. This can be multiple characters, like ".com" */
+ public CharSequence text;
+ /** Popup characters */
+ public CharSequence popupCharacters;
+
+ /**
+ * Flags that specify the anchoring to edges of the keyboard for detecting touch events
+ * that are just out of the boundary of the key. This is a bit mask of
+ * {@link CustomKeyboard#EDGE_LEFT}, {@link CustomKeyboard#EDGE_RIGHT}, {@link CustomKeyboard#EDGE_TOP} and
+ * {@link CustomKeyboard#EDGE_BOTTOM}.
+ */
+ public int edgeFlags;
+ /** Whether this is a modifier key, such as Shift or Alt */
+ public boolean modifier;
+ /** The keyboard that this key belongs to */
+ private CustomKeyboard keyboard;
+ /**
+ * If this key pops up a mini keyboard, this is the resource id for the XML layout for that
+ * keyboard.
+ */
+ public int popupResId;
+ /** Whether this key repeats itself when held down */
+ public boolean repeatable;
+
+
+ private final static int[] KEY_STATE_NORMAL_ON = {
+ android.R.attr.state_checkable,
+ android.R.attr.state_checked
+ };
+
+ private final static int[] KEY_STATE_PRESSED_ON = {
+ android.R.attr.state_pressed,
+ android.R.attr.state_checkable,
+ android.R.attr.state_checked
+ };
+
+ private final static int[] KEY_STATE_NORMAL_OFF = {
+ android.R.attr.state_checkable
+ };
+
+ private final static int[] KEY_STATE_PRESSED_OFF = {
+ android.R.attr.state_pressed,
+ android.R.attr.state_checkable
+ };
+
+ private final static int[] KEY_STATE_NORMAL = { };
+
+ private final static int[] KEY_STATE_PRESSED = {
+ android.R.attr.state_pressed
+ };
+
+ /** Create an empty key with no attributes. */
+ public CustomKey(CustomRow parent) {
+ keyboard = parent.parent;
+ height = parent.defaultHeight;
+ width = parent.defaultWidth;
+ gap = parent.defaultHorizontalGap;
+ edgeFlags = parent.rowEdgeFlags;
+ }
+
+ /** Create a key with the given top-left coordinate and extract its attributes from
+ * the XML parser.
+ * @param res resources associated with the caller's context
+ * @param parent the row that this key belongs to. The row must already be attached to
+ * a {@link CustomKeyboard}.
+ * @param x the x coordinate of the top-left
+ * @param y the y coordinate of the top-left
+ * @param parser the XML parser containing the attributes for this key
+ */
+ public CustomKey(Resources res, CustomRow parent, int x, int y, XmlResourceParser parser) {
+ this(parent);
+
+ this.x = x;
+ this.y = y;
+
+ // obtainAttributes (AttributeSet set, int[] attrs)
// Retrieve a set of basic attribute values from an AttributeSet, not performing styling of them using a theme and/or style resources.
- TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.CustomKeyboard);
-
- width = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_keyWidth,
- keyboard.mDisplayWidth, parent.defaultWidth);
- height = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_keyHeight,
- keyboard.mDisplayHeight, parent.defaultHeight);
- gap = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_horizontalGap,
- keyboard.mDisplayWidth, parent.defaultHorizontalGap);
-
-// Log.d(LOG_TAG, "from CustomKeyboard: wid: " +width + " heigh: " + height + " gap: " + gap );
-
- a.recycle();
- a = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.CustomKeyboard_CustomKey);
- this.x += gap;
- TypedValue codesValue = new TypedValue();
- a.getValue(R.styleable.CustomKeyboard_CustomKey_codes,
- codesValue);
- if (codesValue.type == TypedValue.TYPE_INT_DEC || codesValue.type == TypedValue.TYPE_INT_HEX) {
+ TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.CustomKeyboard);
+
+ width = getDimensionOrFraction(a, R.styleable.CustomKeyboard_keyWidth, keyboard.mDisplayWidth, parent.defaultWidth);
+ height = getDimensionOrFraction(a, R.styleable.CustomKeyboard_keyHeight, keyboard.mDisplayHeight, parent.defaultHeight);
+ gap = getDimensionOrFraction(a, R.styleable.CustomKeyboard_horizontalGap, keyboard.mDisplayWidth, parent.defaultHorizontalGap);
+
+// Log.d(LOG_TAG, "from CustomKeyboard: wid: " +width + " heigh: " + height + " gap: " + gap );
+
+ a.recycle();
+ a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.CustomKeyboard_CustomKey);
+ this.x += gap;
+ TypedValue codesValue = new TypedValue();
+ a.getValue(R.styleable.CustomKeyboard_CustomKey_codes, codesValue);
+ if (codesValue.type == TypedValue.TYPE_INT_DEC || codesValue.type == TypedValue.TYPE_INT_HEX) {
// Log.d(LOG_TAG, "Key codes is INT or HEX");
- codes = new int[] { codesValue.data };
- } else if (codesValue.type == TypedValue.TYPE_STRING) {
+ codes = new int[] { codesValue.data };
+ } else if (codesValue.type == TypedValue.TYPE_STRING) {
// Log.d(LOG_TAG, "Key codes is String");
- codes = parseCSV(codesValue.string.toString());
- }
-
- iconPreview = a.getDrawable(R.styleable.CustomKeyboard_CustomKey_iconPreview);
- if (iconPreview != null) {
- iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(),
- iconPreview.getIntrinsicHeight());
- }
- popupCharacters = a.getText(
- R.styleable.CustomKeyboard_CustomKey_popupCharacters);
- popupResId = a.getResourceId(
- R.styleable.CustomKeyboard_CustomKey_popupKeyboard, 0);
- repeatable = a.getBoolean(
- R.styleable.CustomKeyboard_CustomKey_isRepeatable, false);
- modifier = a.getBoolean(
- R.styleable.CustomKeyboard_CustomKey_isModifier, false);
- sticky = a.getBoolean(
- R.styleable.CustomKeyboard_CustomKey_isSticky, false);
- edgeFlags = a.getInt(R.styleable.CustomKeyboard_CustomKey_keyEdgeFlags, 0);
- edgeFlags |= parent.rowEdgeFlags;
-
- icon = a.getDrawable(
- R.styleable.CustomKeyboard_CustomKey_keyIcon);
- if (icon != null) {
- icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
- }
- label = a.getText(R.styleable.CustomKeyboard_CustomKey_keyLabel);
- Log.d(LOG_TAG, "Key label is " + label);
+ codes = parseCSV(codesValue.string.toString());
+ }
+
+ iconPreview = a.getDrawable(R.styleable.CustomKeyboard_CustomKey_iconPreview);
+ if (iconPreview != null) {
+ iconPreview.setBounds(0, 0, iconPreview.getIntrinsicWidth(), iconPreview.getIntrinsicHeight());
+ }
+ popupCharacters = a.getText(R.styleable.CustomKeyboard_CustomKey_popupCharacters);
+ popupResId = a.getResourceId(R.styleable.CustomKeyboard_CustomKey_popupKeyboard, 0);
+ repeatable = a.getBoolean(R.styleable.CustomKeyboard_CustomKey_isRepeatable, false);
+ modifier = a.getBoolean(R.styleable.CustomKeyboard_CustomKey_isModifier, false);
+ sticky = a.getBoolean(R.styleable.CustomKeyboard_CustomKey_isSticky, false);
+ edgeFlags = a.getInt(R.styleable.CustomKeyboard_CustomKey_keyEdgeFlags, 0);
+ edgeFlags |= parent.rowEdgeFlags;
+
+ icon = a.getDrawable(R.styleable.CustomKeyboard_CustomKey_keyIcon);
+ if (icon != null) {
+ icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ }
+ label = a.getText(R.styleable.CustomKeyboard_CustomKey_keyLabel);
+// Log.d(LOG_TAG, "Key label is " + label);
text = a.getText(R.styleable.CustomKeyboard_CustomKey_keyOutputText);
- if (codes == null && !TextUtils.isEmpty(label)) {
- codes = new int[] { label.charAt(0) };
- }
- a.recycle();
- }
-
- /**
- * Informs the key that it has been pressed, in case it needs to change its appearance or
- * state.
- * @see #onReleased(boolean)
- */
- public void onPressed() {
- pressed = !pressed;
- }
-
- /**
- * Changes the pressed state of the key.
- *
- * <p>Toggled state of the key will be flipped when all the following conditions are
- * fulfilled:</p>
- *
- * <ul>
- * <li>This is a sticky key, that is, {@link #sticky} is {@code true}.
- * <li>The parameter {@code inside} is {@code true}.
- * <li>{@link android.os.Build.VERSION#SDK_INT} is greater than
- * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
- * </ul>
- *
- * @param inside whether the finger was released inside the key. Works only on Android M and
- * later. See the method document for details.
- * @see #onPressed()
- */
- public void onReleased(boolean inside) {
- pressed = !pressed;
- if (sticky && inside) {
- on = !on;
- }
- }
-
- int[] parseCSV(String value) {
- int count = 0;
- int lastIndex = 0;
- if (value.length() > 0) {
- count++;
- while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
- count++;
- }
- }
- int[] values = new int[count];
- count = 0;
- StringTokenizer st = new StringTokenizer(value, ",");
- while (st.hasMoreTokens()) {
- try {
- values[count++] = Integer.parseInt(st.nextToken());
- } catch (NumberFormatException nfe) {
- Log.e(LOG_TAG, "Error parsing keycodes " + value);
- }
- }
- return values;
- }
-
- /**
- * Detects if a point falls inside this key.
- * @param x the x-coordinate of the point
- * @param y the y-coordinate of the point
- * @return whether or not the point falls inside the key. If the key is attached to an edge,
- * it will assume that all points between the key and the edge are considered to be inside
- * the key.
- */
- public boolean isInside(int x, int y) {
- boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0;
- boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0;
- boolean topEdge = (edgeFlags & EDGE_TOP) > 0;
- boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0;
- if ((x >= this.x || (leftEdge && x <= this.x + this.width))
- && (x < this.x + this.width || (rightEdge && x >= this.x))
- && (y >= this.y || (topEdge && y <= this.y + this.height))
- && (y < this.y + this.height || (bottomEdge && y >= this.y))) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Returns the square of the distance between the center of the key and the given point.
- * @param x the x-coordinate of the point
- * @param y the y-coordinate of the point
- * @return the square of the distance of the point from the center of the key
- */
- public int squaredDistanceFrom(int x, int y) {
- int xDist = this.x + width / 2 - x;
- int yDist = this.y + height / 2 - y;
- return xDist * xDist + yDist * yDist;
- }
-
- /**
- * Returns the drawable state for the key, based on the current state and type of the key.
- * @return the drawable state of the key.
- * @see android.graphics.drawable.StateListDrawable#setState(int[])
- */
- public int[] getCurrentDrawableState() {
- int[] states = KEY_STATE_NORMAL;
-
- if (on) {
- if (pressed) {
- states = KEY_STATE_PRESSED_ON;
- } else {
- states = KEY_STATE_NORMAL_ON;
- }
- } else {
- if (sticky) {
- if (pressed) {
- states = KEY_STATE_PRESSED_OFF;
- } else {
- states = KEY_STATE_NORMAL_OFF;
- }
- } else {
- if (pressed) {
- states = KEY_STATE_PRESSED;
- }
- }
- }
- return states;
- }
- }
-
- /**
- * Creates a keyboard from the given xml key layout file.
- * @param context the application or service context
- * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
- */
- public CustomKeyboard(Context context, int xmlLayoutResId) {
- this(context, xmlLayoutResId, 0);
- }
-
- /**
- * Creates a keyboard from the given xml key layout file. Weeds out rows
- * that have a keyboard mode defined but don't match the specified mode.
- * @param context the application or service context
- * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
- * @param modeId keyboard mode identifier
- * @param width sets width of keyboard
- * @param height sets height of keyboard
- */
- public CustomKeyboard(Context context, @XmlRes int xmlLayoutResId, int modeId, int width,
- int height) {
- mDisplayWidth = width;
- mDisplayHeight = height;
-
- mDefaultHorizontalGap = 0;
- mDefaultWidth = mDisplayWidth / 10;
- mDefaultVerticalGap = 0;
- mDefaultHeight = mDefaultWidth;
- mKeys = new ArrayList<CustomKey>();
- mModifierKeys = new ArrayList<CustomKey>();
- mKeyboardMode = modeId;
- loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
- }
-
- /**
- * Creates a keyboard from the given xml key layout file. Weeds out rows
- * that have a keyboard mode defined but don't match the specified mode.
- * @param context the application or service context
- * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
- * @param modeId keyboard mode identifier
- */
- public CustomKeyboard(Context context, @XmlRes int xmlLayoutResId, int modeId) {
- DisplayMetrics dm = context.getResources().getDisplayMetrics();
- mDisplayWidth = dm.widthPixels;
- mDisplayHeight = dm.heightPixels;
- Log.v(LOG_TAG, "keyboard's display metrics:" + dm);
-
- mDefaultHorizontalGap = 0;
- mDefaultWidth = mDisplayWidth / 10;
- mDefaultVerticalGap = 0;
- mDefaultHeight = mDefaultWidth;
- mKeys = new ArrayList<CustomKey>();
- mModifierKeys = new ArrayList<CustomKey>();
- mKeyboardMode = modeId;
+ if (codes == null && !TextUtils.isEmpty(label)) {
+ codes = new int[] { label.charAt(0) };
+ }
+ a.recycle();
+ }
+
+ /**
+ * Informs the key that it has been pressed, in case it needs to change its appearance or
+ * state.
+ * @see #onReleased(boolean)
+ */
+ public void onPressed() {
+ pressed = !pressed;
+ }
+
+ /**
+ * Changes the pressed state of the key.
+ *
+ * <p>Toggled state of the key will be flipped when all the following conditions are
+ * fulfilled:</p>
+ *
+ * <ul>
+ * <li>This is a sticky key, that is, {@link #sticky} is {@code true}.
+ * <li>The parameter {@code inside} is {@code true}.
+ * <li>{@link android.os.Build.VERSION#SDK_INT} is greater than
+ * {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}.
+ * </ul>
+ *
+ * @param inside whether the finger was released inside the key. Works only on Android M and
+ * later. See the method document for details.
+ * @see #onPressed()
+ */
+ public void onReleased(boolean inside) {
+ pressed = !pressed;
+ if (sticky && inside) {
+ on = !on;
+ }
+ }
+
+ int[] parseCSV(String value) {
+ int count = 0;
+ int lastIndex = 0;
+ if (value.length() > 0) {
+ count++;
+ while ((lastIndex = value.indexOf(",", lastIndex + 1)) > 0) {
+ count++;
+ }
+ }
+ int[] values = new int[count];
+ count = 0;
+ StringTokenizer st = new StringTokenizer(value, ",");
+ while (st.hasMoreTokens()) {
+ try {
+ values[count++] = Integer.parseInt(st.nextToken());
+ } catch (NumberFormatException nfe) {
+ Log.e(LOG_TAG, "Error parsing keycodes " + value);
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Detects if a point falls inside this key.
+ * @param x the x-coordinate of the point
+ * @param y the y-coordinate of the point
+ * @return whether or not the point falls inside the key. If the key is attached to an edge,
+ * it will assume that all points between the key and the edge are considered to be inside
+ * the key.
+ */
+ public boolean isInside(int x, int y) {
+ boolean leftEdge = (edgeFlags & EDGE_LEFT) > 0;
+ boolean rightEdge = (edgeFlags & EDGE_RIGHT) > 0;
+ boolean topEdge = (edgeFlags & EDGE_TOP) > 0;
+ boolean bottomEdge = (edgeFlags & EDGE_BOTTOM) > 0;
+ if ((x >= this.x || (leftEdge && x <= this.x + this.width))
+ && (x < this.x + this.width || (rightEdge && x >= this.x))
+ && (y >= this.y || (topEdge && y <= this.y + this.height))
+ && (y < this.y + this.height || (bottomEdge && y >= this.y))) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the square of the distance between the center of the key and the given point.
+ * @param x the x-coordinate of the point
+ * @param y the y-coordinate of the point
+ * @return the square of the distance of the point from the center of the key
+ */
+ public int squaredDistanceFrom(int x, int y) {
+ int xDist = this.x + width / 2 - x;
+ int yDist = this.y + height / 2 - y;
+ return xDist * xDist + yDist * yDist;
+ }
+
+ /**
+ * Returns the drawable state for the key, based on the current state and type of the key.
+ * @return the drawable state of the key.
+ * @see android.graphics.drawable.StateListDrawable#setState(int[])
+ */
+ public int[] getCurrentDrawableState() {
+ int[] states = KEY_STATE_NORMAL;
+
+ if (on) {
+ if (pressed) {
+ states = KEY_STATE_PRESSED_ON;
+ } else {
+ states = KEY_STATE_NORMAL_ON;
+ }
+ } else {
+ if (sticky) {
+ if (pressed) {
+ states = KEY_STATE_PRESSED_OFF;
+ } else {
+ states = KEY_STATE_NORMAL_OFF;
+ }
+ } else {
+ if (pressed) {
+ states = KEY_STATE_PRESSED;
+ }
+ }
+ }
+ return states;
+ }
+ } // end of: static class CustomKey
+
+ /**
+ * Creates a keyboard from the given xml key layout file.
+ * @param context the application or service context
+ * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+ */
+ public CustomKeyboard(Context context, int xmlLayoutResId) {
+ this(context, xmlLayoutResId, 0);
+ }
+
+ /**
+ * Creates a keyboard from the given xml key layout file. Weeds out rows
+ * that have a keyboard mode defined but don't match the specified mode.
+ * @param context the application or service context
+ * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+ * @param modeId keyboard mode identifier
+ * @param width sets width of keyboard
+ * @param height sets height of keyboard
+ */
+ public CustomKeyboard(Context context, @XmlRes int xmlLayoutResId, int modeId, int width, int height) {
+ mDisplayWidth = width;
+ mDisplayHeight = height;
+
+ mDefaultHorizontalGap = 0;
+ mDefaultWidth = mDisplayWidth / 10;
+ mDefaultVerticalGap = 0;
+ mDefaultHeight = mDefaultWidth;
+ mKeys = new ArrayList<CustomKey>();
+ mModifierKeys = new ArrayList<CustomKey>();
+ mKeyboardMode = modeId;
+ loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
+ }
+
+ /**
+ * Creates a keyboard from the given xml key layout file. Weeds out rows
+ * that have a keyboard mode defined but don't match the specified mode.
+ * @param context the application or service context
+ * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
+ * @param modeId keyboard mode identifier
+ */
+ public CustomKeyboard(Context context, @XmlRes int xmlLayoutResId, int modeId) {
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ mDisplayWidth = dm.widthPixels;
+ mDisplayHeight = dm.heightPixels;
+// Log.v(LOG_TAG, "keyboard's display metrics:" + dm);
+
+ mDefaultHorizontalGap = 0;
+ mDefaultWidth = mDisplayWidth / 10;
+ mDefaultVerticalGap = 0;
+ mDefaultHeight = mDefaultWidth;
+ mKeys = new ArrayList<CustomKey>();
+ mModifierKeys = new ArrayList<CustomKey>();
+ mKeyboardMode = modeId;
// Log.v(LOG_TAG, "Resource ID is " + xmlLayoutResId + " and parser is null?" + ((context.getResources().getXml(xmlLayoutResId) == null) ? "yes" : "no"));
- loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
- }
-
- /**
- * <p>Creates a blank keyboard from the given resource file and populates it with the specified
- * characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
- * </p>
- * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as
- * possible in each row.</p>
- * @param context the application or service context
- * @param layoutTemplateResId the layout template file, containing no keys.
- * @param characters the list of characters to display on the keyboard. One key will be created
- * for each character.
- * @param columns the number of columns of keys to display. If this number is greater than the
- * number of keys that can fit in a row, it will be ignored. If this number is -1, the
- * keyboard will fit as many keys as possible in each row.
- */
- public CustomKeyboard(Context context, int layoutTemplateResId,
- CharSequence characters, int columns, int horizontalPadding) {
- this(context, layoutTemplateResId);
- int x = 0;
- int y = 0;
- int column = 0;
- mTotalWidth = 0;
-
- CustomRow row = new CustomRow(this);
- row.defaultHeight = mDefaultHeight;
- row.defaultWidth = mDefaultWidth;
- row.defaultHorizontalGap = mDefaultHorizontalGap;
- row.verticalGap = mDefaultVerticalGap;
- row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM;
- final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
- for (int i = 0; i < characters.length(); i++) {
- char c = characters.charAt(i);
- if (column >= maxColumns
- || x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
- x = 0;
- y += mDefaultVerticalGap + mDefaultHeight;
- column = 0;
- }
- final CustomKey key = new CustomKey(row);
- key.x = x;
- key.y = y;
- key.label = String.valueOf(c);
- key.codes = new int[] { c };
- column++;
- x += key.width + key.gap;
- mKeys.add(key);
- row.mKeys.add(key);
- if (x > mTotalWidth) {
- mTotalWidth = x;
- }
- }
- mTotalHeight = y + mDefaultHeight;
- rows.add(row);
- }
-
-// @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- final void resize(int newWidth, int newHeight) {
- int numRows = rows.size();
- for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) {
- CustomRow row = rows.get(rowIndex);
- int numKeys = row.mKeys.size();
- int totalGap = 0;
- int totalWidth = 0;
- for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
+ loadKeyboard(context, context.getResources().getXml(xmlLayoutResId));
+ }
+
+ /**
+ * <p>Creates a blank keyboard from the given resource file and populates it with the specified
+ * characters in left-to-right, top-to-bottom fashion, using the specified number of columns.
+ * </p>
+ * <p>If the specified number of columns is -1, then the keyboard will fit as many keys as
+ * possible in each row.</p>
+ * @param context the application or service context
+ * @param layoutTemplateResId the layout template file, containing no keys.
+ * @param characters the list of characters to display on the keyboard. One key will be created
+ * for each character.
+ * @param columns the number of columns of keys to display. If this number is greater than the
+ * number of keys that can fit in a row, it will be ignored. If this number is -1, the
+ * keyboard will fit as many keys as possible in each row.
+ */
+ public CustomKeyboard(Context context, int layoutTemplateResId, CharSequence characters, int columns, int horizontalPadding) {
+ this(context, layoutTemplateResId);
+ int x = 0;
+ int y = 0;
+ int column = 0;
+ mTotalWidth = 0;
+
+ CustomRow row = new CustomRow(this);
+ row.defaultHeight = mDefaultHeight;
+ row.defaultWidth = mDefaultWidth;
+ row.defaultHorizontalGap = mDefaultHorizontalGap;
+ row.verticalGap = mDefaultVerticalGap;
+ row.rowEdgeFlags = EDGE_TOP | EDGE_BOTTOM;
+ final int maxColumns = columns == -1 ? Integer.MAX_VALUE : columns;
+ for (int i = 0; i < characters.length(); i++) {
+ char c = characters.charAt(i);
+ if (column >= maxColumns || x + mDefaultWidth + horizontalPadding > mDisplayWidth) {
+ x = 0;
+ y += mDefaultVerticalGap + mDefaultHeight;
+ column = 0;
+ }
+ final CustomKey key = new CustomKey(row);
+ key.x = x;
+ key.y = y;
+ key.label = String.valueOf(c);
+ key.codes = new int[] { c };
+ column++;
+ x += key.width + key.gap;
+ mKeys.add(key);
+ row.mKeys.add(key);
+ if (x > mTotalWidth) {
+ mTotalWidth = x;
+ }
+ }
+ mTotalHeight = y + mDefaultHeight;
+ rows.add(row);
+ }
+
+// @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ final void resize(int newWidth, int newHeight) {
+ int numRows = rows.size();
+ for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) {
+ CustomRow row = rows.get(rowIndex);
+ int numKeys = row.mKeys.size();
+ int totalGap = 0;
+ int totalWidth = 0;
+ for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
CustomKey key = row.mKeys.get(keyIndex);
- if (keyIndex > 0) {
- totalGap += key.gap;
- }
- totalWidth += key.width;
- }
- if (totalGap + totalWidth > newWidth) {
- int x = 0;
- float scaleFactor = (float)(newWidth - totalGap) / totalWidth;
- for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
+ if (keyIndex > 0) {
+ totalGap += key.gap;
+ }
+ totalWidth += key.width;
+ }
+ if (totalGap + totalWidth > newWidth) {
+ int x = 0;
+ float scaleFactor = (float)(newWidth - totalGap) / totalWidth;
+ for (int keyIndex = 0; keyIndex < numKeys; ++keyIndex) {
CustomKey key = row.mKeys.get(keyIndex);
- key.width *= scaleFactor;
- key.x = x;
- x += key.width + key.gap;
- }
- }
- }
- mTotalWidth = newWidth;
- // TODO: This does not adjust the vertical placement according to the new size.
- // The main problem in the previous code was horizontal placement/size, but we should
- // also recalculate the vertical sizes/positions when we get this resize call.
- }
-
- public List<CustomKey> getKeys() {
- return mKeys;
- }
-
- public List<CustomKey> getModifierKeys() {
- return mModifierKeys;
- }
-
- protected int getHorizontalGap() {
- return mDefaultHorizontalGap;
- }
-
- protected void setHorizontalGap(int gap) {
- mDefaultHorizontalGap = gap;
- }
-
- protected int getVerticalGap() {
- return mDefaultVerticalGap;
- }
-
- protected void setVerticalGap(int gap) {
- mDefaultVerticalGap = gap;
- }
-
- protected int getKeyHeight() {
- return mDefaultHeight;
- }
-
- protected void setKeyHeight(int height) {
- mDefaultHeight = height;
- }
-
- protected int getKeyWidth() {
- return mDefaultWidth;
- }
-
- protected void setKeyWidth(int width) {
- mDefaultWidth = width;
- }
-
- /**
- * Returns the total height of the keyboard
- * @return the total height of the keyboard
- */
- public int getHeight() {
- return mTotalHeight;
- }
-
- public int getMinWidth() {
- return mTotalWidth;
- }
-
- public boolean setShifted(boolean shiftState) {
- for (CustomKey shiftKey : mShiftKeys) {
- if (shiftKey != null) {
- shiftKey.on = shiftState;
- }
- }
- if (mShifted != shiftState) {
- mShifted = shiftState;
- return true;
- }
- return false;
- }
-
- public boolean isShifted() {
- return mShifted;
- }
-
- /**
- */
- public int[] getShiftKeyIndices() {
- return mShiftKeyIndices;
- }
-
- public int getShiftKeyIndex() {
- return mShiftKeyIndices[0];
- }
-
- private void computeNearestNeighbors() {
- // Round-up so we don't have any pixels outside the grid
- mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
- mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
- mGridNeighbors = new int[GRID_SIZE][];
- int[] indices = new int[mKeys.size()];
- final int gridWidth = GRID_WIDTH * mCellWidth;
- final int gridHeight = GRID_HEIGHT * mCellHeight;
- for (int x = 0; x < gridWidth; x += mCellWidth) {
- for (int y = 0; y < gridHeight; y += mCellHeight) {
- int count = 0;
- for (int i = 0; i < mKeys.size(); i++) {
- final CustomKey key = mKeys.get(i);
- if (key.squaredDistanceFrom(x, y) < mProximityThreshold ||
- key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold ||
- key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1)
- < mProximityThreshold ||
- key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) {
- indices[count++] = i;
- }
- }
- int [] cell = new int[count];
- System.arraycopy(indices, 0, cell, 0, count);
- mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
- }
- }
- }
-
- /**
- * Returns the indices of the keys that are closest to the given point.
- * @param x the x-coordinate of the point
- * @param y the y-coordinate of the point
- * @return the array of integer indices for the nearest keys to the given point. If the given
- * point is out of range, then an array of size zero is returned.
- */
- public int[] getNearestKeys(int x, int y) {
- if (mGridNeighbors == null) computeNearestNeighbors();
- if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
- int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
- if (index < GRID_SIZE) {
- return mGridNeighbors[index];
- }
- }
- return new int[0];
- }
-
- protected CustomRow createRowFromXml(Resources res, XmlResourceParser parser) {
- return new CustomRow(res, this, parser);
- }
-
- protected CustomKey createKeyFromXml(Resources res, CustomRow parent, int x, int y,
- XmlResourceParser parser) {
- return new CustomKey(res, parent, x, y, parser);
- }
-
- private void loadKeyboard(Context context, XmlResourceParser parser) {
- boolean inKey = false;
- boolean inRow = false;
- boolean leftMostKey = false;
- int row = 0;
- int x = 0;
- int y = 0;
+ key.width *= scaleFactor;
+ key.x = x;
+ x += key.width + key.gap;
+ }
+ }
+ }
+ mTotalWidth = newWidth;
+ // TODO: This does not adjust the vertical placement according to the new size.
+ // The main problem in the previous code was horizontal placement/size, but we should
+ // also recalculate the vertical sizes/positions when we get this resize call.
+ }
+
+ public List<CustomKey> getKeys() {
+ return mKeys;
+ }
+
+ public List<CustomKey> getModifierKeys() {
+ return mModifierKeys;
+ }
+
+ protected int getHorizontalGap() {
+ return mDefaultHorizontalGap;
+ }
+
+ protected void setHorizontalGap(int gap) {
+ mDefaultHorizontalGap = gap;
+ }
+
+ protected int getVerticalGap() {
+ return mDefaultVerticalGap;
+ }
+
+ protected void setVerticalGap(int gap) {
+ mDefaultVerticalGap = gap;
+ }
+
+ protected int getKeyHeight() {
+ return mDefaultHeight;
+ }
+
+ protected void setKeyHeight(int height) {
+ mDefaultHeight = height;
+ }
+
+ protected int getKeyWidth() {
+ return mDefaultWidth;
+ }
+
+ protected void setKeyWidth(int width) {
+ mDefaultWidth = width;
+ }
+
+ /**
+ * Returns the total height of the keyboard
+ * @return the total height of the keyboard
+ */
+ public int getHeight() {
+ return mTotalHeight;
+ }
+
+ public int getMinWidth() {
+ return mTotalWidth;
+ }
+
+ public boolean setShifted(boolean shiftState) {
+ for (CustomKey shiftKey : mShiftKeys) {
+ if (shiftKey != null) {
+ shiftKey.on = shiftState;
+ }
+ }
+ if (mShifted != shiftState) {
+ mShifted = shiftState;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isShifted() {
+ return mShifted;
+ }
+
+ /**
+ */
+ public int[] getShiftKeyIndices() {
+ return mShiftKeyIndices;
+ }
+
+ public int getShiftKeyIndex() {
+ return mShiftKeyIndices[0];
+ }
+
+ private void computeNearestNeighbors() {
+ // Round-up so we don't have any pixels outside the grid
+ mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
+ mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
+ mGridNeighbors = new int[GRID_SIZE][];
+ int[] indices = new int[mKeys.size()];
+ final int gridWidth = GRID_WIDTH * mCellWidth;
+ final int gridHeight = GRID_HEIGHT * mCellHeight;
+ for (int x = 0; x < gridWidth; x += mCellWidth) {
+ for (int y = 0; y < gridHeight; y += mCellHeight) {
+ int count = 0;
+ for (int i = 0; i < mKeys.size(); i++) {
+ final CustomKey key = mKeys.get(i);
+ if (key.squaredDistanceFrom(x, y) < mProximityThreshold
+ || key.squaredDistanceFrom(x + mCellWidth - 1, y) < mProximityThreshold
+ || key.squaredDistanceFrom(x + mCellWidth - 1, y + mCellHeight - 1) < mProximityThreshold
+ || key.squaredDistanceFrom(x, y + mCellHeight - 1) < mProximityThreshold) {
+ indices[count++] = i;
+ }
+ }
+ int [] cell = new int[count];
+ System.arraycopy(indices, 0, cell, 0, count);
+ mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
+ }
+ }
+ }
+
+ /**
+ * Returns the indices of the keys that are closest to the given point.
+ * @param x the x-coordinate of the point
+ * @param y the y-coordinate of the point
+ * @return the array of integer indices for the nearest keys to the given point. If the given
+ * point is out of range, then an array of size zero is returned.
+ */
+ public int[] getNearestKeys(int x, int y) {
+ if (mGridNeighbors == null) computeNearestNeighbors();
+ if (x >= 0 && x < getMinWidth() && y >= 0 && y < getHeight()) {
+ int index = (y / mCellHeight) * GRID_WIDTH + (x / mCellWidth);
+ if (index < GRID_SIZE) {
+ return mGridNeighbors[index];
+ }
+ }
+ return new int[0];
+ }
+
+ protected CustomRow createRowFromXml(Resources res, XmlResourceParser parser) {
+ return new CustomRow(res, this, parser);
+ }
+
+ protected CustomKey createKeyFromXml(Resources res, CustomRow parent, int x, int y, XmlResourceParser parser) {
+ return new CustomKey(res, parent, x, y, parser);
+ }
+
+ private void loadKeyboard(Context context, XmlResourceParser parser) {
+ boolean inKey = false;
+ boolean inRow = false;
+ boolean leftMostKey = false;
+ int row = 0;
+ int x = 0;
+ int y = 0;
CustomKey key = null;
- CustomRow currentRow = null;
- Resources res = context.getResources();
- boolean skipRow = false;
-
- try {
- int event;
- while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
- if (event == XmlResourceParser.START_TAG) {
- String tag = parser.getName();
- if (TAG_ROW.equals(tag)) {
- //Log.d(LOG_TAG, "TAG ROW");
- inRow = true;
- x = 0;
- currentRow = createRowFromXml(res, parser);
- rows.add(currentRow);
- skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
- if (skipRow) {
- skipToEndOfRow(parser);
- inRow = false;
- }
- } else if (TAG_KEY.equals(tag)) {
- //Log.d(LOG_TAG, "TAG KEY");
- inKey = true;
- key = createKeyFromXml(res, currentRow, x, y, parser);
- mKeys.add(key);
+ CustomRow currentRow = null;
+ Resources res = context.getResources();
+ boolean skipRow = false;
+
+ try {
+ int event;
+ while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
+ if (event == XmlResourceParser.START_TAG) {
+ String tag = parser.getName();
+ if (TAG_ROW.equals(tag)) {
+// Log.d(LOG_TAG, "TAG ROW");
+ inRow = true;
+ x = 0;
+ currentRow = createRowFromXml(res, parser);
+ rows.add(currentRow);
+ skipRow = currentRow.mode != 0 && currentRow.mode != mKeyboardMode;
+ if (skipRow) {
+ skipToEndOfRow(parser);
+ inRow = false;
+ }
+ } else if (TAG_KEY.equals(tag)) {
+// Log.d(LOG_TAG, "TAG KEY");
+ inKey = true;
+ key = createKeyFromXml(res, currentRow, x, y, parser);
+ mKeys.add(key);
if (key.codes != null) {
if (key.codes[0] == KEYCODE_SHIFT) {
// Find available shift key slot and put this shift key in it
@@ -829,74 +796,65 @@ public class CustomKeyboard {
}
currentRow.mKeys.add(key);
}
- } else if (TAG_KEYBOARD.equals(tag)) {
- parseKeyboardAttributes(res, parser);
- }
- } else if (event == XmlResourceParser.END_TAG) {
- if (inKey) {
- inKey = false;
- x += key.gap + key.width;
- if (x > mTotalWidth) {
- mTotalWidth = x;
- }
- } else if (inRow) {
- inRow = false;
- y += currentRow.verticalGap;
- y += currentRow.defaultHeight;
- row++;
- } else {
- // TODO: error or extend?
- }
- }
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "Parse error:" + e);
- e.printStackTrace();
- }
- mTotalHeight = y - mDefaultVerticalGap;
- }
-
- private void skipToEndOfRow(XmlResourceParser parser)
- throws XmlPullParserException, IOException {
- int event;
- while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
- if (event == XmlResourceParser.END_TAG
- && parser.getName().equals(TAG_ROW)) {
- break;
- }
- }
- }
-
- private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
- TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
- R.styleable.CustomKeyboard);
-
- mDefaultWidth = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_keyWidth,
- mDisplayWidth, mDisplayWidth / 10);
- mDefaultHeight = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_keyHeight,
- mDisplayHeight, 50);
- mDefaultHorizontalGap = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_horizontalGap,
- mDisplayWidth, 0);
- mDefaultVerticalGap = getDimensionOrFraction(a,
- R.styleable.CustomKeyboard_verticalGap,
- mDisplayHeight, 0);
- mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
- mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison
- a.recycle();
- }
-
- static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) {
- TypedValue value = a.peekValue(index);
- if (value == null) return defValue;
- if (value.type == TypedValue.TYPE_DIMENSION) {
- return a.getDimensionPixelOffset(index, defValue);
- } else if (value.type == TypedValue.TYPE_FRACTION) {
- // Round it to avoid values like 47.9999 from getting truncated
- return Math.round(a.getFraction(index, base, base, defValue));
- }
- return defValue;
- }
+ } else if (TAG_KEYBOARD.equals(tag)) {
+ parseKeyboardAttributes(res, parser);
+ }
+ } else if (event == XmlResourceParser.END_TAG) {
+ if (inKey) {
+ inKey = false;
+ x += key.gap + key.width;
+ if (x > mTotalWidth) {
+ mTotalWidth = x;
+ }
+ } else if (inRow) {
+ inRow = false;
+ y += currentRow.verticalGap;
+ y += currentRow.defaultHeight;
+ row++;
+ } else {
+ // TODO: error or extend?
+ }
+ }
+ }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Parse error:" + e);
+ e.printStackTrace();
+ }
+ mTotalHeight = y - mDefaultVerticalGap;
+ }
+
+ private void skipToEndOfRow(XmlResourceParser parser) throws XmlPullParserException, IOException {
+ int event;
+ while ((event = parser.next()) != XmlResourceParser.END_DOCUMENT) {
+ if (event == XmlResourceParser.END_TAG && parser.getName().equals(TAG_ROW)) {
+ break;
+ }
+ }
+ }
+
+ private void parseKeyboardAttributes(Resources res, XmlResourceParser parser) {
+ TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.CustomKeyboard);
+
+ mDefaultWidth = getDimensionOrFraction(a, R.styleable.CustomKeyboard_keyWidth, mDisplayWidth, mDisplayWidth / 10);
+ mDefaultHeight = getDimensionOrFraction(a, R.styleable.CustomKeyboard_keyHeight, mDisplayHeight, 50);
+ mDefaultHorizontalGap = getDimensionOrFraction(a, R.styleable.CustomKeyboard_horizontalGap, mDisplayWidth, 0);
+ mDefaultVerticalGap = getDimensionOrFraction(a, R.styleable.CustomKeyboard_verticalGap, mDisplayHeight, 0);
+ mProximityThreshold = (int) (mDefaultWidth * SEARCH_DISTANCE);
+ mProximityThreshold = mProximityThreshold * mProximityThreshold; // Square it for comparison
+ a.recycle();
+ }
+
+ static int getDimensionOrFraction(TypedArray a, int index, int base, int defValue) {
+ TypedValue value = a.peekValue(index);
+ if (value == null)
+ return defValue;
+
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ return a.getDimensionPixelOffset(index, defValue);
+ } else if (value.type == TypedValue.TYPE_FRACTION) {
+ // Round it to avoid values like 47.9999 from getting truncated
+ return Math.round(a.getFraction(index, base, base, defValue));
+ }
+ return defValue;
+ }
}
diff --git a/backends/platform/android/org/scummvm/scummvm/CustomKeyboardView.java b/backends/platform/android/org/scummvm/scummvm/CustomKeyboardView.java
index e7253f296bc..a45af0821b0 100755
--- a/backends/platform/android/org/scummvm/scummvm/CustomKeyboardView.java
+++ b/backends/platform/android/org/scummvm/scummvm/CustomKeyboardView.java
@@ -67,1644 +67,1656 @@ import java.util.Map;
public class CustomKeyboardView extends View implements View.OnClickListener {
- /**
- * Listener for virtual keyboard events.
- */
- public interface OnKeyboardActionListener {
-
- /**
- * Called when the user presses a key. This is sent before the {@link #onKey} is called.
- * For keys that repeat, this is only called once.
- * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
- * key, the value will be zero.
- */
- void onPress(int primaryCode);
-
- /**
- * Called when the user releases a key. This is sent after the {@link #onKey} is called.
- * For keys that repeat, this is only called once.
- * @param primaryCode the code of the key that was released
- */
- void onRelease(int primaryCode);
-
- /**
- * Send a key press to the listener.
- * @param primaryCode this is the key that was pressed
- * @param keyCodes the codes for all the possible alternative keys
- * with the primary code being the first. If the primary key code is
- * a single character such as an alphabet or number or symbol, the alternatives
- * will include other characters that may be on the same key or adjacent keys.
- * These codes are useful to correct for accidental presses of a key adjacent to
- * the intended key.
- */
- void onKey(int primaryCode, int[] keyCodes);
-
- /**
- * Sends a sequence of characters to the listener.
- * @param text the sequence of characters to be displayed.
- */
- void onText(CharSequence text);
-
- /**
- * Called when the user quickly moves the finger from right to left.
- */
- void swipeLeft();
-
- /**
- * Called when the user quickly moves the finger from left to right.
- */
- void swipeRight();
-
- /**
- * Called when the user quickly moves the finger from up to down.
- */
- void swipeDown();
-
- /**
- * Called when the user quickly moves the finger from down to up.
- */
- void swipeUp();
- }
-
- private Context mContext;
-
- private static final boolean DEBUG = false;
- private static final int NOT_A_KEY = -1;
- private static final int[] KEY_DELETE = { CustomKeyboard.KEYCODE_DELETE };
- private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
-
- private CustomKeyboard mKeyboard;
- private int mCurrentKeyIndex = NOT_A_KEY;
-
-// @UnsupportedAppUsage
- private int mLabelTextSize;
-
- private int mKeyTextSize;
- private int mKeyTextColor;
- private float mShadowRadius;
- private int mShadowColor;
- private float mBackgroundDimAmount;
-
-// @UnsupportedAppUsage
- private TextView mPreviewText;
-
- private PopupWindow mPreviewPopup;
- private int mPreviewTextSizeLarge;
- private int mPreviewOffset;
- private int mPreviewHeight;
- // Working variable
- private final int[] mCoordinates = new int[2];
-
- private PopupWindow mPopupKeyboard;
- private View mMiniKeyboardContainer;
- private CustomKeyboardView mMiniKeyboard;
- private boolean mMiniKeyboardOnScreen;
- private View mPopupParent;
- private int mMiniKeyboardOffsetX;
- private int mMiniKeyboardOffsetY;
- private Map<CustomKeyboard.CustomKey,View> mMiniKeyboardCache;
- private CustomKeyboard.CustomKey[] mKeys;
-
- /** Listener for {@link OnKeyboardActionListener}. */
- private OnKeyboardActionListener mKeyboardActionListener;
-
- private static final int MSG_SHOW_PREVIEW = 1;
- private static final int MSG_REMOVE_PREVIEW = 2;
- private static final int MSG_REPEAT = 3;
- private static final int MSG_LONGPRESS = 4;
-
- private static final int DELAY_BEFORE_PREVIEW = 0;
- private static final int DELAY_AFTER_PREVIEW = 70;
- private static final int DEBOUNCE_TIME = 70;
-
- private int mVerticalCorrection;
- private int mProximityThreshold;
-
- private boolean mPreviewCentered = false;
- private boolean mShowPreview = true;
- private boolean mShowTouchPoints = true;
- private int mPopupPreviewX;
- private int mPopupPreviewY;
-
- private int mLastX;
- private int mLastY;
- private int mStartX;
- private int mStartY;
-
- private boolean mProximityCorrectOn;
-
- private Paint mPaint;
- private Rect mPadding;
-
- private long mDownTime;
- private long mLastMoveTime;
- private int mLastKey;
- private int mLastCodeX;
- private int mLastCodeY;
- private int mCurrentKey = NOT_A_KEY;
- private int mDownKey = NOT_A_KEY;
-
- // New auxiliary set to keep track of any keys that were not released at the time of closing the keyboard
- private ArraySet<Integer> mKeysDownCodesSet;
-
- private long mLastKeyTime;
- private long mCurrentKeyTime;
- private int[] mKeyIndices = new int[12];
- private GestureDetector mGestureDetector;
- private int mPopupX;
- private int mPopupY;
- private int mRepeatKeyIndex = NOT_A_KEY;
- private int mPopupLayout;
- private boolean mAbortKey;
- private CustomKeyboard.CustomKey mInvalidatedKey;
- private Rect mClipRegion = new Rect(0, 0, 0, 0);
- private boolean mPossiblePoly;
- private SwipeTracker mSwipeTracker = new SwipeTracker();
- private int mSwipeThreshold;
- private boolean mDisambiguateSwipe;
-
- // Variables for dealing with multiple pointers
- private int mOldPointerCount = 1;
- private float mOldPointerX;
- private float mOldPointerY;
-
- // @UnsupportedAppUsage
- // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/UnsupportedAppUsage.java
- // Indicates that a class member, that is not part of the SDK, is used by apps.
- // Since the member is not part of the SDK, such use is not supported.
- //
- // This annotation acts as a heads up that changing a given method or field
- // may affect apps, potentially breaking them when the next Android version is
- // released. In some cases, for members that are heavily used, this annotation
- // may imply restrictions on changes to the member.
- private Drawable mKeyBackground;
-
- private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
- private static final int REPEAT_START_DELAY = 400;
- private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
-
- private static int MAX_NEARBY_KEYS = 12;
- private int[] mDistances = new int[MAX_NEARBY_KEYS];
-
- // For multi-tap
- private int mLastSentIndex;
- private int mTapCount;
- private long mLastTapTime;
- private boolean mInMultiTap;
- private static final int MULTITAP_INTERVAL = 800; // milliseconds
- private StringBuilder mPreviewLabel = new StringBuilder(1);
-
- /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
- private boolean mDrawPending;
- /** The dirty region in the keyboard bitmap */
- private Rect mDirtyRect = new Rect();
- /** The keyboard bitmap for faster updates */
- private Bitmap mBuffer;
- /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
- private boolean mKeyboardChanged;
- /** The canvas for the above mutable keyboard bitmap */
- private Canvas mCanvas;
- /** The accessibility manager for accessibility support */
- private AccessibilityManager mAccessibilityManager;
- /** The audio manager for accessibility support */
- private AudioManager mAudioManager;
- /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */
- private boolean mHeadsetRequiredToHearPasswordsAnnounced;
-
- // Custom handler code (to avoid mem leaks, see warning "This Handler Class Should Be Static Or Leaks Might Occurâ) based on:
- // https://stackoverflow.com/a/27826094
- public static class CustomKeyboardViewHandler extends Handler {
-
- private final WeakReference<CustomKeyboardView> mListenerReference;
-
- public CustomKeyboardViewHandler(CustomKeyboardView listener) {
- super(Looper.getMainLooper());
- mListenerReference = new WeakReference<>(listener);
- }
-
- @Override
- public synchronized void handleMessage(@NonNull Message msg) {
- CustomKeyboardView listener = mListenerReference.get();
- if(listener != null) {
- switch (msg.what) {
- case MSG_SHOW_PREVIEW:
- listener.showKey(msg.arg1);
- break;
- case MSG_REMOVE_PREVIEW:
- listener.mPreviewText.setVisibility(INVISIBLE);
- break;
- case MSG_REPEAT:
- if (listener.repeatKey()) {
- Message repeat = Message.obtain(this, MSG_REPEAT);
- sendMessageDelayed(repeat, REPEAT_INTERVAL);
- }
- break;
- case MSG_LONGPRESS:
- listener.openPopupIfRequired((MotionEvent) msg.obj);
- break;
- }
- }
- }
-
- public void clear() {
- this.removeCallbacksAndMessages(null);
- }
- }
-
-// Handler mHandler;
-// final private CustomKeyboardViewHandler mHandler = new CustomKeyboardViewHandler(this);
- private CustomKeyboardViewHandler mHandler = new CustomKeyboardViewHandler(this);
-
- public void clearEventHandler() {
- mHandler.clear();
- }
-
- public CustomKeyboardView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.keyboardViewStyle);
- }
-
- public CustomKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public CustomKeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-// super(context, attrs, defStyleAttr, defStyleRes); // this call requires API 21. Skip it for now
- super(context, attrs, defStyleAttr);
-
- mContext = context;
-
- TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.CustomKeyboardView, defStyleAttr, defStyleRes);
-
- LayoutInflater inflate =
- (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- int previewLayout = 0;
- int keyTextSize = 0;
-
- int n = a.getIndexCount();
-
- for (int i = 0; i < n; i++) {
- int attr = a.getIndex(i);
-
- // resolve: "resource IDs will be non-final in Android Gradle Plugin version 5.0, avoid using them in switch case statements"
- // We converted the switch statement to if/else as suggested here: http://tools.android.com/tips/non-constant-fields
- if (attr == R.styleable.CustomKeyboardView_keyBackground) {
- mKeyBackground = a.getDrawable(attr);
- } else if (attr == R.styleable.CustomKeyboardView_verticalCorrection) {
- mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
- } else if (attr == R.styleable.CustomKeyboardView_keyPreviewLayout) {
- previewLayout = a.getResourceId(attr, 0);
- } else if (attr == R.styleable.CustomKeyboardView_keyPreviewOffset) {
- mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
- } else if (attr == R.styleable.CustomKeyboardView_keyPreviewHeight) {
- mPreviewHeight = a.getDimensionPixelSize(attr, 80);
- } else if (attr == R.styleable.CustomKeyboardView_keyTextSize) {
- mKeyTextSize = a.getDimensionPixelSize(attr, 18);
- } else if (attr == R.styleable.CustomKeyboardView_keyTextColor) {
- mKeyTextColor = a.getColor(attr, 0xFF000000);
- } else if (attr == R.styleable.CustomKeyboardView_labelTextSize) {
- mLabelTextSize = a.getDimensionPixelSize(attr, 14);
- } else if (attr == R.styleable.CustomKeyboardView_popupLayout) {
- mPopupLayout = a.getResourceId(attr, 0);
- } else if (attr == R.styleable.CustomKeyboardView_shadowColor) {
- mShadowColor = a.getColor(attr, 0);
- } else if (attr == R.styleable.CustomKeyboardView_shadowRadius) {
- mShadowRadius = a.getFloat(attr, 0f);
- }
- }
-
-// // TODO put default values as constants somewhere
-// if (mLabelTextSize == 0) {
-// mLabelTextSize = 14;
-// }
+ /**
+ * Listener for virtual keyboard events.
+ */
+ public interface OnKeyboardActionListener {
+
+ /**
+ * Called when the user presses a key. This is sent before the {@link #onKey} is called.
+ * For keys that repeat, this is only called once.
+ * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid
+ * key, the value will be zero.
+ */
+ void onPress(int primaryCode);
+
+ /**
+ * Called when the user releases a key. This is sent after the {@link #onKey} is called.
+ * For keys that repeat, this is only called once.
+ * @param primaryCode the code of the key that was released
+ */
+ void onRelease(int primaryCode);
+
+ /**
+ * Send a key press to the listener.
+ * @param primaryCode this is the key that was pressed
+ * @param keyCodes the codes for all the possible alternative keys
+ * with the primary code being the first. If the primary key code is
+ * a single character such as an alphabet or number or symbol, the alternatives
+ * will include other characters that may be on the same key or adjacent keys.
+ * These codes are useful to correct for accidental presses of a key adjacent to
+ * the intended key.
+ */
+ void onKey(int primaryCode, int[] keyCodes);
+
+ /**
+ * Sends a sequence of characters to the listener.
+ * @param text the sequence of characters to be displayed.
+ */
+ void onText(CharSequence text);
+
+ /**
+ * Called when the user quickly moves the finger from right to left.
+ */
+ void swipeLeft();
+
+ /**
+ * Called when the user quickly moves the finger from left to right.
+ */
+ void swipeRight();
+
+ /**
+ * Called when the user quickly moves the finger from up to down.
+ */
+ void swipeDown();
+
+ /**
+ * Called when the user quickly moves the finger from down to up.
+ */
+ void swipeUp();
+ }
+
+ private Context mContext;
+
+ private static final boolean DEBUG = false;
+ private static final int NOT_A_KEY = -1;
+ private static final int[] KEY_DELETE = { CustomKeyboard.KEYCODE_DELETE };
+ private static final int[] LONG_PRESSABLE_STATE_SET = { R.attr.state_long_pressable };
+
+ private CustomKeyboard mKeyboard;
+ private int mCurrentKeyIndex = NOT_A_KEY;
+
+// @UnsupportedAppUsage
+ private int mLabelTextSize;
+
+ private int mKeyTextSize;
+ private int mKeyTextColor;
+ private float mShadowRadius;
+ private int mShadowColor;
+ private float mBackgroundDimAmount;
+
+// @UnsupportedAppUsage
+ private TextView mPreviewText;
+
+ private PopupWindow mPreviewPopup;
+ private int mPreviewTextSizeLarge;
+ private int mPreviewOffset;
+ private int mPreviewHeight;
+ // Working variable
+ private final int[] mCoordinates = new int[2];
+
+ private PopupWindow mPopupKeyboard;
+ private View mMiniKeyboardContainer;
+ private CustomKeyboardView mMiniKeyboard;
+ private boolean mMiniKeyboardOnScreen;
+ private View mPopupParent;
+ private int mMiniKeyboardOffsetX;
+ private int mMiniKeyboardOffsetY;
+ private Map<CustomKeyboard.CustomKey,View> mMiniKeyboardCache;
+ private CustomKeyboard.CustomKey[] mKeys;
+
+ /** Listener for {@link OnKeyboardActionListener}. */
+ private OnKeyboardActionListener mKeyboardActionListener;
+
+ private static final int MSG_SHOW_PREVIEW = 1;
+ private static final int MSG_REMOVE_PREVIEW = 2;
+ private static final int MSG_REPEAT = 3;
+ private static final int MSG_LONGPRESS = 4;
+
+ private static final int DELAY_BEFORE_PREVIEW = 0;
+ private static final int DELAY_AFTER_PREVIEW = 70;
+ private static final int DEBOUNCE_TIME = 70;
+
+ private int mVerticalCorrection;
+ private int mProximityThreshold;
+
+ private boolean mPreviewCentered = false;
+ private boolean mShowPreview = true;
+ private boolean mShowTouchPoints = true;
+ private int mPopupPreviewX;
+ private int mPopupPreviewY;
+
+ private int mLastX;
+ private int mLastY;
+ private int mStartX;
+ private int mStartY;
+
+ private boolean mProximityCorrectOn;
+
+ private Paint mPaint;
+ private Rect mPadding;
+
+ private long mDownTime;
+ private long mLastMoveTime;
+ private int mLastKey;
+ private int mLastCodeX;
+ private int mLastCodeY;
+ private int mCurrentKey = NOT_A_KEY;
+ private int mDownKey = NOT_A_KEY;
+
+ // New auxiliary set to keep track of any keys that were not released at the time of closing the keyboard
+ private ArraySet<Integer> mKeysDownCodesSet;
+
+ private long mLastKeyTime;
+ private long mCurrentKeyTime;
+ private int[] mKeyIndices = new int[12];
+ private GestureDetector mGestureDetector;
+ private int mPopupX;
+ private int mPopupY;
+ private int mRepeatKeyIndex = NOT_A_KEY;
+ private int mPopupLayout;
+ private boolean mAbortKey;
+ private CustomKeyboard.CustomKey mInvalidatedKey;
+ private Rect mClipRegion = new Rect(0, 0, 0, 0);
+ private boolean mPossiblePoly;
+ private SwipeTracker mSwipeTracker = new SwipeTracker();
+ private int mSwipeThreshold;
+ private boolean mDisambiguateSwipe;
+
+ // Variables for dealing with multiple pointers
+ private int mOldPointerCount = 1;
+ private float mOldPointerX;
+ private float mOldPointerY;
+
+ // @UnsupportedAppUsage
+ // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/annotation/UnsupportedAppUsage.java
+ // Indicates that a class member, that is not part of the SDK, is used by apps.
+ // Since the member is not part of the SDK, such use is not supported.
+ //
+ // This annotation acts as a heads up that changing a given method or field
+ // may affect apps, potentially breaking them when the next Android version is
+ // released. In some cases, for members that are heavily used, this annotation
+ // may imply restrictions on changes to the member.
+ private Drawable mKeyBackground;
+
+ private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
+ private static final int REPEAT_START_DELAY = 400;
+ private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+
+ private static int MAX_NEARBY_KEYS = 12;
+ private int[] mDistances = new int[MAX_NEARBY_KEYS];
+
+ // For multi-tap
+ private int mLastSentIndex;
+ private int mTapCount;
+ private long mLastTapTime;
+ private boolean mInMultiTap;
+ private static final int MULTITAP_INTERVAL = 800; // milliseconds
+ private StringBuilder mPreviewLabel = new StringBuilder(1);
+
+ /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
+ private boolean mDrawPending;
+ /** The dirty region in the keyboard bitmap */
+ private Rect mDirtyRect = new Rect();
+ /** The keyboard bitmap for faster updates */
+ private Bitmap mBuffer;
+ /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
+ private boolean mKeyboardChanged;
+ /** The canvas for the above mutable keyboard bitmap */
+ private Canvas mCanvas;
+ /** The accessibility manager for accessibility support */
+ private AccessibilityManager mAccessibilityManager;
+ /** The audio manager for accessibility support */
+ private AudioManager mAudioManager;
+ /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */
+ private boolean mHeadsetRequiredToHearPasswordsAnnounced;
+
+ // Custom handler code (to avoid mem leaks, see warning "This Handler Class Should Be Static Or Leaks Might Occurâ) based on:
+ // https://stackoverflow.com/a/27826094
+ public static class CustomKeyboardViewHandler extends Handler {
+
+ private final WeakReference<CustomKeyboardView> mListenerReference;
+
+ public CustomKeyboardViewHandler(CustomKeyboardView listener) {
+ super(Looper.getMainLooper());
+ mListenerReference = new WeakReference<>(listener);
+ }
+
+ @Override
+ public synchronized void handleMessage(@NonNull Message msg) {
+ CustomKeyboardView listener = mListenerReference.get();
+ if(listener != null) {
+ switch (msg.what) {
+ case MSG_SHOW_PREVIEW:
+ listener.showKey(msg.arg1);
+ break;
+
+ case MSG_REMOVE_PREVIEW:
+ listener.mPreviewText.setVisibility(INVISIBLE);
+ break;
+
+ case MSG_REPEAT:
+ if (listener.repeatKey()) {
+ Message repeat = Message.obtain(this, MSG_REPEAT);
+ sendMessageDelayed(repeat, REPEAT_INTERVAL);
+ }
+ break;
+
+ case MSG_LONGPRESS:
+ listener.openPopupIfRequired((MotionEvent) msg.obj);
+ break;
+ }
+ }
+ }
+
+ public void clear() {
+ this.removeCallbacksAndMessages(null);
+ }
+ } // end of: static class CustomKeyboardViewHandler
+
+// Handler mHandler;
+// final private CustomKeyboardViewHandler mHandler = new CustomKeyboardViewHandler(this);
+ private CustomKeyboardViewHandler mHandler = new CustomKeyboardViewHandler(this);
+
+ public void clearEventHandler() {
+ mHandler.clear();
+ }
+
+ public CustomKeyboardView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.keyboardViewStyle);
+ }
+
+ public CustomKeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CustomKeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+// super(context, attrs, defStyleAttr, defStyleRes); // this call requires API 21. Skip it for now
+ super(context, attrs, defStyleAttr);
+
+ mContext = context;
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomKeyboardView, defStyleAttr, defStyleRes);
+
+ LayoutInflater inflate = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ int previewLayout = 0;
+ int keyTextSize = 0;
+
+ int n = a.getIndexCount();
+
+ for (int i = 0; i < n; i++) {
+ int attr = a.getIndex(i);
+
+ // resolve: "resource IDs will be non-final in Android Gradle Plugin version 5.0, avoid using them in switch case statements"
+ // We converted the switch statement to if/else as suggested here: http://tools.android.com/tips/non-constant-fields
+ if (attr == R.styleable.CustomKeyboardView_keyBackground) {
+ mKeyBackground = a.getDrawable(attr);
+ } else if (attr == R.styleable.CustomKeyboardView_verticalCorrection) {
+ mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
+ } else if (attr == R.styleable.CustomKeyboardView_keyPreviewLayout) {
+ previewLayout = a.getResourceId(attr, 0);
+ } else if (attr == R.styleable.CustomKeyboardView_keyPreviewOffset) {
+ mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
+ } else if (attr == R.styleable.CustomKeyboardView_keyPreviewHeight) {
+ mPreviewHeight = a.getDimensionPixelSize(attr, 80);
+ } else if (attr == R.styleable.CustomKeyboardView_keyTextSize) {
+ mKeyTextSize = a.getDimensionPixelSize(attr, 18);
+ } else if (attr == R.styleable.CustomKeyboardView_keyTextColor) {
+ mKeyTextColor = a.getColor(attr, 0xFF000000);
+ } else if (attr == R.styleable.CustomKeyboardView_labelTextSize) {
+ mLabelTextSize = a.getDimensionPixelSize(attr, 14);
+ } else if (attr == R.styleable.CustomKeyboardView_popupLayout) {
+ mPopupLayout = a.getResourceId(attr, 0);
+ } else if (attr == R.styleable.CustomKeyboardView_shadowColor) {
+ mShadowColor = a.getColor(attr, 0);
+ } else if (attr == R.styleable.CustomKeyboardView_shadowRadius) {
+ mShadowRadius = a.getFloat(attr, 0f);
+ }
+ }
+
+ // TODO put default values as constants somewhere
+// if (mLabelTextSize == 0) {
+// mLabelTextSize = 14;
+// }
//
-// if (mKeyTextSize == 0) {
-// mKeyTextSize = 18;
-// }
+// if (mKeyTextSize == 0) {
+// mKeyTextSize = 18;
+// }
//
-// if (mKeyTextColor == 0) {
-// mKeyTextColor = 0xFF000000;
-// }
+// if (mKeyTextColor == 0) {
+// mKeyTextColor = 0xFF000000;
+// }
//
-// if (mPreviewHeight == 0) {
-// mPreviewHeight = 80;
-// }
-
- mBackgroundDimAmount = a.getFloat(R.styleable.CustomKeyboardView_backgroundDimAmount, 0.5f);
- a.recycle();
-
- mPreviewPopup = new PopupWindow(context);
- if (previewLayout != 0) {
- mPreviewText = (TextView) inflate.inflate(previewLayout, null);
- mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
- mPreviewPopup.setContentView(mPreviewText);
- mPreviewPopup.setBackgroundDrawable(null);
- } else {
- mShowPreview = false;
- }
-
- mPreviewPopup.setTouchable(false);
- mPopupKeyboard = new PopupWindow(context);
- mPopupKeyboard.setBackgroundDrawable(null);
- //mPopupKeyboard.setClippingEnabled(false);
-
- mPopupParent = this;
- //mPredicting = true;
-
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
- mPaint.setTextSize(keyTextSize);
- mPaint.setTextAlign(Align.CENTER);
- mPaint.setAlpha(255);
-
- mPadding = new Rect(0, 0, 0, 0);
- mMiniKeyboardCache = new HashMap<>();
-
- if (mKeyBackground == null) {
-// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-// mKeyBackground = context.getResources().getDrawable(R.drawable.btn_keyboard_key, context.getTheme());
-// } else {
-// mKeyBackground = context.getResources().getDrawable(R.drawable.btn_keyboard_key);
-// }
- mKeyBackground = ResourcesCompat.getDrawable(context.getResources(), R.drawable.btn_keyboard_key, context.getTheme());
- }
-
- mKeyBackground.getPadding(mPadding);
-
- mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
- mDisambiguateSwipe = getResources().getBoolean(
- R.bool.config_swipeDisambiguation);
-
- //mAccessibilityManager = AccessibilityManager.getInstance(context);
- mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-
- mKeysDownCodesSet = new ArraySet<>();
-
- resetMultiTap();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- initGestureDetector();
- if (mHandler == null) {
- mHandler = new CustomKeyboardViewHandler(this);
- }
- }
-
- private void initGestureDetector() {
- if (mGestureDetector == null) {
- mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onFling(MotionEvent me1, MotionEvent me2,
- float velocityX, float velocityY) {
- if (mPossiblePoly) return false;
- final float absX = Math.abs(velocityX);
- final float absY = Math.abs(velocityY);
- float deltaX = me2.getX() - me1.getX();
- float deltaY = me2.getY() - me1.getY();
- int travelX = getWidth() / 2; // Half the keyboard width
- int travelY = getHeight() / 2; // Half the keyboard height
- //Log.d(ScummVM.LOG_TAG, "mSwipeTracker.computeCurrentVelocity()");
- mSwipeTracker.computeCurrentVelocity(1000);
- final float endingVelocityX = mSwipeTracker.getXVelocity();
- final float endingVelocityY = mSwipeTracker.getYVelocity();
- boolean sendDownKey = false;
- if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
- if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
- sendDownKey = true;
- } else {
- swipeRight();
- return true;
- }
- } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
- if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
- sendDownKey = true;
- } else {
- swipeLeft();
- return true;
- }
- } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
- if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
- sendDownKey = true;
- } else {
- swipeUp();
- return true;
- }
- } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
- if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
- sendDownKey = true;
- } else {
- swipeDown();
- return true;
- }
- }
-
- if (sendDownKey) {
- //Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: initGestureDetector() - sendDownKey");
- detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime(), false, false);
- }
- return false;
- }
- });
-
- mGestureDetector.setIsLongpressEnabled(false);
- }
- }
-
- public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
- mKeyboardActionListener = listener;
- }
-
- /**
- * Returns the {@link OnKeyboardActionListener} object.
- * @return the listener attached to this keyboard
- */
- protected OnKeyboardActionListener getOnKeyboardActionListener() {
- return mKeyboardActionListener;
- }
-
- /**
- * Attaches a keyboard to this view. The keyboard can be switched at any time and the
- * view will re-layout itself to accommodate the keyboard.
- * @see CustomKeyboard
- * @see #getKeyboard()
- * @param keyboard the keyboard to display in this view
- */
- public void setKeyboard(CustomKeyboard keyboard) {
- if (mKeyboard != null) {
- showPreview(NOT_A_KEY);
- }
- // Remove any pending messages
- clearMessages();
- mKeyboard = keyboard;
- List<CustomKeyboard.CustomKey> keys = mKeyboard.getKeys();
-// mKeys = keys.toArray(new CustomKeyboard.Key[keys.size()]);
- mKeys = keys.toArray(new CustomKeyboard.CustomKey[0]);
- requestLayout();
- // Hint to reallocate the buffer if the size changed
- mKeyboardChanged = true;
- invalidateAllKeys();
- computeProximityThreshold(keyboard);
- mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
- // Switching to a different keyboard should abort any pending keys so that the key up
- // doesn't get delivered to the old or new keyboard
- mAbortKey = true; // Until the next ACTION_DOWN
- }
-
- /**
- * Returns the current keyboard being displayed by this view.
- * @return the currently attached keyboard
- * @see #setKeyboard(CustomKeyboard)
- */
- public CustomKeyboard getKeyboard() {
- return mKeyboard;
- }
-
- /**
- * Sets the state of the shift key of the keyboard, if any.
- * @param shifted whether or not to enable the state of the shift key
- * @return true if the shift key state changed, false if there was no change
- * @see CustomKeyboardView#isShifted()
- */
- public boolean setShifted(boolean shifted) {
- if (mKeyboard != null) {
- if (mKeyboard.setShifted(shifted)) {
- // The whole keyboard probably needs to be redrawn
- invalidateAllKeys();
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns the state of the shift key of the keyboard, if any.
- * @return true if the shift is in a pressed state, false otherwise. If there is
- * no shift key on the keyboard or there is no keyboard attached, it returns false.
- * @see CustomKeyboardView#setShifted(boolean)
- */
- public boolean isShifted() {
- if (mKeyboard != null) {
- return mKeyboard.isShifted();
- }
- return false;
- }
-
- /**
- * Enables or disables the key feedback popup. This is a popup that shows a magnified
- * version of the depressed key. By default the preview is enabled.
- * @param previewEnabled whether or not to enable the key feedback popup
- * @see #isPreviewEnabled()
- */
- public void setPreviewEnabled(boolean previewEnabled) {
- mShowPreview = previewEnabled;
- }
-
- /**
- * Returns the enabled state of the key feedback popup.
- * @return whether or not the key feedback popup is enabled
- * @see #setPreviewEnabled(boolean)
- */
- public boolean isPreviewEnabled() {
- return mShowPreview;
- }
-
- public void setVerticalCorrection(int verticalOffset) { }
-
- public void setPopupParent(View v) {
- mPopupParent = v;
- }
-
- public void setPopupOffset(int x, int y) {
- mMiniKeyboardOffsetX = x;
- mMiniKeyboardOffsetY = y;
- if (mPreviewPopup.isShowing()) {
- mPreviewPopup.dismiss();
- }
- }
-
- /**
- * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
- * codes for adjacent keys. When disabled, only the primary key code will be
- * reported.
- * @param enabled whether or not the proximity correction is enabled
- */
- public void setProximityCorrectionEnabled(boolean enabled) {
- mProximityCorrectOn = enabled;
- }
-
- /**
- * Returns true if proximity correction is enabled.
- */
- public boolean isProximityCorrectionEnabled() {
- return mProximityCorrectOn;
- }
-
- /**
- * Popup keyboard close button clicked.
- */
- public void onClick(View v) {
- dismissPopupKeyboard();
- }
-
- private CharSequence adjustCase(CharSequence label) {
- if (mKeyboard.isShifted() && label != null && label.length() < 3
- && Character.isLowerCase(label.charAt(0))) {
- label = label.toString().toUpperCase();
- }
- return label;
- }
-
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Round up a little
- if (mKeyboard == null) {
- setMeasuredDimension(getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom());
- } else {
- int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
- if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
- width = MeasureSpec.getSize(widthMeasureSpec);
- }
- setMeasuredDimension(width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom());
- }
- }
-
- /**
- * Compute the average distance between adjacent keys (horizontally and vertically)
- * and square it to get the proximity threshold. We use a square here and in computing
- * the touch distance from a key's center to avoid taking a square root.
- * @param keyboard
- */
- private void computeProximityThreshold(CustomKeyboard keyboard) {
- if (keyboard == null) return;
- final CustomKeyboard.CustomKey[] keys = mKeys;
- if (keys == null) return;
- int length = keys.length;
- int dimensionSum = 0;
- for (int i = 0; i < length; i++) {
- CustomKeyboard.CustomKey key = keys[i];
- dimensionSum += Math.min(key.width, key.height) + key.gap;
- }
- if (dimensionSum < 0 || length == 0) return;
- mProximityThreshold = (int) (dimensionSum * 1.4f / length);
- mProximityThreshold *= mProximityThreshold; // Square it
- }
-
- @Override
- public void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- if (mKeyboard != null) {
- mKeyboard.resize(w, h);
- }
- // Release the buffer, if any and it will be reallocated on the next draw
- mBuffer = null;
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (mDrawPending || mBuffer == null || mKeyboardChanged) {
- onBufferDraw();
- }
- canvas.drawBitmap(mBuffer, 0, 0, null);
- }
-
- private void onBufferDraw() {
- if (mBuffer == null || mKeyboardChanged) {
- if (mBuffer == null || mKeyboardChanged &&
- (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
- // Make sure our bitmap is at least 1x1
- final int width = Math.max(1, getWidth());
- final int height = Math.max(1, getHeight());
- mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- mCanvas = new Canvas(mBuffer);
- }
- invalidateAllKeys();
- mKeyboardChanged = false;
- }
-
- if (mKeyboard == null) return;
-
- mCanvas.save();
- final Canvas canvas = mCanvas;
- canvas.clipRect(mDirtyRect);
-
- final Paint paint = mPaint;
- final Drawable keyBackground = mKeyBackground;
- final Rect clipRegion = mClipRegion;
- final Rect padding = mPadding;
- final int kbdPaddingLeft = getPaddingLeft();
- final int kbdPaddingTop = getPaddingTop();
- final CustomKeyboard.CustomKey[] keys = mKeys;
- final CustomKeyboard.CustomKey invalidKey = mInvalidatedKey;
-
- paint.setColor(mKeyTextColor);
- boolean drawSingleKey = false;
- if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
- // Is clipRegion completely contained within the invalidated key?
- if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left &&
- invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top &&
- invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right &&
- invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
- drawSingleKey = true;
- }
- }
- canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
- final int keyCount = keys.length;
- for (int i = 0; i < keyCount; i++) {
- final CustomKeyboard.CustomKey key = keys[i];
- if (drawSingleKey && invalidKey != key) {
- continue;
- }
- int[] drawableState = key.getCurrentDrawableState();
- keyBackground.setState(drawableState);
-
-// Log.d("keyboardView", " key label: " + (key.label == null ? "null" : key.label.toString()));
- // Switch the character to uppercase if shift is pressed
- String label = key.label == null? null : adjustCase(key.label).toString();
-
- final Rect bounds = keyBackground.getBounds();
- if (key.width != bounds.right ||
- key.height != bounds.bottom) {
- keyBackground.setBounds(0, 0, key.width, key.height);
- }
- canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
- keyBackground.draw(canvas);
-
- if (label != null) {
- // For characters, use large font. For labels like "Done", use small font.
- if (label.length() > 1 && key.codes.length < 2) {
- paint.setTextSize(mLabelTextSize);
- paint.setTypeface(Typeface.DEFAULT_BOLD);
- } else {
- paint.setTextSize(mKeyTextSize);
- paint.setTypeface(Typeface.DEFAULT);
- }
- // Draw a drop shadow for the text
- paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
- // Draw the text
- Log.d("keyboardView", "keyW: " + key.width +
- " keyH: " + key.height +
- " padL: " + padding.left +
- " padR: " + padding.right +
- " padT: " + padding.top +
- " padB: " + padding.bottom +
- " paintTs: " + paint.getTextSize() +
- " paintDesce: " + paint.descent());
-
- Log.d("keyboardView", " Draw key: " + label
- + " x: " + ( ((key.width - padding.left - padding.right) / 2.0f ) + padding.left)
- + " y: " + ( ((key.height - padding.top - padding.bottom) / 2.0f ) + ((paint.getTextSize() - paint.descent()) / 2.0f) + padding.top));
- canvas.drawText(label,
- ( ((key.width - padding.left - padding.right) / 2.0f ) + padding.left),
- ( ((key.height - padding.top - padding.bottom) / 2.0f ) + ((paint.getTextSize() - paint.descent()) / 2.0f) + padding.top),
- paint);
- // Turn off drop shadow
- paint.setShadowLayer(0, 0, 0, 0);
- } else if (key.icon != null) {
- final int drawableX = (key.width - padding.left - padding.right
- - key.icon.getIntrinsicWidth()) / 2 + padding.left;
- final int drawableY = (key.height - padding.top - padding.bottom
- - key.icon.getIntrinsicHeight()) / 2 + padding.top;
- canvas.translate(drawableX, drawableY);
- key.icon.setBounds(0, 0,
- key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
- key.icon.draw(canvas);
- canvas.translate(-drawableX, -drawableY);
- }
- canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
- }
- mInvalidatedKey = null;
- // Overlay a dark rectangle to dim the keyboard
- if (mMiniKeyboardOnScreen) {
- paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
- canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
- }
-
- if (DEBUG && mShowTouchPoints) {
- paint.setAlpha(128);
- paint.setColor(0xFFFF0000);
- canvas.drawCircle(mStartX, mStartY, 3, paint);
- canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
- paint.setColor(0xFF0000FF);
- canvas.drawCircle(mLastX, mLastY, 3, paint);
- paint.setColor(0xFF00FF00);
- canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
- }
- mCanvas.restore();
- mDrawPending = false;
- mDirtyRect.setEmpty();
- }
-
- private int getKeyIndices(int x, int y, int[] allKeys) {
- final CustomKeyboard.CustomKey[] keys = mKeys;
- int primaryIndex = NOT_A_KEY;
- int closestKey = NOT_A_KEY;
- int closestKeyDist = mProximityThreshold + 1;
- java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
- int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
- final int keyCount = nearestKeyIndices.length;
- for (int i = 0; i < keyCount; i++) {
- final CustomKeyboard.CustomKey key = keys[nearestKeyIndices[i]];
- int dist = 0;
- boolean isInside = key.isInside(x,y);
- if (isInside) {
- primaryIndex = nearestKeyIndices[i];
- }
-
- if (((mProximityCorrectOn
- && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold)
- || isInside)
- && key.codes[0] > 32) {
- // Find insertion point
- final int nCodes = key.codes.length;
- if (dist < closestKeyDist) {
- closestKeyDist = dist;
- closestKey = nearestKeyIndices[i];
- }
-
- if (allKeys == null) continue;
-
- for (int j = 0; j < mDistances.length; j++) {
- if (mDistances[j] > dist) {
- // Make space for nCodes codes
- System.arraycopy(mDistances, j, mDistances, j + nCodes,
- mDistances.length - j - nCodes);
- System.arraycopy(allKeys, j, allKeys, j + nCodes,
- allKeys.length - j - nCodes);
- for (int c = 0; c < nCodes; c++) {
- allKeys[j + c] = key.codes[c];
- mDistances[j + c] = dist;
- }
- break;
- }
- }
- }
- }
- if (primaryIndex == NOT_A_KEY) {
- primaryIndex = closestKey;
- }
- return primaryIndex;
- }
-
- private void detectAndSendKey(int index, int x, int y, long eventTime, boolean isRepeated, boolean isReleaseKey) {
- //Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: index =" + index + "isRelease: "+ isReleaseKey);
- if (index != NOT_A_KEY && index < mKeys.length) {
- final CustomKeyboard.CustomKey key = mKeys[index];
- if (key.text != null) {
- mKeyboardActionListener.onText(key.text);
- //Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: detectAndSendKey - (key.text != null)");
- mKeyboardActionListener.onRelease(NOT_A_KEY);
- } else {
- int code = key.codes[0];
- //TextEntryState.keyPressedAt(key, x, y);
- int[] codes = new int[MAX_NEARBY_KEYS];
- Arrays.fill(codes, NOT_A_KEY);
- getKeyIndices(x, y, codes);
- // Multi-tap
- if (mInMultiTap) {
- if (mTapCount != -1) {
- mKeyboardActionListener.onKey(CustomKeyboard.KEYCODE_DELETE, KEY_DELETE);
- if (code != NOT_A_KEY) {
- mKeysDownCodesSet.add(CustomKeyboard.KEYCODE_DELETE);
- }
- } else {
- mTapCount = 0;
- }
- code = key.codes[mTapCount];
- }
- //Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: detectAndSendKey - (key.text is null) code = " + code + " x: " + x + " y: " + y);
- if (!isReleaseKey) {
- mKeyboardActionListener.onKey(code, codes);
- if (code != NOT_A_KEY) {
- mKeysDownCodesSet.add(code);
- }
- }
- if (!isRepeated || isReleaseKey) {
- mKeyboardActionListener.onRelease(code);
- if (code != NOT_A_KEY && mKeysDownCodesSet.contains(code)) {
- mKeysDownCodesSet.remove(code);
- }
- }
- }
- mLastSentIndex = index;
- mLastTapTime = eventTime;
- }
- }
-
- /**
- * Handle multi-tap keys by producing the key label for the current multi-tap state.
- */
- private CharSequence getPreviewText(CustomKeyboard.CustomKey key) {
- if (mInMultiTap) {
- // Multi-tap
- mPreviewLabel.setLength(0);
- mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
- return adjustCase(mPreviewLabel);
- } else {
- return adjustCase(key.label);
- }
- }
-
- private void showPreview(int keyIndex) {
- int oldKeyIndex = mCurrentKeyIndex;
- final PopupWindow previewPopup = mPreviewPopup;
-
- mCurrentKeyIndex = keyIndex;
- // Release the old key and press the new key
- final CustomKeyboard.CustomKey[] keys = mKeys;
- if (oldKeyIndex != mCurrentKeyIndex) {
- if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
- CustomKeyboard.CustomKey oldKey = keys[oldKeyIndex];
- oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY);
- invalidateKey(oldKeyIndex);
- final int keyCode = oldKey.codes[0];
- sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,
- keyCode);
- // TODO: We need to implement AccessibilityNodeProvider for this view.
- sendAccessibilityEventForUnicodeCharacter(
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode);
- }
- if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
- CustomKeyboard.CustomKey newKey = keys[mCurrentKeyIndex];
- newKey.onPressed();
- invalidateKey(mCurrentKeyIndex);
- final int keyCode = newKey.codes[0];
- sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
- keyCode);
- // TODO: We need to implement AccessibilityNodeProvider for this view.
- sendAccessibilityEventForUnicodeCharacter(
- AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, keyCode);
- }
- }
- // If key changed and preview is on ...
- if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
- mHandler.removeMessages(MSG_SHOW_PREVIEW);
- if (previewPopup.isShowing()) {
- if (keyIndex == NOT_A_KEY) {
- mHandler.sendMessageDelayed(mHandler
- .obtainMessage(MSG_REMOVE_PREVIEW),
- DELAY_AFTER_PREVIEW);
- }
- }
- if (keyIndex != NOT_A_KEY) {
- if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
- // Show right away, if it's already visible and finger is moving around
- showKey(keyIndex);
- } else {
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0),
- DELAY_BEFORE_PREVIEW);
- }
- }
- }
- }
-
-// @UnsupportedAppUsage
- private void showKey(final int keyIndex) {
- final PopupWindow previewPopup = mPreviewPopup;
- final CustomKeyboard.CustomKey[] keys = mKeys;
- if (keyIndex < 0 || keyIndex >= mKeys.length) return;
- CustomKeyboard.CustomKey key = keys[keyIndex];
- if (key.icon != null) {
- mPreviewText.setCompoundDrawables(null, null, null,
- key.iconPreview != null ? key.iconPreview : key.icon);
- mPreviewText.setText(null);
- } else {
- mPreviewText.setCompoundDrawables(null, null, null, null);
- mPreviewText.setText(getPreviewText(key));
- if (key.label.length() > 1 && key.codes.length < 2) {
- mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
- mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
- } else {
- mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
- mPreviewText.setTypeface(Typeface.DEFAULT);
- }
- }
- mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
- MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
- int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width
- + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
- final int popupHeight = mPreviewHeight;
- LayoutParams lp = mPreviewText.getLayoutParams();
- if (lp != null) {
- lp.width = popupWidth;
- lp.height = popupHeight;
- }
- if (!mPreviewCentered) {
- mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + getPaddingLeft();
- mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
- } else {
- // TODO: Fix this if centering is brought back
- mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
- mPopupPreviewY = - mPreviewText.getMeasuredHeight();
- }
- mHandler.removeMessages(MSG_REMOVE_PREVIEW);
- getLocationInWindow(mCoordinates);
- mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
- mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
-
- // Set the preview background state
- mPreviewText.getBackground().setState(
- key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
- mPopupPreviewX += mCoordinates[0];
- mPopupPreviewY += mCoordinates[1];
-
- // If the popup cannot be shown above the key, put it on the side
- getLocationOnScreen(mCoordinates);
- if (mPopupPreviewY + mCoordinates[1] < 0) {
- // If the key you're pressing is on the left side of the keyboard, show the popup on
- // the right, offset by enough to see at least one key to the left/right.
- if (key.x + key.width <= getWidth() / 2) {
- mPopupPreviewX += (int) (key.width * 2.5);
- } else {
- mPopupPreviewX -= (int) (key.width * 2.5);
- }
- mPopupPreviewY += popupHeight;
- }
-
- if (previewPopup.isShowing()) {
- previewPopup.update(mPopupPreviewX, mPopupPreviewY,
- popupWidth, popupHeight);
- } else {
- previewPopup.setWidth(popupWidth);
- previewPopup.setHeight(popupHeight);
- previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
- mPopupPreviewX, mPopupPreviewY);
- }
- mPreviewText.setVisibility(VISIBLE);
- }
-
- private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
- if (mAccessibilityManager.isEnabled()) {
- AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- onInitializeAccessibilityEvent(event);
- final String text;
- switch (code) {
- case CustomKeyboard.KEYCODE_ALT:
- text = mContext.getString(R.string.customkeyboardview_keycode_alt);
- break;
- case CustomKeyboard.KEYCODE_CANCEL:
- text = mContext.getString(R.string.customkeyboardview_keycode_cancel);
- break;
- case CustomKeyboard.KEYCODE_DELETE:
- text = mContext.getString(R.string.customkeyboardview_keycode_delete);
- break;
- case CustomKeyboard.KEYCODE_DONE:
- text = mContext.getString(R.string.customkeyboardview_keycode_done);
- break;
- case CustomKeyboard.KEYCODE_MODE_CHANGE:
- text = mContext.getString(R.string.customkeyboardview_keycode_mode_change);
- break;
- case CustomKeyboard.KEYCODE_SHIFT:
- text = mContext.getString(R.string.customkeyboardview_keycode_shift);
- break;
- case '\n':
- text = mContext.getString(R.string.customkeyboardview_keycode_enter);
- break;
- default:
- text = String.valueOf((char) code);
- }
- event.getText().add(text);
- mAccessibilityManager.sendAccessibilityEvent(event);
- }
- }
-
- /**
- * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
- * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
- * draws the cached buffer.
- * @see #invalidateKey(int)
- */
- public void invalidateAllKeys() {
- mDirtyRect.union(0, 0, getWidth(), getHeight());
- mDrawPending = true;
- invalidate();
- }
-
- /**
- * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
- * one key is changing it's content. Any changes that affect the position or size of the key
- * may not be honored.
- * @param keyIndex the index of the key in the attached {@link CustomKeyboard}.
- * @see #invalidateAllKeys
- */
- public void invalidateKey(int keyIndex) {
- if (mKeys == null) return;
- if (keyIndex < 0 || keyIndex >= mKeys.length) {
- return;
- }
- final CustomKeyboard.CustomKey key = mKeys[keyIndex];
- mInvalidatedKey = key;
- mDirtyRect.union(key.x + getPaddingLeft(),
- key.y + getPaddingTop(),
- key.x + key.width + getPaddingLeft(),
- key.y + key.height + getPaddingTop());
- onBufferDraw();
-
- // The switch to hardware accelerated rendering in API 14 reduced the importance of the dirty rectangle.
- // In API 21 the given rectangle is ignored entirely in favor of an internally-calculated area instead.
- // Because of this, clients are encouraged to just call invalidate().
- //invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(),key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
- invalidate();
- }
-
-// @UnsupportedAppUsage
- private boolean openPopupIfRequired(MotionEvent me) {
- // Check if we have a popup layout specified first.
- if (mPopupLayout == 0) {
- return false;
- }
- if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
- return false;
- }
-
- CustomKeyboard.CustomKey popupKey = mKeys[mCurrentKey];
- boolean result = onLongPress(popupKey);
- if (result) {
- mAbortKey = true;
- showPreview(NOT_A_KEY);
- }
- return result;
- }
-
- /**
- * Called when a key is long pressed. By default this will open any popup keyboard associated
- * with this key through the attributes popupLayout and popupCharacters.
- * @param popupKey the key that was long pressed
- * @return true if the long press is handled, false otherwise. Subclasses should call the
- * method on the base class if the subclass doesn't wish to handle the call.
- */
- protected boolean onLongPress(CustomKeyboard.CustomKey popupKey) {
- int popupKeyboardId = popupKey.popupResId;
-
- if (popupKeyboardId != 0) {
- mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
- if (mMiniKeyboardContainer == null) {
- LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
- mMiniKeyboard = (CustomKeyboardView) mMiniKeyboardContainer.findViewById(
- R.id.ScummVMKeyboardView);
- View closeButton = mMiniKeyboardContainer.findViewById(
- android.R.id.closeButton);
- if (closeButton != null) closeButton.setOnClickListener(this);
- mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
- public void onKey(int primaryCode, int[] keyCodes) {
- mKeyboardActionListener.onKey(primaryCode, keyCodes);
- if (primaryCode != NOT_A_KEY) {
- mKeysDownCodesSet.add(primaryCode);
- }
- dismissPopupKeyboard();
- }
-
- public void onText(CharSequence text) {
- mKeyboardActionListener.onText(text);
- dismissPopupKeyboard();
- }
-
- public void swipeLeft() { }
- public void swipeRight() { }
- public void swipeUp() { }
- public void swipeDown() { }
- public void onPress(int primaryCode) {
- mKeyboardActionListener.onPress(primaryCode);
- }
- public void onRelease(int primaryCode) {
- mKeyboardActionListener.onRelease(primaryCode);
- if (primaryCode != NOT_A_KEY && mKeysDownCodesSet.contains(primaryCode)) {
- mKeysDownCodesSet.remove(primaryCode);
- }
- }
- });
- //mInputView.setSuggest(mSuggest);
- CustomKeyboard keyboard;
- if (popupKey.popupCharacters != null) {
- keyboard = new CustomKeyboard(getContext(), popupKeyboardId,
- popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
- } else {
- keyboard = new CustomKeyboard(getContext(), popupKeyboardId);
- }
- mMiniKeyboard.setKeyboard(keyboard);
- mMiniKeyboard.setPopupParent(this);
- mMiniKeyboardContainer.measure(
- MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
-
- mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
- } else {
- mMiniKeyboard = (CustomKeyboardView) mMiniKeyboardContainer.findViewById(
- R.id.ScummVMKeyboardView);
- }
- getLocationInWindow(mCoordinates);
- mPopupX = popupKey.x + getPaddingLeft();
- mPopupY = popupKey.y + getPaddingTop();
- mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
- mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
- final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
- final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
- mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
- mMiniKeyboard.setShifted(isShifted());
- mPopupKeyboard.setContentView(mMiniKeyboardContainer);
- mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
- mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
- mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
- mMiniKeyboardOnScreen = true;
- //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
- invalidateAllKeys();
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
- final int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_HOVER_ENTER: {
- event.setAction(MotionEvent.ACTION_DOWN);
- } break;
- case MotionEvent.ACTION_HOVER_MOVE: {
- event.setAction(MotionEvent.ACTION_MOVE);
- } break;
- case MotionEvent.ACTION_HOVER_EXIT: {
- event.setAction(MotionEvent.ACTION_UP);
- } break;
- }
- return onTouchEvent(event);
- }
- return true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent me) {
- // Convert multi-pointer up/down events to single up/down events to
- // deal with the typical multi-pointer behavior of two-thumb typing
- final int pointerCount = me.getPointerCount();
- final int action = me.getAction();
- boolean result = false;
- final long now = me.getEventTime();
-
- if (pointerCount != mOldPointerCount) {
- if (pointerCount == 1) {
- // Send a down event for the latest pointer
- MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
- me.getX(), me.getY(), me.getMetaState());
- result = onModifiedTouchEvent(down, false);
- down.recycle();
- // If it's an up action, then deliver the up as well.
- if (action == MotionEvent.ACTION_UP) {
- //// TODO should we do this performClick here?
- //performClick();
- result = onModifiedTouchEvent(me, true);
- }
- } else {
- // Send an up event for the last pointer
- MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP,
- mOldPointerX, mOldPointerY, me.getMetaState());
- result = onModifiedTouchEvent(up, true);
- //// TODO should we do this performClick here?
- //performClick();
- up.recycle();
- }
- } else {
- if (pointerCount == 1) {
- // TODO should we do this performClick here?
- //if (action == MotionEvent.ACTION_UP) {
- // performClick();
- //}
- result = onModifiedTouchEvent(me, false);
- mOldPointerX = me.getX();
- mOldPointerY = me.getY();
- } else {
- // Don't do anything when 2 pointers are down and moving.
- result = true;
- }
- }
- mOldPointerCount = pointerCount;
-
- return result;
- }
-
- @Override
- public boolean performClick() {
- super.performClick();
- return true;
- }
-
- private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
- int touchX = (int) me.getX() - getPaddingLeft();
- int touchY = (int) me.getY() - getPaddingTop();
- if (touchY >= -mVerticalCorrection)
- touchY += mVerticalCorrection;
- final int action = me.getAction();
- final long eventTime = me.getEventTime();
- int keyIndex = getKeyIndices(touchX, touchY, null);
- mPossiblePoly = possiblePoly;
-
- // Track the last few movements to look for spurious swipes.
- if (action == MotionEvent.ACTION_DOWN) mSwipeTracker.clear();
- mSwipeTracker.addMovement(me);
-
- // Ignore all motion events until a DOWN.
- if (mAbortKey
- && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
- return true;
- }
-
- if (mGestureDetector.onTouchEvent(me)) {
- showPreview(NOT_A_KEY);
- mHandler.removeMessages(MSG_REPEAT);
- mHandler.removeMessages(MSG_LONGPRESS);
- return true;
- }
-
- // Needs to be called after the gesture detector gets a turn, as it may have
- // displayed the mini keyboard
- if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
- return true;
- }
-
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mAbortKey = false;
- mStartX = touchX;
- mStartY = touchY;
- mLastCodeX = touchX;
- mLastCodeY = touchY;
- mLastKeyTime = 0;
- mCurrentKeyTime = 0;
- mLastKey = NOT_A_KEY;
- mCurrentKey = keyIndex;
- mDownKey = keyIndex;
- mDownTime = me.getEventTime();
- mLastMoveTime = mDownTime;
- checkMultiTap(eventTime, keyIndex);
- mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ?
- mKeys[keyIndex].codes[0] : 0);
- if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
- mRepeatKeyIndex = mCurrentKey;
- Message msg = mHandler.obtainMessage(MSG_REPEAT);
- mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
- repeatKey();
- // Delivering the key could have caused an abort
- if (mAbortKey) {
- mRepeatKeyIndex = NOT_A_KEY;
- break;
- }
- }
- if (mCurrentKey != NOT_A_KEY) {
- Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
- mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
- }
- showPreview(keyIndex);
- break;
-
- case MotionEvent.ACTION_MOVE:
- boolean continueLongPress = false;
- if (keyIndex != NOT_A_KEY) {
- if (mCurrentKey == NOT_A_KEY) {
- mCurrentKey = keyIndex;
- mCurrentKeyTime = eventTime - mDownTime;
- } else {
- if (keyIndex == mCurrentKey) {
- mCurrentKeyTime += eventTime - mLastMoveTime;
- continueLongPress = true;
- } else if (mRepeatKeyIndex == NOT_A_KEY) {
- resetMultiTap();
- mLastKey = mCurrentKey;
- mLastCodeX = mLastX;
- mLastCodeY = mLastY;
- mLastKeyTime =
- mCurrentKeyTime + eventTime - mLastMoveTime;
- mCurrentKey = keyIndex;
- mCurrentKeyTime = 0;
- } else {
- // if (mRepeatKeyIndex != NOT_A_KEY)
- // New - handle the case where the user holds their finger and moves out of the key button
- // Unfortunately, we will also get a "release" event on MotionEvent.ACTION_UP but that is safe since it is ignored
- clearMessages();
- if (mRepeatKeyIndex >= 0 && !mMiniKeyboardOnScreen && !mAbortKey) {
- //Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: onModifiedTouchEvent - MotionEvent.ACTION_MOVE Final Rep");
- detectAndSendKey(mCurrentKey, touchX, touchY, eventTime, true, true);
- }
- showPreview(NOT_A_KEY);
- Arrays.fill(mKeyIndices, NOT_A_KEY);
- invalidateKey(keyIndex);
- mRepeatKeyIndex = NOT_A_KEY;
- }
- }
- }
- if (!continueLongPress) {
- // Cancel old longpress
- mHandler.removeMessages(MSG_LONGPRESS);
- // Start new longpress if key has changed
- if (keyIndex != NOT_A_KEY) {
- Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
- mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
- }
- }
- showPreview(mCurrentKey);
- mLastMoveTime = eventTime;
- break;
-
- case MotionEvent.ACTION_UP:
- clearMessages();
- if (keyIndex == mCurrentKey) {
- mCurrentKeyTime += eventTime - mLastMoveTime;
- } else {
- resetMultiTap();
- mLastKey = mCurrentKey;
- mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
- mCurrentKey = keyIndex;
- mCurrentKeyTime = 0;
- }
- if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
- && mLastKey != NOT_A_KEY) {
- mCurrentKey = mLastKey;
- touchX = mLastCodeX;
- touchY = mLastCodeY;
- }
- showPreview(NOT_A_KEY);
- Arrays.fill(mKeyIndices, NOT_A_KEY);
- // If we're not on a repeating key (which sends on a DOWN event)
- if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
- //Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: onModifiedTouchEvent - MotionEvent.ACTION_UP No Rep");
- detectAndSendKey(mCurrentKey, touchX, touchY, eventTime, false, true);
- } else if (mRepeatKeyIndex >= 0 && mRepeatKeyIndex != NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
- //Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: onModifiedTouchEvent - MotionEvent.ACTION_UP Final Rep");
- detectAndSendKey(mCurrentKey, touchX, touchY, eventTime, true, true);
- }
- invalidateKey(keyIndex);
- mRepeatKeyIndex = NOT_A_KEY;
- break;
- case MotionEvent.ACTION_CANCEL:
- clearMessages();
- dismissPopupKeyboard();
- mAbortKey = true;
- showPreview(NOT_A_KEY);
- invalidateKey(mCurrentKey);
- break;
- }
- mLastX = touchX;
- mLastY = touchY;
- return true;
- }
-
-// @UnsupportedAppUsage
- private boolean repeatKey() {
- CustomKeyboard.CustomKey key = mKeys[mRepeatKeyIndex];
- detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime, true, false);
- return true;
- }
-
- protected void swipeRight() {
- mKeyboardActionListener.swipeRight();
- }
-
- protected void swipeLeft() {
- mKeyboardActionListener.swipeLeft();
- }
-
- protected void swipeUp() {
- mKeyboardActionListener.swipeUp();
- }
-
- protected void swipeDown() {
- mKeyboardActionListener.swipeDown();
- }
-
- public void closing() {
- if (mPreviewPopup.isShowing()) {
- mPreviewPopup.dismiss();
- }
- clearMessages();
-
- dismissPopupKeyboard();
- mBuffer = null;
- mCanvas = null;
- mMiniKeyboardCache.clear();
-
- // Send onRelease to listener for a key that has sent down but on the time of closing has not sent release
- Iterator<Integer> it = mKeysDownCodesSet.iterator();
- while (it.hasNext()) {
- int keyCode = it.next();
- mKeyboardActionListener.onRelease(keyCode);
- //Log.d(ScummVM.LOG_TAG, "CustomKeyboardView closing - Send release for: " + keyCode);
- }
- mKeysDownCodesSet.clear();
- }
-
- private void clearMessages() {
- if (mHandler != null) {
- mHandler.removeMessages(MSG_REPEAT);
- mHandler.removeMessages(MSG_LONGPRESS);
- mHandler.removeMessages(MSG_SHOW_PREVIEW);
- mHandler.removeCallbacksAndMessages(null);
- }
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- closing();
- }
-
- private void dismissPopupKeyboard() {
- if (mPopupKeyboard.isShowing()) {
- mPopupKeyboard.dismiss();
- mMiniKeyboardOnScreen = false;
- invalidateAllKeys();
- }
- }
-
- public boolean handleBack() {
- if (mPopupKeyboard.isShowing()) {
- dismissPopupKeyboard();
- return true;
- }
- return false;
- }
-
- private void resetMultiTap() {
- mLastSentIndex = NOT_A_KEY;
- mTapCount = 0;
- mLastTapTime = -1;
- mInMultiTap = false;
- }
-
- private void checkMultiTap(long eventTime, int keyIndex) {
- if (keyIndex == NOT_A_KEY) return;
- CustomKeyboard.CustomKey key = mKeys[keyIndex];
- if (key.codes.length > 1) {
- mInMultiTap = true;
- if (eventTime < mLastTapTime + MULTITAP_INTERVAL
- && keyIndex == mLastSentIndex) {
- mTapCount = (mTapCount + 1) % key.codes.length;
- return;
- } else {
- mTapCount = -1;
- return;
- }
- }
- if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
- resetMultiTap();
- }
- }
-
- private static class SwipeTracker {
-
- static final int NUM_PAST = 4;
- static final int LONGEST_PAST_TIME = 200;
-
- final float mPastX[] = new float[NUM_PAST];
- final float mPastY[] = new float[NUM_PAST];
- final long mPastTime[] = new long[NUM_PAST];
-
- float mYVelocity;
- float mXVelocity;
-
- public void clear() {
- mPastTime[0] = 0;
- }
-
- public void addMovement(MotionEvent ev) {
- //Log.d(ScummVM.LOG_TAG, "SwipeTracker - Add Movement");
- long time = ev.getEventTime();
- final int N = ev.getHistorySize();
- for (int i=0; i<N; i++) {
- addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i),
- ev.getHistoricalEventTime(i));
- }
- addPoint(ev.getX(), ev.getY(), time);
- }
-
- private void addPoint(float x, float y, long time) {
- int drop = -1;
- int i;
- final long[] pastTime = mPastTime;
- for (i=0; i<NUM_PAST; i++) {
- if (pastTime[i] == 0) {
- break;
- } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
- drop = i;
- }
- }
- if (i == NUM_PAST && drop < 0) {
- drop = 0;
- }
- if (drop == i) drop--;
- final float[] pastX = mPastX;
- final float[] pastY = mPastY;
- if (drop >= 0) {
- final int start = drop+1;
- final int count = NUM_PAST-drop-1;
- System.arraycopy(pastX, start, pastX, 0, count);
- System.arraycopy(pastY, start, pastY, 0, count);
- System.arraycopy(pastTime, start, pastTime, 0, count);
- i -= (drop+1);
- }
- pastX[i] = x;
- pastY[i] = y;
- pastTime[i] = time;
- i++;
- if (i < NUM_PAST) {
- pastTime[i] = 0;
- }
- }
-
- public void computeCurrentVelocity(int units) {
- computeCurrentVelocity(units, Float.MAX_VALUE);
- }
-
- public void computeCurrentVelocity(int units, float maxVelocity) {
- final float[] pastX = mPastX;
- final float[] pastY = mPastY;
- final long[] pastTime = mPastTime;
-
- final float oldestX = pastX[0];
- final float oldestY = pastY[0];
- final long oldestTime = pastTime[0];
- float accumX = 0;
- float accumY = 0;
- int N=0;
- while (N < NUM_PAST) {
- if (pastTime[N] == 0) {
- break;
- }
- N++;
- }
-
- for (int i=1; i < N; i++) {
- final int dur = (int)(pastTime[i] - oldestTime);
- if (dur == 0) continue;
- float dist = pastX[i] - oldestX;
- float vel = (dist/dur) * units; // pixels/frame.
- if (accumX == 0) accumX = vel;
- else accumX = (accumX + vel) * .5f;
-
- dist = pastY[i] - oldestY;
- vel = (dist/dur) * units; // pixels/frame.
- if (accumY == 0) accumY = vel;
- else accumY = (accumY + vel) * .5f;
- }
- mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
- : Math.min(accumX, maxVelocity);
- mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
- : Math.min(accumY, maxVelocity);
- }
-
- public float getXVelocity() {
- return mXVelocity;
- }
-
- public float getYVelocity() {
- return mYVelocity;
- }
- }
+// if (mPreviewHeight == 0) {
+// mPreviewHeight = 80;
+// }
+
+ mBackgroundDimAmount = a.getFloat(R.styleable.CustomKeyboardView_backgroundDimAmount, 0.5f);
+ a.recycle();
+
+ mPreviewPopup = new PopupWindow(context);
+ if (previewLayout != 0) {
+ mPreviewText = (TextView) inflate.inflate(previewLayout, null);
+ mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
+ mPreviewPopup.setContentView(mPreviewText);
+ mPreviewPopup.setBackgroundDrawable(null);
+ } else {
+ mShowPreview = false;
+ }
+
+ mPreviewPopup.setTouchable(false);
+ mPopupKeyboard = new PopupWindow(context);
+ mPopupKeyboard.setBackgroundDrawable(null);
+ //mPopupKeyboard.setClippingEnabled(false);
+
+ mPopupParent = this;
+ //mPredicting = true;
+
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setTextSize(keyTextSize);
+ mPaint.setTextAlign(Align.CENTER);
+ mPaint.setAlpha(255);
+
+ mPadding = new Rect(0, 0, 0, 0);
+ mMiniKeyboardCache = new HashMap<>();
+
+ if (mKeyBackground == null) {
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+// mKeyBackground = context.getResources().getDrawable(R.drawable.btn_keyboard_key, context.getTheme());
+// } else {
+// mKeyBackground = context.getResources().getDrawable(R.drawable.btn_keyboard_key);
+// }
+ mKeyBackground = ResourcesCompat.getDrawable(context.getResources(), R.drawable.btn_keyboard_key, context.getTheme());
+ }
+
+ mKeyBackground.getPadding(mPadding);
+
+ mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
+ mDisambiguateSwipe = getResources().getBoolean(R.bool.config_swipeDisambiguation);
+
+ //mAccessibilityManager = AccessibilityManager.getInstance(context);
+ mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+ mKeysDownCodesSet = new ArraySet<>();
+
+ resetMultiTap();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ initGestureDetector();
+ if (mHandler == null) {
+ mHandler = new CustomKeyboardViewHandler(this);
+ }
+ }
+
+ private void initGestureDetector() {
+ if (mGestureDetector == null) {
+ mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
+ if (mPossiblePoly) return false;
+ final float absX = Math.abs(velocityX);
+ final float absY = Math.abs(velocityY);
+ float deltaX = me2.getX() - me1.getX();
+ float deltaY = me2.getY() - me1.getY();
+ int travelX = getWidth() / 2; // Half the keyboard width
+ int travelY = getHeight() / 2; // Half the keyboard height
+ //Log.d(ScummVM.LOG_TAG, "mSwipeTracker.computeCurrentVelocity()");
+ mSwipeTracker.computeCurrentVelocity(1000);
+ final float endingVelocityX = mSwipeTracker.getXVelocity();
+ final float endingVelocityY = mSwipeTracker.getYVelocity();
+ boolean sendDownKey = false;
+ if (velocityX > mSwipeThreshold && absY < absX && deltaX > travelX) {
+ if (mDisambiguateSwipe && endingVelocityX < velocityX / 4) {
+ sendDownKey = true;
+ } else {
+ swipeRight();
+ return true;
+ }
+ } else if (velocityX < -mSwipeThreshold && absY < absX && deltaX < -travelX) {
+ if (mDisambiguateSwipe && endingVelocityX > velocityX / 4) {
+ sendDownKey = true;
+ } else {
+ swipeLeft();
+ return true;
+ }
+ } else if (velocityY < -mSwipeThreshold && absX < absY && deltaY < -travelY) {
+ if (mDisambiguateSwipe && endingVelocityY > velocityY / 4) {
+ sendDownKey = true;
+ } else {
+ swipeUp();
+ return true;
+ }
+ } else if (velocityY > mSwipeThreshold && absX < absY / 2 && deltaY > travelY) {
+ if (mDisambiguateSwipe && endingVelocityY < velocityY / 4) {
+ sendDownKey = true;
+ } else {
+ swipeDown();
+ return true;
+ }
+ }
+
+ if (sendDownKey) {
+// Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: initGestureDetector() - sendDownKey");
+ detectAndSendKey(mDownKey, mStartX, mStartY, me1.getEventTime(), false, false);
+ }
+ return false;
+ }
+ });
+
+ mGestureDetector.setIsLongpressEnabled(false);
+ }
+ }
+
+ public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
+ mKeyboardActionListener = listener;
+ }
+
+ /**
+ * Returns the {@link OnKeyboardActionListener} object.
+ * @return the listener attached to this keyboard
+ */
+ protected OnKeyboardActionListener getOnKeyboardActionListener() {
+ return mKeyboardActionListener;
+ }
+
+ /**
+ * Attaches a keyboard to this view. The keyboard can be switched at any time and the
+ * view will re-layout itself to accommodate the keyboard.
+ * @see CustomKeyboard
+ * @see #getKeyboard()
+ * @param keyboard the keyboard to display in this view
+ */
+ public void setKeyboard(CustomKeyboard keyboard) {
+ if (mKeyboard != null) {
+ showPreview(NOT_A_KEY);
+ }
+ // Remove any pending messages
+ clearMessages();
+ mKeyboard = keyboard;
+ List<CustomKeyboard.CustomKey> keys = mKeyboard.getKeys();
+// mKeys = keys.toArray(new CustomKeyboard.Key[keys.size()]);
+ mKeys = keys.toArray(new CustomKeyboard.CustomKey[0]);
+ requestLayout();
+ // Hint to reallocate the buffer if the size changed
+ mKeyboardChanged = true;
+ invalidateAllKeys();
+ computeProximityThreshold(keyboard);
+ mMiniKeyboardCache.clear(); // Not really necessary to do every time, but will free up views
+ // Switching to a different keyboard should abort any pending keys so that the key up
+ // doesn't get delivered to the old or new keyboard
+ mAbortKey = true; // Until the next ACTION_DOWN
+ }
+
+ /**
+ * Returns the current keyboard being displayed by this view.
+ * @return the currently attached keyboard
+ * @see #setKeyboard(CustomKeyboard)
+ */
+ public CustomKeyboard getKeyboard() {
+ return mKeyboard;
+ }
+
+ /**
+ * Sets the state of the shift key of the keyboard, if any.
+ * @param shifted whether or not to enable the state of the shift key
+ * @return true if the shift key state changed, false if there was no change
+ * @see CustomKeyboardView#isShifted()
+ */
+ public boolean setShifted(boolean shifted) {
+ if (mKeyboard != null) {
+ if (mKeyboard.setShifted(shifted)) {
+ // The whole keyboard probably needs to be redrawn
+ invalidateAllKeys();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the state of the shift key of the keyboard, if any.
+ * @return true if the shift is in a pressed state, false otherwise. If there is
+ * no shift key on the keyboard or there is no keyboard attached, it returns false.
+ * @see CustomKeyboardView#setShifted(boolean)
+ */
+ public boolean isShifted() {
+ if (mKeyboard != null) {
+ return mKeyboard.isShifted();
+ }
+ return false;
+ }
+
+ /**
+ * Enables or disables the key feedback popup. This is a popup that shows a magnified
+ * version of the depressed key. By default the preview is enabled.
+ * @param previewEnabled whether or not to enable the key feedback popup
+ * @see #isPreviewEnabled()
+ */
+ public void setPreviewEnabled(boolean previewEnabled) {
+ mShowPreview = previewEnabled;
+ }
+
+ /**
+ * Returns the enabled state of the key feedback popup.
+ * @return whether or not the key feedback popup is enabled
+ * @see #setPreviewEnabled(boolean)
+ */
+ public boolean isPreviewEnabled() {
+ return mShowPreview;
+ }
+
+ public void setVerticalCorrection(int verticalOffset) { }
+
+ public void setPopupParent(View v) {
+ mPopupParent = v;
+ }
+
+ public void setPopupOffset(int x, int y) {
+ mMiniKeyboardOffsetX = x;
+ mMiniKeyboardOffsetY = y;
+ if (mPreviewPopup.isShowing()) {
+ mPreviewPopup.dismiss();
+ }
+ }
+
+ /**
+ * When enabled, calls to {@link OnKeyboardActionListener#onKey} will include key
+ * codes for adjacent keys. When disabled, only the primary key code will be
+ * reported.
+ * @param enabled whether or not the proximity correction is enabled
+ */
+ public void setProximityCorrectionEnabled(boolean enabled) {
+ mProximityCorrectOn = enabled;
+ }
+
+ /**
+ * Returns true if proximity correction is enabled.
+ */
+ public boolean isProximityCorrectionEnabled() {
+ return mProximityCorrectOn;
+ }
+
+ /**
+ * Popup keyboard close button clicked.
+ */
+ public void onClick(View v) {
+ dismissPopupKeyboard();
+ }
+
+ private CharSequence adjustCase(CharSequence label) {
+ if (mKeyboard.isShifted() && label != null && label.length() < 3 && Character.isLowerCase(label.charAt(0))) {
+ label = label.toString().toUpperCase();
+ }
+ return label;
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Round up a little
+ if (mKeyboard == null) {
+ setMeasuredDimension(getPaddingLeft() + getPaddingRight(), getPaddingTop() + getPaddingBottom());
+ } else {
+ int width = mKeyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
+ if (MeasureSpec.getSize(widthMeasureSpec) < width + 10) {
+ width = MeasureSpec.getSize(widthMeasureSpec);
+ }
+ setMeasuredDimension(width, mKeyboard.getHeight() + getPaddingTop() + getPaddingBottom());
+ }
+ }
+
+ /**
+ * Compute the average distance between adjacent keys (horizontally and vertically)
+ * and square it to get the proximity threshold. We use a square here and in computing
+ * the touch distance from a key's center to avoid taking a square root.
+ * @param keyboard
+ */
+ private void computeProximityThreshold(CustomKeyboard keyboard) {
+ if (keyboard == null) {
+ return;
+ }
+
+ final CustomKeyboard.CustomKey[] keys = mKeys;
+ if (keys == null) {
+ return;
+ }
+
+ int length = keys.length;
+ int dimensionSum = 0;
+ for (int i = 0; i < length; i++) {
+ CustomKeyboard.CustomKey key = keys[i];
+ dimensionSum += Math.min(key.width, key.height) + key.gap;
+ }
+ if (dimensionSum < 0 || length == 0) {
+ return;
+ }
+
+ mProximityThreshold = (int) (dimensionSum * 1.4f / length);
+ mProximityThreshold *= mProximityThreshold; // Square it
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (mKeyboard != null) {
+ mKeyboard.resize(w, h);
+ }
+ // Release the buffer, if any and it will be reallocated on the next draw
+ mBuffer = null;
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mDrawPending || mBuffer == null || mKeyboardChanged) {
+ onBufferDraw();
+ }
+ canvas.drawBitmap(mBuffer, 0, 0, null);
+ }
+
+ private void onBufferDraw() {
+ if (mBuffer == null || mKeyboardChanged) {
+ if (mBuffer == null || mKeyboardChanged
+ && (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
+ // Make sure our bitmap is at least 1x1
+ final int width = Math.max(1, getWidth());
+ final int height = Math.max(1, getHeight());
+ mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBuffer);
+ }
+ invalidateAllKeys();
+ mKeyboardChanged = false;
+ }
+
+ if (mKeyboard == null) {
+ return;
+ }
+
+ mCanvas.save();
+ final Canvas canvas = mCanvas;
+ canvas.clipRect(mDirtyRect);
+
+ final Paint paint = mPaint;
+ final Drawable keyBackground = mKeyBackground;
+ final Rect clipRegion = mClipRegion;
+ final Rect padding = mPadding;
+ final int kbdPaddingLeft = getPaddingLeft();
+ final int kbdPaddingTop = getPaddingTop();
+ final CustomKeyboard.CustomKey[] keys = mKeys;
+ final CustomKeyboard.CustomKey invalidKey = mInvalidatedKey;
+
+ paint.setColor(mKeyTextColor);
+ boolean drawSingleKey = false;
+ if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
+ // Is clipRegion completely contained within the invalidated key?
+ if (invalidKey.x + kbdPaddingLeft - 1 <= clipRegion.left
+ && invalidKey.y + kbdPaddingTop - 1 <= clipRegion.top
+ && invalidKey.x + invalidKey.width + kbdPaddingLeft + 1 >= clipRegion.right
+ && invalidKey.y + invalidKey.height + kbdPaddingTop + 1 >= clipRegion.bottom) {
+ drawSingleKey = true;
+ }
+ }
+ canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
+ final int keyCount = keys.length;
+ for (int i = 0; i < keyCount; i++) {
+ final CustomKeyboard.CustomKey key = keys[i];
+ if (drawSingleKey && invalidKey != key) {
+ continue;
+ }
+ int[] drawableState = key.getCurrentDrawableState();
+ keyBackground.setState(drawableState);
+
+// Log.d("keyboardView", " key label: " + (key.label == null ? "null" : key.label.toString()));
+ // Switch the character to uppercase if shift is pressed
+ String label = key.label == null? null : adjustCase(key.label).toString();
+
+ final Rect bounds = keyBackground.getBounds();
+ if (key.width != bounds.right || key.height != bounds.bottom) {
+ keyBackground.setBounds(0, 0, key.width, key.height);
+ }
+ canvas.translate(key.x + kbdPaddingLeft, key.y + kbdPaddingTop);
+ keyBackground.draw(canvas);
+
+ if (label != null) {
+ // For characters, use large font. For labels like "Done", use small font.
+ if (label.length() > 1 && key.codes.length < 2) {
+ paint.setTextSize(mLabelTextSize);
+ paint.setTypeface(Typeface.DEFAULT_BOLD);
+ } else {
+ paint.setTextSize(mKeyTextSize);
+ paint.setTypeface(Typeface.DEFAULT);
+ }
+ // Draw a drop shadow for the text
+ paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+ // Draw the text
+// Log.d("keyboardView", "keyW: " + key.width +
+// " keyH: " + key.height +
+// " padL: " + padding.left +
+// " padR: " + padding.right +
+// " padT: " + padding.top +
+// " padB: " + padding.bottom +
+// " paintTs: " + paint.getTextSize() +
+// " paintDesce: " + paint.descent());
+//
+// Log.d("keyboardView", " Draw key: " + label
+// + " x: " + ( ((key.width - padding.left - padding.right) / 2.0f ) + padding.left)
+// + " y: " + ( ((key.height - padding.top - padding.bottom) / 2.0f ) + ((paint.getTextSize() - paint.descent()) / 2.0f) + padding.top));
+ canvas.drawText(label,
+ ( ((key.width - padding.left - padding.right) / 2.0f ) + padding.left),
+ ( ((key.height - padding.top - padding.bottom) / 2.0f ) + ((paint.getTextSize() - paint.descent()) / 2.0f) + padding.top),
+ paint);
+ // Turn off drop shadow
+ paint.setShadowLayer(0, 0, 0, 0);
+ } else if (key.icon != null) {
+ final int drawableX = (key.width - padding.left - padding.right - key.icon.getIntrinsicWidth()) / 2 + padding.left;
+ final int drawableY = (key.height - padding.top - padding.bottom - key.icon.getIntrinsicHeight()) / 2 + padding.top;
+ canvas.translate(drawableX, drawableY);
+ key.icon.setBounds(0, 0, key.icon.getIntrinsicWidth(), key.icon.getIntrinsicHeight());
+ key.icon.draw(canvas);
+ canvas.translate(-drawableX, -drawableY);
+ }
+ canvas.translate(-key.x - kbdPaddingLeft, -key.y - kbdPaddingTop);
+ }
+ mInvalidatedKey = null;
+ // Overlay a dark rectangle to dim the keyboard
+ if (mMiniKeyboardOnScreen) {
+ paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
+ canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+ }
+
+ if (DEBUG && mShowTouchPoints) {
+ paint.setAlpha(128);
+ paint.setColor(0xFFFF0000);
+ canvas.drawCircle(mStartX, mStartY, 3, paint);
+ canvas.drawLine(mStartX, mStartY, mLastX, mLastY, paint);
+ paint.setColor(0xFF0000FF);
+ canvas.drawCircle(mLastX, mLastY, 3, paint);
+ paint.setColor(0xFF00FF00);
+ canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
+ }
+ mCanvas.restore();
+ mDrawPending = false;
+ mDirtyRect.setEmpty();
+ }
+
+ private int getKeyIndices(int x, int y, int[] allKeys) {
+ final CustomKeyboard.CustomKey[] keys = mKeys;
+ int primaryIndex = NOT_A_KEY;
+ int closestKey = NOT_A_KEY;
+ int closestKeyDist = mProximityThreshold + 1;
+ java.util.Arrays.fill(mDistances, Integer.MAX_VALUE);
+ int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
+ final int keyCount = nearestKeyIndices.length;
+ for (int i = 0; i < keyCount; i++) {
+ final CustomKeyboard.CustomKey key = keys[nearestKeyIndices[i]];
+ int dist = 0;
+ boolean isInside = key.isInside(x,y);
+ if (isInside) {
+ primaryIndex = nearestKeyIndices[i];
+ }
+
+ if (((mProximityCorrectOn && (dist = key.squaredDistanceFrom(x, y)) < mProximityThreshold) || isInside)
+ && key.codes[0] > 32) {
+ // Find insertion point
+ final int nCodes = key.codes.length;
+ if (dist < closestKeyDist) {
+ closestKeyDist = dist;
+ closestKey = nearestKeyIndices[i];
+ }
+
+ if (allKeys == null)
+ continue;
+
+ for (int j = 0; j < mDistances.length; j++) {
+ if (mDistances[j] > dist) {
+ // Make space for nCodes codes
+ System.arraycopy(mDistances, j, mDistances, j + nCodes, mDistances.length - j - nCodes);
+ System.arraycopy(allKeys, j, allKeys, j + nCodes, allKeys.length - j - nCodes);
+ for (int c = 0; c < nCodes; c++) {
+ allKeys[j + c] = key.codes[c];
+ mDistances[j + c] = dist;
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (primaryIndex == NOT_A_KEY) {
+ primaryIndex = closestKey;
+ }
+ return primaryIndex;
+ }
+
+ private void detectAndSendKey(int index, int x, int y, long eventTime, boolean isRepeated, boolean isReleaseKey) {
+// Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: index =" + index + "isRelease: "+ isReleaseKey);
+ if (index != NOT_A_KEY && index < mKeys.length) {
+ final CustomKeyboard.CustomKey key = mKeys[index];
+ if (key.text != null) {
+ mKeyboardActionListener.onText(key.text);
+// Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: detectAndSendKey - (key.text != null)");
+ mKeyboardActionListener.onRelease(NOT_A_KEY);
+ } else {
+ int code = key.codes[0];
+ //TextEntryState.keyPressedAt(key, x, y);
+ int[] codes = new int[MAX_NEARBY_KEYS];
+ Arrays.fill(codes, NOT_A_KEY);
+ getKeyIndices(x, y, codes);
+ // Multi-tap
+ if (mInMultiTap) {
+ if (mTapCount != -1) {
+ mKeyboardActionListener.onKey(CustomKeyboard.KEYCODE_DELETE, KEY_DELETE);
+ if (code != NOT_A_KEY) {
+ mKeysDownCodesSet.add(CustomKeyboard.KEYCODE_DELETE);
+ }
+ } else {
+ mTapCount = 0;
+ }
+ code = key.codes[mTapCount];
+ }
+// Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: detectAndSendKey - (key.text is null) code = " + code + " x: " + x + " y: " + y);
+ if (!isReleaseKey) {
+ mKeyboardActionListener.onKey(code, codes);
+ if (code != NOT_A_KEY) {
+ mKeysDownCodesSet.add(code);
+ }
+ }
+ if (!isRepeated || isReleaseKey) {
+ mKeyboardActionListener.onRelease(code);
+ if (code != NOT_A_KEY && mKeysDownCodesSet.contains(code)) {
+ mKeysDownCodesSet.remove(code);
+ }
+ }
+ }
+ mLastSentIndex = index;
+ mLastTapTime = eventTime;
+ }
+ }
+
+ /**
+ * Handle multi-tap keys by producing the key label for the current multi-tap state.
+ */
+ private CharSequence getPreviewText(CustomKeyboard.CustomKey key) {
+ if (mInMultiTap) {
+ // Multi-tap
+ mPreviewLabel.setLength(0);
+ mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
+ return adjustCase(mPreviewLabel);
+ } else {
+ return adjustCase(key.label);
+ }
+ }
+
+ private void showPreview(int keyIndex) {
+ int oldKeyIndex = mCurrentKeyIndex;
+ final PopupWindow previewPopup = mPreviewPopup;
+
+ mCurrentKeyIndex = keyIndex;
+ // Release the old key and press the new key
+ final CustomKeyboard.CustomKey[] keys = mKeys;
+ if (oldKeyIndex != mCurrentKeyIndex) {
+ if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
+ CustomKeyboard.CustomKey oldKey = keys[oldKeyIndex];
+ oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY);
+ invalidateKey(oldKeyIndex);
+ final int keyCode = oldKey.codes[0];
+ sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT, keyCode);
+ // TODO: We need to implement AccessibilityNodeProvider for this view.
+ sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, keyCode);
+ }
+ if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
+ CustomKeyboard.CustomKey newKey = keys[mCurrentKeyIndex];
+ newKey.onPressed();
+ invalidateKey(mCurrentKeyIndex);
+ final int keyCode = newKey.codes[0];
+ sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, keyCode);
+ // TODO: We need to implement AccessibilityNodeProvider for this view.
+ sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, keyCode);
+ }
+ }
+ // If key changed and preview is on ...
+ if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
+ mHandler.removeMessages(MSG_SHOW_PREVIEW);
+ if (previewPopup.isShowing()) {
+ if (keyIndex == NOT_A_KEY) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_REMOVE_PREVIEW), DELAY_AFTER_PREVIEW);
+ }
+ }
+ if (keyIndex != NOT_A_KEY) {
+ if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+ // Show right away, if it's already visible and finger is moving around
+ showKey(keyIndex);
+ } else {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0), DELAY_BEFORE_PREVIEW);
+ }
+ }
+ }
+ }
+
+// @UnsupportedAppUsage
+ private void showKey(final int keyIndex) {
+ final PopupWindow previewPopup = mPreviewPopup;
+ final CustomKeyboard.CustomKey[] keys = mKeys;
+ if (keyIndex < 0 || keyIndex >= mKeys.length) {
+ return;
+ }
+
+ CustomKeyboard.CustomKey key = keys[keyIndex];
+ if (key.icon != null) {
+ mPreviewText.setCompoundDrawables(null, null, null, key.iconPreview != null ? key.iconPreview : key.icon);
+ mPreviewText.setText(null);
+ } else {
+ mPreviewText.setCompoundDrawables(null, null, null, null);
+ mPreviewText.setText(getPreviewText(key));
+ if (key.label.length() > 1 && key.codes.length < 2) {
+ mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
+ mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
+ } else {
+ mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mPreviewTextSizeLarge);
+ mPreviewText.setTypeface(Typeface.DEFAULT);
+ }
+ }
+ mPreviewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ int popupWidth = Math.max(mPreviewText.getMeasuredWidth(), key.width + mPreviewText.getPaddingLeft() + mPreviewText.getPaddingRight());
+ final int popupHeight = mPreviewHeight;
+ LayoutParams lp = mPreviewText.getLayoutParams();
+ if (lp != null) {
+ lp.width = popupWidth;
+ lp.height = popupHeight;
+ }
+ if (!mPreviewCentered) {
+ mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + getPaddingLeft();
+ mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
+ } else {
+ // TODO: Fix this if centering is brought back
+ mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
+ mPopupPreviewY = - mPreviewText.getMeasuredHeight();
+ }
+ mHandler.removeMessages(MSG_REMOVE_PREVIEW);
+ getLocationInWindow(mCoordinates);
+ mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
+ mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
+
+ // Set the preview background state
+ mPreviewText.getBackground().setState(key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
+ mPopupPreviewX += mCoordinates[0];
+ mPopupPreviewY += mCoordinates[1];
+
+ // If the popup cannot be shown above the key, put it on the side
+ getLocationOnScreen(mCoordinates);
+ if (mPopupPreviewY + mCoordinates[1] < 0) {
+ // If the key you're pressing is on the left side of the keyboard, show the popup on
+ // the right, offset by enough to see at least one key to the left/right.
+ if (key.x + key.width <= getWidth() / 2) {
+ mPopupPreviewX += (int) (key.width * 2.5);
+ } else {
+ mPopupPreviewX -= (int) (key.width * 2.5);
+ }
+ mPopupPreviewY += popupHeight;
+ }
+
+ if (previewPopup.isShowing()) {
+ previewPopup.update(mPopupPreviewX, mPopupPreviewY, popupWidth, popupHeight);
+ } else {
+ previewPopup.setWidth(popupWidth);
+ previewPopup.setHeight(popupHeight);
+ previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY, mPopupPreviewX, mPopupPreviewY);
+ }
+ mPreviewText.setVisibility(VISIBLE);
+ }
+
+ private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
+ if (mAccessibilityManager.isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ onInitializeAccessibilityEvent(event);
+ final String text;
+ switch (code) {
+ case CustomKeyboard.KEYCODE_ALT:
+ text = mContext.getString(R.string.customkeyboardview_keycode_alt);
+ break;
+
+ case CustomKeyboard.KEYCODE_CANCEL:
+ text = mContext.getString(R.string.customkeyboardview_keycode_cancel);
+ break;
+
+ case CustomKeyboard.KEYCODE_DELETE:
+ text = mContext.getString(R.string.customkeyboardview_keycode_delete);
+ break;
+
+ case CustomKeyboard.KEYCODE_DONE:
+ text = mContext.getString(R.string.customkeyboardview_keycode_done);
+ break;
+
+ case CustomKeyboard.KEYCODE_MODE_CHANGE:
+ text = mContext.getString(R.string.customkeyboardview_keycode_mode_change);
+ break;
+
+ case CustomKeyboard.KEYCODE_SHIFT:
+ text = mContext.getString(R.string.customkeyboardview_keycode_shift);
+ break;
+
+ case '\n':
+ text = mContext.getString(R.string.customkeyboardview_keycode_enter);
+ break;
+
+ default:
+ text = String.valueOf((char) code);
+ }
+ event.getText().add(text);
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+ }
+
+ /**
+ * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
+ * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
+ * draws the cached buffer.
+ * @see #invalidateKey(int)
+ */
+ public void invalidateAllKeys() {
+ mDirtyRect.union(0, 0, getWidth(), getHeight());
+ mDrawPending = true;
+ invalidate();
+ }
+
+ /**
+ * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
+ * one key is changing it's content. Any changes that affect the position or size of the key
+ * may not be honored.
+ * @param keyIndex the index of the key in the attached {@link CustomKeyboard}.
+ * @see #invalidateAllKeys
+ */
+ public void invalidateKey(int keyIndex) {
+ if (mKeys == null) {
+ return;
+ }
+
+ if (keyIndex < 0 || keyIndex >= mKeys.length) {
+ return;
+ }
+
+ final CustomKeyboard.CustomKey key = mKeys[keyIndex];
+ mInvalidatedKey = key;
+ mDirtyRect.union(key.x + getPaddingLeft(),
+ key.y + getPaddingTop(),
+ key.x + key.width + getPaddingLeft(),
+ key.y + key.height + getPaddingTop());
+ onBufferDraw();
+
+ // The switch to hardware accelerated rendering in API 14 reduced the importance of the dirty rectangle.
+ // In API 21 the given rectangle is ignored entirely in favor of an internally-calculated area instead.
+ // Because of this, clients are encouraged to just call invalidate().
+ //invalidate(key.x + getPaddingLeft(), key.y + getPaddingTop(),key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
+ invalidate();
+ }
+
+// @UnsupportedAppUsage
+ private boolean openPopupIfRequired(MotionEvent me) {
+ // Check if we have a popup layout specified first.
+ if (mPopupLayout == 0) {
+ return false;
+ }
+
+ if (mCurrentKey < 0 || mCurrentKey >= mKeys.length) {
+ return false;
+ }
+
+ CustomKeyboard.CustomKey popupKey = mKeys[mCurrentKey];
+ boolean result = onLongPress(popupKey);
+ if (result) {
+ mAbortKey = true;
+ showPreview(NOT_A_KEY);
+ }
+ return result;
+ }
+
+ /**
+ * Called when a key is long pressed. By default this will open any popup keyboard associated
+ * with this key through the attributes popupLayout and popupCharacters.
+ * @param popupKey the key that was long pressed
+ * @return true if the long press is handled, false otherwise. Subclasses should call the
+ * method on the base class if the subclass doesn't wish to handle the call.
+ */
+ protected boolean onLongPress(CustomKeyboard.CustomKey popupKey) {
+ int popupKeyboardId = popupKey.popupResId;
+
+ if (popupKeyboardId != 0) {
+ mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
+ if (mMiniKeyboardContainer == null) {
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
+ mMiniKeyboard = (CustomKeyboardView) mMiniKeyboardContainer.findViewById(R.id.ScummVMKeyboardView);
+ View closeButton = mMiniKeyboardContainer.findViewById(android.R.id.closeButton);
+ if (closeButton != null) {
+ closeButton.setOnClickListener(this);
+ }
+ mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
+ public void onKey(int primaryCode, int[] keyCodes) {
+ mKeyboardActionListener.onKey(primaryCode, keyCodes);
+ if (primaryCode != NOT_A_KEY) {
+ mKeysDownCodesSet.add(primaryCode);
+ }
+ dismissPopupKeyboard();
+ }
+
+ public void onText(CharSequence text) {
+ mKeyboardActionListener.onText(text);
+ dismissPopupKeyboard();
+ }
+
+ public void swipeLeft() { }
+
+ public void swipeRight() { }
+
+ public void swipeUp() { }
+
+ public void swipeDown() { }
+
+ public void onPress(int primaryCode) {
+ mKeyboardActionListener.onPress(primaryCode);
+ }
+
+ public void onRelease(int primaryCode) {
+ mKeyboardActionListener.onRelease(primaryCode);
+ if (primaryCode != NOT_A_KEY && mKeysDownCodesSet.contains(primaryCode)) {
+ mKeysDownCodesSet.remove(primaryCode);
+ }
+ }
+ });
+ //mInputView.setSuggest(mSuggest);
+ CustomKeyboard keyboard;
+ if (popupKey.popupCharacters != null) {
+ keyboard = new CustomKeyboard(getContext(), popupKeyboardId, popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
+ } else {
+ keyboard = new CustomKeyboard(getContext(), popupKeyboardId);
+ }
+ mMiniKeyboard.setKeyboard(keyboard);
+ mMiniKeyboard.setPopupParent(this);
+ mMiniKeyboardContainer.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
+ mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
+ } else {
+ mMiniKeyboard = (CustomKeyboardView) mMiniKeyboardContainer.findViewById(R.id.ScummVMKeyboardView);
+ }
+ getLocationInWindow(mCoordinates);
+ mPopupX = popupKey.x + getPaddingLeft();
+ mPopupY = popupKey.y + getPaddingTop();
+ mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
+ mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
+ final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
+ final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
+ mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
+ mMiniKeyboard.setShifted(isShifted());
+ mPopupKeyboard.setContentView(mMiniKeyboardContainer);
+ mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
+ mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
+ mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
+ mMiniKeyboardOnScreen = true;
+ //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
+ invalidateAllKeys();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
+ final int action = event.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER: {
+ event.setAction(MotionEvent.ACTION_DOWN);
+ } break;
+
+ case MotionEvent.ACTION_HOVER_MOVE: {
+ event.setAction(MotionEvent.ACTION_MOVE);
+ } break;
+
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ event.setAction(MotionEvent.ACTION_UP);
+ } break;
+
+ }
+ return onTouchEvent(event);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent me) {
+ // Convert multi-pointer up/down events to single up/down events to
+ // deal with the typical multi-pointer behavior of two-thumb typing
+ final int pointerCount = me.getPointerCount();
+ final int action = me.getAction();
+ boolean result = false;
+ final long now = me.getEventTime();
+
+ if (pointerCount != mOldPointerCount) {
+ if (pointerCount == 1) {
+ // Send a down event for the latest pointer
+ MotionEvent down = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, me.getX(), me.getY(), me.getMetaState());
+ result = onModifiedTouchEvent(down, false);
+ down.recycle();
+ // If it's an up action, then deliver the up as well.
+ if (action == MotionEvent.ACTION_UP) {
+ // TODO should we do this performClick here?
+ //performClick();
+ result = onModifiedTouchEvent(me, true);
+ }
+ } else {
+ // Send an up event for the last pointer
+ MotionEvent up = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, mOldPointerX, mOldPointerY, me.getMetaState());
+ result = onModifiedTouchEvent(up, true);
+ // TODO should we do this performClick here?
+ //performClick();
+ up.recycle();
+ }
+ } else {
+ if (pointerCount == 1) {
+ // TODO should we do this performClick here?
+ //if (action == MotionEvent.ACTION_UP) {
+ // performClick();
+ //}
+ result = onModifiedTouchEvent(me, false);
+ mOldPointerX = me.getX();
+ mOldPointerY = me.getY();
+ } else {
+ // Don't do anything when 2 pointers are down and moving.
+ result = true;
+ }
+ }
+ mOldPointerCount = pointerCount;
+
+ return result;
+ }
+
+ @Override
+ public boolean performClick() {
+ super.performClick();
+ return true;
+ }
+
+ private boolean onModifiedTouchEvent(MotionEvent me, boolean possiblePoly) {
+ int touchX = (int) me.getX() - getPaddingLeft();
+ int touchY = (int) me.getY() - getPaddingTop();
+ if (touchY >= -mVerticalCorrection) {
+ touchY += mVerticalCorrection;
+ }
+ final int action = me.getAction();
+ final long eventTime = me.getEventTime();
+ int keyIndex = getKeyIndices(touchX, touchY, null);
+ mPossiblePoly = possiblePoly;
+
+ // Track the last few movements to look for spurious swipes.
+ if (action == MotionEvent.ACTION_DOWN) {
+ mSwipeTracker.clear();
+ }
+ mSwipeTracker.addMovement(me);
+
+ // Ignore all motion events until a DOWN.
+ if (mAbortKey && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
+ return true;
+ }
+
+ if (mGestureDetector.onTouchEvent(me)) {
+ showPreview(NOT_A_KEY);
+ mHandler.removeMessages(MSG_REPEAT);
+ mHandler.removeMessages(MSG_LONGPRESS);
+ return true;
+ }
+
+ // Needs to be called after the gesture detector gets a turn, as it may have
+ // displayed the mini keyboard
+ if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
+ return true;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mAbortKey = false;
+ mStartX = touchX;
+ mStartY = touchY;
+ mLastCodeX = touchX;
+ mLastCodeY = touchY;
+ mLastKeyTime = 0;
+ mCurrentKeyTime = 0;
+ mLastKey = NOT_A_KEY;
+ mCurrentKey = keyIndex;
+ mDownKey = keyIndex;
+ mDownTime = me.getEventTime();
+ mLastMoveTime = mDownTime;
+ checkMultiTap(eventTime, keyIndex);
+ mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? mKeys[keyIndex].codes[0] : 0);
+ if (mCurrentKey >= 0 && mKeys[mCurrentKey].repeatable) {
+ mRepeatKeyIndex = mCurrentKey;
+ Message msg = mHandler.obtainMessage(MSG_REPEAT);
+ mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
+ repeatKey();
+ // Delivering the key could have caused an abort
+ if (mAbortKey) {
+ mRepeatKeyIndex = NOT_A_KEY;
+ break;
+ }
+ }
+ if (mCurrentKey != NOT_A_KEY) {
+ Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
+ mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
+ }
+ showPreview(keyIndex);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ boolean continueLongPress = false;
+ if (keyIndex != NOT_A_KEY) {
+ if (mCurrentKey == NOT_A_KEY) {
+ mCurrentKey = keyIndex;
+ mCurrentKeyTime = eventTime - mDownTime;
+ } else {
+ if (keyIndex == mCurrentKey) {
+ mCurrentKeyTime += eventTime - mLastMoveTime;
+ continueLongPress = true;
+ } else if (mRepeatKeyIndex == NOT_A_KEY) {
+ resetMultiTap();
+ mLastKey = mCurrentKey;
+ mLastCodeX = mLastX;
+ mLastCodeY = mLastY;
+ mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
+ mCurrentKey = keyIndex;
+ mCurrentKeyTime = 0;
+ } else {
+ // if (mRepeatKeyIndex != NOT_A_KEY)
+ // New - handle the case where the user holds their finger and moves out of the key button
+ // Unfortunately, we will also get a "release" event on MotionEvent.ACTION_UP but that is safe since it is ignored
+ clearMessages();
+ if (mRepeatKeyIndex >= 0 && !mMiniKeyboardOnScreen && !mAbortKey) {
+// Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: onModifiedTouchEvent - MotionEvent.ACTION_MOVE Final Rep");
+ detectAndSendKey(mCurrentKey, touchX, touchY, eventTime, true, true);
+ }
+ showPreview(NOT_A_KEY);
+ Arrays.fill(mKeyIndices, NOT_A_KEY);
+ invalidateKey(keyIndex);
+ mRepeatKeyIndex = NOT_A_KEY;
+ }
+ }
+ }
+ if (!continueLongPress) {
+ // Cancel old longpress
+ mHandler.removeMessages(MSG_LONGPRESS);
+ // Start new longpress if key has changed
+ if (keyIndex != NOT_A_KEY) {
+ Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me);
+ mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT);
+ }
+ }
+ showPreview(mCurrentKey);
+ mLastMoveTime = eventTime;
+ break;
+
+ case MotionEvent.ACTION_UP:
+ clearMessages();
+ if (keyIndex == mCurrentKey) {
+ mCurrentKeyTime += eventTime - mLastMoveTime;
+ } else {
+ resetMultiTap();
+ mLastKey = mCurrentKey;
+ mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
+ mCurrentKey = keyIndex;
+ mCurrentKeyTime = 0;
+ }
+ if (mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME && mLastKey != NOT_A_KEY) {
+ mCurrentKey = mLastKey;
+ touchX = mLastCodeX;
+ touchY = mLastCodeY;
+ }
+ showPreview(NOT_A_KEY);
+ Arrays.fill(mKeyIndices, NOT_A_KEY);
+ // If we're not on a repeating key (which sends on a DOWN event)
+ if (mRepeatKeyIndex == NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
+// Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: onModifiedTouchEvent - MotionEvent.ACTION_UP No Rep");
+ detectAndSendKey(mCurrentKey, touchX, touchY, eventTime, false, true);
+ } else if (mRepeatKeyIndex >= 0 && mRepeatKeyIndex != NOT_A_KEY && !mMiniKeyboardOnScreen && !mAbortKey) {
+// Log.d(ScummVM.LOG_TAG, "CustomKeyboardView:: onModifiedTouchEvent - MotionEvent.ACTION_UP Final Rep");
+ detectAndSendKey(mCurrentKey, touchX, touchY, eventTime, true, true);
+ }
+ invalidateKey(keyIndex);
+ mRepeatKeyIndex = NOT_A_KEY;
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ clearMessages();
+ dismissPopupKeyboard();
+ mAbortKey = true;
+ showPreview(NOT_A_KEY);
+ invalidateKey(mCurrentKey);
+ break;
+ }
+ mLastX = touchX;
+ mLastY = touchY;
+ return true;
+ }
+
+// @UnsupportedAppUsage
+ private boolean repeatKey() {
+ CustomKeyboard.CustomKey key = mKeys[mRepeatKeyIndex];
+ detectAndSendKey(mCurrentKey, key.x, key.y, mLastTapTime, true, false);
+ return true;
+ }
+
+ protected void swipeRight() {
+ mKeyboardActionListener.swipeRight();
+ }
+
+ protected void swipeLeft() {
+ mKeyboardActionListener.swipeLeft();
+ }
+
+ protected void swipeUp() {
+ mKeyboardActionListener.swipeUp();
+ }
+
+ protected void swipeDown() {
+ mKeyboardActionListener.swipeDown();
+ }
+
+ public void closing() {
+ if (mPreviewPopup.isShowing()) {
+ mPreviewPopup.dismiss();
+ }
+ clearMessages();
+
+ dismissPopupKeyboard();
+ mBuffer = null;
+ mCanvas = null;
+ mMiniKeyboardCache.clear();
+
+ // Send onRelease to listener for a key that has sent down but on the time of closing has not sent release
+ Iterator<Integer> it = mKeysDownCodesSet.iterator();
+ while (it.hasNext()) {
+ int keyCode = it.next();
+ mKeyboardActionListener.onRelease(keyCode);
+// Log.d(ScummVM.LOG_TAG, "CustomKeyboardView closing - Send release for: " + keyCode);
+ }
+ mKeysDownCodesSet.clear();
+ }
+
+ private void clearMessages() {
+ if (mHandler != null) {
+ mHandler.removeMessages(MSG_REPEAT);
+ mHandler.removeMessages(MSG_LONGPRESS);
+ mHandler.removeMessages(MSG_SHOW_PREVIEW);
+ mHandler.removeCallbacksAndMessages(null);
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ closing();
+ }
+
+ private void dismissPopupKeyboard() {
+ if (mPopupKeyboard.isShowing()) {
+ mPopupKeyboard.dismiss();
+ mMiniKeyboardOnScreen = false;
+ invalidateAllKeys();
+ }
+ }
+
+ public boolean handleBack() {
+ if (mPopupKeyboard.isShowing()) {
+ dismissPopupKeyboard();
+ return true;
+ }
+ return false;
+ }
+
+ private void resetMultiTap() {
+ mLastSentIndex = NOT_A_KEY;
+ mTapCount = 0;
+ mLastTapTime = -1;
+ mInMultiTap = false;
+ }
+
+ private void checkMultiTap(long eventTime, int keyIndex) {
+ if (keyIndex == NOT_A_KEY) {
+ return;
+ }
+
+ CustomKeyboard.CustomKey key = mKeys[keyIndex];
+ if (key.codes.length > 1) {
+ mInMultiTap = true;
+ if (eventTime < mLastTapTime + MULTITAP_INTERVAL && keyIndex == mLastSentIndex) {
+ mTapCount = (mTapCount + 1) % key.codes.length;
+ return;
+ } else {
+ mTapCount = -1;
+ return;
+ }
+ }
+ if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
+ resetMultiTap();
+ }
+ }
+
+ private static class SwipeTracker {
+
+ static final int NUM_PAST = 4;
+ static final int LONGEST_PAST_TIME = 200;
+
+ final float mPastX[] = new float[NUM_PAST];
+ final float mPastY[] = new float[NUM_PAST];
+ final long mPastTime[] = new long[NUM_PAST];
+
+ float mYVelocity;
+ float mXVelocity;
+
+ public void clear() {
+ mPastTime[0] = 0;
+ }
+
+ public void addMovement(MotionEvent ev) {
+// Log.d(ScummVM.LOG_TAG, "SwipeTracker - Add Movement");
+ long time = ev.getEventTime();
+ final int N = ev.getHistorySize();
+ for (int i=0; i<N; i++) {
+ addPoint(ev.getHistoricalX(i), ev.getHistoricalY(i), ev.getHistoricalEventTime(i));
+ }
+ addPoint(ev.getX(), ev.getY(), time);
+ }
+
+ private void addPoint(float x, float y, long time) {
+ int drop = -1;
+ int i;
+ final long[] pastTime = mPastTime;
+ for (i = 0; i < NUM_PAST; i++) {
+ if (pastTime[i] == 0) {
+ break;
+ } else if (pastTime[i] < time-LONGEST_PAST_TIME) {
+ drop = i;
+ }
+ }
+ if (i == NUM_PAST && drop < 0) {
+ drop = 0;
+ }
+ if (drop == i) {
+ drop--;
+ }
+ final float[] pastX = mPastX;
+ final float[] pastY = mPastY;
+ if (drop >= 0) {
+ final int start = drop+1;
+ final int count = NUM_PAST-drop-1;
+ System.arraycopy(pastX, start, pastX, 0, count);
+ System.arraycopy(pastY, start, pastY, 0, count);
+ System.arraycopy(pastTime, start, pastTime, 0, count);
+ i -= (drop+1);
+ }
+ pastX[i] = x;
+ pastY[i] = y;
+ pastTime[i] = time;
+ i++;
+ if (i < NUM_PAST) {
+ pastTime[i] = 0;
+ }
+ }
+
+ public void computeCurrentVelocity(int units) {
+ computeCurrentVelocity(units, Float.MAX_VALUE);
+ }
+
+ public void computeCurrentVelocity(int units, float maxVelocity) {
+ final float[] pastX = mPastX;
+ final float[] pastY = mPastY;
+ final long[] pastTime = mPastTime;
+
+ final float oldestX = pastX[0];
+ final float oldestY = pastY[0];
+ final long oldestTime = pastTime[0];
+ float accumX = 0;
+ float accumY = 0;
+ int N = 0;
+ while (N < NUM_PAST) {
+ if (pastTime[N] == 0) {
+ break;
+ }
+ N++;
+ }
+
+ for (int i = 1; i < N; i++) {
+ final int dur = (int)(pastTime[i] - oldestTime);
+ if (dur == 0) {
+ continue;
+ }
+
+ float dist = pastX[i] - oldestX;
+ float vel = (dist/dur) * units; // pixels/frame.
+ if (accumX == 0) {
+ accumX = vel;
+ } else {
+ accumX = (accumX + vel) * .5f;
+ }
+
+ dist = pastY[i] - oldestY;
+ vel = (dist/dur) * units; // pixels/frame.
+ if (accumY == 0) {
+ accumY = vel;
+ } else {
+ accumY = (accumY + vel) * .5f;
+ }
+ }
+ mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math.min(accumX, maxVelocity);
+ mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math.min(accumY, maxVelocity);
+ }
+
+ public float getXVelocity() {
+ return mXVelocity;
+ }
+
+ public float getYVelocity() {
+ return mYVelocity;
+ }
+ } // end of: static class SwipeTracker
}
Commit: 95f1ca28475a69c66f8bac2e7b86bed8ced76196
https://github.com/scummvm/scummvm/commit/95f1ca28475a69c66f8bac2e7b86bed8ced76196
Author: antoniou79 (a.antoniou79 at gmail.com)
Date: 2023-08-07T17:31:02+03:00
Commit Message:
ANDROID: Fix double tap and right click hold for direct mouse mode
Also re-work slightly the system for handling the delayed mouse up touch event
Changed paths:
backends/platform/android/android.cpp
backends/platform/android/android.h
backends/platform/android/events.cpp
diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp
index ecc4caa84dc..4fd7c1a031e 100644
--- a/backends/platform/android/android.cpp
+++ b/backends/platform/android/android.cpp
@@ -168,7 +168,6 @@ OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) :
_audio_buffer_size(audio_buffer_size),
_screen_changeid(0),
_mixer(0),
- _queuedEventTime(0),
_event_queue_lock(0),
_touch_pt_down(),
_touch_pt_scroll(),
diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h
index af18643028f..4e6079d6915 100644
--- a/backends/platform/android/android.h
+++ b/backends/platform/android/android.h
@@ -100,6 +100,32 @@ void *androidGLgetProcAddress(const char *name);
class OSystem_Android : public ModularGraphicsBackend, Common::EventSource {
private:
+ static const int kQueuedInputEventDelay = 50;
+
+ struct EventWithDelay : public Common::Event {
+ /** The time which the delay starts counting from */
+ uint32 referTimeMillis;
+
+ /** The delay for the event to be handled */
+ uint32 delayMillis;
+
+ /** The connected EventType of the "connected" event that should be handled before this one */
+ Common::EventType connectedType;
+
+ /** A status flag indicating whether the "connected" event was handled */
+ bool connectedTypeExecuted;
+
+ EventWithDelay() : referTimeMillis(0), delayMillis(0), connectedType(Common::EVENT_INVALID), connectedTypeExecuted(false) {
+ }
+
+ void reset() {
+ referTimeMillis = 0;
+ delayMillis = 0;
+ connectedType = Common::EVENT_INVALID;
+ connectedTypeExecuted = false;
+ }
+ };
+
// passed from the dark side
int _audio_sample_rate;
int _audio_buffer_size;
@@ -120,8 +146,7 @@ private:
timeval _startTime;
Common::Queue<Common::Event> _event_queue;
- Common::Event _queuedEvent;
- uint32 _queuedEventTime;
+ EventWithDelay _delayedMouseBtnUpEvent;
Common::Mutex *_event_queue_lock;
Common::Point _touch_pt_down, _touch_pt_scroll, _touch_pt_dt, _touch_pt_multi;
@@ -183,6 +208,7 @@ public:
void pushEvent(int type, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6);
void pushEvent(const Common::Event &event);
void pushEvent(const Common::Event &event1, const Common::Event &event2);
+ void pushDelayedMouseBtnUpEvent();
TouchControls &getTouchControls() { return _touchControls; }
void applyTouchSettings(bool _3dMode, bool overlayShown);
diff --git a/backends/platform/android/events.cpp b/backends/platform/android/events.cpp
index 9b6d85cd676..dc5174f9ce1 100644
--- a/backends/platform/android/events.cpp
+++ b/backends/platform/android/events.cpp
@@ -50,8 +50,6 @@ static inline T scalef(T in, float numerator, float denominator) {
return static_cast<float>(in) * numerator / denominator;
}
-static const int kQueuedInputEventDelay = 50;
-
// analog joystick axis id (for internal use) - Should match the logic in ScummVMEventsModern.java
enum {
// auxilliary movement axis bitflags
@@ -389,8 +387,8 @@ static const Common::KeyCode jkeymap[] = {
};
void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
- int arg4, int arg5, int arg6) {
- Common::Event e;
+ int arg4, int arg5, int arg6) {
+ Common::Event ev0;
switch (type) {
case JE_SYS_KEY:
@@ -398,11 +396,11 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case JE_KEY:
switch (arg1) {
case AKEY_EVENT_ACTION_DOWN:
- e.type = Common::EVENT_KEYDOWN;
+ ev0.type = Common::EVENT_KEYDOWN;
break;
case AKEY_EVENT_ACTION_UP:
- e.type = Common::EVENT_KEYUP;
+ ev0.type = Common::EVENT_KEYUP;
break;
default:
@@ -416,96 +414,96 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
return;
} else {
// lets bet on the ascii code
- e.kbd.keycode = Common::KEYCODE_INVALID;
+ ev0.kbd.keycode = Common::KEYCODE_INVALID;
}
} else {
- e.kbd.keycode = jkeymap[arg2];
+ ev0.kbd.keycode = jkeymap[arg2];
}
if (arg5 > 0) {
- e.kbdRepeat = true;
+ ev0.kbdRepeat = true;
}
// map special keys to 'our' ascii codes
- switch (e.kbd.keycode) {
+ switch (ev0.kbd.keycode) {
case Common::KEYCODE_BACKSPACE:
// LOGD("received BACKSPACE");
- e.kbd.ascii = Common::ASCII_BACKSPACE;
+ ev0.kbd.ascii = Common::ASCII_BACKSPACE;
break;
case Common::KEYCODE_TAB:
- e.kbd.ascii = Common::ASCII_TAB;
+ ev0.kbd.ascii = Common::ASCII_TAB;
break;
case Common::KEYCODE_RETURN:
- e.kbd.ascii = Common::ASCII_RETURN;
+ ev0.kbd.ascii = Common::ASCII_RETURN;
break;
case Common::KEYCODE_ESCAPE:
- e.kbd.ascii = Common::ASCII_ESCAPE;
+ ev0.kbd.ascii = Common::ASCII_ESCAPE;
break;
case Common::KEYCODE_SPACE:
- e.kbd.ascii = Common::ASCII_SPACE;
+ ev0.kbd.ascii = Common::ASCII_SPACE;
break;
case Common::KEYCODE_F1:
- e.kbd.ascii = Common::ASCII_F1;
+ ev0.kbd.ascii = Common::ASCII_F1;
break;
case Common::KEYCODE_F2:
- e.kbd.ascii = Common::ASCII_F2;
+ ev0.kbd.ascii = Common::ASCII_F2;
break;
case Common::KEYCODE_F3:
- e.kbd.ascii = Common::ASCII_F3;
+ ev0.kbd.ascii = Common::ASCII_F3;
break;
case Common::KEYCODE_F4:
- e.kbd.ascii = Common::ASCII_F4;
+ ev0.kbd.ascii = Common::ASCII_F4;
break;
case Common::KEYCODE_F5:
- e.kbd.ascii = Common::ASCII_F5;
+ ev0.kbd.ascii = Common::ASCII_F5;
break;
case Common::KEYCODE_F6:
- e.kbd.ascii = Common::ASCII_F6;
+ ev0.kbd.ascii = Common::ASCII_F6;
break;
case Common::KEYCODE_F7:
- e.kbd.ascii = Common::ASCII_F7;
+ ev0.kbd.ascii = Common::ASCII_F7;
break;
case Common::KEYCODE_F8:
- e.kbd.ascii = Common::ASCII_F8;
+ ev0.kbd.ascii = Common::ASCII_F8;
break;
case Common::KEYCODE_F9:
- e.kbd.ascii = Common::ASCII_F9;
+ ev0.kbd.ascii = Common::ASCII_F9;
break;
case Common::KEYCODE_F10:
- e.kbd.ascii = Common::ASCII_F10;
+ ev0.kbd.ascii = Common::ASCII_F10;
break;
case Common::KEYCODE_F11:
- e.kbd.ascii = Common::ASCII_F11;
+ ev0.kbd.ascii = Common::ASCII_F11;
break;
case Common::KEYCODE_F12:
- e.kbd.ascii = Common::ASCII_F12;
+ ev0.kbd.ascii = Common::ASCII_F12;
break;
default:
- e.kbd.ascii = arg3;
+ ev0.kbd.ascii = arg3;
break;
}
// arg4 is the metastate of the key press event
// check for "Shift" key modifier
if (arg4 & AMETA_SHIFT_MASK) {
- e.kbd.flags |= Common::KBD_SHIFT;
+ ev0.kbd.flags |= Common::KBD_SHIFT;
}
// We revert the commit to disable the Alt modifier
@@ -522,41 +520,41 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
// Fn combos will be broken (like Fn+q, which needs to end as 1 and
// not ALT+1). Do not want.
//if (arg4 & JMETA_ALT)
- // e.kbd.flags |= Common::KBD_ALT;
+ // ev0.kbd.flags |= Common::KBD_ALT;
// end of old comment --
// check for "Alt" key modifier
if (arg4 & (AMETA_ALT_MASK)) {
- e.kbd.flags |= Common::KBD_ALT;
+ ev0.kbd.flags |= Common::KBD_ALT;
}
// check for "Ctrl" key modifier (We set Sym key to also behave as Ctrl)
if (arg4 & (AMETA_SYM_ON | AMETA_CTRL_MASK)) {
- e.kbd.flags |= Common::KBD_CTRL;
+ ev0.kbd.flags |= Common::KBD_CTRL;
}
// check for "Meta" key modifier
if (arg4 & (AMETA_META_MASK)) {
- e.kbd.flags |= Common::KBD_META;
+ ev0.kbd.flags |= Common::KBD_META;
}
// check for CAPS key modifier
if (arg4 & (AMETA_CAPS_LOCK_ON)) {
- e.kbd.flags |= Common::KBD_CAPS;
+ ev0.kbd.flags |= Common::KBD_CAPS;
}
// check for NUM Lock key modifier
if (arg4 & (AMETA_NUM_LOCK_ON)) {
- e.kbd.flags |= Common::KBD_NUM;
+ ev0.kbd.flags |= Common::KBD_NUM;
}
// check for Scroll Lock key modifier
if (arg4 & (AMETA_SCROLL_LOCK_ON)) {
- e.kbd.flags |= Common::KBD_SCRL;
+ ev0.kbd.flags |= Common::KBD_SCRL;
}
- pushEvent(e);
-
- return;
+// LOGD("JE_KEY pushing event type: %d kbdcode: %d ascii: %d flags: %d repeats: %d", ev0.type, ev0.kbd.keycode, ev0.kbd.ascii, ev0.kbd.flags, ev0.kbdRepeat? 1: 0);
+ pushEvent(ev0);
+ break;
case JE_DPAD:
// For now, this behavior, emulating mouse movement and left mouse clicking here for DPAD button presses,
@@ -571,21 +569,19 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case AKEYCODE_DPAD_RIGHT:
// Treat as mouse movement
if (arg1 != AKEY_EVENT_ACTION_DOWN)
- return;
-
- e.type = Common::EVENT_MOUSEMOVE;
-
- e.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
+ break;
+ ev0.type = Common::EVENT_MOUSEMOVE;
+ ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
{
int16 *c;
int s;
if (arg2 == AKEYCODE_DPAD_UP || arg2 == AKEYCODE_DPAD_DOWN) {
- c = &e.mouse.y;
+ c = &ev0.mouse.y;
s = _eventScaleY;
} else {
- c = &e.mouse.x;
+ c = &ev0.mouse.x;
s = _eventScaleX;
}
@@ -599,20 +595,18 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
*c += f;
}
}
-
- pushEvent(e);
-
- return;
+ pushEvent(ev0);
+ break;
case AKEYCODE_DPAD_CENTER:
// Treat as mouse click (left click)
switch (arg1) {
case AKEY_EVENT_ACTION_DOWN:
- e.type = Common::EVENT_LBUTTONDOWN;
+ ev0.type = Common::EVENT_LBUTTONDOWN;
break;
case AKEY_EVENT_ACTION_UP:
- e.type = Common::EVENT_LBUTTONUP;
+ ev0.type = Common::EVENT_LBUTTONUP;
break;
default:
@@ -620,21 +614,20 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
return;
}
- e.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
-
- pushEvent(e);
-
- return;
+ ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
+ pushEvent(ev0);
+ break;
}
+ break;
case JE_TV_REMOTE:
switch (arg1) {
case AKEY_EVENT_ACTION_DOWN:
- e.type = Common::EVENT_KEYDOWN;
+ ev0.type = Common::EVENT_KEYDOWN;
break;
case AKEY_EVENT_ACTION_UP:
- e.type = Common::EVENT_KEYUP;
+ ev0.type = Common::EVENT_KEYUP;
break;
default:
@@ -649,9 +642,9 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
// fall through
case AKEYCODE_MEDIA_PLAY_PAUSE:
// Treat as keyboard presses, since they have equivalent hardware keyboard keys
- e.kbd.keycode = jkeymap[arg2];
+ ev0.kbd.keycode = jkeymap[arg2];
if (arg5 > 0) {
- e.kbdRepeat = true;
+ ev0.kbdRepeat = true;
}
break;
@@ -666,9 +659,8 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
}
- pushEvent(e);
-
- return;
+ pushEvent(ev0);
+ break;
case JE_DOWN:
// LOGD("JE_DOWN");
@@ -689,7 +681,7 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case JE_SCROLL:
// LOGD("JE_SCROLL");
- e.type = Common::EVENT_MOUSEMOVE;
+ ev0.type = Common::EVENT_MOUSEMOVE;
if (_touch_mode == TOUCH_MODE_TOUCHPAD) {
if (_touch_pt_scroll.x == -1 && _touch_pt_scroll.y == -1) {
@@ -698,19 +690,18 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
return;
}
- e.mouse.x = (arg3 - _touch_pt_scroll.x) * 100 / _touchpad_scale;
- e.mouse.y = (arg4 - _touch_pt_scroll.y) * 100 / _touchpad_scale;
- e.mouse += _touch_pt_down;
+ ev0.mouse.x = (arg3 - _touch_pt_scroll.x) * 100 / _touchpad_scale;
+ ev0.mouse.y = (arg4 - _touch_pt_scroll.y) * 100 / _touchpad_scale;
+ ev0.mouse += _touch_pt_down;
} else {
- e.mouse.x = arg3;
- e.mouse.y = arg4;
+ ev0.mouse.x = arg3;
+ ev0.mouse.y = arg4;
}
- e.relMouse.x = arg5;
- e.relMouse.y = arg6;
-
- pushEvent(e);
+ ev0.relMouse.x = arg5;
+ ev0.relMouse.y = arg6;
- return;
+ pushEvent(ev0);
+ break;
case JE_TAP:
// arg1 = mouse x
@@ -718,13 +709,13 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
// arg3 is (int)(e.getEventTime() - e.getDownTime())
// LOGD("JE_TAP - arg3 %d", arg3);
- e.type = Common::EVENT_MOUSEMOVE;
+ ev0.type = Common::EVENT_MOUSEMOVE;
if (_touch_mode == TOUCH_MODE_TOUCHPAD) {
- e.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
+ ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
} else {
- e.mouse.x = arg1;
- e.mouse.y = arg2;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
}
{
@@ -740,6 +731,8 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
// Note: for hold one finger and move gesture, this is the "move cursor around" action.
// Thus, the simple tap and hold cannot be used for "hold left mouse button and drag" behavior.
// This is the reason we use "double tap and move" to emulate that specific behavior.
+ // Note: This one-finger gesture cannot currently be used for "hold right mouse and drag" or "hold middle mouse and drag" either.
+ // The gesture for those uses more than one fingers (see JE_MULTI).
// TODO This might be unwanted "alternate" behavior (better to have it as optional?)
// TODO put these time (in milliseconds) values in some option dlg?
if (arg3 > 1500) {
@@ -756,53 +749,48 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
up = Common::EVENT_LBUTTONUP;
}
+ pushDelayedMouseBtnUpEvent();
+ Common::Event ev1 = ev0;
+ ev1.type = down; // mouse down
_event_queue_lock->lock();
-
-// LOGD("JE_TAP - _queuedEventTime %d ", _queuedEventTime);
- if (_queuedEventTime) {
- // if another event is queued to be served by PollEvent (but not in _event_queue)
- // at the time of this JE_TAP event
- // then push that pending _queuedEvent event to the queue for direct service
- // This is done because we will replace the _queuedEvent below
-// LOGD("JE_TAP -pushing old _queuedEvent to event queue");
- _event_queue.push(_queuedEvent);
- }
-
if (_touch_mode != TOUCH_MODE_TOUCHPAD) {
// In this case the mouse move is done in "direct mode"
// ie. the cursor jumps to where the tap occurred
// so we don't have relMouse coordinates to set for the event
- _event_queue.push(e);
+ _event_queue.push(ev0);
}
-
- e.type = down;
- _event_queue.push(e);
-
- e.type = up;
- _queuedEvent = e;
- _queuedEventTime = getMillis() + kQueuedInputEventDelay;
-
+ _event_queue.push(ev1);
+
+ _delayedMouseBtnUpEvent.mouse = ev1.mouse;
+ _delayedMouseBtnUpEvent.type = up;
+ // Mouse-up event is handled with a small delay as some engines require such a delay for mouse up events (Gob, Toonstruck)
+ // See bug ticket: https://bugs.scummvm.org/ticket/5942
+// LOGD("JE_TAP - VALID: ev0: mx %d my %d t: %d | ev1: mx %d my %d t: %d | ev2: mx %d my %d t: %d", ev0.mouse.x, ev0.mouse.y, ev0.type, ev1.mouse.x, ev1.mouse.y, ev1.type, ev2.mouse.x, ev2.mouse.y, ev2.type);
+ _delayedMouseBtnUpEvent.referTimeMillis = getMillis(true);
+ _delayedMouseBtnUpEvent.delayMillis = kQueuedInputEventDelay;
+ _delayedMouseBtnUpEvent.connectedType = ev1.type;
+ _delayedMouseBtnUpEvent.connectedTypeExecuted = false;
_event_queue_lock->unlock();
}
-
- return;
+ break;
case JE_DOUBLE_TAP:
// arg1 = mouse x
// arg2 = mouse y
// arg3 = AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_ACTION_UP, AMOTION_EVENT_ACTION_MOVE
-// LOGD("JE_DOUBLE_TAP - arg3 %d", arg3);
+ // NOTE: Typically in a double tap event:
+ // Before the ACTION_DOWN event, we also have ALREADY pushed a JE_DOWN event (via ScummVMEventsBase's onDown())
+ // and then a JE_TAP event (action UP) via ScummVMEventsBase's onSingleTapUp().
+ // Before the ACTION_UP event, we also have ALREADY pushed a JE_DOWN event (via ScummVMEventsBase's onDown()).
+// LOGD("JE_DOUBLE_TAP - x: %d y: %d, arg3: %d", arg1, arg2, arg3);
- e.type = Common::EVENT_MOUSEMOVE;
+ ev0.type = Common::EVENT_MOUSEMOVE;
+ ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
- if (_touch_mode == TOUCH_MODE_TOUCHPAD) {
- e.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
- } else {
- e.mouse.x = arg1;
- e.mouse.y = arg2;
- }
+// LOGD("JE_DOUBLE_TAP 2 - x: %d y: %d, type: %d, arg3: %d", ev0.mouse.x , ev0.mouse.y, ev0.type, arg3);
{
+ Common::Event ev1 = ev0;
Common::EventType dptype = Common::EVENT_INVALID;
switch (arg3) {
@@ -819,6 +807,7 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case AMOTION_EVENT_ACTION_MOVE:
// held and moved
if (_touch_pt_dt.x == -1 && _touch_pt_dt.y == -1) {
+// LOGD("JE_DOUBLE_TAP MOVE UPDATE dtx = %d, dty = %d and return", arg1, arg2);
_touch_pt_dt.x = arg1;
_touch_pt_dt.y = arg2;
return;
@@ -827,9 +816,13 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
dptype = Common::EVENT_MOUSEMOVE;
if (_touch_mode == TOUCH_MODE_TOUCHPAD) {
- e.mouse.x = (arg1 - _touch_pt_dt.x) * 100 / _touchpad_scale;
- e.mouse.y = (arg2 - _touch_pt_dt.y) * 100 / _touchpad_scale;
- e.mouse += _touch_pt_down;
+ ev1.mouse.x = (arg1 - _touch_pt_dt.x) * 100 / _touchpad_scale;
+ ev1.mouse.y = (arg2 - _touch_pt_dt.y) * 100 / _touchpad_scale;
+ ev1.mouse += _touch_pt_down;
+// LOGD("JE_DOUBLE_TAP MOVE EVENT updating coords and mouse- x: %d y: %d, arg3: %d", ev1.mouse.x , ev1.mouse.y, arg3);
+ } else {
+ ev1.mouse.x = arg1;
+ ev1.mouse.y = arg2;
}
break;
@@ -839,14 +832,16 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
return;
}
- _event_queue_lock->lock();
- _event_queue.push(e);
- e.type = dptype;
- _event_queue.push(e);
- _event_queue_lock->unlock();
+ ev1.type = dptype;
+ if (ev1.type == Common::EVENT_LBUTTONDOWN) {
+ pushDelayedMouseBtnUpEvent();
+ }
+// // Commented out: Don't do a "move mouse" pre-event on a double_tap
+// pushEvent(ev0, ev1);
+// LOGD("JE_DOUBLE_TAP - Pushing DOUBLE TAP event - x: %d y: %d, type: %d, arg3: %d", ev1.mouse.x , ev1.mouse.y, ev1.type, arg3);
+ pushEvent(ev1);
}
-
- return;
+ break;
case JE_MULTI:
// Documentation: https://developer.android.com/training/gestures/multi
@@ -860,18 +855,19 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
// - ACTION_DOWN â For the first pointer that touches the screen. This starts the gesture. The pointer data for this pointer is always at index 0 in the MotionEvent.
// - ACTION_MOVE â A change has happened during a press gesture
// - ACTION_UP â Sent when the last pointer leaves the screen.
-// LOGD("JE_MULTI - fingersDown=%d arg2=%d", arg1, arg2);
+ LOGD("JE_MULTI - fingersDown=%d arg2=%d", arg1, arg2);
- e.type = Common::EVENT_MOUSEMOVE;
+ ev0.type = Common::EVENT_MOUSEMOVE;
if (_touch_mode == TOUCH_MODE_TOUCHPAD) {
- e.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
+ ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
} else {
- e.mouse.x = arg3;
- e.mouse.y = arg4;
+ ev0.mouse.x = arg3;
+ ev0.mouse.y = arg4;
}
{
+ Common::Event ev1 = ev0;
Common::EventType multitype = Common::EVENT_INVALID;
switch (arg2 & AMOTION_EVENT_ACTION_MASK) {
@@ -895,25 +891,17 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
case 2:
// LOGD("AMOTION_EVENT_ACTION_POINTER_DOWN arg1: %d --> Right Mouse Button", arg1);
multitype = Common::EVENT_RBUTTONDOWN;
- //up = Common::EVENT_RBUTTONUP;
-// // Don't add immediately in case it is superseded by a third finger --> middle mouse button
break;
case 3:
-// if (_queuedEventTime && _queuedEvent.type == Common::EVENT_RBUTTONDOWN) {
-// _queuedEventTime = 0; // cancel right mouse button down
-// LOGD("AMOTION_EVENT_ACTION_POINTER_DOWN arg1: %d --> Middle Mouse Button", arg1);
-// multitype = Common::EVENT_MBUTTONDOWN;
-// }
+// LOGD("AMOTION_EVENT_ACTION_POINTER_DOWN arg1: %d --> Middle Mouse Button", arg1);
multitype = Common::EVENT_MBUTTONDOWN;
- //up = Common::EVENT_MBUTTONUP;
break;
default:
LOGE("AMOTION_EVENT_ACTION_POINTER_DOWN - unmapped multi tap (arg1): %d", arg1);
return;
}
-
break;
case AMOTION_EVENT_ACTION_CANCEL:
@@ -934,20 +922,14 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
// return;
// }
- //Common::EventType up;
-
switch (arg1) {
case 2:
// LOGD("AMOTION_EVENT_ACTION_POINTER_UP arg1: %d --> Right Mouse Button", arg1);
- //e.type = Common::EVENT_RBUTTONDOWN;
- //up = Common::EVENT_RBUTTONUP;
multitype = Common::EVENT_RBUTTONUP;
break;
case 3:
// LOGD("AMOTION_EVENT_ACTION_POINTER_UP arg1: %d --> Middle Mouse Button", arg1);
- //e.type = Common::EVENT_MBUTTONDOWN;
- //up = Common::EVENT_MBUTTONUP;
multitype = Common::EVENT_MBUTTONUP;
break;
@@ -955,24 +937,6 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
LOGE("AMOTION_EVENT_ACTION_POINTER_UP - unmapped multi tap (arg1): %d", arg1);
return;
}
-
-// e.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
-//
-// _event_queue_lock->lock();
-//
-// LOGD("AMOTION_EVENT_ACTION_POINTER_UP - _queuedEventTime %d ", _queuedEventTime);
-// if (_queuedEventTime) {
-// LOGD("AMOTION_EVENT_ACTION_POINTER_UP -pushing _queuedEvent (up) to event queue");
-// _event_queue.push(_queuedEvent);
-// }
-//
-// _event_queue.push(e);
-//
-// e.type = up;
-// _queuedEvent = e;
-// _queuedEventTime = getMillis() + kQueuedInputEventDelay;
-//
-// _event_queue_lock->unlock();
break;
case AMOTION_EVENT_ACTION_MOVE:
@@ -990,11 +954,13 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
multitype = Common::EVENT_MOUSEMOVE;
- // TODO TO TEST for non-touchpad mode too!
if (_touch_mode == TOUCH_MODE_TOUCHPAD) {
- e.mouse.x = (arg3 - _touch_pt_multi.x) * 100 / _touchpad_scale;
- e.mouse.y = (arg4 - _touch_pt_multi.y) * 100 / _touchpad_scale;
- e.mouse += _touch_pt_down; // TODO maybe we need another reference point???
+ ev1.mouse.x = (arg3 - _touch_pt_multi.x) * 100 / _touchpad_scale;
+ ev1.mouse.y = (arg4 - _touch_pt_multi.y) * 100 / _touchpad_scale;
+ ev1.mouse += _touch_pt_down; // TODO maybe we need another reference point???
+ } else {
+ ev1.mouse.x = arg3;
+ ev1.mouse.y = arg4;
}
break;
@@ -1003,68 +969,37 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
return;
}
- _event_queue_lock->lock();
- if (_queuedEventTime) {
-// // if another event is queued to be served by PollEvent (but not in _event_queue)
-// // at the time of this JE_MULTI event
-// // then push that pending _queuedEvent event to the queue for direct service
-// // This is done because we will push a new event to the queue and also might replace the _queuedEvent below
-// LOGD("JE_MULTI -pushing old _queuedEvent to event queue");
- _event_queue.push(_queuedEvent);
- }
-// if (_queuedEventTime && _queuedEvent.type != Common::EVENT_RBUTTONDOWN) {
-// // if another event is queued to be served by PollEvent (but not in _event_queue)
-// // and it is not Common::EVENT_RBUTTONDOWN (which is only created by the JE_MULTI event as a _queuedEvent)
-// // at the time of this JE_MULTI event
-// // then push that pending _queuedEvent event to the queue for direct service
-// // This is done because we will push a new event to the queue and also might replace the _queuedEvent below
-// LOGD("JE_MULTI -pushing old _queuedEvent to event queue");
-// _event_queue.push(_queuedEvent);
-// }
-
- // push the default move event
- _event_queue.push(e);
- e.type = multitype;
-
- if (e.type != Common::EVENT_INVALID) {
-// if (e.type == Common::EVENT_RBUTTONDOWN) {
-// _queuedEvent = e; // enqueue the Right Button DOWN event with a 200 ms delay
-// _queuedEventTime = getMillis() + 200; // TODO add to constants like kQueuedInputEventDelay is
-// } else {
-// // TODO what if this is a quick Common::EVENT_RBUTTONUP?
-// if (_queuedEventTime && _queuedEvent.type == Common::EVENT_RBUTTONDOWN && e.type == Common::EVENT_RBUTTONUP) {
-// _event_queue.push(_queuedEvent);
-// _queuedEvent = e; // enqueue the Right Button UP event with a kQueuedInputEventDelay
-// _queuedEventTime = getMillis() + kQueuedInputEventDelay;
-// } else {
- _event_queue.push(e);
-// }
-// }
+ ev1.type = multitype;
+ if (ev1.type == Common::EVENT_RBUTTONDOWN || ev1.type == Common::EVENT_MBUTTONDOWN) {
+ pushDelayedMouseBtnUpEvent();
}
- _event_queue_lock->unlock();
+ if (ev1.type != Common::EVENT_INVALID) {
+ pushEvent(ev0, ev1);
+ } else {
+ pushEvent(ev0);
+ }
}
-
- return;
+ break;
case JE_BALL:
- e.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
+ ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
switch (arg1) {
case AMOTION_EVENT_ACTION_DOWN:
- e.type = Common::EVENT_LBUTTONDOWN;
+ ev0.type = Common::EVENT_LBUTTONDOWN;
break;
case AMOTION_EVENT_ACTION_UP:
- e.type = Common::EVENT_LBUTTONUP;
+ ev0.type = Common::EVENT_LBUTTONUP;
break;
case AMOTION_EVENT_ACTION_MOVE:
- e.type = Common::EVENT_MOUSEMOVE;
+ ev0.type = Common::EVENT_MOUSEMOVE;
// already multiplied by 100
- e.mouse.x += arg2 * _trackball_scale / _eventScaleX;
- e.mouse.y += arg3 * _trackball_scale / _eventScaleY;
+ ev0.mouse.x += arg2 * _trackball_scale / _eventScaleX;
+ ev0.mouse.y += arg3 * _trackball_scale / _eventScaleY;
break;
@@ -1072,139 +1007,111 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
LOGE("unhandled JE_BALL jaction on system key: %d", arg1);
return;
}
-
- pushEvent(e);
-
- return;
+ pushEvent(ev0);
+ break;
case JE_MOUSE_MOVE:
- e.type = Common::EVENT_MOUSEMOVE;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_MOUSEMOVE;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_LMB_DOWN:
- e.type = Common::EVENT_LBUTTONDOWN;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_LBUTTONDOWN;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_LMB_UP:
- e.type = Common::EVENT_LBUTTONUP;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_LBUTTONUP;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_RMB_DOWN:
- e.type = Common::EVENT_RBUTTONDOWN;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_RBUTTONDOWN;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_RMB_UP:
- e.type = Common::EVENT_RBUTTONUP;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_RBUTTONUP;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_MMB_DOWN:
- e.type = Common::EVENT_MBUTTONDOWN;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_MBUTTONDOWN;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_MMB_UP:
- e.type = Common::EVENT_MBUTTONUP;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_MBUTTONUP;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_BMB_DOWN:
- e.type = Common::EVENT_X1BUTTONDOWN;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_X1BUTTONDOWN;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_BMB_UP:
- e.type = Common::EVENT_X1BUTTONUP;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_X1BUTTONUP;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_FMB_DOWN:
- e.type = Common::EVENT_X2BUTTONDOWN;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_X2BUTTONDOWN;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_FMB_UP:
- e.type = Common::EVENT_X2BUTTONUP;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_X2BUTTONUP;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+ pushEvent(ev0);
+ break;
case JE_MOUSE_WHEEL_UP:
// Rolling wheel upwards
- e.type = Common::EVENT_WHEELUP;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-// e.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_WHEELUP;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+// ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
+ pushEvent(ev0);
+ break;
case JE_MOUSE_WHEEL_DOWN:
// Rolling wheel downwards
- e.type = Common::EVENT_WHEELDOWN;
- e.mouse.x = arg1;
- e.mouse.y = arg2;
-// e.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_WHEELDOWN;
+ ev0.mouse.x = arg1;
+ ev0.mouse.y = arg2;
+// ev0.mouse = dynamic_cast<AndroidCommonGraphics *>(_graphicsManager)->getMousePosition();
+ pushEvent(ev0);
+ break;
case JE_GAMEPAD:
switch (arg1) {
case AKEY_EVENT_ACTION_DOWN:
- e.type = Common::EVENT_JOYBUTTON_DOWN;
+ ev0.type = Common::EVENT_JOYBUTTON_DOWN;
break;
case AKEY_EVENT_ACTION_UP:
- e.type = Common::EVENT_JOYBUTTON_UP;
+ ev0.type = Common::EVENT_JOYBUTTON_UP;
break;
default:
LOGE("unhandled jaction on gamepad key: %d", arg1);
@@ -1213,39 +1120,39 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
switch (arg2) {
case AKEYCODE_BUTTON_START:
- e.joystick.button = Common::JOYSTICK_BUTTON_START;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_START;
break;
case AKEYCODE_BUTTON_SELECT:
- e.joystick.button = Common::JOYSTICK_BUTTON_BACK;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_BACK;
break;
case AKEYCODE_BUTTON_MODE:
- e.joystick.button = Common::JOYSTICK_BUTTON_GUIDE;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_GUIDE;
break;
case AKEYCODE_BUTTON_A:
- e.joystick.button = Common::JOYSTICK_BUTTON_A;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_A;
break;
case AKEYCODE_BUTTON_B:
- e.joystick.button = Common::JOYSTICK_BUTTON_B;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_B;
break;
case AKEYCODE_BUTTON_X:
- e.joystick.button = Common::JOYSTICK_BUTTON_X;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_X;
break;
case AKEYCODE_BUTTON_Y:
- e.joystick.button = Common::JOYSTICK_BUTTON_Y;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_Y;
break;
case AKEYCODE_BUTTON_L1:
- e.joystick.button = Common::JOYSTICK_BUTTON_LEFT_SHOULDER;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_LEFT_SHOULDER;
break;
case AKEYCODE_BUTTON_R1:
- e.joystick.button = Common::JOYSTICK_BUTTON_RIGHT_SHOULDER;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_RIGHT_SHOULDER;
break;
// // NOTE As of yet JOYSTICK_BUTTON_LEFT_TRIGGER, JOYSTICK_BUTTON_RIGHT_TRIGGER are missing as "buttons" from the hardware-input source code
// // There are controllers like PS5's DualSense that trigger these buttons presses, albeit for wrong buttons (Create and Menu gamepad buttons)
@@ -1253,104 +1160,101 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
// // PS3's DS3 also triggers these button presses but also generates a movement event so perhaps we can capture them that way
// // (as generic joystick movement, "JOYSTICK_AXIS_LEFT_TRIGGER", "JOYSTICK_AXIS_RIGHT_TRIGGER" hardware-input).
// case AKEYCODE_BUTTON_L2:
-// e.joystick.button = Common::JOYSTICK_BUTTON_LEFT_TRIGGER;
+// ev0.joystick.button = Common::JOYSTICK_BUTTON_LEFT_TRIGGER;
// break;
//
// case AKEYCODE_BUTTON_R2:
-// e.joystick.button = Common::JOYSTICK_BUTTON_RIGHT_TRIGGER;
+// ev0.joystick.button = Common::JOYSTICK_BUTTON_RIGHT_TRIGGER;
// break;
//
case AKEYCODE_BUTTON_THUMBL:
- e.joystick.button = Common::JOYSTICK_BUTTON_LEFT_STICK;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_LEFT_STICK;
break;
case AKEYCODE_BUTTON_THUMBR:
- e.joystick.button = Common::JOYSTICK_BUTTON_RIGHT_STICK;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_RIGHT_STICK;
break;
case AKEYCODE_DPAD_UP:
- e.joystick.button = Common::JOYSTICK_BUTTON_DPAD_UP;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_DPAD_UP;
break;
case AKEYCODE_DPAD_DOWN:
- e.joystick.button = Common::JOYSTICK_BUTTON_DPAD_DOWN;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_DPAD_DOWN;
break;
case AKEYCODE_DPAD_LEFT:
- e.joystick.button = Common::JOYSTICK_BUTTON_DPAD_LEFT;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_DPAD_LEFT;
break;
case AKEYCODE_DPAD_RIGHT:
- e.joystick.button = Common::JOYSTICK_BUTTON_DPAD_RIGHT;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_DPAD_RIGHT;
break;
case AKEYCODE_DPAD_CENTER:
- e.joystick.button = Common::JOYSTICK_BUTTON_DPAD_CENTER;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_DPAD_CENTER;
break;
default:
LOGW("unmapped gamepad key: %d", arg2);
return;
}
-
- pushEvent(e);
-
+ pushEvent(ev0);
break;
case JE_JOYSTICK:
-
switch (arg1) {
// AMOTION_EVENT_ACTION_MOVE is 2 in NDK (https://developer.android.com/ndk/reference/group/input)
case AMOTION_EVENT_ACTION_MOVE:
- e.type = Common::EVENT_JOYAXIS_MOTION;
+ ev0.type = Common::EVENT_JOYAXIS_MOTION;
switch (arg4) {
case JE_JOY_AXIS_X_bf:
- e.joystick.axis = Common::JOYSTICK_AXIS_LEFT_STICK_X;
- e.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
- pushEvent(e);
+ ev0.joystick.axis = Common::JOYSTICK_AXIS_LEFT_STICK_X;
+ ev0.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
+ pushEvent(ev0);
break;
case JE_JOY_AXIS_Y_bf:
- e.joystick.axis = Common::JOYSTICK_AXIS_LEFT_STICK_Y;
- e.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
- pushEvent(e);
+ ev0.joystick.axis = Common::JOYSTICK_AXIS_LEFT_STICK_Y;
+ ev0.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
+ pushEvent(ev0);
break;
case JE_JOY_AXIS_HAT_X_bf:
- e.joystick.axis = Common::JOYSTICK_AXIS_HAT_X;
- e.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
- pushEvent(e);
+ ev0.joystick.axis = Common::JOYSTICK_AXIS_HAT_X;
+ ev0.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
+ pushEvent(ev0);
break;
case JE_JOY_AXIS_HAT_Y_bf:
- e.joystick.axis = Common::JOYSTICK_AXIS_HAT_Y;
- e.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
- pushEvent(e);
+ ev0.joystick.axis = Common::JOYSTICK_AXIS_HAT_Y;
+ ev0.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
+ pushEvent(ev0);
break;
case JE_JOY_AXIS_Z_bf:
- e.joystick.axis = Common::JOYSTICK_AXIS_RIGHT_STICK_X;
- e.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
- pushEvent(e);
+ ev0.joystick.axis = Common::JOYSTICK_AXIS_RIGHT_STICK_X;
+ ev0.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
+ pushEvent(ev0);
break;
case JE_JOY_AXIS_RZ_bf:
- e.joystick.axis = Common::JOYSTICK_AXIS_RIGHT_STICK_Y;
- e.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
- pushEvent(e);
+ ev0.joystick.axis = Common::JOYSTICK_AXIS_RIGHT_STICK_Y;
+ ev0.joystick.position = CLIP<int32>(arg2, Common::JOYAXIS_MIN, Common::JOYAXIS_MAX);
+ pushEvent(ev0);
break;
case JE_JOY_AXIS_LTRIGGER_bf:
- e.joystick.axis = Common::JOYSTICK_AXIS_LEFT_TRIGGER;
- e.joystick.position = CLIP<int32>(arg2, 0, Common::JOYAXIS_MAX);
- pushEvent(e);
+ ev0.joystick.axis = Common::JOYSTICK_AXIS_LEFT_TRIGGER;
+ ev0.joystick.position = CLIP<int32>(arg2, 0, Common::JOYAXIS_MAX);
+ pushEvent(ev0);
break;
case JE_JOY_AXIS_RTRIGGER_bf:
- e.joystick.axis = Common::JOYSTICK_AXIS_RIGHT_TRIGGER;
- e.joystick.position = CLIP<int32>(arg2, 0, Common::JOYAXIS_MAX);
- pushEvent(e);
+ ev0.joystick.axis = Common::JOYSTICK_AXIS_RIGHT_TRIGGER;
+ ev0.joystick.position = CLIP<int32>(arg2, 0, Common::JOYAXIS_MAX);
+ pushEvent(ev0);
break;
default:
@@ -1360,11 +1264,11 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
break;
case AKEY_EVENT_ACTION_DOWN:
- e.type = Common::EVENT_JOYBUTTON_DOWN;
+ ev0.type = Common::EVENT_JOYBUTTON_DOWN;
break;
case AKEY_EVENT_ACTION_UP:
- e.type = Common::EVENT_JOYBUTTON_UP;
+ ev0.type = Common::EVENT_JOYBUTTON_UP;
break;
default:
@@ -1375,48 +1279,41 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
if (arg1 != AMOTION_EVENT_ACTION_MOVE) {
switch (arg2) {
case AKEYCODE_BUTTON_1:
- e.joystick.button = Common::JOYSTICK_BUTTON_A;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_A;
break;
case AKEYCODE_BUTTON_2:
- e.joystick.button = Common::JOYSTICK_BUTTON_B;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_B;
break;
case AKEYCODE_BUTTON_3:
- e.joystick.button = Common::JOYSTICK_BUTTON_X;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_X;
break;
case AKEYCODE_BUTTON_4:
- e.joystick.button = Common::JOYSTICK_BUTTON_Y;
+ ev0.joystick.button = Common::JOYSTICK_BUTTON_Y;
break;
default:
LOGW("unmapped gamepad key: %d", arg2);
return;
}
-
- pushEvent(e);
+ pushEvent(ev0);
}
-
- return;
+ break;
case JE_QUIT:
- e.type = Common::EVENT_QUIT;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_QUIT;
+ pushEvent(ev0);
+ break;
case JE_MENU:
- e.type = Common::EVENT_MAINMENU;
-
- pushEvent(e);
-
- return;
+ ev0.type = Common::EVENT_MAINMENU;
+ pushEvent(ev0);
+ break;
default:
LOGE("unknown jevent type: %d", type);
-
break;
}
}
@@ -1453,22 +1350,30 @@ bool OSystem_Android::pollEvent(Common::Event &event) {
_event_queue_lock->lock();
- if (_queuedEventTime && (getMillis() > _queuedEventTime)) {
+ // We currently allow only one delayed event at any time, and it's always a mouse up event that comes from a touch event/gesture
+ // Handling multiple delayed events gets complicated and is practically unnecessary too
+ if (_delayedMouseBtnUpEvent.delayMillis > 0
+ && _delayedMouseBtnUpEvent.connectedTypeExecuted
+ && (getMillis(true) - _delayedMouseBtnUpEvent.referTimeMillis > _delayedMouseBtnUpEvent.delayMillis)) {
// if (_queuedEvent.type == Common::EVENT_RBUTTONDOWN) {
// LOGD("Executing delayed RIGHT MOUSE DOWN!!!!");
// } else if (_queuedEvent.type == Common::EVENT_RBUTTONUP) {
// LOGD("Executing delayed RIGHT MOUSE UP!!!!");
// }
- event = _queuedEvent;
- _queuedEventTime = 0;
- // _event_queue_lock->unlock();
- // return true;
+ Common::Event evHP = _delayedMouseBtnUpEvent;
+ event = evHP;
+ _delayedMouseBtnUpEvent.reset();
} else if (_event_queue.empty()) {
_event_queue_lock->unlock();
return false;
} else {
event = _event_queue.pop();
+ if ((_delayedMouseBtnUpEvent.delayMillis > 0)
+ && (event.type == _delayedMouseBtnUpEvent.connectedType)) {
+ _delayedMouseBtnUpEvent.connectedTypeExecuted = true;
+ _delayedMouseBtnUpEvent.referTimeMillis = getMillis(true);
+ }
}
_event_queue_lock->unlock();
@@ -1493,6 +1398,16 @@ void OSystem_Android::pushEvent(const Common::Event &event1, const Common::Event
_event_queue_lock->unlock();
}
+void OSystem_Android::pushDelayedMouseBtnUpEvent() {
+ _event_queue_lock->lock();
+ if (_delayedMouseBtnUpEvent.delayMillis > 0) {
+ Common::Event evHP = _delayedMouseBtnUpEvent;
+ _event_queue.push(_delayedMouseBtnUpEvent);
+ _delayedMouseBtnUpEvent.reset();
+ }
+ _event_queue_lock->unlock();
+}
+
void OSystem_Android::setupTouchMode(int oldValue, int newValue) {
_touch_mode = newValue;
Commit: 7959c431aa03d45a3f50a16f963dc91c7f4a963e
https://github.com/scummvm/scummvm/commit/7959c431aa03d45a3f50a16f963dc91c7f4a963e
Author: antoniou79 (a.antoniou79 at gmail.com)
Date: 2023-08-07T17:31:02+03:00
Commit Message:
ANDROID: Make virtual keyboard arrow keys similar to physical
Basically treats the arrow keys from the virtual keyboard in a special manner
It translates them to KEYCODE_UP(/DOWN/LEFT/RIGHT) and if held down sents consecutive KEYUP / KEYDOWN events
instead of the previous behavior of setting the kbdRepeat flag and just sending repeated KEYDOWN events and one final KEYUP.
Previous behavior would result in poor navigation on lists when the arror keys were held down. The new system works better.
Changed paths:
backends/platform/android/events.cpp
backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
diff --git a/backends/platform/android/events.cpp b/backends/platform/android/events.cpp
index dc5174f9ce1..158be8a69de 100644
--- a/backends/platform/android/events.cpp
+++ b/backends/platform/android/events.cpp
@@ -424,6 +424,26 @@ void OSystem_Android::pushEvent(int type, int arg1, int arg2, int arg3,
ev0.kbdRepeat = true;
}
+ // Special case for when the arrow keys on the virtual keyboard are pressed and held down
+ // This emulates what happens when the arrow keys on a physical keyboard are pressed and held down
+ // In the case of the physical keyboard Android keeps sending successive EVENT_KEYDOWN, EVENT_KEYUP events, non-"repeated"
+ if (ev0.kbdRepeat
+ && ev0.type == Common::EVENT_KEYDOWN
+ && (ev0.kbd.keycode == Common::KEYCODE_UP
+ || ev0.kbd.keycode == Common::KEYCODE_DOWN
+ || ev0.kbd.keycode == Common::KEYCODE_LEFT
+ || ev0.kbd.keycode == Common::KEYCODE_RIGHT)) {
+ ev0.kbd.ascii = 0;
+ // TODO Maybe only handle 1 every X such events to lower the spam on the queue of these very fast up/down events?
+ ev0.kbdRepeat = false;
+ Common::Event ev1 = ev0;
+ ev1.type = Common::EVENT_KEYUP;
+// LOGD("JE_KEY pushing rep event type: %d kbdcode: %d ascii: %d flags: %d repeats: %d", ev1.type, ev1.kbd.keycode, ev1.kbd.ascii, ev1.kbd.flags, ev1.kbdRepeat? 1: 0);
+// LOGD("JE_KEY pushing rep event type: %d kbdcode: %d ascii: %d flags: %d repeats: %d", ev0.type, ev0.kbd.keycode, ev0.kbd.ascii, ev0.kbd.flags, ev0.kbdRepeat? 1: 0);
+ pushEvent(ev1, ev0);
+ return;
+ }
+
// map special keys to 'our' ascii codes
switch (ev0.kbd.keycode) {
case Common::KEYCODE_BACKSPACE:
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
index 524a9e5ae17..85d56a421dd 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
@@ -412,7 +412,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
KeyEvent.ACTION_UP,
key,
builtinKeyboard.mKeyRepeatedCount,
- compiledMetaState);
+ compiledMetaState, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
_main_surface.dispatchKeyEvent(compiledKeyEvent);
builtinKeyboard.resetEventAndTimestamps();
@@ -476,7 +476,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
KeyEvent.ACTION_DOWN,
key,
builtinKeyboard.mKeyRepeatedCount,
- compiledMetaState);
+ compiledMetaState, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
_main_surface.dispatchKeyEvent(compiledKeyEvent);
}
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java b/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
index b8a19939d98..af6bb9bc259 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMEventsBase.java
@@ -5,7 +5,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.content.Context;
-//import android.util.Log;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.KeyCharacterMap;
import android.view.MotionEvent;
@@ -209,7 +209,7 @@ public class ScummVMEventsBase implements
}
public boolean onTrackballEvent(MotionEvent e) {
- //Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onTrackballEvent");
+// Log.d(ScummVM.LOG_TAG, "SCUMMV-EVENTS-BASE - onTrackballEvent");
_scummvm.pushEvent(JE_BALL, e.getAction(),
(int)(e.getX() * e.getXPrecision() * 100),
(int)(e.getY() * e.getYPrecision() * 100),
@@ -398,6 +398,8 @@ public class ScummVMEventsBase implements
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_CENTER:
// NOTE 1 For now, we're handling DPAD keys as JE_GAMEPAD events, regardless the source InputDevice
+ // EXCEPT for the case where the event comes from our virtual keyboard (currently applicable for UP/DOWN/LEFT/RIGHT arrows, not CENTER)
+ //
// We delegate these keypresses to ScummVM's keymapper as JOYSTICK_BUTTON_DPAD presses.
// (JOYSTICK_BUTTON_DPAD_UP, JOYSTICK_BUTTON_DPAD_DOWN, JOYSTICK_BUTTON_DPAD_LEFT, JOYSTICK_BUTTON_DPAD_RIGHT and JOYSTICK_BUTTON_DPAD_CENTER)
// By default mapped to virtual mouse (VMOUSE).
@@ -408,7 +410,13 @@ public class ScummVMEventsBase implements
// and *not* DPAD_UP/DOWN/LEFT/RIGHT button press events. Hence, for those controllers these DPAD key events won't be triggered.
// Those are handled in ScummVMEventsModern class within its onGenericMotionEvent() implementation.
//
- // fall-through
+ if ((e.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) == KeyEvent.FLAG_SOFT_KEYBOARD) {
+ type = JE_KEY;
+ } else {
+ type = JE_GAMEPAD;
+ }
+ break;
+
case KeyEvent.KEYCODE_BUTTON_A:
case KeyEvent.KEYCODE_BUTTON_B:
case KeyEvent.KEYCODE_BUTTON_C:
@@ -448,6 +456,7 @@ public class ScummVMEventsBase implements
//_scummvm.displayMessageOnOSD("GetKey: " + keyCode + " unic=" + eventUnicodeChar+ " arg3= " + (eventUnicodeChar& KeyCharacterMap.COMBINING_ACCENT_MASK) + " meta: " + e.getMetaState());
//_scummvm.displayMessageOnOSD("GetKey: " + keyCode + " type=" + type + " source=" + e.getSource() + " action= " + action + " arg5= " + e.getRepeatCount());
//Log.d(ScummVM.LOG_TAG,"GetKey: " + keyCode + " unic=" + eventUnicodeChar+ " arg3= " + (eventUnicodeChar& KeyCharacterMap.COMBINING_ACCENT_MASK) + " meta: " + e.getMetaState());
+ //Log.d(ScummVM.LOG_TAG,"GetKey: " + keyCode + " type=" + type + " source=" + e.getSource() + " flags=" + e.getFlags() + " action= " + action + " arg5= " + e.getRepeatCount());
// look in events.cpp for how this is handled
_scummvm.pushEvent(type,
More information about the Scummvm-git-logs
mailing list