Allow splitting all layouts instead of having to define split layouts in xml (#258)

This commit is contained in:
Helium314 2023-11-03 12:28:00 +01:00 committed by GitHub
parent 778f160c07
commit 1c0fb0c672
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 442 additions and 290 deletions

View file

@ -282,18 +282,24 @@ public class Key implements Comparable<Key> {
mEnabled = keyParams.mEnabled; mEnabled = keyParams.mEnabled;
// stuff to create // stuff to create
mWidth = keyParams.mWidth; // interestingly it looks a little better when rounding horizontalGap to int immediately instead of using horizontalGapFloat
mHeight = keyParams.mHeight; // but using float for determining mX and mWidth is more correct and keyboard ends up looking exactly like before introduction of KeyParams
final float horizontalGapFloat = isSpacer() ? 0 : keyParams.mKeyboardParams.mHorizontalGap;
mHorizontalGap = Math.round(horizontalGapFloat);
mVerticalGap = Math.round(keyParams.mKeyboardParams.mVerticalGap);
mWidth = Math.round(keyParams.mFullWidth - horizontalGapFloat);
// todo (later): height better should be rounded, but this may end up shifting all keys up by one pixel,
// increasing the keyboard height by one pixel, but not for emoji keyboard -> the 1 pixel shift feels very wrong
// how to do it properly? check again when keyboard height is same for all views!
mHeight = (int) (keyParams.mFullHeight - keyParams.mKeyboardParams.mVerticalGap);
if (!isSpacer() && (mWidth == 0 || mHeight == 0)) { if (!isSpacer() && (mWidth == 0 || mHeight == 0)) {
throw new IllegalStateException("key needs positive width and height"); throw new IllegalStateException("key needs positive width and height");
} }
mHorizontalGap = isSpacer() ? 0 : keyParams.mHorizontalGap;
mVerticalGap = keyParams.mVerticalGap;
// Horizontal gap is divided equally to both sides of the key. // Horizontal gap is divided equally to both sides of the key.
mX = Math.round(keyParams.xPos + ((float) keyParams.mHorizontalGap) / 2); mX = Math.round(keyParams.xPos + horizontalGapFloat / 2);
mY = keyParams.yPos; mY = Math.round(keyParams.yPos);
mHitBox.set(Math.round(keyParams.xPos), keyParams.yPos, Math.round(keyParams.xPos + mWidth + mHorizontalGap) + 1, mHitBox.set(Math.round(keyParams.xPos), (int) keyParams.yPos, Math.round(keyParams.xPos + keyParams.mFullWidth) + 1,
keyParams.yPos + mHeight + mHorizontalGap); Math.round(keyParams.yPos + keyParams.mFullHeight));
mHashCode = computeHashCode(this); mHashCode = computeHashCode(this);
} }
@ -937,32 +943,30 @@ public class Key implements Comparable<Key> {
// for creating keys that might get modified later // for creating keys that might get modified later
public static class KeyParams { public static class KeyParams {
// params for building // params for building
boolean isSpacer; private boolean isSpacer;
private final KeyboardParams mParams; // for reading gaps and keyboard width / height private final KeyboardParams mKeyboardParams; // for reading gaps and keyboard width / height
public float mRelativeWidth; // also allows -1f as value, this means "fill right" public float mRelativeWidth;
public float mRelativeHeight; // also allows negative values, indicating absolute height is defined public float mRelativeHeight; // also should allow negative values, indicating absolute height is defined
// stuff that likely remains after constructor, maybe make final // params that may change
final int mCode; public float mFullWidth;
public float mFullHeight;
public float xPos;
public float yPos;
// params that remains constant
public final int mCode;
@Nullable final String mLabel; @Nullable final String mLabel;
@Nullable final String mHintLabel; @Nullable final String mHintLabel;
final int mLabelFlags; final int mLabelFlags;
final int mIconId; final int mIconId;
public final MoreKeySpec[] mMoreKeys; public final MoreKeySpec[] mMoreKeys;
final int mMoreKeysColumnAndFlags; final int mMoreKeysColumnAndFlags;
final int mBackgroundType; public final int mBackgroundType;
final int mActionFlags; final int mActionFlags;
@Nullable final KeyVisualAttributes mKeyVisualAttributes; @Nullable final KeyVisualAttributes mKeyVisualAttributes;
@Nullable final OptionalAttributes mOptionalAttributes; @Nullable final OptionalAttributes mOptionalAttributes;
public boolean mEnabled = true; public final boolean mEnabled;
// stuff that may very well change
private int mWidth;
private int mHeight;
private int mHorizontalGap;
private int mVerticalGap;
private float xPos;
private int yPos;
public static KeyParams newSpacer(final TypedArray keyAttr, final KeyStyle keyStyle, public static KeyParams newSpacer(final TypedArray keyAttr, final KeyStyle keyStyle,
final KeyboardParams params, final XmlKeyboardRow row) { final KeyboardParams params, final XmlKeyboardRow row) {
@ -971,36 +975,25 @@ public class Key implements Comparable<Key> {
return keyParams; return keyParams;
} }
public static KeyParams newSpacer(final KeyboardParams params) {
return new KeyParams(params);
}
public Key createKey() { public Key createKey() {
if (isSpacer) return new Spacer(this); if (isSpacer) return new Spacer(this);
return new Key(this); return new Key(this);
} }
// todo: use it public void setDimensionsFromRelativeSize(final float newX, final float newY) {
// first for inserting spacers to get a split keyboard
// any use for adjusting width or height?
// width is already more or less done with one-handed mode, but this could be more flexible
// height is already implemented via the setting
// any use in combination with number row?
// when completely replacing number row stuff, also moreKeys stuff would need to be adjusted
public void setDimensionsFromRelativeSize(final int newX, final int newY) {
if (mRelativeHeight == 0 || mRelativeWidth == 0) if (mRelativeHeight == 0 || mRelativeWidth == 0)
throw new IllegalStateException("can't use setUsingRelativeHeight, not all fields are set"); throw new IllegalStateException("can't use setUsingRelativeHeight, not all fields are set");
if (mRelativeHeight < 0) if (mRelativeHeight < 0)
throw new IllegalStateException("can't (yet) deal with absolute height"); // todo: decide... maybe just use it and deal with it properly when it needs to be adjusted? // todo (later): deal with it properly when it needs to be adjusted, i.e. when changing moreKeys or moreSuggestions
throw new IllegalStateException("can't (yet) deal with absolute height");
xPos = newX; xPos = newX;
yPos = newY; yPos = newY;
float horizontalGap = isSpacer ? 0f : mParams.mRelativeHorizontalGap * mParams.mId.mWidth; // gap width / height is based on params.mId.height / width mFullWidth = mRelativeWidth * mKeyboardParams.mBaseWidth;
float verticalGap = mParams.mRelativeVerticalGap * mParams.mId.mHeight; mFullHeight = mRelativeHeight * mKeyboardParams.mBaseHeight;
mHorizontalGap = (int) horizontalGap;
mVerticalGap = (int) verticalGap;
float keyWidth;
if (mRelativeWidth > 0)
keyWidth = mRelativeWidth * mParams.mBaseWidth; // key width / height is based on params.mBaseHeight / width
else // fillRight
keyWidth = (mParams.mOccupiedWidth - mParams.mRightPadding) - xPos; // right keyboard edge - x
mWidth = Math.round(keyWidth - horizontalGap);
mHeight = (int) (mRelativeHeight * mParams.mBaseHeight - verticalGap);
} }
/** /**
@ -1017,26 +1010,23 @@ public class Key implements Comparable<Key> {
public KeyParams(@Nullable final String keySpec, @NonNull final TypedArray keyAttr, public KeyParams(@Nullable final String keySpec, @NonNull final TypedArray keyAttr,
@NonNull final KeyStyle style, @NonNull final KeyboardParams params, @NonNull final KeyStyle style, @NonNull final KeyboardParams params,
@NonNull final XmlKeyboardRow row) { @NonNull final XmlKeyboardRow row) {
mParams = params; mKeyboardParams = params;
mRelativeHeight = row.mRelativeRowHeight; mRelativeHeight = row.mRelativeRowHeight;
mRelativeWidth = row.getRelativeKeyWidth(keyAttr); mRelativeWidth = row.getRelativeKeyWidth(keyAttr);
mHorizontalGap = params.mHorizontalGap;
mVerticalGap = params.mVerticalGap;
final float horizontalGapFloat = mHorizontalGap;
final int rowHeight = row.getRowHeight();
mHeight = rowHeight - mVerticalGap;
mFullHeight = row.getRowHeight();
xPos = row.getKeyX(keyAttr); xPos = row.getKeyX(keyAttr);
final float keyWidth = row.getKeyWidth(keyAttr, xPos); mFullWidth = row.getKeyWidth(keyAttr, xPos);
if (mRelativeWidth == -1f) {
// determine from actual width if using fillRight
mRelativeWidth = mFullWidth / mKeyboardParams.mBaseWidth;
}
yPos = row.getKeyY(); yPos = row.getKeyY();
mWidth = Math.round(keyWidth - horizontalGapFloat);
// Update row to have current x coordinate. // Update row to have current x coordinate.
row.setXPos(xPos + keyWidth); row.setXPos(xPos + mFullWidth);
mBackgroundType = style.getInt(keyAttr, mBackgroundType = style.getInt(keyAttr, R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType());
R.styleable.Keyboard_Key_backgroundType, row.getDefaultBackgroundType());
final int baseWidth = params.mBaseWidth; final int baseWidth = params.mBaseWidth;
final int visualInsetsLeft = Math.round(keyAttr.getFraction( final int visualInsetsLeft = Math.round(keyAttr.getFraction(
@ -1058,13 +1048,11 @@ public class Key implements Comparable<Key> {
int value; int value;
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
// Override with fixed column order number and set a relevant mode value. // Override with fixed column order number and set a relevant mode value.
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER | (value & MORE_KEYS_COLUMN_NUMBER_MASK);
| (value & MORE_KEYS_COLUMN_NUMBER_MASK);
} }
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
// Override with fixed column order number and set a relevant mode value. // Override with fixed column order number and set a relevant mode value.
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER | (value & MORE_KEYS_COLUMN_NUMBER_MASK);
| (value & MORE_KEYS_COLUMN_NUMBER_MASK);
} }
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS; moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS;
@ -1081,8 +1069,7 @@ public class Key implements Comparable<Key> {
if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) { if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
additionalMoreKeys = null; additionalMoreKeys = null;
} else { } else {
additionalMoreKeys = style.getStringArray(keyAttr, additionalMoreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_additionalMoreKeys);
R.styleable.Keyboard_Key_additionalMoreKeys);
} }
moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys); moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys);
if (moreKeys != null) { if (moreKeys != null) {
@ -1117,8 +1104,7 @@ public class Key implements Comparable<Key> {
if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) { if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
mHintLabel = null; mHintLabel = null;
} else { } else {
final String hintLabel = style.getString( final String hintLabel = style.getString(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
mHintLabel = needsToUpcase mHintLabel = needsToUpcase
? StringUtils.toTitleCaseOfKeyLabel(hintLabel, localeForUpcasing) ? StringUtils.toTitleCaseOfKeyLabel(hintLabel, localeForUpcasing)
: hintLabel; : hintLabel;
@ -1128,12 +1114,12 @@ public class Key implements Comparable<Key> {
outputText = StringUtils.toTitleCaseOfKeyLabel(outputText, localeForUpcasing); outputText = StringUtils.toTitleCaseOfKeyLabel(outputText, localeForUpcasing);
} }
// Choose the first letter of the label as primary code if not specified. // Choose the first letter of the label as primary code if not specified.
if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) && !TextUtils.isEmpty(mLabel)) {
&& !TextUtils.isEmpty(mLabel)) {
if (StringUtils.codePointCount(mLabel) == 1) { if (StringUtils.codePointCount(mLabel) == 1) {
// Use the first letter of the hint label if shiftedLetterActivated flag is // Use the first letter of the hint label if shiftedLetterActivated flag is
// specified. // specified.
if ((mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0 && !TextUtils.isEmpty(mHintLabel)) { if ((mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0
&& !TextUtils.isEmpty(mHintLabel)) {
mCode = mHintLabel.codePointAt(0); mCode = mHintLabel.codePointAt(0);
} else { } else {
mCode = mLabel.codePointAt(0); mCode = mLabel.codePointAt(0);
@ -1152,8 +1138,7 @@ public class Key implements Comparable<Key> {
mCode = CODE_OUTPUT_TEXT; mCode = CODE_OUTPUT_TEXT;
} }
} else { } else {
mCode = needsToUpcase ? StringUtils.toTitleCaseOfKeyCode(code, localeForUpcasing) mCode = needsToUpcase ? StringUtils.toTitleCaseOfKeyCode(code, localeForUpcasing) : code;
: code;
} }
final int altCodeInAttr = KeySpecParser.parseCode( final int altCodeInAttr = KeySpecParser.parseCode(
style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED); style.getString(keyAttr, R.styleable.Keyboard_Key_altCode), CODE_UNSPECIFIED);
@ -1163,6 +1148,7 @@ public class Key implements Comparable<Key> {
mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode, mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode,
disabledIconId, visualInsetsLeft, visualInsetsRight); disabledIconId, visualInsetsLeft, visualInsetsRight);
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
mEnabled = true;
} }
/** for <GridRows/> */ /** for <GridRows/> */
@ -1170,11 +1156,9 @@ public class Key implements Comparable<Key> {
@Nullable final String hintLabel, @Nullable final String moreKeySpecs, @Nullable final String hintLabel, @Nullable final String moreKeySpecs,
final int labelFlags, final int backgroundType, final int x, final int y, final int labelFlags, final int backgroundType, final int x, final int y,
final int width, final int height, final KeyboardParams params) { final int width, final int height, final KeyboardParams params) {
mParams = params; mKeyboardParams = params;
mWidth = width - params.mHorizontalGap; mFullWidth = width;
mHeight = height - params.mVerticalGap; mFullHeight = height;
mHorizontalGap = params.mHorizontalGap;
mVerticalGap = params.mVerticalGap;
mHintLabel = hintLabel; mHintLabel = hintLabel;
mLabelFlags = labelFlags; mLabelFlags = labelFlags;
mBackgroundType = backgroundType; mBackgroundType = backgroundType;
@ -1184,8 +1168,7 @@ public class Key implements Comparable<Key> {
if (moreKeySpecs != null) { if (moreKeySpecs != null) {
String[] moreKeys = MoreKeySpec.splitKeySpecs(moreKeySpecs); String[] moreKeys = MoreKeySpec.splitKeySpecs(moreKeySpecs);
// Get maximum column order number and set a relevant mode value. // Get maximum column order number and set a relevant mode value.
int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER | params.mMaxMoreKeysKeyboardColumn;
| params.mMaxMoreKeysKeyboardColumn;
int value; int value;
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) { if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
// Override with fixed column order number and set a relevant mode value. // Override with fixed column order number and set a relevant mode value.
@ -1194,8 +1177,7 @@ public class Key implements Comparable<Key> {
} }
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) { if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
// Override with fixed column order number and set a relevant mode value. // Override with fixed column order number and set a relevant mode value.
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER | (value & MORE_KEYS_COLUMN_NUMBER_MASK);
| (value & MORE_KEYS_COLUMN_NUMBER_MASK);
} }
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) { if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS; moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS;
@ -1235,5 +1217,48 @@ public class Key implements Comparable<Key> {
mIconId = KeyboardIconsSet.ICON_UNDEFINED; mIconId = KeyboardIconsSet.ICON_UNDEFINED;
mKeyVisualAttributes = null; mKeyVisualAttributes = null;
} }
/** constructor for a spacer whose size MUST be determined using setDimensionsFromRelativeSize */
private KeyParams(final KeyboardParams params) {
isSpacer = true; // this is only for spacer!
mKeyboardParams = params;
mCode = CODE_UNSPECIFIED;
mLabel = null;
mHintLabel = null;
mKeyVisualAttributes = null;
mOptionalAttributes = null;
mIconId = KeyboardIconsSet.ICON_UNDEFINED;
mBackgroundType = BACKGROUND_TYPE_NORMAL;
mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW;
mMoreKeys = null;
mMoreKeysColumnAndFlags = 0;
mLabelFlags = LABEL_FLAGS_FONT_NORMAL;
mEnabled = true;
}
public KeyParams(final KeyParams keyParams) {
xPos = keyParams.xPos;
yPos = keyParams.yPos;
mRelativeWidth = keyParams.mRelativeWidth;
mRelativeHeight = keyParams.mRelativeHeight;
isSpacer = keyParams.isSpacer;
mKeyboardParams = keyParams.mKeyboardParams;
mEnabled = keyParams.mEnabled;
mCode = keyParams.mCode;
mLabel = keyParams.mLabel;
mHintLabel = keyParams.mHintLabel;
mLabelFlags = keyParams.mLabelFlags;
mIconId = keyParams.mIconId;
mFullWidth = keyParams.mFullWidth;
mFullHeight = keyParams.mFullHeight;
mMoreKeys = keyParams.mMoreKeys;
mMoreKeysColumnAndFlags = keyParams.mMoreKeysColumnAndFlags;
mBackgroundType = keyParams.mBackgroundType;
mActionFlags = keyParams.mActionFlags;
mKeyVisualAttributes = keyParams.mKeyVisualAttributes;
mOptionalAttributes = keyParams.mOptionalAttributes;
}
} }
} }

View file

@ -457,8 +457,7 @@ public final class KeyboardLayoutSet {
elementParams.mProximityCharsCorrectionEnabled = a.getBoolean( elementParams.mProximityCharsCorrectionEnabled = a.getBoolean(
R.styleable.KeyboardLayoutSet_Element_enableProximityCharsCorrection, R.styleable.KeyboardLayoutSet_Element_enableProximityCharsCorrection,
false); false);
elementParams.mSupportsSplitLayout = a.getBoolean( elementParams.mSupportsSplitLayout = false; // this is to avoid xml parser reading split layouts, todo (later): remove mSupportsSplitLayout
R.styleable.KeyboardLayoutSet_Element_supportsSplitLayout, false);
elementParams.mAllowRedundantMoreKeys = a.getBoolean( elementParams.mAllowRedundantMoreKeys = a.getBoolean(
R.styleable.KeyboardLayoutSet_Element_allowRedundantMoreKeys, true); R.styleable.KeyboardLayoutSet_Element_allowRedundantMoreKeys, true);
mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams); mParams.mKeyboardLayoutSetElementIdToParamsMap.put(elementName, elementParams);

View file

@ -1,152 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
* modified
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
*/
package org.dslul.openboard.inputmethod.keyboard.internal;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import androidx.annotation.NonNull;
import org.dslul.openboard.inputmethod.annotations.UsedForTesting;
import org.dslul.openboard.inputmethod.keyboard.Key;
import org.dslul.openboard.inputmethod.keyboard.Keyboard;
import org.dslul.openboard.inputmethod.keyboard.KeyboardId;
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.XmlKeyboardParser;
import org.dslul.openboard.inputmethod.latin.R;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
// TODO: Write unit tests for this class.
public class KeyboardBuilder<KP extends KeyboardParams> {
private static final String BUILDER_TAG = "Keyboard.Builder";
@NonNull
protected final KP mParams;
protected final Context mContext;
protected final Resources mResources;
private int mCurrentY = 0;
// currently not used, but will be relevant when resizing a row or inserting a new key
private float mCurrentX = 0f;
private boolean mLeftEdge;
private boolean mTopEdge;
private Key mRightEdgeKey = null;
private ArrayList<ArrayList<Key.KeyParams>> keysInRows;
public KeyboardBuilder(final Context context, @NonNull final KP params) {
mContext = context;
final Resources res = context.getResources();
mResources = res;
mParams = params;
params.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
params.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
}
public void setAllowRedundantMoreKeys(final boolean enabled) {
mParams.mAllowRedundantMoreKeys = enabled;
}
public KeyboardBuilder<KP> loadFromXml(final int xmlId, final KeyboardId id) {
mParams.mId = id;
// loading a keyboard should set default params like mParams.readAttributes(mContext, attrs);
// attrs may be null, then default values are used (looks good for "normal" keyboards)
try (XmlKeyboardParser keyboardParser = new XmlKeyboardParser(xmlId, mParams, mContext)) {
keysInRows = keyboardParser.parseKeyboard();
} catch (XmlPullParserException e) {
Log.w(BUILDER_TAG, "keyboard XML parse error", e);
throw new IllegalArgumentException(e.getMessage(), e);
} catch (IOException e) {
Log.w(BUILDER_TAG, "keyboard XML parse error", e);
throw new RuntimeException(e.getMessage(), e);
}
return this;
}
@UsedForTesting
public void disableTouchPositionCorrectionDataForTest() {
mParams.mTouchPositionCorrection.setEnabled(false);
}
public void setProximityCharsCorrectionEnabled(final boolean enabled) {
mParams.mProximityCharsCorrectionEnabled = enabled;
}
@NonNull
public Keyboard build() {
addKeysToParams();
return new Keyboard(mParams);
}
private void startKeyboard() {
mCurrentY += mParams.mTopPadding;
mTopEdge = true;
}
private void startRow() {
addEdgeSpace(mParams.mLeftPadding);
mLeftEdge = true;
mRightEdgeKey = null;
}
private void endRow() {
int lastKeyHeight = 0;
if (mRightEdgeKey != null) {
mRightEdgeKey.markAsRightEdge(mParams);
lastKeyHeight = mRightEdgeKey.getHeight() + mRightEdgeKey.getVerticalGap();
mRightEdgeKey = null;
}
addEdgeSpace(mParams.mRightPadding);
mCurrentY += lastKeyHeight;
mTopEdge = false;
}
private void endKey(@NonNull final Key key) {
mParams.onAddKey(key);
if (mLeftEdge) {
key.markAsLeftEdge(mParams);
mLeftEdge = false;
}
if (mTopEdge) {
key.markAsTopEdge(mParams);
}
mRightEdgeKey = key;
}
private void endKeyboard() {
mParams.removeRedundantMoreKeys();
// {@link #parseGridRows(XmlPullParser,boolean)} may populate keyboard rows higher than
// previously expected.
final int actualHeight = mCurrentY - mParams.mVerticalGap + mParams.mBottomPadding;
mParams.mOccupiedHeight = Math.max(mParams.mOccupiedHeight, actualHeight);
}
private void addKeysToParams() {
// need to reset it, we need to sum it up to get the height nicely
// (though in the end we could just not touch it at all, final used value is the same as the one before resetting)
mCurrentY = 0;
startKeyboard();
for (ArrayList<Key.KeyParams> row : keysInRows) {
startRow();
for (Key.KeyParams keyParams : row) {
endKey(keyParams.createKey());
}
endRow();
}
endKeyboard();
}
private void addEdgeSpace(final float width) {
mCurrentX += width;
mLeftEdge = false;
mRightEdgeKey = null;
}
}

View file

@ -0,0 +1,279 @@
/*
* Copyright (C) 2012 The Android Open Source Project
* modified
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
*/
package org.dslul.openboard.inputmethod.keyboard.internal
import android.content.Context
import android.content.res.Resources
import android.util.Log
import org.dslul.openboard.inputmethod.annotations.UsedForTesting
import org.dslul.openboard.inputmethod.keyboard.Key
import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams
import org.dslul.openboard.inputmethod.keyboard.Keyboard
import org.dslul.openboard.inputmethod.keyboard.KeyboardId
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.XmlKeyboardParser
import org.dslul.openboard.inputmethod.latin.R
import org.dslul.openboard.inputmethod.latin.common.Constants
import org.dslul.openboard.inputmethod.latin.settings.Settings
import org.xmlpull.v1.XmlPullParserException
import java.io.IOException
// TODO: Write unit tests for this class.
open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context, @JvmField val mParams: KP) {
@JvmField
protected val mResources: Resources
private var mCurrentY = 0
private var mLeftEdge = false
private var mTopEdge = false
private var mRightEdgeKey: Key? = null
private lateinit var keysInRows: ArrayList<ArrayList<KeyParams>>
init {
val res = mContext.resources
mResources = res
mParams.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width)
mParams.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height)
}
fun setAllowRedundantMoreKeys(enabled: Boolean) {
mParams.mAllowRedundantMoreKeys = enabled
}
fun loadFromXml(xmlId: Int, id: KeyboardId): KeyboardBuilder<KP> {
mParams.mId = id
// loading a keyboard should set default params like mParams.readAttributes(mContext, attrs);
// attrs may be null, then default values are used (looks good for "normal" keyboards)
try {
XmlKeyboardParser(xmlId, mParams, mContext).use { keyboardParser ->
keysInRows = keyboardParser.parseKeyboard()
}
} catch (e: XmlPullParserException) {
Log.w(BUILDER_TAG, "keyboard XML parse error", e)
throw IllegalArgumentException(e.message, e)
} catch (e: IOException) {
Log.w(BUILDER_TAG, "keyboard XML parse error", e)
throw RuntimeException(e.message, e)
}
return this
}
@UsedForTesting
fun disableTouchPositionCorrectionDataForTest() {
mParams.mTouchPositionCorrection.setEnabled(false)
}
fun setProximityCharsCorrectionEnabled(enabled: Boolean) {
mParams.mProximityCharsCorrectionEnabled = enabled
}
open fun build(): Keyboard {
if (Settings.getInstance().current.mIsSplitKeyboardEnabled
&& mParams.mId.mElementId in KeyboardId.ELEMENT_ALPHABET..KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
addSplit()
}
addKeysToParams()
return Keyboard(mParams)
}
// resize keyboard using relative params
// ideally this should not change anything
// but it does a little, depending on how float -> int is done (cast or round, and when to sum up gaps and width)
// still should not be more than a pixel difference
// keep it around for a while, for testing
private fun useRelative() {
for (row in keysInRows) {
if (row.isEmpty()) continue
fillGapsWithSpacers(row)
val y = row.first().yPos
assert(row.all { it.yPos == y })
var currentX = 0f
row.forEach {
it.setDimensionsFromRelativeSize(currentX, y)
currentX += it.mFullWidth
}
}
}
// necessary for adjusting widths and positions properly
// without adding spacers whose width can then be adjusted, we would have to deal with keyXPos,
// which is more complicated than expected
private fun fillGapsWithSpacers(row: MutableList<KeyParams>) {
if (mParams.mId.mElementId !in KeyboardId.ELEMENT_ALPHABET..KeyboardId.ELEMENT_SYMBOLS_SHIFTED) return
if (row.isEmpty()) return
var currentX = 0f + mParams.mLeftPadding
var i = 0
while (i < row.size) {
val currentKeyXPos = row[i].xPos
if (currentKeyXPos > currentX) {
// insert spacer
val spacer = KeyParams.newSpacer(mParams)
spacer.mRelativeWidth = (currentKeyXPos - currentX) / mParams.mBaseWidth
spacer.yPos = row[i].yPos
spacer.mRelativeHeight = row[i].mRelativeHeight
row.add(i, spacer)
i++
currentX += currentKeyXPos - currentX
}
currentX += row[i].mFullWidth
i++
}
if (currentX < mParams.mOccupiedWidth) {
// insert spacer
val spacer = KeyParams.newSpacer(mParams)
spacer.mRelativeWidth = (mParams.mOccupiedWidth - currentX) / mParams.mBaseWidth
spacer.mRelativeHeight = row.last().mRelativeHeight
spacer.yPos = row.last().yPos
row.add(spacer)
}
}
private fun addSplit() {
val spacerRelativeWidth = Settings.getInstance().current.mSpacerRelativeWidth
// adjust gaps for the whole keyboard, so it's the same for all rows
// todo: maybe remove? not sure if narrower gaps are desirable
mParams.mRelativeHorizontalGap *= 1f / (1f + spacerRelativeWidth)
mParams.mHorizontalGap = (mParams.mRelativeHorizontalGap * mParams.mId.mWidth).toInt()
var maxWidthBeforeSpacer = 0f
var maxWidthAfterSpacer = 0f
for (row in keysInRows) {
fillGapsWithSpacers(row)
val y = row.first().yPos // all have the same y, so this is fine
val relativeWidthSum = row.sumOf { it.mRelativeWidth } // sum up relative widths
val spacer = KeyParams.newSpacer(mParams)
spacer.mRelativeWidth = spacerRelativeWidth
spacer.mRelativeHeight = row.first().mRelativeHeight
// insert spacer before first key that starts right of the center (also consider gap)
var insertIndex = row.indexOfFirst { it.xPos > mParams.mOccupiedWidth / 2 }
.takeIf { it > -1 } ?: (row.size / 2) // fallback should never be needed, but better than having an error
if (row.any { it.mCode == Constants.CODE_SPACE }) {
val spaceLeft = row.single { it.mCode == Constants.CODE_SPACE }
reduceSymbolAndActionKeyWidth(row)
insertIndex = row.indexOf(spaceLeft) + 1
val widthBeforeSpace = row.subList(0, insertIndex - 1).sumOf { it.mRelativeWidth }
val widthAfterSpace = row.subList(insertIndex, row.size).sumOf { it.mRelativeWidth }
val spaceLeftWidth = (maxWidthBeforeSpacer - widthBeforeSpace).coerceAtLeast(mParams.mDefaultRelativeKeyWidth)
val spaceRightWidth = (maxWidthAfterSpacer - widthAfterSpace).coerceAtLeast(mParams.mDefaultRelativeKeyWidth)
val spacerWidth = spaceLeft.mRelativeWidth + spacerRelativeWidth - spaceLeftWidth - spaceRightWidth
if (spacerWidth > 0.05f) {
// only insert if the spacer has a reasonable width
val spaceRight = KeyParams(spaceLeft)
spaceLeft.mRelativeWidth = spaceLeftWidth
spaceRight.mRelativeWidth = spaceRightWidth
spacer.mRelativeWidth = spacerWidth
row.add(insertIndex, spaceRight)
row.add(insertIndex, spacer)
} else {
// otherwise increase space width, so other keys are resized properly
spaceLeft.mRelativeWidth += spacerWidth
}
} else {
val widthBeforeSpacer = row.subList(0, insertIndex).sumOf { it.mRelativeWidth }
val widthAfterSpacer = row.subList(insertIndex, row.size).sumOf { it.mRelativeWidth }
maxWidthBeforeSpacer = maxWidthBeforeSpacer.coerceAtLeast(widthBeforeSpacer)
maxWidthAfterSpacer = maxWidthAfterSpacer.coerceAtLeast(widthAfterSpacer)
row.add(insertIndex, spacer)
}
// re-calculate relative widths
val relativeWidthSumNew = row.sumOf { it.mRelativeWidth }
val widthFactor = relativeWidthSum / relativeWidthSumNew
// re-calculate absolute sizes and positions
var currentX = 0f
row.forEach {
it.mRelativeWidth *= widthFactor
it.setDimensionsFromRelativeSize(currentX, y)
currentX += it.mFullWidth
}
}
}
// reduce width of symbol and action key if in the row, and add this width to space to keep other key size constant
private fun reduceSymbolAndActionKeyWidth(row: ArrayList<KeyParams>) {
val spaceKey = row.first { it.mCode == Constants.CODE_SPACE }
val symbolKey = row.firstOrNull { it.mCode == Constants.CODE_SWITCH_ALPHA_SYMBOL }
val symbolKeyWidth = symbolKey?.mRelativeWidth ?: 0f
if (symbolKeyWidth > mParams.mDefaultRelativeKeyWidth) {
val widthToChange = symbolKey!!.mRelativeWidth - mParams.mDefaultRelativeKeyWidth
symbolKey.mRelativeWidth -= widthToChange
spaceKey.mRelativeWidth += widthToChange
}
val actionKey = row.firstOrNull { it.mBackgroundType == Key.BACKGROUND_TYPE_ACTION }
val actionKeyWidth = actionKey?.mRelativeWidth ?: 0f
if (actionKeyWidth > mParams.mDefaultRelativeKeyWidth * 1.1f) { // allow it to stay a little wider
val widthToChange = actionKey!!.mRelativeWidth - mParams.mDefaultRelativeKeyWidth * 1.1f
actionKey.mRelativeWidth -= widthToChange
spaceKey.mRelativeWidth += widthToChange
}
}
private fun startKeyboard() {
mCurrentY += mParams.mTopPadding
mTopEdge = true
}
private fun startRow() {
mLeftEdge = true
mRightEdgeKey = null
}
private fun endRow() {
val rightEdgeKey = mRightEdgeKey
if (rightEdgeKey != null) {
rightEdgeKey.markAsRightEdge(mParams)
mCurrentY += rightEdgeKey.height + rightEdgeKey.verticalGap
mRightEdgeKey = null
}
mLeftEdge = false
mTopEdge = false
}
private fun endKey(key: Key) {
mParams.onAddKey(key)
if (mLeftEdge) {
key.markAsLeftEdge(mParams)
mLeftEdge = false
}
if (mTopEdge) {
key.markAsTopEdge(mParams)
}
mRightEdgeKey = key
}
private fun endKeyboard() {
mParams.removeRedundantMoreKeys()
// {@link #parseGridRows(XmlPullParser,boolean)} may populate keyboard rows higher than
// previously expected.
val actualHeight = mCurrentY - mParams.mVerticalGap + mParams.mBottomPadding
mParams.mOccupiedHeight = Math.max(mParams.mOccupiedHeight, actualHeight)
}
private fun addKeysToParams() {
// need to reset it, we need to sum it up to get the height nicely
// (though in the end we could just not touch it at all, final used value is the same as the one before resetting)
mCurrentY = 0
startKeyboard()
for (row in keysInRows) {
startRow()
for (keyParams in row) {
endKey(keyParams.createKey())
}
endRow()
}
endKeyboard()
}
companion object {
private const val BUILDER_TAG = "Keyboard.Builder"
}
}
// adapted from Kotlin source: https://github.com/JetBrains/kotlin/blob/7a7d392b3470b38d42f80c896b7270678d0f95c3/libraries/stdlib/common/src/generated/_Collections.kt#L3004
private inline fun <T> Iterable<T>.sumOf(selector: (T) -> Float): Float {
var sum = 0f
for (element in this) {
sum += selector(element)
}
return sum
}

View file

@ -217,11 +217,10 @@ public class KeyboardParams {
mRightPadding = (int) keyboardAttr.getFraction( mRightPadding = (int) keyboardAttr.getFraction(
R.styleable.Keyboard_keyboardRightPadding, width, width, 0); R.styleable.Keyboard_keyboardRightPadding, width, width, 0);
final int baseWidth = mOccupiedWidth - mLeftPadding - mRightPadding; mBaseWidth = mOccupiedWidth - mLeftPadding - mRightPadding;
mBaseWidth = baseWidth;
mDefaultRelativeKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, mDefaultRelativeKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
1, 1, 1f / DEFAULT_KEYBOARD_COLUMNS); 1, 1, 1f / DEFAULT_KEYBOARD_COLUMNS);
mDefaultKeyWidth = (int) (mDefaultRelativeKeyWidth * baseWidth); mDefaultKeyWidth = (int) (mDefaultRelativeKeyWidth * mBaseWidth);
// todo: maybe settings should not be accessed from here? // todo: maybe settings should not be accessed from here?
if (Settings.getInstance().getCurrent().mNarrowKeyGaps) { if (Settings.getInstance().getCurrent().mNarrowKeyGaps) {
@ -241,15 +240,14 @@ public class KeyboardParams {
mHorizontalGap = (int) (mRelativeHorizontalGap * width); mHorizontalGap = (int) (mRelativeHorizontalGap * width);
mVerticalGap = (int) (mRelativeVerticalGap * height); mVerticalGap = (int) (mRelativeVerticalGap * height);
final int baseHeight = mOccupiedHeight - mTopPadding - mBottomPadding + mVerticalGap; mBaseHeight = mOccupiedHeight - mTopPadding - mBottomPadding + mVerticalGap;
mBaseHeight = baseHeight;
mDefaultRelativeRowHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr, mDefaultRelativeRowHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_rowHeight, 1, 1f / DEFAULT_KEYBOARD_ROWS); R.styleable.Keyboard_rowHeight, 1, 1f / DEFAULT_KEYBOARD_ROWS);
if (mDefaultRelativeRowHeight > 1) { // can be absolute size, in that case will be > 1 if (mDefaultRelativeRowHeight > 1) { // can be absolute size, in that case will be > 1
mDefaultRowHeight = (int) mDefaultRelativeRowHeight; mDefaultRowHeight = (int) mDefaultRelativeRowHeight;
mDefaultRelativeRowHeight *= -1; // make it negative when it's absolute mDefaultRelativeRowHeight *= -1; // make it negative when it's absolute
} else { } else {
mDefaultRowHeight = (int) (mDefaultRelativeRowHeight * baseHeight); mDefaultRowHeight = (int) (mDefaultRelativeRowHeight * mBaseHeight);
} }
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);

View file

@ -38,8 +38,8 @@ public final class XmlKeyboardRow {
// TODO: Add keyActionFlags. // TODO: Add keyActionFlags.
private static class RowAttributes { private static class RowAttributes {
/** Default width of a key in this row. */ /** Default width of a key in this row, relative to keyboard width */
public final float mDefaultKeyWidth; public final float mDefaultRelativeKeyWidth;
/** Default keyLabelFlags in this row. */ /** Default keyLabelFlags in this row. */
public final int mDefaultKeyLabelFlags; public final int mDefaultKeyLabelFlags;
/** Default backgroundType for this row */ /** Default backgroundType for this row */
@ -49,13 +49,11 @@ public final class XmlKeyboardRow {
* Parse and create key attributes. This constructor is used to parse Row tag. * Parse and create key attributes. This constructor is used to parse Row tag.
* *
* @param keyAttr an attributes array of Row tag. * @param keyAttr an attributes array of Row tag.
* @param defaultKeyWidth a default key width. * @param defaultRelativeKeyWidth a default key width relative to keyboardWidth.
* @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute.
*/ */
public RowAttributes(final TypedArray keyAttr, final float defaultKeyWidth, public RowAttributes(final TypedArray keyAttr, final float defaultRelativeKeyWidth) {
final int keyboardWidth) { mDefaultRelativeKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, 1, 1, defaultRelativeKeyWidth);
keyboardWidth, keyboardWidth, defaultKeyWidth);
mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0); mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0);
mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType, mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
Key.BACKGROUND_TYPE_NORMAL); Key.BACKGROUND_TYPE_NORMAL);
@ -67,12 +65,10 @@ public final class XmlKeyboardRow {
* *
* @param keyAttr an attributes array of include tag. * @param keyAttr an attributes array of include tag.
* @param defaultRowAttr default Row attributes. * @param defaultRowAttr default Row attributes.
* @param keyboardWidth the keyboard width that is required to calculate keyWidth attribute.
*/ */
public RowAttributes(final TypedArray keyAttr, final RowAttributes defaultRowAttr, public RowAttributes(final TypedArray keyAttr, final RowAttributes defaultRowAttr) {
final int keyboardWidth) { mDefaultRelativeKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, 1, 1, defaultRowAttr.mDefaultRelativeKeyWidth);
keyboardWidth, keyboardWidth, defaultRowAttr.mDefaultKeyWidth);
mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0) mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0)
| defaultRowAttr.mDefaultKeyLabelFlags; | defaultRowAttr.mDefaultKeyLabelFlags;
mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType, mDefaultBackgroundType = keyAttr.getInt(R.styleable.Keyboard_Key_backgroundType,
@ -101,8 +97,7 @@ public final class XmlKeyboardRow {
} }
final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser), final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key); R.styleable.Keyboard_Key);
mRowAttributesStack.push(new RowAttributes( mRowAttributesStack.push(new RowAttributes(keyAttr, params.mDefaultRelativeKeyWidth));
keyAttr, params.mDefaultKeyWidth, params.mBaseWidth));
keyAttr.recycle(); keyAttr.recycle();
mCurrentY = y; mCurrentY = y;
@ -114,8 +109,7 @@ public final class XmlKeyboardRow {
} }
public void pushRowAttributes(final TypedArray keyAttr) { public void pushRowAttributes(final TypedArray keyAttr) {
final RowAttributes newAttributes = new RowAttributes( final RowAttributes newAttributes = new RowAttributes(keyAttr, mRowAttributesStack.peek());
keyAttr, mRowAttributesStack.peek(), mParams.mBaseWidth);
mRowAttributesStack.push(newAttributes); mRowAttributesStack.push(newAttributes);
} }
@ -123,8 +117,8 @@ public final class XmlKeyboardRow {
mRowAttributesStack.pop(); mRowAttributesStack.pop();
} }
public float getDefaultKeyWidth() { public float getDefaultRelativeKeyWidth() {
return mRowAttributesStack.peek().mDefaultKeyWidth; return mRowAttributesStack.peek().mDefaultRelativeKeyWidth;
} }
public int getDefaultKeyLabelFlags() { public int getDefaultKeyLabelFlags() {
@ -167,30 +161,24 @@ public final class XmlKeyboardRow {
public float getRelativeKeyWidth(final TypedArray keyAttr) { public float getRelativeKeyWidth(final TypedArray keyAttr) {
if (keyAttr == null) if (keyAttr == null)
return mParams.mDefaultRelativeKeyWidth; return getDefaultRelativeKeyWidth();
final int widthType = ResourceUtils.getEnumValue(keyAttr, final int widthType = ResourceUtils.getEnumValue(keyAttr,
R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM); R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
if (widthType == KEYWIDTH_FILL_RIGHT) if (widthType == KEYWIDTH_FILL_RIGHT)
return -1; return -1f;
// else KEYWIDTH_NOT_ENUM
return keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, return keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
1, 1, mParams.mDefaultRelativeKeyWidth); 1, 1, getDefaultRelativeKeyWidth());
} }
public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) { public float getKeyWidth(final TypedArray keyAttr, final float keyXPos) {
if (keyAttr == null) { final float relativeWidth = getRelativeKeyWidth(keyAttr);
return getDefaultKeyWidth(); if (relativeWidth == -1f) {
}
final int widthType = ResourceUtils.getEnumValue(keyAttr,
R.styleable.Keyboard_Key_keyWidth, KEYWIDTH_NOT_ENUM);
switch (widthType) {
case KEYWIDTH_FILL_RIGHT:
// If keyWidth is fillRight, the actual key width will be determined to fill // If keyWidth is fillRight, the actual key width will be determined to fill
// out the area up to the right edge of the keyboard. // out the area up to the right edge of the keyboard.
final int keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding; final int keyboardRightEdge = mParams.mOccupiedWidth - mParams.mRightPadding;
return keyboardRightEdge - keyXPos; return keyboardRightEdge - keyXPos;
default: // KEYWIDTH_NOT_ENUM }
return keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth, return relativeWidth * mParams.mBaseWidth;
mParams.mBaseWidth, mParams.mBaseWidth, getDefaultKeyWidth());
}
} }
} }

View file

@ -21,7 +21,6 @@ import java.util.*
* "Appearance" settings sub screen. * "Appearance" settings sub screen.
*/ */
class AppearanceSettingsFragment : SubScreenFragment() { class AppearanceSettingsFragment : SubScreenFragment() {
private var needsReload = false private var needsReload = false
private val stylePref: ListPreference by lazy { preferenceScreen.findPreference(Settings.PREF_THEME_STYLE)!! } private val stylePref: ListPreference by lazy { preferenceScreen.findPreference(Settings.PREF_THEME_STYLE)!! }
@ -30,17 +29,26 @@ class AppearanceSettingsFragment : SubScreenFragment() {
private val dayNightPref: TwoStatePreference? by lazy { preferenceScreen.findPreference(Settings.PREF_THEME_DAY_NIGHT) } private val dayNightPref: TwoStatePreference? by lazy { preferenceScreen.findPreference(Settings.PREF_THEME_DAY_NIGHT) }
private val userColorsPref: Preference by lazy { preferenceScreen.findPreference("theme_select_colors")!! } private val userColorsPref: Preference by lazy { preferenceScreen.findPreference("theme_select_colors")!! }
private val userColorsPrefNight: Preference? by lazy { preferenceScreen.findPreference("theme_select_colors_night") } private val userColorsPrefNight: Preference? by lazy { preferenceScreen.findPreference("theme_select_colors_night") }
private val splitPref: TwoStatePreference? by lazy { preferenceScreen.findPreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD) }
private val splitScalePref: Preference? by lazy { preferenceScreen.findPreference(Settings.PREF_SPLIT_SPACER_SCALE) }
override fun onCreate(savedInstanceState: Bundle?) {
override fun onCreate(icicle: Bundle?) { super.onCreate(savedInstanceState)
super.onCreate(icicle)
addPreferencesFromResource(R.xml.prefs_screen_appearance) addPreferencesFromResource(R.xml.prefs_screen_appearance)
removeUnsuitablePreferences() removeUnsuitablePreferences()
setupTheme() setupTheme()
setThemeVariantPrefs(sharedPreferences.getString(Settings.PREF_THEME_STYLE, KeyboardTheme.STYLE_MATERIAL)!!) setThemeVariantPrefs(sharedPreferences.getString(Settings.PREF_THEME_STYLE, KeyboardTheme.STYLE_MATERIAL)!!)
setupKeyboardHeight(Settings.PREF_KEYBOARD_HEIGHT_SCALE, SettingsValues.DEFAULT_SIZE_SCALE) setupScalePrefs(Settings.PREF_KEYBOARD_HEIGHT_SCALE, SettingsValues.DEFAULT_SIZE_SCALE)
if (splitScalePref != null) {
setupScalePrefs(Settings.PREF_SPLIT_SPACER_SCALE, SettingsValues.DEFAULT_SIZE_SCALE)
splitScalePref?.isVisible = splitPref?.isChecked == true
splitPref?.setOnPreferenceChangeListener { _, value ->
splitScalePref?.isVisible = value as Boolean
true
}
}
} }
override fun onPause() { override fun onPause() {
@ -52,7 +60,7 @@ class AppearanceSettingsFragment : SubScreenFragment() {
override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {
super.onSharedPreferenceChanged(prefs, key) super.onSharedPreferenceChanged(prefs, key)
needsReload = true // may not always be the necessary, but that's ok needsReload = true // may not always necessary, but that's ok
} }
private fun removeUnsuitablePreferences() { private fun removeUnsuitablePreferences() {
@ -79,6 +87,7 @@ class AppearanceSettingsFragment : SubScreenFragment() {
val heightDp = metrics.heightPixels / metrics.density val heightDp = metrics.heightPixels / metrics.density
if (!ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED || (min(widthDp, heightDp) < 600 && max(widthDp, heightDp) < 720)) { if (!ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED || (min(widthDp, heightDp) < 600 && max(widthDp, heightDp) < 720)) {
removePreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD) removePreference(Settings.PREF_ENABLE_SPLIT_KEYBOARD)
removePreference(Settings.PREF_SPLIT_SPACER_SCALE)
} }
} }
@ -140,7 +149,7 @@ class AppearanceSettingsFragment : SubScreenFragment() {
userColorsPrefNight?.isVisible = dayNightPref?.isChecked == true && colorsNightPref?.value == KeyboardTheme.THEME_USER_NIGHT userColorsPrefNight?.isVisible = dayNightPref?.isChecked == true && colorsNightPref?.value == KeyboardTheme.THEME_USER_NIGHT
} }
private fun setupKeyboardHeight(prefKey: String, defaultValue: Float) { private fun setupScalePrefs(prefKey: String, defaultValue: Float) {
val prefs = sharedPreferences val prefs = sharedPreferences
val pref = findPreference(prefKey) as? SeekBarDialogPreference val pref = findPreference(prefKey) as? SeekBarDialogPreference
pref?.setInterface(object : SeekBarDialogPreference.ValueProxy { pref?.setInterface(object : SeekBarDialogPreference.ValueProxy {
@ -149,13 +158,11 @@ class AppearanceSettingsFragment : SubScreenFragment() {
private fun getPercentageFromValue(floatValue: Float) = (floatValue * PERCENTAGE_FLOAT).toInt() private fun getPercentageFromValue(floatValue: Float) = (floatValue * PERCENTAGE_FLOAT).toInt()
override fun writeValue(value: Int, key: String) = prefs.edit() override fun writeValue(value: Int, key: String) = prefs.edit().putFloat(key, getValueFromPercentage(value)).apply()
.putFloat(key, getValueFromPercentage(value)).apply()
override fun writeDefaultValue(key: String) = prefs.edit().remove(key).apply() override fun writeDefaultValue(key: String) = prefs.edit().remove(key).apply()
override fun readValue(key: String) = getPercentageFromValue( override fun readValue(key: String) = getPercentageFromValue(prefs.getFloat(prefKey, defaultValue))
Settings.readKeyboardHeight(prefs, defaultValue))
override fun readDefaultValue(key: String) = getPercentageFromValue(defaultValue) override fun readDefaultValue(key: String) = getPercentageFromValue(defaultValue)

View file

@ -83,6 +83,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_SHOW_EMOJI_KEY = "pref_show_emoji_key"; public static final String PREF_SHOW_EMOJI_KEY = "pref_show_emoji_key";
public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles"; public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
public static final String PREF_ENABLE_SPLIT_KEYBOARD = "pref_split_keyboard"; public static final String PREF_ENABLE_SPLIT_KEYBOARD = "pref_split_keyboard";
public static final String PREF_SPLIT_SPACER_SCALE = "pref_split_spacer_scale";
public static final String PREF_KEYBOARD_HEIGHT_SCALE = "pref_keyboard_height_scale"; public static final String PREF_KEYBOARD_HEIGHT_SCALE = "pref_keyboard_height_scale";
public static final String PREF_SPACE_TRACKPAD = "pref_space_trackpad"; public static final String PREF_SPACE_TRACKPAD = "pref_space_trackpad";
public static final String PREF_DELETE_SWIPE = "pref_delete_swipe"; public static final String PREF_DELETE_SWIPE = "pref_delete_swipe";
@ -341,13 +342,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return res.getInteger(R.integer.config_clipboard_history_retention_time); return res.getInteger(R.integer.config_clipboard_history_retention_time);
} }
public static float readKeyboardHeight(final SharedPreferences prefs,
final float defaultValue) {
final float percentage = prefs.getFloat(
Settings.PREF_KEYBOARD_HEIGHT_SCALE, UNDEFINED_PREFERENCE_VALUE_FLOAT);
return (percentage != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? percentage : defaultValue;
}
public static boolean readSpaceTrackpadEnabled(final SharedPreferences prefs) { public static boolean readSpaceTrackpadEnabled(final SharedPreferences prefs) {
return prefs.getBoolean(PREF_SPACE_TRACKPAD, true); return prefs.getBoolean(PREF_SPACE_TRACKPAD, true);
} }

View file

@ -95,6 +95,7 @@ public class SettingsValues {
public final boolean mShouldShowLxxSuggestionUi; public final boolean mShouldShowLxxSuggestionUi;
// Use split layout for keyboard. // Use split layout for keyboard.
public final boolean mIsSplitKeyboardEnabled; public final boolean mIsSplitKeyboardEnabled;
public final float mSpacerRelativeWidth;
public final int mScreenMetrics; public final int mScreenMetrics;
public final boolean mAddToPersonalDictionary; public final boolean mAddToPersonalDictionary;
public final boolean mUseContactsDictionary; public final boolean mUseContactsDictionary;
@ -162,7 +163,12 @@ public class SettingsValues {
mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res); mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res);
mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout); mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout);
mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration()); mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration());
mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false); final float displayWidthDp = res.getDisplayMetrics().widthPixels / res.getDisplayMetrics().density;
mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false) && displayWidthDp > 600; // require display width of 600 dp for split
// determine spacerWidth from display width and scale setting
mSpacerRelativeWidth = mIsSplitKeyboardEnabled
? Math.min(Math.max((displayWidthDp - 600) / 6000f + 0.15f, 0.15f), 0.25f) * prefs.getFloat(Settings.PREF_SPLIT_SPACER_SCALE, DEFAULT_SIZE_SCALE)
: 0f;
mScreenMetrics = Settings.readScreenMetrics(res); mScreenMetrics = Settings.readScreenMetrics(res);
mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI
@ -188,7 +194,7 @@ public class SettingsValues {
readSuggestionsEnabled(prefs); readSuggestionsEnabled(prefs);
mIncognitoModeEnabled = Settings.readAlwaysIncognitoMode(prefs) || mInputAttributes.mNoLearning mIncognitoModeEnabled = Settings.readAlwaysIncognitoMode(prefs) || mInputAttributes.mNoLearning
|| mInputAttributes.mIsPasswordField; || mInputAttributes.mIsPasswordField;
mKeyboardHeightScale = Settings.readKeyboardHeight(prefs, DEFAULT_SIZE_SCALE); mKeyboardHeightScale = prefs.getFloat(Settings.PREF_KEYBOARD_HEIGHT_SCALE, DEFAULT_SIZE_SCALE);
mDisplayOrientation = res.getConfiguration().orientation; mDisplayOrientation = res.getConfiguration().orientation;
mAppWorkarounds = new AsyncResultHolder<>("AppWorkarounds"); mAppWorkarounds = new AsyncResultHolder<>("AppWorkarounds");
final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo( final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo(

View file

@ -51,6 +51,8 @@
<string name="settings_category_miscellaneous">Miscellaneous</string> <string name="settings_category_miscellaneous">Miscellaneous</string>
<!-- Option for enabling or disabling the split keyboard layout. [CHAR LIMIT=65]--> <!-- Option for enabling or disabling the split keyboard layout. [CHAR LIMIT=65]-->
<string name="enable_split_keyboard">Enable split keyboard</string> <string name="enable_split_keyboard">Enable split keyboard</string>
<!-- Option for setting distance for split keyboard -->
<string name="split_spacer_scale">Split distance</string>
<!-- Option name for including other IMEs in the language key switch list [CHAR LIMIT=30] --> <!-- Option name for including other IMEs in the language key switch list [CHAR LIMIT=30] -->
<string name="language_switch_key_switch_input_method">Switch to other input methods</string> <string name="language_switch_key_switch_input_method">Switch to other input methods</string>
<!-- Option name for switching language / subtype only [CHAR LIMIT=30] --> <!-- Option name for switching language / subtype only [CHAR LIMIT=30] -->

View file

@ -63,6 +63,12 @@
android:persistent="true" android:persistent="true"
android:defaultValue="false" /> android:defaultValue="false" />
<org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference
android:key="pref_split_spacer_scale"
android:title="@string/split_spacer_scale"
latin:minValue="50"
latin:maxValue="200" /> <!-- percentage -->
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="pref_narrow_key_gaps" android:key="pref_narrow_key_gaps"
android:title="@string/prefs_narrow_key_gaps" android:title="@string/prefs_narrow_key_gaps"