Extended 'more keys' support to emoji keyboards

This commit is contained in:
pdroidandroid@gmail.com 2020-12-06 00:06:06 +01:00 committed by Daniele Laudani
parent c30fce58c4
commit 2079be88d3
11 changed files with 596 additions and 84 deletions

View file

@ -22,6 +22,7 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.Log;
import org.dslul.openboard.inputmethod.keyboard.internal.KeyDrawParams;
import org.dslul.openboard.inputmethod.keyboard.internal.KeySpecParser;
import org.dslul.openboard.inputmethod.keyboard.internal.KeyStyle;
@ -208,8 +209,7 @@ public class Key implements Comparable<Key> {
private boolean mEnabled = true;
/**
* Constructor for a key on <code>MoreKeyKeyboard</code>, on <code>MoreSuggestions</code>,
* and in a <GridRows/>.
* Constructor for a key on <code>MoreKeyKeyboard</code> and on <code>MoreSuggestions</code>.
*/
public Key(@Nullable final String label, final int iconId, final int code,
@Nullable final String outputText, @Nullable final String hintLabel,
@ -241,6 +241,82 @@ public class Key implements Comparable<Key> {
mHashCode = computeHashCode(this);
}
/**
* Constructor for a key in a <GridRows/>.
*/
public Key(@Nullable final String label, final int code, @Nullable final String outputText,
@Nullable final String hintLabel, @Nullable final String moreKeySpecs,
final int labelFlags, final int backgroundType, final int x, final int y,
final int width, final int height, final KeyboardParams params) {
mWidth = width - params.mHorizontalGap;
mHeight = height - params.mVerticalGap;
mHorizontalGap = params.mHorizontalGap;
mVerticalGap = params.mVerticalGap;
mHintLabel = hintLabel;
mLabelFlags = labelFlags;
mBackgroundType = backgroundType;
if (moreKeySpecs != null) {
String[] moreKeys = MoreKeySpec.splitKeySpecs(moreKeySpecs);
// Get maximum column order number and set a relevant mode value.
int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER
| params.mMaxMoreKeysKeyboardColumn;
int value;
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
// Override with fixed column order number and set a relevant mode value.
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER
| (value & MORE_KEYS_COLUMN_NUMBER_MASK);
}
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
// Override with fixed column order number and set a relevant mode value.
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER
| (value & MORE_KEYS_COLUMN_NUMBER_MASK);
}
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS;
}
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
}
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
}
mMoreKeysColumnAndFlags = moreKeysColumnAndFlags;
moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, null);
int actionFlags = 0;
if (moreKeys != null) {
actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
mMoreKeys = new MoreKeySpec[moreKeys.length];
for (int i = 0; i < moreKeys.length; i++) {
mMoreKeys[i] = new MoreKeySpec(moreKeys[i], false, Locale.getDefault());
}
} else {
mMoreKeys = null;
}
mActionFlags = actionFlags;
} else {
// TODO: Pass keyActionFlags as an argument.
mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW;
mMoreKeys = null;
mMoreKeysColumnAndFlags = 0;
}
mLabel = label;
mOptionalAttributes = OptionalAttributes.newInstance(outputText, CODE_UNSPECIFIED,
ICON_UNDEFINED, 0 /* visualInsetsLeft */, 0 /* visualInsetsRight */);
mCode = code;
mEnabled = (code != CODE_UNSPECIFIED);
mIconId = KeyboardIconsSet.ICON_UNDEFINED;
// Horizontal gap is divided equally to both sides of the key.
mX = x + mHorizontalGap / 2;
mY = y;
mHitBox.set(x, y, x + width + 1, y + height);
mKeyVisualAttributes = null;
mHashCode = computeHashCode(this);
}
/**
* Create a key with the given top-left coordinate and extract its attributes from a key
* specification string, Key attribute array, key style, and etc.
@ -410,9 +486,35 @@ public class Key implements Comparable<Key> {
* Copy constructor for DynamicGridKeyboard.GridKey.
*
* @param key the original key.
* @param moreKeys the more keys that should be assigned to this key.
* @param labelHint the label hint that should be assigned to this key.
* @param backgroundType the background type that should be assigned to this key.
*/
protected Key(@Nonnull final Key key) {
this(key, key.mMoreKeys);
protected Key(@Nonnull final Key key, @Nullable final MoreKeySpec[] moreKeys,
@Nullable final String labelHint, final int backgroundType) {
// Final attributes.
mCode = key.mCode;
mLabel = key.mLabel;
mHintLabel = labelHint;
mLabelFlags = key.mLabelFlags;
mIconId = key.mIconId;
mWidth = key.mWidth;
mHeight = key.mHeight;
mHorizontalGap = key.mHorizontalGap;
mVerticalGap = key.mVerticalGap;
mX = key.mX;
mY = key.mY;
mHitBox.set(key.mHitBox);
mMoreKeys = moreKeys;
mMoreKeysColumnAndFlags = key.mMoreKeysColumnAndFlags;
mBackgroundType = backgroundType;
mActionFlags = key.mActionFlags;
mKeyVisualAttributes = key.mKeyVisualAttributes;
mOptionalAttributes = key.mOptionalAttributes;
mHashCode = key.mHashCode;
// Key state.
mPressed = key.mPressed;
mEnabled = key.mEnabled;
}
private Key(@Nonnull final Key key, @Nullable final MoreKeySpec[] moreKeys) {
@ -826,6 +928,21 @@ public class Key implements Comparable<Key> {
return iconSet.getIconDrawable(getIconId());
}
/**
* Gets the background type of this key.
* @return Background type.
* @see Key#BACKGROUND_TYPE_EMPTY
* @see Key#BACKGROUND_TYPE_NORMAL
* @see Key#BACKGROUND_TYPE_FUNCTIONAL
* @see Key#BACKGROUND_TYPE_STICKY_OFF
* @see Key#BACKGROUND_TYPE_STICKY_ON
* @see Key#BACKGROUND_TYPE_ACTION
* @see Key#BACKGROUND_TYPE_SPACEBAR
*/
public int getBackgroundType() {
return mBackgroundType;
}
/**
* Gets the width of the key in pixels, excluding the gap.
* @return The width of the key in pixels, excluding the gap.

View file

@ -28,6 +28,7 @@ import android.view.ViewGroup;
import org.dslul.openboard.inputmethod.accessibility.AccessibilityUtils;
import org.dslul.openboard.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate;
import org.dslul.openboard.inputmethod.keyboard.emoji.OnKeyEventListener;
import org.dslul.openboard.inputmethod.keyboard.internal.KeyDrawParams;
import org.dslul.openboard.inputmethod.latin.R;
import org.dslul.openboard.inputmethod.latin.common.Constants;
@ -44,6 +45,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
protected final KeyDetector mKeyDetector;
private Controller mController = EMPTY_CONTROLLER;
protected KeyboardActionListener mListener;
protected OnKeyEventListener mKeyEventListener;
private int mOriginX;
private int mOriginY;
private Key mCurrentKey;
@ -118,11 +120,31 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
}
}
/**
* {@inheritDoc}
*/
@Override
public void showMoreKeysPanel(final View parentView, final Controller controller,
final int pointX, final int pointY, final KeyboardActionListener listener) {
mController = controller;
mListener = listener;
mKeyEventListener = null;
showMoreKeysPanelInternal(parentView, controller, pointX, pointY);
}
/**
* {@inheritDoc}
*/
@Override
public void showMoreKeysPanel(final View parentView, final Controller controller,
final int pointX, final int pointY, final OnKeyEventListener listener) {
mListener = null;
mKeyEventListener = listener;
showMoreKeysPanelInternal(parentView, controller, pointX, pointY);
}
private void showMoreKeysPanelInternal(final View parentView, final Controller controller,
final int pointX, final int pointY) {
mController = controller;
final View container = getContainerView();
// The coordinates of panel's left-top corner in parentView's coordinate system.
// We need to consider background drawable paddings.
@ -193,6 +215,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
* Performs the specific action for this panel when the user presses a key on the panel.
*/
protected void onKeyInput(final Key key, final int x, final int y) {
if (mListener != null) {
final int code = key.getCode();
if (code == Constants.CODE_OUTPUT_TEXT) {
mListener.onTextInput(mCurrentKey.getOutputText());
@ -204,6 +227,9 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
false /* isKeyRepeat */);
}
}
} else if (mKeyEventListener != null) {
mKeyEventListener.onReleaseKey(key);
}
}
private Key detectKey(int x, int y) {

View file

@ -18,6 +18,7 @@ package org.dslul.openboard.inputmethod.keyboard;
import android.view.View;
import android.view.ViewGroup;
import org.dslul.openboard.inputmethod.keyboard.emoji.OnKeyEventListener;
public interface MoreKeysPanel {
interface Controller {
@ -63,6 +64,25 @@ public interface MoreKeysPanel {
void showMoreKeysPanel(View parentView, Controller controller, int pointX,
int pointY, KeyboardActionListener listener);
/**
*
* Initializes the layout and event handling of this {@link MoreKeysPanel} and calls the
* controller's onShowMoreKeysPanel to add the panel's container view.
* Same as {@link MoreKeysPanel#showMoreKeysPanel(View, Controller, int, int, KeyboardActionListener)},
* but with a {@link OnKeyEventListener}.
*
* @param parentView the parent view of this {@link MoreKeysPanel}
* @param controller the controller that can dismiss this {@link MoreKeysPanel}
* @param pointX x coordinate of this {@link MoreKeysPanel}
* @param pointY y coordinate of this {@link MoreKeysPanel}
* @param listener the listener that will receive keyboard action from this
* {@link MoreKeysPanel}.
*/
// TODO: Currently the MoreKeysPanel is inside a container view that is added to the parent.
// Consider the simpler approach of placing the MoreKeysPanel itself into the parent view.
void showMoreKeysPanel(View parentView, Controller controller, int pointX,
int pointY, OnKeyEventListener listener);
/**
* Dismisses the more keys panel and calls the controller's onDismissMoreKeysPanel to remove
* the panel's container view.

View file

@ -22,9 +22,12 @@ import android.util.Log;
import org.dslul.openboard.inputmethod.keyboard.Key;
import org.dslul.openboard.inputmethod.keyboard.Keyboard;
import org.dslul.openboard.inputmethod.keyboard.internal.MoreKeySpec;
import org.dslul.openboard.inputmethod.latin.settings.Settings;
import org.dslul.openboard.inputmethod.latin.utils.JsonUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@ -105,7 +108,16 @@ final class DynamicGridKeyboard extends Keyboard {
}
synchronized (mLock) {
mCachedGridKeys = null;
final GridKey key = new GridKey(usedKey);
// When a key is added to recents keyboard, we don't want to keep its more keys
// neither its hint label. Also, we make sure its background type is matching our keyboard
// if key comes from another keyboard (ie. a {@link MoreKeysKeyboard}).
final boolean dropMoreKeys = mIsRecents;
// Check if hint was a more emoji indicator and prevent its copy if more keys aren't copied
final boolean dropHintLabel = dropMoreKeys && "\u25E5".equals(usedKey.getHintLabel());
final GridKey key = new GridKey(usedKey,
dropMoreKeys ? null : usedKey.getMoreKeys(),
dropHintLabel ? null : usedKey.getHintLabel(),
mIsRecents ? Key.BACKGROUND_TYPE_EMPTY : usedKey.getBackgroundType());
while (mGridKeys.remove(key)) {
// Remove duplicate keys.
}
@ -227,8 +239,9 @@ final class DynamicGridKeyboard extends Keyboard {
private int mCurrentX;
private int mCurrentY;
public GridKey(final Key originalKey) {
super(originalKey);
public GridKey(@Nonnull final Key originalKey, @Nullable final MoreKeySpec[] moreKeys,
@Nullable final String labelHint, final int backgroundType) {
super(originalKey, moreKeys, labelHint, backgroundType);
}
public void updateCoordinates(final int x0, final int y0, final int x1, final int y1) {

View file

@ -17,19 +17,37 @@
package org.dslul.openboard.inputmethod.keyboard.emoji;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
import org.dslul.openboard.inputmethod.accessibility.AccessibilityUtils;
import org.dslul.openboard.inputmethod.accessibility.KeyboardAccessibilityDelegate;
import org.dslul.openboard.inputmethod.keyboard.Key;
import org.dslul.openboard.inputmethod.keyboard.KeyDetector;
import org.dslul.openboard.inputmethod.keyboard.Keyboard;
import org.dslul.openboard.inputmethod.keyboard.KeyboardView;
import org.dslul.openboard.inputmethod.keyboard.MoreKeysKeyboard;
import org.dslul.openboard.inputmethod.keyboard.MoreKeysKeyboardView;
import org.dslul.openboard.inputmethod.keyboard.MoreKeysPanel;
import org.dslul.openboard.inputmethod.keyboard.internal.MoreKeySpec;
import org.dslul.openboard.inputmethod.latin.R;
import org.dslul.openboard.inputmethod.latin.common.CoordinateUtils;
import org.dslul.openboard.inputmethod.latin.settings.Settings;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.WeakHashMap;
/**
* This is an extended {@link KeyboardView} class that hosts an emoji page keyboard.
@ -37,15 +55,12 @@ import org.dslul.openboard.inputmethod.latin.R;
*/
// TODO: Implement key popup preview.
final class EmojiPageKeyboardView extends KeyboardView implements
GestureDetector.OnGestureListener {
MoreKeysPanel.Controller {
private static final String TAG = "EmojiPageKeyboardView";
private static final boolean LOG = true;
private static final long KEY_PRESS_DELAY_TIME = 250; // msec
private static final long KEY_RELEASE_DELAY_TIME = 30; // msec
public interface OnKeyEventListener {
void onPressKey(Key key);
void onReleaseKey(Key key);
}
private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() {
@Override
public void onPressKey(final Key key) {}
@ -55,9 +70,25 @@ final class EmojiPageKeyboardView extends KeyboardView implements
private OnKeyEventListener mListener = EMPTY_LISTENER;
private final KeyDetector mKeyDetector = new KeyDetector();
private final GestureDetector mGestureDetector;
private KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate;
// Touch inputs
private int mPointerId = MotionEvent.INVALID_POINTER_ID;
private int mLastX, mLastY;
private Key mCurrentKey;
private Runnable mPendingKeyDown;
private Runnable mPendingLongPress;
private final Handler mHandler;
// More keys keyboard
private final View mMoreKeysKeyboardContainer;
private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = new WeakHashMap<>();
private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint;
private final ViewGroup mMoreKeysPlacerView;
// More keys panel (used by more keys keyboard view)
// TODO: Consider extending to support multiple more keys panels
private MoreKeysPanel mMoreKeysPanel;
public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.keyboardViewStyle);
}
@ -65,9 +96,74 @@ final class EmojiPageKeyboardView extends KeyboardView implements
public EmojiPageKeyboardView(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
mHandler = new Handler();
mMoreKeysPlacerView = new FrameLayout(context, attrs);
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView);
final int moreKeysKeyboardLayoutId = keyboardViewAttr.getResourceId(
R.styleable.MainKeyboardView_moreKeysKeyboardLayout, 0);
mConfigShowMoreKeysKeyboardAtTouchedPoint = keyboardViewAttr.getBoolean(
R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false);
keyboardViewAttr.recycle();
final LayoutInflater inflater = LayoutInflater.from(getContext());
mMoreKeysKeyboardContainer = inflater.inflate(moreKeysKeyboardLayoutId, null);
}
@Override
public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
super.setHardwareAcceleratedDrawingEnabled(enabled);
if (!enabled) return;
final Paint layerPaint = new Paint();
layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
mMoreKeysPlacerView.setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
installMoreKeysPlacerView();
}
private void installMoreKeysPlacerView() {
final View rootView = getRootView();
if (rootView == null) {
Log.w(TAG, "Cannot find root view");
return;
}
final ViewGroup windowContentView = rootView.findViewById(android.R.id.content);
// Note: It'd be very weird if we get null by android.R.id.content.
if (windowContentView == null) {
Log.w(TAG, "Cannot find android.R.id.content view to add DrawingPreviewPlacerView");
return;
}
windowContentView.addView(mMoreKeysPlacerView);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mMoreKeysPlacerView.removeAllViews();
uninstallMoreKeysPlacerView();
}
private void uninstallMoreKeysPlacerView() {
final View rootView = getRootView();
if (rootView == null) {
Log.w(TAG, "Cannot find root view");
return;
}
final ViewGroup windowContentView = rootView.findViewById(android.R.id.content);
// Note: It'd be very weird if we get null by android.R.id.content.
if (windowContentView == null) {
Log.w(TAG, "Cannot find android.R.id.content view to add DrawingPreviewPlacerView");
return;
}
windowContentView.removeView(mMoreKeysPlacerView);
}
public void setOnKeyEventListener(final OnKeyEventListener listener) {
@ -81,6 +177,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements
public void setKeyboard(final Keyboard keyboard) {
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */);
mMoreKeysKeyboardCache.clear();
if (AccessibilityUtils.Companion.getInstance().isAccessibilityEnabled()) {
if (mAccessibilityDelegate == null) {
mAccessibilityDelegate = new KeyboardAccessibilityDelegate<>(this, mKeyDetector);
@ -91,12 +188,82 @@ final class EmojiPageKeyboardView extends KeyboardView implements
}
}
@Nullable
public MoreKeysPanel showMoreKeysKeyboard(@Nonnull final Key key, final int lastX, final int lastY) {
final MoreKeySpec[] moreKeys = key.getMoreKeys();
if (moreKeys == null) {
return null;
}
Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key);
if (moreKeysKeyboard == null) {
final MoreKeysKeyboard.Builder builder = new MoreKeysKeyboard.Builder(
getContext(), key, getKeyboard(),
true, key.getWidth(), key.getHeight(), // TODO This is cheating
newLabelPaint(key));
moreKeysKeyboard = builder.build();
mMoreKeysKeyboardCache.put(key, moreKeysKeyboard);
}
final View container = mMoreKeysKeyboardContainer;
final MoreKeysKeyboardView moreKeysKeyboardView =
container.findViewById(R.id.more_keys_keyboard_view);
moreKeysKeyboardView.setKeyboard(moreKeysKeyboard);
container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
final int[] lastCoords = CoordinateUtils.newCoordinateArray(1, lastX, lastY);
// The more keys keyboard is usually horizontally aligned with the center of the parent key.
// If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
// keys keyboard is placed at the touch point of the parent key.
final int pointX = mConfigShowMoreKeysKeyboardAtTouchedPoint
? CoordinateUtils.x(lastCoords)
: key.getX() + key.getWidth() / 2;
final int pointY = key.getY();
moreKeysKeyboardView.showMoreKeysPanel(this, this,
pointX, pointY, mListener);
return moreKeysKeyboardView;
}
@Override
public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
// Dismiss another {@link MoreKeysPanel} that may be being showed.
onDismissMoreKeysPanel();
panel.showInParent(mMoreKeysPlacerView);
mMoreKeysPanel = panel;
}
public boolean isShowingMoreKeysPanel() {
return mMoreKeysPanel != null;
}
@Override
public void onCancelMoreKeysPanel() {
// Nothing to do
}
@Override
public void onDismissMoreKeysPanel() {
if (isShowingMoreKeysPanel()) {
mMoreKeysPanel.removeFromParent();
mMoreKeysPanel = null;
}
}
private void dismissMoreKeysPanel() {
if (isShowingMoreKeysPanel()) {
mMoreKeysPanel.dismissMoreKeysPanel();
}
}
@Override
public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) {
// Don't populate accessibility event with all Emoji keys.
return true;
}
private int getLongPressTimeout() {
return Settings.getInstance().getCurrent().mKeyLongpressTimeout;
}
/**
* {@inheritDoc}
*/
@ -116,28 +283,66 @@ final class EmojiPageKeyboardView extends KeyboardView implements
*/
@Override
public boolean onTouchEvent(final MotionEvent e) {
if (mGestureDetector.onTouchEvent(e)) {
return true;
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mPointerId = e.getPointerId(0);
return onDown(e);
case MotionEvent.ACTION_UP:
return onUp(e);
case MotionEvent.ACTION_MOVE:
return onMove(e);
case MotionEvent.ACTION_CANCEL:
return onCancel(e);
default:
return false;
}
final Key key = getKey(e);
if (key != null && key != mCurrentKey) {
releaseCurrentKey(false /* withKeyRegistering */);
}
return true;
}
// {@link GestureEnabler#OnGestureListener} methods.
private Key mCurrentKey;
private Runnable mPendingKeyDown;
private final Handler mHandler;
private Key getKey(final MotionEvent e) {
final int index = e.getActionIndex();
final int x = (int)e.getX(index);
final int y = (int)e.getY(index);
private Key getKey(final int x, final int y) {
return mKeyDetector.detectHitKey(x, y);
}
private void onLongPressed(final Key key) {
if (isShowingMoreKeysPanel()) {
return;
}
if (key == null) {
if (LOG) Log.d(TAG, "Long press ignored because detected key is null");
return;
}
final int x = mLastX;
final int y = mLastY;
final MoreKeysPanel moreKeysPanel = showMoreKeysKeyboard(key, x, y);
if (moreKeysPanel != null) {
final int translatedX = moreKeysPanel.translateX(x);
final int translatedY = moreKeysPanel.translateY(y);
moreKeysPanel.onDownEvent(translatedX, translatedY, mPointerId, 0 /* nor used for now */);
}
}
private void registerPress(final Key key) {
// Do not trigger key-down effect right now in case this is actually a fling action.
mPendingKeyDown = new Runnable() {
@Override
public void run() {
callListenerOnPressKey(key);
}
};
mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME);
}
private void registerLongPress(final Key key) {
mPendingLongPress = new Runnable() {
@Override
public void run() {
onLongPressed(key);
}
};
mHandler.postDelayed(mPendingLongPress, getLongPressTimeout());
}
void callListenerOnReleaseKey(final Key releasedKey, final boolean withKeyRegistering) {
releasedKey.onReleased();
invalidateKey(releasedKey);
@ -164,40 +369,48 @@ final class EmojiPageKeyboardView extends KeyboardView implements
mCurrentKey = null;
}
@Override
public void cancelLongPress() {
mHandler.removeCallbacks(mPendingLongPress);
mPendingLongPress = null;
}
public boolean onDown(final MotionEvent e) {
final Key key = getKey(e);
final int x = (int) e.getX();
final int y = (int) e.getY();
final Key key = getKey(x, y);
releaseCurrentKey(false /* withKeyRegistering */);
mCurrentKey = key;
if (key == null) {
return false;
}
// Do not trigger key-down effect right now in case this is actually a fling action.
mPendingKeyDown = new Runnable() {
@Override
public void run() {
callListenerOnPressKey(key);
}
};
mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME);
return false;
registerPress(key);
registerLongPress(key);
mLastX = x;
mLastY = y;
return true;
}
@Override
public void onShowPress(final MotionEvent e) {
// User feedback is done at {@link #onDown(MotionEvent)}.
}
@Override
public boolean onSingleTapUp(final MotionEvent e) {
final Key key = getKey(e);
public boolean onUp(final MotionEvent e) {
final int x = (int) e.getX();
final int y = (int) e.getY();
final Key key = getKey(x, y);
final Runnable pendingKeyDown = mPendingKeyDown;
final Key currentKey = mCurrentKey;
releaseCurrentKey(false /* withKeyRegistering */);
if (key == null) {
return false;
}
if (key == currentKey && pendingKeyDown != null) {
final boolean isShowingMoreKeysPanel = isShowingMoreKeysPanel();
if (isShowingMoreKeysPanel) {
final long eventTime = e.getEventTime();
final int translatedX = mMoreKeysPanel.translateX(x);
final int translatedY = mMoreKeysPanel.translateY(y);
mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime);
dismissMoreKeysPanel();
} else if (key == currentKey && pendingKeyDown != null) {
pendingKeyDown.run();
// Trigger key-release event a little later so that a user can see visual feedback.
mHandler.postDelayed(new Runnable() {
@ -209,25 +422,47 @@ final class EmojiPageKeyboardView extends KeyboardView implements
} else {
callListenerOnReleaseKey(key, true /* withRegistering */);
}
cancelLongPress();
return true;
}
@Override
public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
final float distanceY) {
public boolean onCancel(final MotionEvent e) {
releaseCurrentKey(false /* withKeyRegistering */);
return false;
dismissMoreKeysPanel();
cancelLongPress();
return true;
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
final float velocityY) {
public boolean onMove(final MotionEvent e) {
final int x = (int)e.getX();
final int y = (int)e.getY();
final Key key = getKey(x, y);
final boolean isShowingMoreKeysPanel = isShowingMoreKeysPanel();
// Touched key has changed, release previous key's callbacks and
// re-register them for the new key.
if (key != mCurrentKey && !isShowingMoreKeysPanel) {
releaseCurrentKey(false /* withKeyRegistering */);
mCurrentKey = key;
if (key == null) {
return false;
}
registerPress(key);
@Override
public void onLongPress(final MotionEvent e) {
// Long press detection of {@link #mGestureDetector} is disabled and not used.
cancelLongPress();
registerLongPress(key);
}
if (isShowingMoreKeysPanel) {
final long eventTime = e.getEventTime();
final int translatedX = mMoreKeysPanel.translateX(x);
final int translatedY = mMoreKeysPanel.translateY(y);
mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime);
}
mLastX = x;
mLastY = y;
return true;
}
}

View file

@ -34,14 +34,14 @@ final class EmojiPalettesAdapter extends RecyclerView.Adapter<EmojiPalettesAdapt
private static final String TAG = EmojiPalettesAdapter.class.getSimpleName();
private static final boolean DEBUG_PAGER = false;
private final EmojiPageKeyboardView.OnKeyEventListener mListener;
private final OnKeyEventListener mListener;
private final DynamicGridKeyboard mRecentsKeyboard;
private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews = new SparseArray<>();
private final EmojiCategory mEmojiCategory;
private int mActivePosition = 0;
public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
final EmojiPageKeyboardView.OnKeyEventListener listener) {
final OnKeyEventListener listener) {
mEmojiCategory = emojiCategory;
mListener = listener;
mRecentsKeyboard = mEmojiCategory.getKeyboard(EmojiCategory.ID_RECENTS, 0);

View file

@ -66,7 +66,7 @@ import static org.dslul.openboard.inputmethod.latin.common.Constants.NOT_A_COORD
*/
public final class EmojiPalettesView extends LinearLayout
implements OnTabChangeListener, View.OnClickListener, View.OnTouchListener,
EmojiPageKeyboardView.OnKeyEventListener{
OnKeyEventListener{
private final int mFunctionalKeyBackgroundId;
private final int mSpacebarBackgroundId;
private final boolean mCategoryIndicatorEnabled;
@ -325,7 +325,7 @@ public final class EmojiPalettesView extends LinearLayout
/**
* Called from {@link EmojiPageKeyboardView} through
* {@link org.dslul.openboard.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener}
* {@link org.dslul.openboard.inputmethod.keyboard.emoji.OnKeyEventListener}
* interface to handle touch events from non-View-based elements such as Emoji buttons.
*/
@Override
@ -336,8 +336,9 @@ public final class EmojiPalettesView extends LinearLayout
/**
* Called from {@link EmojiPageKeyboardView} through
* {@link org.dslul.openboard.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener}
* {@link org.dslul.openboard.inputmethod.keyboard.emoji.OnKeyEventListener}
* interface to handle touch events from non-View-based elements such as Emoji buttons.
* This may be called without any prior call to {@link OnKeyEventListener#onPressKey(Key)}.
*/
@Override
public void onReleaseKey(final Key key) {

View file

@ -0,0 +1,22 @@
package org.dslul.openboard.inputmethod.keyboard.emoji;
import org.dslul.openboard.inputmethod.keyboard.Key;
/**
* Interface to handle touch events from non-View-based elements
* such as Emoji buttons.
*/
public interface OnKeyEventListener {
/**
* Called when a key is pressed by the user
*/
void onPressKey(Key key);
/**
* Called when a key is released.
* This may be called without any prior call to {@link OnKeyEventListener#onPressKey(Key)},
* for example when a key from a more keys keyboard is selected by releasing touch on it.
*/
void onReleaseKey(Key key);
}

View file

@ -403,6 +403,8 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
R.styleable.Keyboard_GridRows_codesArray, 0);
final int textsArrayId = gridRowAttr.getResourceId(
R.styleable.Keyboard_GridRows_textsArray, 0);
final int moreCodesArrayId = gridRowAttr.getResourceId(
R.styleable.Keyboard_GridRows_moreCodesArray, 0);
gridRowAttr.recycle();
if (codesArrayId == 0 && textsArrayId == 0) {
throw new XmlParseUtils.ParseException(
@ -412,9 +414,19 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
throw new XmlParseUtils.ParseException(
"Both codesArray and textsArray attributes specifed", parser);
}
if (textsArrayId != 0 && moreCodesArrayId != 0) {
throw new XmlParseUtils.ParseException(
"moreCodesArray is not compatible with textsArray", parser);
}
final String[] array = mResources.getStringArray(
codesArrayId != 0 ? codesArrayId : textsArrayId);
final String[] arrayMore = moreCodesArrayId != 0 ?
mResources.getStringArray(moreCodesArrayId) : null;
final int counts = array.length;
if (arrayMore != null && counts != arrayMore.length) {
throw new XmlParseUtils.ParseException(
"Inconsistent array size between codesArray and moreKeysArray", parser);
}
final float keyWidth = gridRows.getKeyWidth(null, 0.0f);
final int numColumns = (int)(mParams.mOccupiedWidth / keyWidth);
for (int index = 0; index < counts; index += numColumns) {
@ -429,6 +441,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
final int code;
final String outputText;
final int supportedMinSdkVersion;
final String moreKeySpecs;
if (codesArrayId != 0) {
final String codeArraySpec = array[i];
label = CodesArrayParser.parseLabel(codeArraySpec);
@ -436,6 +449,8 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
outputText = CodesArrayParser.parseOutputText(codeArraySpec);
supportedMinSdkVersion =
CodesArrayParser.getMinSupportSdkVersion(codeArraySpec);
moreKeySpecs = MoreCodesArrayParser.parseKeySpecs(
arrayMore != null ? arrayMore[i] : null);
} else {
final String textArraySpec = array[i];
// TODO: Utilize KeySpecParser or write more generic TextsArrayParser.
@ -443,6 +458,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
code = Constants.CODE_OUTPUT_TEXT;
outputText = textArraySpec + (char)Constants.CODE_SPACE;
supportedMinSdkVersion = 0;
moreKeySpecs = null;
}
if (Build.VERSION.SDK_INT < supportedMinSdkVersion) {
continue;
@ -454,9 +470,10 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
final int y = row.getKeyY();
final int width = (int)keyWidth;
final int height = row.getRowHeight();
final Key key = new Key(label, KeyboardIconsSet.ICON_UNDEFINED, code, outputText,
null /* hintLabel */, labelFlags, backgroundType, x, y, width, height,
mParams.mHorizontalGap, mParams.mVerticalGap);
final String hintLabel = moreKeySpecs != null ? "\u25E5" : null;
final KeyboardParams params = mParams;
final Key key = new Key(label, code, outputText, hintLabel, moreKeySpecs,
labelFlags, backgroundType, x, y, width, height, params);
endKey(key);
row.advanceXPos(keyWidth);
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dslul.openboard.inputmethod.keyboard.internal;
import org.dslul.openboard.inputmethod.latin.common.StringUtils;
import javax.annotation.Nullable;
/**
* The string parser of moreCodesArray specification for <GridRows />. The attribute moreCodesArray is an
* array of string.
* The more codes array specification is semicolon separated "codes array specification" each of which represents one
* "more key".
* Each element of the array defines a sequence of key labels specified as hexadecimal strings
* representing code points separated by a vertical bar.
*
*/
public final class MoreCodesArrayParser {
// Constants for parsing.
private static final char SEMICOLON = ';';
private static final String SEMICOLON_REGEX = StringUtils.newSingleCodePointString(SEMICOLON);
private MoreCodesArrayParser() {
// This utility class is not publicly instantiable.
}
public static String parseKeySpecs(@Nullable String codeArraySpecs) {
if (codeArraySpecs == null) {
return null;
}
final StringBuilder sb = new StringBuilder();
for (final String codeArraySpec : codeArraySpecs.split(SEMICOLON_REGEX)) {
final String label = CodesArrayParser.parseLabel(codeArraySpec);
final String outputText = CodesArrayParser.parseOutputText(codeArraySpec);
sb.append(label).append("|").append(outputText);
sb.append(",");
}
// Remove last comma
if (sb.length() > 0) sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
}

View file

@ -291,6 +291,7 @@
<declare-styleable name="Keyboard_GridRows">
<attr name="codesArray" format="reference" />
<attr name="textsArray" format="reference" />
<attr name="moreCodesArray" format="reference" />
</declare-styleable>
<declare-styleable name="Keyboard_Key">