Fixed key code and key coordinates when move debounce has been in action
This change refactors a key index and pointer position variables into a separate static inner class KeyState . This change also disables time debouncing. Bug: 3033737 Change-Id: Ie4fc37316c260330d8f0861e0771ea903a99cfcemain
parent
008e9b3e1a
commit
6e5a398685
|
@ -42,7 +42,6 @@ public class PointerTracker {
|
||||||
/* package */ static final int REPEAT_INTERVAL = 50; // ~20 keys per second
|
/* package */ static final int REPEAT_INTERVAL = 50; // ~20 keys per second
|
||||||
private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
|
private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
|
||||||
private static final int MULTITAP_INTERVAL = 800; // milliseconds
|
private static final int MULTITAP_INTERVAL = 800; // milliseconds
|
||||||
private static final int KEY_DEBOUNCE_TIME = 70;
|
|
||||||
|
|
||||||
// Miscellaneous constants
|
// Miscellaneous constants
|
||||||
private static final int NOT_A_KEY = LatinKeyboardBaseView.NOT_A_KEY;
|
private static final int NOT_A_KEY = LatinKeyboardBaseView.NOT_A_KEY;
|
||||||
|
@ -57,10 +56,7 @@ public class PointerTracker {
|
||||||
private Key[] mKeys;
|
private Key[] mKeys;
|
||||||
private int mKeyHysteresisDistanceSquared = -1;
|
private int mKeyHysteresisDistanceSquared = -1;
|
||||||
|
|
||||||
private int mCurrentKey = NOT_A_KEY;
|
private final KeyState mKeyState;
|
||||||
private int mStartX;
|
|
||||||
private int mStartY;
|
|
||||||
private long mDownTime;
|
|
||||||
|
|
||||||
// true if event is already translated to a key action (long press or mini-keyboard)
|
// true if event is already translated to a key action (long press or mini-keyboard)
|
||||||
private boolean mKeyAlreadyProcessed;
|
private boolean mKeyAlreadyProcessed;
|
||||||
|
@ -68,18 +64,6 @@ public class PointerTracker {
|
||||||
// true if this pointer is repeatable key
|
// true if this pointer is repeatable key
|
||||||
private boolean mIsRepeatableKey;
|
private boolean mIsRepeatableKey;
|
||||||
|
|
||||||
// for move de-bouncing
|
|
||||||
private int mLastCodeX;
|
|
||||||
private int mLastCodeY;
|
|
||||||
private int mLastX;
|
|
||||||
private int mLastY;
|
|
||||||
|
|
||||||
// for time de-bouncing
|
|
||||||
private int mLastKey;
|
|
||||||
private long mLastKeyTime;
|
|
||||||
private long mLastMoveTime;
|
|
||||||
private long mCurrentKeyTime;
|
|
||||||
|
|
||||||
// For multi-tap
|
// For multi-tap
|
||||||
private int mLastSentIndex;
|
private int mLastSentIndex;
|
||||||
private int mTapCount;
|
private int mTapCount;
|
||||||
|
@ -90,6 +74,95 @@ public class PointerTracker {
|
||||||
// pressed key
|
// pressed key
|
||||||
private int mPreviousKey = NOT_A_KEY;
|
private int mPreviousKey = NOT_A_KEY;
|
||||||
|
|
||||||
|
// This class keeps track of a key index and a position where this pointer is.
|
||||||
|
private static class KeyState {
|
||||||
|
private final KeyDetector mKeyDetector;
|
||||||
|
|
||||||
|
// The position and time at which first down event occurred.
|
||||||
|
private int mStartX;
|
||||||
|
private int mStartY;
|
||||||
|
private long mDownTime;
|
||||||
|
|
||||||
|
// The current key index where this pointer is.
|
||||||
|
private int mKeyIndex = NOT_A_KEY;
|
||||||
|
// The position where mKeyIndex was recognized for the first time.
|
||||||
|
private int mKeyX;
|
||||||
|
private int mKeyY;
|
||||||
|
|
||||||
|
// Last pointer position.
|
||||||
|
private int mLastX;
|
||||||
|
private int mLastY;
|
||||||
|
|
||||||
|
public KeyState(KeyDetector keyDetecor) {
|
||||||
|
mKeyDetector = keyDetecor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKeyIndex() {
|
||||||
|
return mKeyIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKeyX() {
|
||||||
|
return mKeyX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKeyY() {
|
||||||
|
return mKeyY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartX() {
|
||||||
|
return mStartX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartY() {
|
||||||
|
return mStartY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDownTime() {
|
||||||
|
return mDownTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastX() {
|
||||||
|
return mLastX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastY() {
|
||||||
|
return mLastY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int onDownKey(int x, int y, long eventTime) {
|
||||||
|
mStartX = x;
|
||||||
|
mStartY = y;
|
||||||
|
mDownTime = eventTime;
|
||||||
|
|
||||||
|
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int onMoveKeyInternal(int x, int y) {
|
||||||
|
mLastX = x;
|
||||||
|
mLastY = y;
|
||||||
|
return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int onMoveKey(int x, int y) {
|
||||||
|
return onMoveKeyInternal(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int onMoveToNewKey(int keyIndex, int x, int y) {
|
||||||
|
mKeyIndex = keyIndex;
|
||||||
|
mKeyX = x;
|
||||||
|
mKeyY = y;
|
||||||
|
return keyIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int onUpKey(int x, int y) {
|
||||||
|
return onMoveKeyInternal(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSetKeyboard() {
|
||||||
|
mKeyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(mKeyX, mKeyY, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy,
|
public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy,
|
||||||
boolean hasDistinctMultitouch) {
|
boolean hasDistinctMultitouch) {
|
||||||
if (proxy == null || handler == null || keyDetector == null)
|
if (proxy == null || handler == null || keyDetector == null)
|
||||||
|
@ -98,6 +171,7 @@ public class PointerTracker {
|
||||||
mProxy = proxy;
|
mProxy = proxy;
|
||||||
mHandler = handler;
|
mHandler = handler;
|
||||||
mKeyDetector = keyDetector;
|
mKeyDetector = keyDetector;
|
||||||
|
mKeyState = new KeyState(keyDetector);
|
||||||
mHasDistinctMultitouch = hasDistinctMultitouch;
|
mHasDistinctMultitouch = hasDistinctMultitouch;
|
||||||
resetMultiTap();
|
resetMultiTap();
|
||||||
}
|
}
|
||||||
|
@ -112,7 +186,7 @@ public class PointerTracker {
|
||||||
mKeys = keys;
|
mKeys = keys;
|
||||||
mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
|
mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
|
||||||
// Update current key index because keyboard layout has been changed.
|
// Update current key index because keyboard layout has been changed.
|
||||||
mCurrentKey = mKeyDetector.getKeyIndexAndNearbyCodes(mStartX, mStartY, null);
|
mKeyState.onSetKeyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidKeyIndex(int keyIndex) {
|
private boolean isValidKeyIndex(int keyIndex) {
|
||||||
|
@ -133,7 +207,7 @@ public class PointerTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isModifier() {
|
public boolean isModifier() {
|
||||||
return isModifierInternal(mCurrentKey);
|
return isModifierInternal(mKeyState.getKeyIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOnModifierKey(int x, int y) {
|
public boolean isOnModifierKey(int x, int y) {
|
||||||
|
@ -190,21 +264,16 @@ public class PointerTracker {
|
||||||
public void onDownEvent(int x, int y, long eventTime) {
|
public void onDownEvent(int x, int y, long eventTime) {
|
||||||
if (DEBUG)
|
if (DEBUG)
|
||||||
debugLog("onDownEvent:", x, y);
|
debugLog("onDownEvent:", x, y);
|
||||||
int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
|
int keyIndex = mKeyState.onDownKey(x, y, eventTime);
|
||||||
mCurrentKey = keyIndex;
|
|
||||||
mStartX = x;
|
|
||||||
mStartY = y;
|
|
||||||
mDownTime = eventTime;
|
|
||||||
mKeyAlreadyProcessed = false;
|
mKeyAlreadyProcessed = false;
|
||||||
mIsRepeatableKey = false;
|
mIsRepeatableKey = false;
|
||||||
startMoveDebouncing(x, y);
|
|
||||||
startTimeDebouncing(eventTime);
|
|
||||||
checkMultiTap(eventTime, keyIndex);
|
checkMultiTap(eventTime, keyIndex);
|
||||||
if (mListener != null) {
|
if (mListener != null) {
|
||||||
int primaryCode = isValidKeyIndex(keyIndex) ? mKeys[keyIndex].codes[0] : 0;
|
int primaryCode = isValidKeyIndex(keyIndex) ? mKeys[keyIndex].codes[0] : 0;
|
||||||
mListener.onPress(primaryCode);
|
mListener.onPress(primaryCode);
|
||||||
// This onPress call may have changed keyboard layout and have updated mCurrentKey
|
// This onPress call may have changed keyboard layout and have updated mKeyIndex.
|
||||||
keyIndex = mCurrentKey;
|
// If that's the case, mKeyIndex has been updated in setKeyboard().
|
||||||
|
keyIndex = mKeyState.getKeyIndex();
|
||||||
}
|
}
|
||||||
if (isValidKeyIndex(keyIndex)) {
|
if (isValidKeyIndex(keyIndex)) {
|
||||||
if (mKeys[keyIndex].repeatable) {
|
if (mKeys[keyIndex].repeatable) {
|
||||||
|
@ -215,7 +284,6 @@ public class PointerTracker {
|
||||||
mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
|
mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
|
||||||
}
|
}
|
||||||
showKeyPreviewAndUpdateKey(keyIndex);
|
showKeyPreviewAndUpdateKey(keyIndex);
|
||||||
updateMoveDebouncing(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMoveEvent(int x, int y, long eventTime) {
|
public void onMoveEvent(int x, int y, long eventTime) {
|
||||||
|
@ -223,44 +291,28 @@ public class PointerTracker {
|
||||||
debugLog("onMoveEvent:", x, y);
|
debugLog("onMoveEvent:", x, y);
|
||||||
if (mKeyAlreadyProcessed)
|
if (mKeyAlreadyProcessed)
|
||||||
return;
|
return;
|
||||||
int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
|
KeyState keyState = mKeyState;
|
||||||
|
int keyIndex = keyState.onMoveKey(x, y);
|
||||||
if (isValidKeyIndex(keyIndex)) {
|
if (isValidKeyIndex(keyIndex)) {
|
||||||
if (mCurrentKey == NOT_A_KEY) {
|
if (keyState.getKeyIndex() == NOT_A_KEY) {
|
||||||
updateTimeDebouncing(eventTime);
|
keyState.onMoveToNewKey(keyIndex, x, y);
|
||||||
mCurrentKey = keyIndex;
|
|
||||||
mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
|
mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
|
||||||
} else if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) {
|
} else if (!isMinorMoveBounce(x, y, keyIndex)) {
|
||||||
updateTimeDebouncing(eventTime);
|
|
||||||
} else {
|
|
||||||
resetMultiTap();
|
resetMultiTap();
|
||||||
resetTimeDebouncing(eventTime, mCurrentKey);
|
keyState.onMoveToNewKey(keyIndex, x, y);
|
||||||
resetMoveDebouncing();
|
|
||||||
mCurrentKey = keyIndex;
|
|
||||||
mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
|
mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mCurrentKey != NOT_A_KEY) {
|
if (keyState.getKeyIndex() != NOT_A_KEY) {
|
||||||
updateTimeDebouncing(eventTime);
|
keyState.onMoveToNewKey(keyIndex, x ,y);
|
||||||
mCurrentKey = keyIndex;
|
|
||||||
mHandler.cancelLongPressTimer();
|
mHandler.cancelLongPressTimer();
|
||||||
} else if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) {
|
} else if (!isMinorMoveBounce(x, y, keyIndex)) {
|
||||||
updateTimeDebouncing(eventTime);
|
|
||||||
} else {
|
|
||||||
resetMultiTap();
|
resetMultiTap();
|
||||||
resetTimeDebouncing(eventTime, mCurrentKey);
|
keyState.onMoveToNewKey(keyIndex, x ,y);
|
||||||
resetMoveDebouncing();
|
|
||||||
mCurrentKey = keyIndex;
|
|
||||||
mHandler.cancelLongPressTimer();
|
mHandler.cancelLongPressTimer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
showKeyPreviewAndUpdateKey(mKeyState.getKeyIndex());
|
||||||
* While time debouncing is in effect, mCurrentKey holds the new key and this tracker
|
|
||||||
* holds the last key. At ACTION_UP event if time debouncing will be in effect
|
|
||||||
* eventually, the last key should be sent as the result. In such case mCurrentKey
|
|
||||||
* should not be showed as popup preview.
|
|
||||||
*/
|
|
||||||
showKeyPreviewAndUpdateKey(isMinorTimeBounce() ? mLastKey : mCurrentKey);
|
|
||||||
updateMoveDebouncing(x, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUpEvent(int x, int y, long eventTime) {
|
public void onUpEvent(int x, int y, long eventTime) {
|
||||||
|
@ -270,23 +322,18 @@ public class PointerTracker {
|
||||||
return;
|
return;
|
||||||
mHandler.cancelKeyTimers();
|
mHandler.cancelKeyTimers();
|
||||||
mHandler.cancelPopupPreview();
|
mHandler.cancelPopupPreview();
|
||||||
int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
|
int keyIndex = mKeyState.onUpKey(x, y);
|
||||||
if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) {
|
if (isMinorMoveBounce(x, y, keyIndex)) {
|
||||||
updateTimeDebouncing(eventTime);
|
// Use previous fixed key index and coordinates.
|
||||||
} else {
|
keyIndex = mKeyState.getKeyIndex();
|
||||||
resetMultiTap();
|
x = mKeyState.getKeyX();
|
||||||
resetTimeDebouncing(eventTime, mCurrentKey);
|
y = mKeyState.getKeyY();
|
||||||
mCurrentKey = keyIndex;
|
|
||||||
}
|
|
||||||
if (isMinorTimeBounce()) {
|
|
||||||
mCurrentKey = mLastKey;
|
|
||||||
x = mLastCodeX;
|
|
||||||
y = mLastCodeY;
|
|
||||||
}
|
}
|
||||||
showKeyPreviewAndUpdateKey(NOT_A_KEY);
|
showKeyPreviewAndUpdateKey(NOT_A_KEY);
|
||||||
if (!mIsRepeatableKey) {
|
if (!mIsRepeatableKey) {
|
||||||
detectAndSendKey(mCurrentKey, x, y, eventTime);
|
detectAndSendKey(keyIndex, x, y, eventTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isValidKeyIndex(keyIndex))
|
if (isValidKeyIndex(keyIndex))
|
||||||
mProxy.invalidateKey(mKeys[keyIndex]);
|
mProxy.invalidateKey(mKeys[keyIndex]);
|
||||||
}
|
}
|
||||||
|
@ -297,7 +344,7 @@ public class PointerTracker {
|
||||||
mHandler.cancelKeyTimers();
|
mHandler.cancelKeyTimers();
|
||||||
mHandler.cancelPopupPreview();
|
mHandler.cancelPopupPreview();
|
||||||
showKeyPreviewAndUpdateKey(NOT_A_KEY);
|
showKeyPreviewAndUpdateKey(NOT_A_KEY);
|
||||||
int keyIndex = mCurrentKey;
|
int keyIndex = mKeyState.getKeyIndex();
|
||||||
if (isValidKeyIndex(keyIndex))
|
if (isValidKeyIndex(keyIndex))
|
||||||
mProxy.invalidateKey(mKeys[keyIndex]);
|
mProxy.invalidateKey(mKeys[keyIndex]);
|
||||||
}
|
}
|
||||||
|
@ -312,44 +359,30 @@ public class PointerTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLastX() {
|
public int getLastX() {
|
||||||
return mLastX;
|
return mKeyState.getLastX();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLastY() {
|
public int getLastY() {
|
||||||
return mLastY;
|
return mKeyState.getLastY();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDownTime() {
|
public long getDownTime() {
|
||||||
return mDownTime;
|
return mKeyState.getDownTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
// These package scope methods are only for debugging purpose.
|
// These package scope methods are only for debugging purpose.
|
||||||
/* package */ int getStartX() {
|
/* package */ int getStartX() {
|
||||||
return mStartX;
|
return mKeyState.getStartX();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ int getStartY() {
|
/* package */ int getStartY() {
|
||||||
return mStartY;
|
return mKeyState.getStartY();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startMoveDebouncing(int x, int y) {
|
private boolean isMinorMoveBounce(int x, int y, int newKey) {
|
||||||
mLastCodeX = x;
|
|
||||||
mLastCodeY = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateMoveDebouncing(int x, int y) {
|
|
||||||
mLastX = x;
|
|
||||||
mLastY = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetMoveDebouncing() {
|
|
||||||
mLastCodeX = mLastX;
|
|
||||||
mLastCodeY = mLastY;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMinorMoveBounce(int x, int y, int newKey, int curKey) {
|
|
||||||
if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
|
if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
|
||||||
throw new IllegalStateException("keyboard and/or hysteresis not set");
|
throw new IllegalStateException("keyboard and/or hysteresis not set");
|
||||||
|
int curKey = mKeyState.getKeyIndex();
|
||||||
if (newKey == curKey) {
|
if (newKey == curKey) {
|
||||||
return true;
|
return true;
|
||||||
} else if (isValidKeyIndex(curKey)) {
|
} else if (isValidKeyIndex(curKey)) {
|
||||||
|
@ -371,30 +404,6 @@ public class PointerTracker {
|
||||||
return dx * dx + dy * dy;
|
return dx * dx + dy * dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startTimeDebouncing(long eventTime) {
|
|
||||||
mLastKey = NOT_A_KEY;
|
|
||||||
mLastKeyTime = 0;
|
|
||||||
mCurrentKeyTime = 0;
|
|
||||||
mLastMoveTime = eventTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTimeDebouncing(long eventTime) {
|
|
||||||
mCurrentKeyTime += eventTime - mLastMoveTime;
|
|
||||||
mLastMoveTime = eventTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetTimeDebouncing(long eventTime, int currentKey) {
|
|
||||||
mLastKey = currentKey;
|
|
||||||
mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
|
|
||||||
mCurrentKeyTime = 0;
|
|
||||||
mLastMoveTime = eventTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMinorTimeBounce() {
|
|
||||||
return mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < KEY_DEBOUNCE_TIME
|
|
||||||
&& mLastKey != NOT_A_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showKeyPreviewAndUpdateKey(int keyIndex) {
|
private void showKeyPreviewAndUpdateKey(int keyIndex) {
|
||||||
updateKey(keyIndex);
|
updateKey(keyIndex);
|
||||||
// The modifier key, such as shift key, should not be shown as preview when multi-touch is
|
// The modifier key, such as shift key, should not be shown as preview when multi-touch is
|
||||||
|
@ -497,7 +506,8 @@ public class PointerTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void debugLog(String title, int x, int y) {
|
private void debugLog(String title, int x, int y) {
|
||||||
Key key = getKey(mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null));
|
int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
|
||||||
|
Key key = getKey(keyIndex);
|
||||||
final String code;
|
final String code;
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
code = "----";
|
code = "----";
|
||||||
|
@ -505,7 +515,7 @@ public class PointerTracker {
|
||||||
int primaryCode = key.codes[0];
|
int primaryCode = key.codes[0];
|
||||||
code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode);
|
code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode);
|
||||||
}
|
}
|
||||||
Log.d(TAG, String.format("%s [%d] %3d,%3d %s %s", title, mPointerId, x, y, code,
|
Log.d(TAG, String.format("%s [%d] %3d,%3d %3d(%s) %s", title, mPointerId, x, y, keyIndex,
|
||||||
isModifier() ? "modifier" : ""));
|
code, isModifier() ? "modifier" : ""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue