mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-06-25 18:40:53 +00:00
Migrate functional key layouts to json (#778)
Now the functional key layouts should be (mostly) compatible to FlorisBoard Not yet customizable, this is a large step towards customizable functional key layouts
This commit is contained in:
parent
691ae017bc
commit
34d8bd16f0
30 changed files with 931 additions and 712 deletions
21
app/src/main/assets/layouts/functional_keys.json
Normal file
21
app/src/main/assets/layouts/functional_keys.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
[
|
||||
[
|
||||
{ "label": "shift", "width": 0.15 },
|
||||
{ "type": "placeholder" },
|
||||
{ "label": "delete", "width": 0.15 }
|
||||
],
|
||||
[
|
||||
{ "label": "symbol_alpha", "width": 0.15 },
|
||||
{ "$": "variation_selector",
|
||||
"default": { "label": "comma" },
|
||||
"email": { "label": "@", "groupId": 1, "type": "function" },
|
||||
"uri": { "label": "/", "groupId": 1, "type": "function" }
|
||||
},
|
||||
{ "label": "language_switch" },
|
||||
{ "label": "emoji" },
|
||||
{ "label": "numpad" },
|
||||
{ "label": "space" },
|
||||
{ "label": "period" },
|
||||
{ "label": "action", "width": 0.15 }
|
||||
]
|
||||
]
|
36
app/src/main/assets/layouts/functional_keys_tablet.json
Normal file
36
app/src/main/assets/layouts/functional_keys_tablet.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
[
|
||||
[
|
||||
{ "type": "placeholder" },
|
||||
{ "label": "delete", "width": 0.1 }
|
||||
],
|
||||
[
|
||||
{ "type": "placeholder" }
|
||||
],
|
||||
[
|
||||
{ "type": "placeholder" },
|
||||
{ "label": "action", "width": 0.1 }
|
||||
],
|
||||
[
|
||||
{ "label": "shift", "width": 0.1 },
|
||||
{ "type": "placeholder" },
|
||||
{ "label": "shift" }
|
||||
],
|
||||
[
|
||||
{ "label": "symbol_alpha" },
|
||||
{ "$": "variation_selector",
|
||||
"default": { "label": "comma" },
|
||||
"email": { "label": "@", "groupId": 1, "type": "function" },
|
||||
"uri": { "label": "/", "groupId": 1, "type": "function" }
|
||||
},
|
||||
{ "label": "language_switch" },
|
||||
{ "label": "emoji" },
|
||||
{ "label": "numpad" },
|
||||
{ "label": "space" },
|
||||
{ "label": "period" },
|
||||
{ "$": "variation_selector",
|
||||
"default": { "label": "emoji" },
|
||||
"email": { "label": "com" },
|
||||
"uri": { "label": "com" }
|
||||
}
|
||||
]
|
||||
]
|
|
@ -29,7 +29,7 @@ internal class KeyCodeDescriptionMapper private constructor() {
|
|||
put(KeyCode.SETTINGS, R.string.spoken_description_settings)
|
||||
put(KeyCode.SHIFT, R.string.spoken_description_shift)
|
||||
put(KeyCode.VOICE_INPUT, R.string.spoken_description_mic)
|
||||
put(KeyCode.ALPHA_SYMBOL, R.string.spoken_description_to_symbol)
|
||||
put(KeyCode.SYMBOL_ALPHA, R.string.spoken_description_to_symbol)
|
||||
put(Constants.CODE_TAB, R.string.spoken_description_tab)
|
||||
put(KeyCode.LANGUAGE_SWITCH, R.string.spoken_description_language_switch)
|
||||
put(KeyCode.ACTION_NEXT, R.string.spoken_description_action_next)
|
||||
|
@ -58,7 +58,7 @@ internal class KeyCodeDescriptionMapper private constructor() {
|
|||
*/
|
||||
fun getDescriptionForKey(context: Context, keyboard: Keyboard?, key: Key, shouldObscure: Boolean): String? {
|
||||
val code = key.code
|
||||
if (code == KeyCode.ALPHA_SYMBOL || code == KeyCode.SYMBOL || code == KeyCode.ALPHA) {
|
||||
if (code == KeyCode.SYMBOL_ALPHA || code == KeyCode.SYMBOL || code == KeyCode.ALPHA) {
|
||||
val description = getDescriptionForSwitchAlphaSymbol(context, keyboard)
|
||||
if (description != null) {
|
||||
return description
|
||||
|
|
|
@ -308,17 +308,17 @@ public class Key implements Comparable<Key> {
|
|||
final float horizontalGapFloat = isSpacer() ? 0 : (keyParams.mKeyboardParams.mRelativeHorizontalGap * keyParams.mKeyboardParams.mOccupiedWidth);
|
||||
mHorizontalGap = Math.round(horizontalGapFloat);
|
||||
mVerticalGap = Math.round(keyParams.mKeyboardParams.mRelativeVerticalGap * keyParams.mKeyboardParams.mOccupiedHeight);
|
||||
mWidth = Math.round(keyParams.mFullWidth - horizontalGapFloat);
|
||||
mWidth = Math.round(keyParams.mAbsoluteWidth - horizontalGapFloat);
|
||||
// height is always rounded down, because rounding up may make the keyboard too high to fit, leading to issues
|
||||
mHeight = (int) (keyParams.mFullHeight - keyParams.mKeyboardParams.mVerticalGap);
|
||||
mHeight = (int) (keyParams.mAbsoluteHeight - keyParams.mKeyboardParams.mVerticalGap);
|
||||
if (!isSpacer() && (mWidth == 0 || mHeight == 0)) {
|
||||
throw new IllegalStateException("key needs positive width and height");
|
||||
}
|
||||
// Horizontal gap is divided equally to both sides of the key.
|
||||
mX = Math.round(keyParams.xPos + horizontalGapFloat / 2);
|
||||
mY = Math.round(keyParams.yPos);
|
||||
mHitBox.set(Math.round(keyParams.xPos), Math.round(keyParams.yPos), Math.round(keyParams.xPos + keyParams.mFullWidth) + 1,
|
||||
Math.round(keyParams.yPos + keyParams.mFullHeight));
|
||||
mHitBox.set(Math.round(keyParams.xPos), Math.round(keyParams.yPos), Math.round(keyParams.xPos + keyParams.mAbsoluteWidth) + 1,
|
||||
Math.round(keyParams.yPos + keyParams.mAbsoluteHeight));
|
||||
mHashCode = computeHashCode(this);
|
||||
}
|
||||
|
||||
|
@ -504,7 +504,7 @@ public class Key implements Comparable<Key> {
|
|||
return this instanceof Spacer;
|
||||
}
|
||||
|
||||
public final boolean isActionKey() {
|
||||
public final boolean hasActionKeyBackground() {
|
||||
return mBackgroundType == BACKGROUND_TYPE_ACTION;
|
||||
}
|
||||
|
||||
|
@ -513,7 +513,7 @@ public class Key implements Comparable<Key> {
|
|||
}
|
||||
|
||||
public final boolean isModifier() {
|
||||
return mCode == KeyCode.SHIFT || mCode == KeyCode.ALPHA_SYMBOL || mCode == KeyCode.ALPHA || mCode == KeyCode.SYMBOL;
|
||||
return mCode == KeyCode.SHIFT || mCode == KeyCode.SYMBOL_ALPHA || mCode == KeyCode.ALPHA || mCode == KeyCode.SYMBOL;
|
||||
}
|
||||
|
||||
public final boolean isRepeatable() {
|
||||
|
@ -906,7 +906,7 @@ public class Key implements Comparable<Key> {
|
|||
final Drawable background;
|
||||
if (isAccentColored()) {
|
||||
background = actionKeyBackground;
|
||||
} else if (isFunctional()) {
|
||||
} else if (hasFunctionalBackground()) {
|
||||
background = functionalKeyBackground;
|
||||
} else if (mBackgroundType == BACKGROUND_TYPE_SPACEBAR) {
|
||||
background = spacebarBackground;
|
||||
|
@ -919,7 +919,7 @@ public class Key implements Comparable<Key> {
|
|||
}
|
||||
|
||||
public final boolean isAccentColored() {
|
||||
if (isActionKey()) return true;
|
||||
if (hasActionKeyBackground()) return true;
|
||||
final String iconName = KeyboardIconsSet.getIconName(getIconId());
|
||||
return iconName.equals(KeyboardIconsSet.NAME_NEXT_KEY)
|
||||
|| iconName.equals(KeyboardIconsSet.NAME_PREVIOUS_KEY)
|
||||
|
@ -927,7 +927,7 @@ public class Key implements Comparable<Key> {
|
|||
|| iconName.equals(KeyboardIconsSet.NAME_EMOJI_ACTION_KEY);
|
||||
}
|
||||
|
||||
public boolean isFunctional() {
|
||||
public boolean hasFunctionalBackground() {
|
||||
return mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL
|
||||
|| mBackgroundType == BACKGROUND_TYPE_STICKY_OFF
|
||||
|| mBackgroundType == BACKGROUND_TYPE_STICKY_ON;
|
||||
|
@ -954,12 +954,12 @@ public class Key implements Comparable<Key> {
|
|||
// params for building
|
||||
public boolean isSpacer;
|
||||
private final KeyboardParams mKeyboardParams; // for reading gaps and keyboard width / height
|
||||
public float mRelativeWidth;
|
||||
public float mRelativeHeight; // also should allow negative values, indicating absolute height is defined
|
||||
public float mWidth;
|
||||
public float mHeight; // also should allow negative values, indicating absolute height is defined
|
||||
|
||||
// params that may change
|
||||
public float mFullWidth;
|
||||
public float mFullHeight;
|
||||
public float mAbsoluteWidth;
|
||||
public float mAbsoluteHeight;
|
||||
public float xPos;
|
||||
public float yPos;
|
||||
|
||||
|
@ -977,10 +977,10 @@ public class Key implements Comparable<Key> {
|
|||
@Nullable public final OptionalAttributes mOptionalAttributes;
|
||||
public final boolean mEnabled;
|
||||
|
||||
public static KeyParams newSpacer(final KeyboardParams params, final float relativeWidth) {
|
||||
public static KeyParams newSpacer(final KeyboardParams params, final float width) {
|
||||
final KeyParams spacer = new KeyParams(params);
|
||||
spacer.mRelativeWidth = relativeWidth;
|
||||
spacer.mRelativeHeight = params.mDefaultRelativeRowHeight;
|
||||
spacer.mWidth = width;
|
||||
spacer.mHeight = params.mDefaultRowHeight;
|
||||
return spacer;
|
||||
}
|
||||
|
||||
|
@ -989,18 +989,18 @@ public class Key implements Comparable<Key> {
|
|||
return new Key(this);
|
||||
}
|
||||
|
||||
public void setDimensionsFromRelativeSize(final float newX, final float newY) {
|
||||
if (mRelativeHeight == 0)
|
||||
mRelativeHeight = mKeyboardParams.mDefaultRelativeRowHeight;
|
||||
if (!isSpacer && mRelativeWidth == 0)
|
||||
mRelativeWidth = mKeyboardParams.mDefaultRelativeKeyWidth;
|
||||
if (mRelativeHeight < 0)
|
||||
public void setAbsoluteDimensions(final float newX, final float newY) {
|
||||
if (mHeight == 0)
|
||||
mHeight = mKeyboardParams.mDefaultRowHeight;
|
||||
if (!isSpacer && mWidth == 0)
|
||||
throw new IllegalStateException("width = 0 should have been evaluated already");
|
||||
if (mHeight < 0)
|
||||
// todo (later): deal with it properly when it needs to be adjusted, i.e. when changing popupKeys or moreSuggestions
|
||||
throw new IllegalStateException("can't (yet) deal with absolute height");
|
||||
xPos = newX;
|
||||
yPos = newY;
|
||||
mFullWidth = mRelativeWidth * mKeyboardParams.mBaseWidth;
|
||||
mFullHeight = mRelativeHeight * mKeyboardParams.mBaseHeight;
|
||||
mAbsoluteWidth = mWidth * mKeyboardParams.mBaseWidth;
|
||||
mAbsoluteHeight = mHeight * mKeyboardParams.mBaseHeight;
|
||||
}
|
||||
|
||||
private static int getPopupKeysColumnAndFlagsAndSetNullInArray(final KeyboardParams params, final String[] popupKeys) {
|
||||
|
@ -1052,7 +1052,7 @@ public class Key implements Comparable<Key> {
|
|||
@NonNull final String keySpec, // key text or some special string for KeySpecParser, e.g. "!icon/shift_key|!code/key_shift" (avoid using !text, should be removed)
|
||||
final int code,
|
||||
@NonNull final KeyboardParams params,
|
||||
final float relativeWidth,
|
||||
final float width,
|
||||
final int labelFlags,
|
||||
final int backgroundType,
|
||||
@Nullable final PopupSet<?> popupSet
|
||||
|
@ -1060,8 +1060,8 @@ public class Key implements Comparable<Key> {
|
|||
mKeyboardParams = params;
|
||||
mBackgroundType = backgroundType;
|
||||
mLabelFlags = labelFlags;
|
||||
mRelativeWidth = relativeWidth;
|
||||
mRelativeHeight = params.mDefaultRelativeRowHeight;
|
||||
mWidth = width;
|
||||
mHeight = params.mDefaultRowHeight;
|
||||
mIconId = KeySpecParser.getIconId(keySpec);
|
||||
|
||||
final boolean needsToUpcase = needsToUpcase(mLabelFlags, params.mId.mElementId);
|
||||
|
@ -1144,9 +1144,9 @@ public class Key implements Comparable<Key> {
|
|||
}
|
||||
|
||||
// action flags don't need to be specified, they can be deduced from the key
|
||||
if (backgroundType == BACKGROUND_TYPE_SPACEBAR
|
||||
if (mCode == Constants.CODE_SPACE
|
||||
|| mCode == KeyCode.LANGUAGE_SWITCH
|
||||
|| (mCode == KeyCode.ALPHA_SYMBOL && !params.mId.isAlphabetKeyboard())
|
||||
|| (mCode == KeyCode.SYMBOL_ALPHA && !params.mId.isAlphabetKeyboard())
|
||||
)
|
||||
actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
|
||||
if (mCode <= Constants.CODE_SPACE && mCode != KeyCode.MULTIPLE_CODE_POINTS)
|
||||
|
@ -1237,8 +1237,8 @@ public class Key implements Comparable<Key> {
|
|||
public KeyParams(final KeyParams keyParams) {
|
||||
xPos = keyParams.xPos;
|
||||
yPos = keyParams.yPos;
|
||||
mRelativeWidth = keyParams.mRelativeWidth;
|
||||
mRelativeHeight = keyParams.mRelativeHeight;
|
||||
mWidth = keyParams.mWidth;
|
||||
mHeight = keyParams.mHeight;
|
||||
isSpacer = keyParams.isSpacer;
|
||||
mKeyboardParams = keyParams.mKeyboardParams;
|
||||
mEnabled = keyParams.mEnabled;
|
||||
|
@ -1248,8 +1248,8 @@ public class Key implements Comparable<Key> {
|
|||
mHintLabel = keyParams.mHintLabel;
|
||||
mLabelFlags = keyParams.mLabelFlags;
|
||||
mIconId = keyParams.mIconId;
|
||||
mFullWidth = keyParams.mFullWidth;
|
||||
mFullHeight = keyParams.mFullHeight;
|
||||
mAbsoluteWidth = keyParams.mAbsoluteWidth;
|
||||
mAbsoluteHeight = keyParams.mAbsoluteHeight;
|
||||
mPopupKeys = keyParams.mPopupKeys;
|
||||
mPopupKeysColumnAndFlags = keyParams.mPopupKeysColumnAndFlags;
|
||||
mBackgroundType = keyParams.mBackgroundType;
|
||||
|
|
|
@ -149,6 +149,10 @@ public final class KeyboardId {
|
|||
return elementId < ELEMENT_SYMBOLS;
|
||||
}
|
||||
|
||||
public boolean isAlphaOrSymbolKeyboard() {
|
||||
return mElementId <= ELEMENT_SYMBOLS_SHIFTED;
|
||||
}
|
||||
|
||||
public boolean isAlphabetKeyboard() {
|
||||
return isAlphabetKeyboard(mElementId);
|
||||
}
|
||||
|
|
|
@ -472,7 +472,7 @@ public class KeyboardView extends View {
|
|||
blendAlpha(paint, params.mAnimAlpha);
|
||||
final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
|
||||
final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
|
||||
final boolean isFunctionalKeyAndRoundedStyle = mColors.getThemeStyle().equals(STYLE_ROUNDED) && key.isFunctional();
|
||||
final boolean isFunctionalKeyAndRoundedStyle = mColors.getThemeStyle().equals(STYLE_ROUNDED) && key.hasFunctionalBackground();
|
||||
final float hintX, hintBaseline;
|
||||
if (key.hasHintLabel()) {
|
||||
// The hint label is placed just right of the key label. Used mainly on
|
||||
|
@ -555,7 +555,7 @@ public class KeyboardView extends View {
|
|||
if (key.getBackgroundType() == Key.BACKGROUND_TYPE_SPACEBAR)
|
||||
hintX = keyWidth + hintBaseline + labelCharWidth * 0.1f;
|
||||
else
|
||||
hintX = key.isFunctional() || key.isActionKey() ? keyWidth / 2.0f : keyWidth - mKeyHintLetterPadding - labelCharWidth / 2.0f;
|
||||
hintX = key.hasFunctionalBackground() || key.hasActionKeyBackground() ? keyWidth / 2.0f : keyWidth - mKeyHintLetterPadding - labelCharWidth / 2.0f;
|
||||
} else {
|
||||
hintX = keyWidth - mKeyHintLetterPadding - TypefaceUtils.getReferenceCharWidth(paint) / 2.0f;
|
||||
}
|
||||
|
|
|
@ -540,7 +540,7 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy
|
|||
mPopupKeysKeyboardCache.put(key, popupKeysKeyboard);
|
||||
}
|
||||
|
||||
final View container = key.isActionKey() ? mPopupKeysKeyboardForActionContainer
|
||||
final View container = key.hasActionKeyBackground() ? mPopupKeysKeyboardForActionContainer
|
||||
: mPopupKeysKeyboardContainer;
|
||||
final PopupKeysKeyboardView popupKeysKeyboardView =
|
||||
container.findViewById(R.id.popup_keys_keyboard_view);
|
||||
|
|
|
@ -1107,7 +1107,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (code == KeyCode.ALPHA_SYMBOL && Settings.getInstance().getCurrent().mLongPressSymbolsForNumpad) {
|
||||
if (code == KeyCode.SYMBOL_ALPHA && Settings.getInstance().getCurrent().mLongPressSymbolsForNumpad) {
|
||||
sListener.onCodeInput(KeyCode.NUMPAD, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ public final class PopupKeysKeyboard extends Keyboard {
|
|||
|
||||
PopupKeysKeyboard(final PopupKeysKeyboardParams params) {
|
||||
super(params);
|
||||
mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
|
||||
mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultAbsoluteKeyWidth / 2;
|
||||
}
|
||||
|
||||
public int getDefaultCoordX() {
|
||||
|
@ -71,8 +71,8 @@ public final class PopupKeysKeyboard extends Keyboard {
|
|||
throw new IllegalArgumentException("Keyboard is too small to hold popup keys: "
|
||||
+ parentKeyboardWidth + " " + keyWidth + " " + numKeys + " " + numColumn);
|
||||
}
|
||||
mDefaultKeyWidth = keyWidth;
|
||||
mDefaultRowHeight = rowHeight;
|
||||
mDefaultAbsoluteKeyWidth = keyWidth;
|
||||
mDefaultAbsoluteRowHeight = rowHeight;
|
||||
|
||||
mNumRows = (numKeys + numColumn - 1) / numColumn;
|
||||
final int numColumns = isPopupKeysFixedColumn ? Math.min(numKeys, numColumn)
|
||||
|
@ -116,10 +116,10 @@ public final class PopupKeysKeyboard extends Keyboard {
|
|||
mTopRowAdjustment = isPopupKeysFixedOrder ? getFixedOrderTopRowAdjustment()
|
||||
: getAutoOrderTopRowAdjustment();
|
||||
mDividerWidth = dividerWidth;
|
||||
mColumnWidth = mDefaultKeyWidth + mDividerWidth;
|
||||
mColumnWidth = mDefaultAbsoluteKeyWidth + mDividerWidth;
|
||||
mBaseWidth = mOccupiedWidth = mNumColumns * mColumnWidth - mDividerWidth;
|
||||
// Need to subtract the bottom row's gutter only.
|
||||
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight - mVerticalGap
|
||||
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultAbsoluteRowHeight - mVerticalGap
|
||||
+ mTopPadding + mBottomPadding;
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ public final class PopupKeysKeyboard extends Keyboard {
|
|||
}
|
||||
|
||||
public int getY(final int row) {
|
||||
return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
|
||||
return (mNumRows - 1 - row) * mDefaultAbsoluteRowHeight + mTopPadding;
|
||||
}
|
||||
|
||||
public void markAsEdgeKey(final Key key, final int row) {
|
||||
|
@ -287,8 +287,8 @@ public final class PopupKeysKeyboard extends Keyboard {
|
|||
final float padding = context.getResources().getDimension(
|
||||
R.dimen.config_popup_keys_keyboard_key_horizontal_padding)
|
||||
+ (key.hasLabelsInPopupKeys()
|
||||
? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f);
|
||||
keyWidth = getMaxKeyWidth(key, mParams.mDefaultKeyWidth, padding, paintToMeasure);
|
||||
? mParams.mDefaultAbsoluteKeyWidth * LABEL_PADDING_RATIO : 0.0f);
|
||||
keyWidth = getMaxKeyWidth(key, mParams.mDefaultAbsoluteKeyWidth, padding, paintToMeasure);
|
||||
rowHeight = keyboard.mMostCommonKeyHeight;
|
||||
}
|
||||
final int dividerWidth;
|
||||
|
@ -342,9 +342,9 @@ public final class PopupKeysKeyboard extends Keyboard {
|
|||
// left of the default position.
|
||||
if (params.mDividerWidth > 0 && pos != 0) {
|
||||
final int dividerX = (pos > 0) ? x - params.mDividerWidth
|
||||
: x + params.mDefaultKeyWidth;
|
||||
: x + params.mDefaultAbsoluteKeyWidth;
|
||||
final Key divider = new PopupKeyDivider(
|
||||
params, dividerX, y, params.mDividerWidth, params.mDefaultRowHeight);
|
||||
params, dividerX, y, params.mDividerWidth, params.mDefaultAbsoluteRowHeight);
|
||||
params.onAddKey(divider);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,12 +111,12 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
|||
fillGapsWithSpacers(row)
|
||||
var currentX = mParams.mLeftPadding.toFloat()
|
||||
row.forEach {
|
||||
it.setDimensionsFromRelativeSize(currentX, currentY)
|
||||
it.setAbsoluteDimensions(currentX, currentY)
|
||||
if (DebugFlags.DEBUG_ENABLED)
|
||||
Log.d(TAG, "setting size and position for ${it.mLabel}, ${it.mCode}: x ${currentX.toInt()}, w ${it.mFullWidth.toInt()}")
|
||||
currentX += it.mFullWidth
|
||||
Log.d(TAG, "setting size and position for ${it.mLabel}, ${it.mCode}: x ${currentX.toInt()}, w ${it.mAbsoluteWidth.toInt()}")
|
||||
currentX += it.mAbsoluteWidth
|
||||
}
|
||||
currentY += row.first().mFullHeight
|
||||
currentY += row.first().mAbsoluteHeight
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
|||
i++
|
||||
currentX += currentKeyXPos - currentX
|
||||
}
|
||||
currentX += row[i].mFullWidth
|
||||
currentX += row[i].mAbsoluteWidth
|
||||
i++
|
||||
}
|
||||
if (currentX < mParams.mOccupiedWidth) {
|
||||
|
@ -161,48 +161,48 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
|||
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 relativeWidthSum = row.sumOf { it.mWidth } // sum up relative widths
|
||||
val spacer = KeyParams.newSpacer(mParams, spacerRelativeWidth)
|
||||
// insert spacer before first key that starts right of the center (also consider gap)
|
||||
var insertIndex = row.indexOfFirst { it.xPos + it.mFullWidth / 3 > mParams.mOccupiedWidth / 2 }
|
||||
var insertIndex = row.indexOfFirst { it.xPos + it.mAbsoluteWidth / 3 > 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
|
||||
val widthBeforeSpace = row.subList(0, insertIndex - 1).sumOf { it.mWidth }
|
||||
val widthAfterSpace = row.subList(insertIndex, row.size).sumOf { it.mWidth }
|
||||
val spaceLeftWidth = (maxWidthBeforeSpacer - widthBeforeSpace).coerceAtLeast(mParams.mDefaultKeyWidth)
|
||||
val spaceRightWidth = (maxWidthAfterSpacer - widthAfterSpace).coerceAtLeast(mParams.mDefaultKeyWidth)
|
||||
val spacerWidth = spaceLeft.mWidth + 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
|
||||
spaceLeft.mWidth = spaceLeftWidth
|
||||
spaceRight.mWidth = spaceRightWidth
|
||||
spacer.mWidth = spacerWidth
|
||||
row.add(insertIndex, spaceRight)
|
||||
row.add(insertIndex, spacer)
|
||||
} else {
|
||||
// otherwise increase space width, so other keys are resized properly
|
||||
spaceLeft.mRelativeWidth += spacerWidth
|
||||
spaceLeft.mWidth += spacerWidth
|
||||
}
|
||||
} else {
|
||||
val widthBeforeSpacer = row.subList(0, insertIndex).sumOf { it.mRelativeWidth }
|
||||
val widthAfterSpacer = row.subList(insertIndex, row.size).sumOf { it.mRelativeWidth }
|
||||
val widthBeforeSpacer = row.subList(0, insertIndex).sumOf { it.mWidth }
|
||||
val widthAfterSpacer = row.subList(insertIndex, row.size).sumOf { it.mWidth }
|
||||
maxWidthBeforeSpacer = maxWidthBeforeSpacer.coerceAtLeast(widthBeforeSpacer)
|
||||
maxWidthAfterSpacer = maxWidthAfterSpacer.coerceAtLeast(widthAfterSpacer)
|
||||
row.add(insertIndex, spacer)
|
||||
}
|
||||
// re-calculate relative widths
|
||||
val relativeWidthSumNew = row.sumOf { it.mRelativeWidth }
|
||||
val relativeWidthSumNew = row.sumOf { it.mWidth }
|
||||
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
|
||||
it.mWidth *= widthFactor
|
||||
it.setAbsoluteDimensions(currentX, y)
|
||||
currentX += it.mAbsoluteWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,19 +211,19 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
|||
// todo: this assumes fixed layout for symbols keys, which will change soon!
|
||||
private fun reduceSymbolAndActionKeyWidth(row: ArrayList<KeyParams>) {
|
||||
val spaceKey = row.first { it.mCode == Constants.CODE_SPACE }
|
||||
val symbolKey = row.firstOrNull { it.mCode == KeyCode.ALPHA_SYMBOL }
|
||||
val symbolKeyWidth = symbolKey?.mRelativeWidth ?: 0f
|
||||
if (symbolKeyWidth > mParams.mDefaultRelativeKeyWidth) {
|
||||
val widthToChange = symbolKey!!.mRelativeWidth - mParams.mDefaultRelativeKeyWidth
|
||||
symbolKey.mRelativeWidth -= widthToChange
|
||||
spaceKey.mRelativeWidth += widthToChange
|
||||
val symbolKey = row.firstOrNull { it.mCode == KeyCode.SYMBOL_ALPHA }
|
||||
val symbolKeyWidth = symbolKey?.mWidth ?: 0f
|
||||
if (symbolKeyWidth > mParams.mDefaultKeyWidth) {
|
||||
val widthToChange = symbolKey!!.mWidth - mParams.mDefaultKeyWidth
|
||||
symbolKey.mWidth -= widthToChange
|
||||
spaceKey.mWidth += 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
|
||||
val actionKeyWidth = actionKey?.mWidth ?: 0f
|
||||
if (actionKeyWidth > mParams.mDefaultKeyWidth * 1.1f) { // allow it to stay a little wider
|
||||
val widthToChange = actionKey!!.mWidth - mParams.mDefaultKeyWidth * 1.1f
|
||||
actionKey.mWidth -= widthToChange
|
||||
spaceKey.mWidth += widthToChange
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ public final class KeyboardCodesSet {
|
|||
Constants.CODE_SPACE,
|
||||
KeyCode.SHIFT,
|
||||
KeyCode.CAPS_LOCK,
|
||||
KeyCode.ALPHA_SYMBOL,
|
||||
KeyCode.SYMBOL_ALPHA,
|
||||
KeyCode.ALPHA,
|
||||
KeyCode.SYMBOL,
|
||||
KeyCode.MULTIPLE_CODE_POINTS,
|
||||
|
|
|
@ -19,7 +19,6 @@ import helium314.keyboard.keyboard.KeyboardId;
|
|||
import helium314.keyboard.keyboard.internal.keyboard_parser.LocaleKeyboardInfos;
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.common.Constants;
|
||||
import helium314.keyboard.latin.settings.Settings;
|
||||
import helium314.keyboard.latin.utils.ResourceUtils;
|
||||
|
||||
|
@ -55,13 +54,13 @@ public class KeyboardParams {
|
|||
@Nullable
|
||||
public KeyVisualAttributes mKeyVisualAttributes;
|
||||
|
||||
public float mDefaultRelativeRowHeight;
|
||||
public float mDefaultRelativeKeyWidth;
|
||||
public float mDefaultRowHeight;
|
||||
public float mDefaultKeyWidth;
|
||||
public float mRelativeHorizontalGap;
|
||||
public float mRelativeVerticalGap;
|
||||
// relative values multiplied with baseHeight / baseWidth
|
||||
public int mDefaultRowHeight;
|
||||
public int mDefaultKeyWidth;
|
||||
public int mDefaultAbsoluteRowHeight;
|
||||
public int mDefaultAbsoluteKeyWidth;
|
||||
public int mHorizontalGap;
|
||||
public int mVerticalGap;
|
||||
|
||||
|
@ -227,9 +226,9 @@ public class KeyboardParams {
|
|||
mBaseWidth = mOccupiedWidth - mLeftPadding - mRightPadding;
|
||||
final float defaultKeyWidthFactor = context.getResources().getInteger(R.integer.config_screen_metrics) > 2
|
||||
? 0.9f : 1f;
|
||||
mDefaultRelativeKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
|
||||
mDefaultKeyWidth = keyAttr.getFraction(R.styleable.Keyboard_Key_keyWidth,
|
||||
1, 1, defaultKeyWidthFactor / DEFAULT_KEYBOARD_COLUMNS);
|
||||
mDefaultKeyWidth = (int) (mDefaultRelativeKeyWidth * mBaseWidth);
|
||||
mDefaultAbsoluteKeyWidth = (int) (mDefaultKeyWidth * mBaseWidth);
|
||||
|
||||
// todo: maybe settings should not be accessed from here?
|
||||
if (Settings.getInstance().getCurrent().mNarrowKeyGaps) {
|
||||
|
@ -250,13 +249,13 @@ public class KeyboardParams {
|
|||
mVerticalGap = (int) (mRelativeVerticalGap * height);
|
||||
|
||||
mBaseHeight = mOccupiedHeight - mTopPadding - mBottomPadding + mVerticalGap;
|
||||
mDefaultRelativeRowHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
|
||||
mDefaultRowHeight = ResourceUtils.getDimensionOrFraction(keyboardAttr,
|
||||
R.styleable.Keyboard_rowHeight, 1, 1f / DEFAULT_KEYBOARD_ROWS);
|
||||
if (mDefaultRelativeRowHeight > 1) { // can be absolute size, in that case will be > 1
|
||||
mDefaultRowHeight = (int) mDefaultRelativeRowHeight;
|
||||
mDefaultRelativeRowHeight *= -1; // make it negative when it's absolute
|
||||
if (mDefaultRowHeight > 1) { // can be absolute size, in that case will be > 1
|
||||
mDefaultAbsoluteRowHeight = (int) mDefaultRowHeight;
|
||||
mDefaultRowHeight *= -1; // make it negative when it's absolute
|
||||
} else {
|
||||
mDefaultRowHeight = (int) (mDefaultRelativeRowHeight * mBaseHeight);
|
||||
mDefaultAbsoluteRowHeight = (int) (mDefaultRowHeight * mBaseHeight);
|
||||
}
|
||||
|
||||
mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
|
||||
|
|
|
@ -422,7 +422,7 @@ public final class KeyboardState {
|
|||
onPressShift();
|
||||
} else if (code == KeyCode.CAPS_LOCK) {
|
||||
// Nothing to do here. See {@link #onReleaseKey(int,boolean)}.
|
||||
} else if (code == KeyCode.ALPHA_SYMBOL) {
|
||||
} else if (code == KeyCode.SYMBOL_ALPHA) {
|
||||
onPressAlphaSymbol(autoCapsFlags, recapitalizeMode);
|
||||
} else if (code == KeyCode.SYMBOL) {
|
||||
// don't start sliding, causes issues with fully customizable layouts
|
||||
|
@ -463,7 +463,7 @@ public final class KeyboardState {
|
|||
onReleaseShift(withSliding, autoCapsFlags, recapitalizeMode);
|
||||
} else if (code == KeyCode.CAPS_LOCK) {
|
||||
setShiftLocked(!mAlphabetShiftState.isShiftLocked());
|
||||
} else if (code == KeyCode.ALPHA_SYMBOL) {
|
||||
} else if (code == KeyCode.SYMBOL_ALPHA) {
|
||||
onReleaseAlphaSymbol(withSliding, autoCapsFlags, recapitalizeMode);
|
||||
} else if (code == KeyCode.SYMBOL) {
|
||||
onReleaseSymbol(withSliding, autoCapsFlags, recapitalizeMode);
|
||||
|
@ -700,7 +700,7 @@ public final class KeyboardState {
|
|||
|
||||
switch (mSwitchState) {
|
||||
case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
|
||||
if (code == KeyCode.ALPHA_SYMBOL) {
|
||||
if (code == KeyCode.SYMBOL_ALPHA) {
|
||||
// Detected only the mode change key has been pressed, and then released.
|
||||
if (mMode == MODE_ALPHABET) {
|
||||
mSwitchState = SWITCH_STATE_ALPHA;
|
||||
|
|
|
@ -70,7 +70,7 @@ public final class PopupKeySpec {
|
|||
public Key buildKey(final int x, final int y, final int labelFlags,
|
||||
@NonNull final KeyboardParams params) {
|
||||
return new Key(mLabel, mIconId, mCode, mOutputText, null /* hintLabel */, labelFlags,
|
||||
Key.BACKGROUND_TYPE_NORMAL, x, y, params.mDefaultKeyWidth, params.mDefaultRowHeight,
|
||||
Key.BACKGROUND_TYPE_NORMAL, x, y, params.mDefaultAbsoluteKeyWidth, params.mDefaultAbsoluteRowHeight,
|
||||
params.mHorizontalGap, params.mVerticalGap);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import helium314.keyboard.keyboard.KeyboardId
|
|||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.common.Constants
|
||||
import helium314.keyboard.latin.common.StringUtils
|
||||
import helium314.keyboard.latin.settings.Settings
|
||||
import helium314.keyboard.latin.utils.ResourceUtils
|
||||
|
@ -44,20 +43,20 @@ class EmojiParser(private val params: KeyboardParams, private val context: Conte
|
|||
|
||||
// determine key width for default settings (no number row, no one-handed mode, 100% height and bottom padding scale)
|
||||
// this is a bit long, but ensures that emoji size stays the same, independent of these settings
|
||||
val defaultKeyWidth = (ResourceUtils.getDefaultKeyboardWidth(context.resources) - params.mLeftPadding - params.mRightPadding) * params.mDefaultRelativeKeyWidth
|
||||
val defaultKeyWidth = (ResourceUtils.getDefaultKeyboardWidth(context.resources) - params.mLeftPadding - params.mRightPadding) * params.mDefaultKeyWidth
|
||||
val keyWidth = defaultKeyWidth * sqrt(Settings.getInstance().current.mKeyboardHeightScale)
|
||||
val defaultKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(context.resources, false)
|
||||
val defaultBottomPadding = context.resources.getFraction(R.fraction.config_keyboard_bottom_padding_holo, defaultKeyboardHeight, defaultKeyboardHeight)
|
||||
val emojiKeyboardHeight = ResourceUtils.getDefaultKeyboardHeight(context.resources, false) * 0.75f + params.mVerticalGap - defaultBottomPadding - context.resources.getDimensionPixelSize(R.dimen.config_emoji_category_page_id_height)
|
||||
val keyHeight = emojiKeyboardHeight * params.mDefaultRelativeRowHeight * Settings.getInstance().current.mKeyboardHeightScale // still apply height scale to key
|
||||
val keyHeight = emojiKeyboardHeight * params.mDefaultRowHeight * Settings.getInstance().current.mKeyboardHeightScale // still apply height scale to key
|
||||
|
||||
emojiArray.forEachIndexed { i, codeArraySpec ->
|
||||
val keyParams = parseEmojiKey(codeArraySpec, popupEmojisArray?.get(i)?.takeIf { it.isNotEmpty() }) ?: return@forEachIndexed
|
||||
keyParams.xPos = currentX
|
||||
keyParams.yPos = currentY
|
||||
keyParams.mFullWidth = keyWidth
|
||||
keyParams.mFullHeight = keyHeight
|
||||
currentX += keyParams.mFullWidth
|
||||
keyParams.mAbsoluteWidth = keyWidth
|
||||
keyParams.mAbsoluteHeight = keyHeight
|
||||
currentX += keyParams.mAbsoluteWidth
|
||||
row.add(keyParams)
|
||||
}
|
||||
return arrayListOf(row)
|
||||
|
|
|
@ -9,18 +9,17 @@ import androidx.annotation.StringRes
|
|||
import helium314.keyboard.keyboard.Key
|
||||
import helium314.keyboard.keyboard.Key.KeyParams
|
||||
import helium314.keyboard.keyboard.KeyboardId
|
||||
import helium314.keyboard.keyboard.KeyboardTheme
|
||||
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyData
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyLabel
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyType
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.SimplePopups
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.TextKeyData
|
||||
import helium314.keyboard.latin.R
|
||||
import helium314.keyboard.latin.common.Constants
|
||||
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
|
||||
import helium314.keyboard.latin.common.isEmoji
|
||||
import helium314.keyboard.latin.common.splitOnWhitespace
|
||||
import helium314.keyboard.latin.define.DebugFlags
|
||||
import helium314.keyboard.latin.settings.Settings
|
||||
import helium314.keyboard.latin.spellcheck.AndroidSpellCheckerService
|
||||
|
@ -31,7 +30,10 @@ import helium314.keyboard.latin.utils.POPUP_KEYS_NUMBER
|
|||
import helium314.keyboard.latin.utils.ScriptUtils
|
||||
import helium314.keyboard.latin.utils.ScriptUtils.script
|
||||
import helium314.keyboard.latin.utils.getLayoutFile
|
||||
import helium314.keyboard.latin.utils.removeFirst
|
||||
import helium314.keyboard.latin.utils.replaceFirst
|
||||
import helium314.keyboard.latin.utils.runInLocale
|
||||
import helium314.keyboard.latin.utils.splitAt
|
||||
import helium314.keyboard.latin.utils.sumOf
|
||||
import java.io.File
|
||||
|
||||
|
@ -65,103 +67,260 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
params.mTouchPositionCorrection.load(context.resources.getStringArray(infos.touchPositionCorrectionData))
|
||||
|
||||
val baseKeys: MutableList<List<KeyData>> = parseCoreLayout(layoutContent)
|
||||
val keysInRows: ArrayList<ArrayList<KeyParams>>
|
||||
if (params.mId.mElementId <= KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
|
||||
keysInRows = createAlphaSymbolRows(baseKeys)
|
||||
val keysInRows = if (params.mId.isAlphaOrSymbolKeyboard) {
|
||||
createAlphaSymbolRows(baseKeys)
|
||||
} else if (params.mId.isNumberLayout) {
|
||||
keysInRows = createNumericRows(baseKeys)
|
||||
createNumericRows(baseKeys)
|
||||
} else {
|
||||
throw(UnsupportedOperationException("creating KeyboardId ${params.mId.mElementId} not supported"))
|
||||
}
|
||||
// rescale height if we have more than 4 rows
|
||||
val heightRescale = if (keysInRows.size > 4) 4f / keysInRows.size else 1f
|
||||
if (heightRescale != 1f) {
|
||||
keysInRows.forEach { row -> row.forEach { it.mRelativeHeight *= heightRescale } }
|
||||
keysInRows.forEach { row -> row.forEach { it.mHeight *= heightRescale } }
|
||||
}
|
||||
|
||||
return keysInRows
|
||||
}
|
||||
|
||||
// this should be ready for customizable functional layouts, but needs cleanup
|
||||
// todo (later): remove this as part of adding a cache for parsed layouts
|
||||
private fun getFunctionalKeyLayoutText(): String {
|
||||
if (!params.mId.isAlphaOrSymbolKeyboard) throw IllegalStateException("functional key layout only for aloha and symbol layouts")
|
||||
val layouts = Settings.getLayoutsDir(context).list() ?: emptyArray()
|
||||
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
|
||||
if ("functional_keys_symbols_shifted.json" in layouts)
|
||||
return getLayoutFile("functional_keys_symbols_shifted.json", context).readText()
|
||||
}
|
||||
if (!params.mId.isAlphabetKeyboard) {
|
||||
if ("functional_keys_symbols.json" in layouts)
|
||||
return getLayoutFile("functional_keys_symbols.json", context).readText()
|
||||
}
|
||||
if ("functional_keys.json" in layouts)
|
||||
return getLayoutFile("functional_keys.json", context).readText()
|
||||
val fileName = if (Settings.getInstance().isTablet) "functional_keys_tablet.json" else "functional_keys.json"
|
||||
return context.readAssetsLayoutFile(fileName)
|
||||
}
|
||||
|
||||
private fun createAlphaSymbolRows(baseKeys: MutableList<List<KeyData>>): ArrayList<ArrayList<KeyParams>> {
|
||||
addNumberRowOrPopupKeys(baseKeys)
|
||||
if (params.mId.isAlphabetKeyboard)
|
||||
addSymbolPopupKeys(baseKeys)
|
||||
val bottomRow = getBottomRowAndAdjustBaseKeys(baseKeys) // not really fast, but irrelevant compared to the loop
|
||||
if (params.mId.mNumberRowEnabled)
|
||||
baseKeys.add(
|
||||
0,
|
||||
params.mLocaleKeyboardInfos.getNumberRow()
|
||||
.map { it.copy(newLabelFlags = Key.LABEL_FLAGS_DISABLE_HINT_LABEL or defaultLabelFlags) })
|
||||
|
||||
val keysInRows = ArrayList<ArrayList<KeyParams>>()
|
||||
val functionalKeys = parseFunctionalKeys(R.string.key_def_functional)
|
||||
val functionalKeysTop = parseFunctionalKeys(R.string.key_def_functional_top_row)
|
||||
val allFunctionalKeys = JsonKeyboardParser(params, context).parseCoreLayout(getFunctionalKeyLayoutText())
|
||||
adjustBottomFunctionalRowAndBaseKeys(allFunctionalKeys, baseKeys)
|
||||
|
||||
// todo: this loop could use some performance improvements
|
||||
baseKeys.forEachIndexed { i, it ->
|
||||
val row: List<KeyData> = if (i == baseKeys.lastIndex && isTablet()) {
|
||||
if (allFunctionalKeys.none { it.singleOrNull()?.isKeyPlaceholder() == true })
|
||||
// add a placeholder so splitAt does what we really want
|
||||
allFunctionalKeys.add(0, listOf(TextKeyData(type = KeyType.PLACEHOLDER)))
|
||||
|
||||
val (functionalKeysTop, functionalKeysBottom) = allFunctionalKeys.splitAt { it.singleOrNull()?.isKeyPlaceholder() == true }
|
||||
|
||||
// offset for bottom, relevant for getting correct functional key rows
|
||||
val bottomIndexOffset = baseKeys.size - functionalKeysBottom.size
|
||||
|
||||
val functionalKeys = mutableListOf<Pair<List<KeyParams>, List<KeyParams>>>()
|
||||
val baseKeyParams = baseKeys.mapIndexed { i, it ->
|
||||
val row: List<KeyData> = if (i == baseKeys.lastIndex - 1 && Settings.getInstance().isTablet) {
|
||||
// add bottom row extra keys
|
||||
// todo (later): this can make very customized layouts look awkward
|
||||
// decide when to (not) add it
|
||||
// when not adding, consider that punctuation popup keys should not remove those keys!
|
||||
val tabletExtraKeys = params.mLocaleKeyboardInfos.getTabletExtraKeys(params.mId.mElementId)
|
||||
tabletExtraKeys.first + it + tabletExtraKeys.second
|
||||
} else {
|
||||
it
|
||||
}
|
||||
// parse functional keys for this row (if any)
|
||||
val offset = baseKeys.size - functionalKeys.size
|
||||
val functionalKeysDefs = if (i >= offset) functionalKeys[i - offset] // functional keys are aligned to bottom
|
||||
else emptyList<String>() to emptyList()
|
||||
val outerFunctionalKeyDefs = if (i == 0 && functionalKeysTop.isNotEmpty()) functionalKeysTop.first() // top row
|
||||
else emptyList<String>() to emptyList()
|
||||
// if we have a top row and top row entries from normal functional key defs, use top row as outer keys
|
||||
val functionalKeysLeft = outerFunctionalKeyDefs.first.map { getFunctionalKeyParams(it) } + functionalKeysDefs.first.map { getFunctionalKeyParams(it) }
|
||||
val functionalKeysRight = functionalKeysDefs.second.map { getFunctionalKeyParams(it) } + outerFunctionalKeyDefs.second.map { getFunctionalKeyParams(it) }
|
||||
val paramsRow = ArrayList<KeyParams>(functionalKeysLeft)
|
||||
|
||||
// determine key width, maybe scale factor for keys, and spacers to add
|
||||
val usedKeyWidth = params.mDefaultRelativeKeyWidth * row.size
|
||||
val functionalKeyWidth = (functionalKeysLeft.sumOf { it.mRelativeWidth }) + (functionalKeysRight.sumOf { it.mRelativeWidth })
|
||||
val availableWidth = 1f - functionalKeyWidth
|
||||
var keyWidth: Float
|
||||
val spacerWidth: Float
|
||||
if (availableWidth - usedKeyWidth > 0.0001f) { // don't add spacers if only a tiny bit is empty
|
||||
// width available, add spacer
|
||||
keyWidth = params.mDefaultRelativeKeyWidth
|
||||
spacerWidth = (availableWidth - usedKeyWidth) / 2
|
||||
} else {
|
||||
// need more width, re-scale
|
||||
spacerWidth = 0f
|
||||
keyWidth = availableWidth / row.size
|
||||
}
|
||||
if (spacerWidth != 0f) {
|
||||
paramsRow.add(KeyParams.newSpacer(params, spacerWidth))
|
||||
}
|
||||
if (keyWidth < params.mDefaultRelativeKeyWidth * 0.82 && spacerWidth == 0f) {
|
||||
// keys are very narrow, also rescale the functional keys to make keys a little wider
|
||||
// 0.82 is just some guess for "too narrow"
|
||||
val allKeyScale = 1f / (functionalKeyWidth + row.size * params.mDefaultRelativeKeyWidth)
|
||||
keyWidth = params.mDefaultRelativeKeyWidth * allKeyScale
|
||||
functionalKeysLeft.forEach { it.mRelativeWidth *= allKeyScale }
|
||||
functionalKeysRight.forEach { it.mRelativeWidth *= allKeyScale }
|
||||
}
|
||||
// build list of functional keys of same size as baseKeys
|
||||
val functionalKeysFromTop = functionalKeysTop.getOrNull(i) ?: emptyList()
|
||||
val functionalKeysFromBottom = functionalKeysBottom.getOrNull(i - bottomIndexOffset) ?: emptyList()
|
||||
functionalKeys.add(getFunctionalKeysBySide(functionalKeysFromTop, functionalKeysFromBottom))
|
||||
|
||||
for (key in row) {
|
||||
row.map { key ->
|
||||
val extraFlags = if (key.label.length > 2 && key.label.codePointCount(0, key.label.length) > 2 && !isEmoji(key.label))
|
||||
Key.LABEL_FLAGS_AUTO_X_SCALE
|
||||
else 0
|
||||
val keyData = key.compute(params)
|
||||
if (DebugFlags.DEBUG_ENABLED)
|
||||
Log.d(TAG, "adding key ${keyData.label}, ${keyData.code}")
|
||||
val keyParams = keyData.toKeyParams(params, keyWidth, defaultLabelFlags or extraFlags)
|
||||
paramsRow.add(keyParams)
|
||||
Log.d(TAG, "adding key ${key.label}, ${key.code}")
|
||||
key.toKeyParams(params, defaultLabelFlags or extraFlags)
|
||||
}
|
||||
if (spacerWidth != 0f) {
|
||||
paramsRow.add(KeyParams.newSpacer(params, spacerWidth))
|
||||
}
|
||||
functionalKeysRight.forEach { paramsRow.add(it) }
|
||||
keysInRows.add(paramsRow)
|
||||
}
|
||||
resizeLastRowIfNecessaryForAlignment(keysInRows)
|
||||
keysInRows.add(bottomRow)
|
||||
if (params.mId.mNumberRowEnabled)
|
||||
keysInRows.add(0, getNumberRow())
|
||||
return setReasonableWidths(baseKeyParams, functionalKeys)
|
||||
}
|
||||
|
||||
/** interprets key width -1, adjusts row size to nicely fit on screen, adds spacers if necessary */
|
||||
private fun setReasonableWidths(bassKeyParams: List<List<KeyParams>>, functionalKeys: List<Pair<List<KeyParams>, List<KeyParams>>>): ArrayList<ArrayList<KeyParams>> {
|
||||
val keysInRows = ArrayList<ArrayList<KeyParams>>()
|
||||
// expand width = -1 keys and make sure rows fit on screen, insert spacers if necessary
|
||||
bassKeyParams.forEachIndexed { i, keys ->
|
||||
val (functionalKeysLeft, functionalKeysRight) = functionalKeys[i]
|
||||
// sum up width, excluding -1 elements (put those in a separate list)
|
||||
val varWidthKeys = mutableListOf<KeyParams>()
|
||||
var totalWidth = 0f
|
||||
val allKeys = (functionalKeysLeft + keys + functionalKeysRight)
|
||||
allKeys.forEach {
|
||||
if (it.mWidth == -1f) varWidthKeys.add(it)
|
||||
else totalWidth += it.mWidth
|
||||
}
|
||||
|
||||
// set width for varWidthKeys
|
||||
if (varWidthKeys.isNotEmpty()) {
|
||||
val width = if (totalWidth + varWidthKeys.size * params.mDefaultKeyWidth > 1)
|
||||
params.mDefaultKeyWidth // never go below default width
|
||||
else (1f - totalWidth) / varWidthKeys.size // split remaining space evenly
|
||||
varWidthKeys.forEach { it.mWidth = width }
|
||||
|
||||
// re-calculate total width
|
||||
totalWidth = allKeys.sumOf { it.mWidth }
|
||||
}
|
||||
|
||||
// re-scale total width, or add spacers (or do nothing if totalWidth is near 1)
|
||||
if (totalWidth < 0.9999f) { // add spacers
|
||||
val spacerWidth = (1f - totalWidth) / 2
|
||||
val paramsRow = ArrayList<KeyParams>(functionalKeysLeft + KeyParams.newSpacer(params, spacerWidth) + keys +
|
||||
KeyParams.newSpacer(params, spacerWidth) + functionalKeysRight)
|
||||
keysInRows.add(paramsRow)
|
||||
} else {
|
||||
if (totalWidth > 1.0001f) { // re-scale total width
|
||||
val normalKeysWith = keys.sumOf { it.mWidth }
|
||||
val functionalKeysWidth = totalWidth - normalKeysWith
|
||||
val scaleFactor = (1f - functionalKeysWidth) / normalKeysWith
|
||||
// re-scale normal keys if factor is > 0.82, otherwise re-scale all keys
|
||||
if (scaleFactor > 0.82f) keys.forEach { it.mWidth *= scaleFactor }
|
||||
else allKeys.forEach { it.mWidth /= totalWidth }
|
||||
}
|
||||
keysInRows.add(ArrayList(allKeys))
|
||||
}
|
||||
}
|
||||
|
||||
// adjust last normal row key widths to be aligned with row above, assuming a reasonably close-to-default alpha / symbol layout
|
||||
// like in original layouts, e.g. for nordic and swiss layouts
|
||||
if (!params.mId.isAlphaOrSymbolKeyboard || bassKeyParams.size < 3 || bassKeyParams.last().isNotEmpty())
|
||||
return keysInRows
|
||||
val lastNormalRow = bassKeyParams[bassKeyParams.lastIndex - 1]
|
||||
val rowAboveLast = bassKeyParams[bassKeyParams.lastIndex - 2]
|
||||
val lastNormalRowKeyWidth = lastNormalRow.first().mWidth
|
||||
val rowAboveLastNormalRowKeyWidth = rowAboveLast.first().mWidth
|
||||
if (lastNormalRowKeyWidth <= rowAboveLastNormalRowKeyWidth + 0.0001f // no need
|
||||
|| lastNormalRowKeyWidth / rowAboveLastNormalRowKeyWidth > 1.1f // don't resize on large size difference
|
||||
|| lastNormalRow.any { it.isSpacer } || rowAboveLast.any { it.isSpacer } // annoying to deal with, and probably no resize wanted anyway
|
||||
|| lastNormalRow.any { it.mWidth != lastNormalRowKeyWidth } || rowAboveLast.any { it.mWidth != rowAboveLastNormalRowKeyWidth })
|
||||
return keysInRows
|
||||
val numberOfKeysInLast = lastNormalRow.count { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL }
|
||||
val widthBefore = numberOfKeysInLast * lastNormalRowKeyWidth
|
||||
val widthAfter = numberOfKeysInLast * rowAboveLastNormalRowKeyWidth
|
||||
val spacerWidth = (widthBefore - widthAfter) / 2
|
||||
// resize keys
|
||||
lastNormalRow.forEach { if (it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL) it.mWidth = rowAboveLastNormalRowKeyWidth }
|
||||
// add spacers
|
||||
val lastNormalFullRow = keysInRows[keysInRows.lastIndex - 1]
|
||||
lastNormalFullRow.add(lastNormalFullRow.indexOfFirst { it == lastNormalRow.first() }, KeyParams.newSpacer(params, spacerWidth))
|
||||
lastNormalFullRow.add(lastNormalFullRow.indexOfLast { it == lastNormalRow.last() } + 1, KeyParams.newSpacer(params, spacerWidth))
|
||||
|
||||
return keysInRows
|
||||
}
|
||||
|
||||
/**
|
||||
* adds / removes keys to the bottom row
|
||||
* assumes a close-to-default bottom row consisting only of functional keys
|
||||
* does nothing if not isAlphaOrSymbolKeyboard or assumptions not met
|
||||
* adds an empty row to baseKeys, to have a baseKey row for the bottom functional row
|
||||
*/
|
||||
private fun adjustBottomFunctionalRowAndBaseKeys(allFunctionalKeys: MutableList<List<KeyData>>, baseKeys: MutableList<List<KeyData>>) {
|
||||
val functionalKeysBottom = allFunctionalKeys.lastOrNull()?.toMutableList() ?: return
|
||||
if (!params.mId.isAlphaOrSymbolKeyboard || functionalKeysBottom.isEmpty() || functionalKeysBottom.any { it.isKeyPlaceholder() })
|
||||
return
|
||||
if (true /* Settings.getInstance().current.mSingleFunctionalLayout */) { // todo with the customizable functional layout
|
||||
// remove unwanted keys (emoji, numpad, language switch)
|
||||
if (!Settings.getInstance().current.mShowsEmojiKey || !params.mId.isAlphabetKeyboard)
|
||||
functionalKeysBottom.removeFirst { it.label == KeyLabel.EMOJI }
|
||||
if (!Settings.getInstance().current.isLanguageSwitchKeyEnabled || !params.mId.isAlphabetKeyboard)
|
||||
functionalKeysBottom.removeFirst { it.label == KeyLabel.LANGUAGE_SWITCH }
|
||||
if (params.mId.mElementId != KeyboardId.ELEMENT_SYMBOLS)
|
||||
functionalKeysBottom.removeFirst { it.label == KeyLabel.NUMPAD }
|
||||
}
|
||||
// replace comma / period if 2 keys in normal bottom row
|
||||
if (baseKeys.last().size == 2) {
|
||||
Log.i("test", "$functionalKeysBottom")
|
||||
functionalKeysBottom.replaceFirst(
|
||||
{ it.label == KeyLabel.COMMA || it.groupId == KeyData.GROUP_COMMA},
|
||||
{ baseKeys.last()[0].copy(newGroupId = 1, newType = baseKeys.last()[0].type ?: it.type) }
|
||||
)
|
||||
functionalKeysBottom.replaceFirst(
|
||||
{ it.label == KeyLabel.PERIOD || it.groupId == KeyData.GROUP_PERIOD},
|
||||
{ baseKeys.last()[1].copy(newGroupId = 2, newType = baseKeys.last()[1].type ?: it.type) }
|
||||
)
|
||||
Log.i("test", "$functionalKeysBottom")
|
||||
baseKeys.removeLast()
|
||||
}
|
||||
// add those extra keys depending on layout (remove later)
|
||||
val spaceIndex = functionalKeysBottom.indexOfFirst { it.label == KeyLabel.SPACE && it.width <= 0 } // 0 or -1
|
||||
if (spaceIndex >= 0) {
|
||||
if (params.mLocaleKeyboardInfos.hasZwnjKey && params.mId.isAlphabetKeyboard) {
|
||||
// add zwnj key next to space
|
||||
functionalKeysBottom.add(spaceIndex + 1, TextKeyData(label = KeyLabel.ZWNJ))
|
||||
} else if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS) {
|
||||
// add / key next to space, todo (later): not any more, but keep it so this PR can be released without too many people complaining
|
||||
functionalKeysBottom.add(spaceIndex + 1, TextKeyData(label = "/", type = KeyType.FUNCTION))
|
||||
} else if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
|
||||
// add < and > keys next to space, todo (later): not any more, but keep it so this PR can be released without too many people complaining
|
||||
val key1 = TextKeyData(
|
||||
label = "<",
|
||||
popup = SimplePopups(listOf("!fixedColumnOrder!3", "‹", "≤", "«")),
|
||||
labelFlags = Key.LABEL_FLAGS_HAS_POPUP_HINT,
|
||||
type = KeyType.FUNCTION
|
||||
)
|
||||
val key2 = TextKeyData(
|
||||
label = ">",
|
||||
popup = SimplePopups(listOf("!fixedColumnOrder!3", "›", "≥", "»")),
|
||||
labelFlags = Key.LABEL_FLAGS_HAS_POPUP_HINT,
|
||||
type = KeyType.FUNCTION
|
||||
)
|
||||
functionalKeysBottom.add(spaceIndex + 1, key2)
|
||||
functionalKeysBottom.add(spaceIndex, key1)
|
||||
}
|
||||
}
|
||||
allFunctionalKeys[allFunctionalKeys.lastIndex] = functionalKeysBottom
|
||||
baseKeys.add(emptyList())
|
||||
}
|
||||
|
||||
// ideally we would get all functional keys in a nice list of pairs from the start, but at least it works...
|
||||
private fun getFunctionalKeysBySide(functionalKeysFromTop: List<KeyData>, functionalKeysFromBottom: List<KeyData>): Pair<List<KeyParams>, List<KeyParams>> {
|
||||
val (functionalKeysFromTopLeft, functionalKeysFromTopRight) = functionalKeysFromTop.splitAt { it.isKeyPlaceholder() }
|
||||
val (functionalKeysFromBottomLeft, functionalKeysFromBottomRight) = functionalKeysFromBottom.splitAt { it.isKeyPlaceholder() }
|
||||
// functional keys from top rows are the outermost, if there are some in the same row
|
||||
functionalKeysFromTopLeft.addAll(functionalKeysFromBottomLeft)
|
||||
functionalKeysFromBottomRight.addAll(functionalKeysFromTopRight)
|
||||
val functionalKeysLeft = functionalKeysFromTopLeft.mapNotNull { it.processFunctionalKeys()?.toKeyParams(params) }
|
||||
val functionalKeysRight = functionalKeysFromBottomRight.mapNotNull { it.processFunctionalKeys()?.toKeyParams(params) }
|
||||
return functionalKeysLeft to functionalKeysRight
|
||||
}
|
||||
|
||||
// this is not nice in here, but otherwise we'd need context, and defaultLabelFlags and infos for toKeyParams
|
||||
// improve it later, but currently this messy way is still ok
|
||||
private fun KeyData.processFunctionalKeys(): KeyData? {
|
||||
if (label == KeyLabel.PERIOD) {
|
||||
// todo: why defaultLabelFlags exactly here? is this for armenian or bengali period labels? try removing also check in holo theme
|
||||
return copy(newLabelFlags = labelFlags or defaultLabelFlags)
|
||||
}
|
||||
if (label == KeyLabel.SHIFT && !infos.hasShiftKey) return null
|
||||
if (label != KeyLabel.ACTION) return this
|
||||
return copy(
|
||||
// todo: evaluating the label should actually only happen in toKeyParams
|
||||
// this label change already makes it necessary to provide the background in here too, because toKeyParams can't use action as label
|
||||
newLabel = "${getActionKeyLabel()}|${getActionKeyCode()}",
|
||||
newPopup = popup.merge(getActionKeyPopupKeys()?.let { SimplePopups(it) }),
|
||||
// the label change is messing with toKeyParams, so we need to supply the appropriate BG type here
|
||||
newType = type ?: KeyType.ENTER_EDITING
|
||||
)
|
||||
}
|
||||
|
||||
private fun addNumberRowOrPopupKeys(baseKeys: MutableList<List<KeyData>>) {
|
||||
if (!params.mId.mNumberRowEnabled && params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS) {
|
||||
// replace first symbols row with number row, but use the labels as popupKeys
|
||||
|
@ -194,7 +353,7 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
else SimpleKeyboardParser(params, context, false)
|
||||
parser.parseCoreLayout(getLayoutFile(layoutName, context).readText())
|
||||
} else {
|
||||
SimpleKeyboardParser(params, context, false).parseCoreLayout(context.readAssetsFile("layouts/$layoutName.txt"))
|
||||
SimpleKeyboardParser(params, context, false).parseCoreLayout(context.readAssetsLayoutFile("$layoutName.txt"))
|
||||
}
|
||||
layout.forEachIndexed { i, row ->
|
||||
val baseRow = baseKeys.getOrNull(i) ?: return@forEachIndexed
|
||||
|
@ -204,34 +363,6 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
}
|
||||
}
|
||||
|
||||
// resize keys in last row if they are wider than keys in the row above
|
||||
// this is done so the keys align with the keys above, like in original layouts
|
||||
// e.g. for nordic and swiss layouts
|
||||
private fun resizeLastRowIfNecessaryForAlignment(keysInRows: ArrayList<ArrayList<KeyParams>>) {
|
||||
if (keysInRows.size < 3)
|
||||
return
|
||||
val lastRow = keysInRows.last()
|
||||
val rowAboveLast = keysInRows[keysInRows.lastIndex - 1]
|
||||
if (lastRow.any { it.isSpacer } || rowAboveLast.any { it.isSpacer })
|
||||
return // annoying to deal with, and probably no resize needed anyway
|
||||
val lastNormalRowKeyWidth = lastRow.first { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL }.mRelativeWidth
|
||||
val rowAboveLastNormalRowKeyWidth = rowAboveLast.first { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL }.mRelativeWidth
|
||||
if (lastNormalRowKeyWidth <= rowAboveLastNormalRowKeyWidth + 0.0001f)
|
||||
return // no need
|
||||
if (lastNormalRowKeyWidth / rowAboveLastNormalRowKeyWidth > 1.1f)
|
||||
return // don't resize on large size difference
|
||||
if (lastRow.any { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL && it.mRelativeWidth != lastNormalRowKeyWidth })
|
||||
return // normal keys have different width, don't deal with this
|
||||
val numberOfNormalKeys = lastRow.count { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL }
|
||||
val widthBefore = numberOfNormalKeys * lastNormalRowKeyWidth
|
||||
val widthAfter = numberOfNormalKeys * rowAboveLastNormalRowKeyWidth
|
||||
val spacerWidth = (widthBefore - widthAfter) / 2
|
||||
// resize keys and add spacers
|
||||
lastRow.forEach { if (it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL) it.mRelativeWidth = rowAboveLastNormalRowKeyWidth }
|
||||
lastRow.add(lastRow.indexOfFirst { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL }, KeyParams.newSpacer(params, spacerWidth))
|
||||
lastRow.add(lastRow.indexOfLast { it.mBackgroundType == Key.BACKGROUND_TYPE_NORMAL } + 1, KeyParams.newSpacer(params, spacerWidth))
|
||||
}
|
||||
|
||||
private fun createNumericRows(baseKeys: MutableList<List<KeyData>>): ArrayList<ArrayList<KeyParams>> {
|
||||
val keysInRows = ArrayList<ArrayList<KeyParams>>()
|
||||
if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && params.mId.mElementId != KeyboardId.ELEMENT_NUMPAD) {
|
||||
|
@ -245,16 +376,15 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
row.forEach { key ->
|
||||
var keyParams: KeyParams? = null
|
||||
// try parsing a functional key
|
||||
// todo: note that this is ignoring code on those keys, if any
|
||||
val functionalKeyName = when (key.label) {
|
||||
// todo (later): maybe add special popupKeys for phone and number layouts?
|
||||
"." -> if (params.mId.mElementId == KeyboardId.ELEMENT_NUMPAD) "period" else "."
|
||||
"," -> if (params.mId.mElementId == KeyboardId.ELEMENT_NUMPAD) "comma" else ","
|
||||
"." -> if (params.mId.mElementId == KeyboardId.ELEMENT_NUMPAD) KeyLabel.PERIOD else "."
|
||||
"," -> if (params.mId.mElementId == KeyboardId.ELEMENT_NUMPAD) KeyLabel.COMMA else ","
|
||||
else -> key.label
|
||||
}
|
||||
if (functionalKeyName.length > 1 && key.type != KeyType.NUMERIC) { // todo: why exception for numeric?
|
||||
if (functionalKeyName.length > 1 && key.type != KeyType.NUMERIC) {
|
||||
try {
|
||||
keyParams = getFunctionalKeyParams(functionalKeyName)
|
||||
keyParams = key.copy(newLabel = functionalKeyName).processFunctionalKeys()!!.toKeyParams(params)
|
||||
} catch (_: Throwable) {} // just use normal label
|
||||
}
|
||||
if (keyParams == null) {
|
||||
|
@ -264,11 +394,11 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
KeyboardId.ELEMENT_PHONE_SYMBOLS -> 0
|
||||
else -> Key.LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO
|
||||
}
|
||||
key.compute(params).toKeyParams(params, 0.17f, labelFlags or defaultLabelFlags)
|
||||
key.toKeyParams(params, labelFlags or defaultLabelFlags)
|
||||
} else if (key.label.length == 1 && (params.mId.mElementId == KeyboardId.ELEMENT_PHONE || params.mId.mElementId == KeyboardId.ELEMENT_NUMBER))
|
||||
key.compute(params).toKeyParams(params, additionalLabelFlags = Key.LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO or defaultLabelFlags)
|
||||
key.toKeyParams(params, additionalLabelFlags = Key.LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO or defaultLabelFlags)
|
||||
else
|
||||
key.compute(params).toKeyParams(params, additionalLabelFlags = defaultLabelFlags)
|
||||
key.toKeyParams(params, additionalLabelFlags = defaultLabelFlags)
|
||||
}
|
||||
if (key.type != KeyType.NUMERIC && keyParams.mBackgroundType != Key.BACKGROUND_TYPE_ACTION)
|
||||
keyParams.mBackgroundType = Key.BACKGROUND_TYPE_FUNCTIONAL
|
||||
|
@ -296,256 +426,24 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
// make those keys same width as numeric keys except in numpad layout
|
||||
// but determine from row size instead of from elementId, in case user wants to adjust numpad layout
|
||||
if (row.size == baseKeys[0].size) {
|
||||
paramsRow.getOrNull(n - 1)?.mRelativeWidth = paramsRow[n].mRelativeWidth
|
||||
paramsRow.getOrNull(n + 1)?.mRelativeWidth = paramsRow[n].mRelativeWidth
|
||||
paramsRow.getOrNull(n - 1)?.mWidth = paramsRow[n].mWidth
|
||||
paramsRow.getOrNull(n + 1)?.mWidth = paramsRow[n].mWidth
|
||||
} else if (row.size == baseKeys[0].size + 2) {
|
||||
// numpad last row -> make sure the keys next to 0 fit nicely
|
||||
paramsRow.getOrNull(n - 1)?.mRelativeWidth = paramsRow[n].mRelativeWidth * 0.55f
|
||||
paramsRow.getOrNull(n - 2)?.mRelativeWidth = paramsRow[n].mRelativeWidth * 0.45f
|
||||
paramsRow.getOrNull(n + 1)?.mRelativeWidth = paramsRow[n].mRelativeWidth * 0.55f
|
||||
paramsRow.getOrNull(n + 2)?.mRelativeWidth = paramsRow[n].mRelativeWidth * 0.45f
|
||||
paramsRow.getOrNull(n - 1)?.mWidth = paramsRow[n].mWidth * 0.55f
|
||||
paramsRow.getOrNull(n - 2)?.mWidth = paramsRow[n].mWidth * 0.45f
|
||||
paramsRow.getOrNull(n + 1)?.mWidth = paramsRow[n].mWidth * 0.55f
|
||||
paramsRow.getOrNull(n + 2)?.mWidth = paramsRow[n].mWidth * 0.45f
|
||||
}
|
||||
}
|
||||
}
|
||||
val widthSum = paramsRow.sumOf { it.mRelativeWidth }
|
||||
paramsRow.forEach { it.mRelativeWidth /= widthSum }
|
||||
val widthSum = paramsRow.sumOf { it.mWidth }
|
||||
paramsRow.forEach { it.mWidth /= widthSum }
|
||||
keysInRows.add(paramsRow)
|
||||
}
|
||||
return keysInRows
|
||||
}
|
||||
|
||||
private fun parseFunctionalKeys(@StringRes id: Int): List<Pair<List<String>, List<String>>> =
|
||||
context.getString(id).split("\n").mapNotNull { line ->
|
||||
if (line.isBlank()) return@mapNotNull null
|
||||
val p = line.split(";")
|
||||
splitFunctionalKeyDefs(p.first()) to splitFunctionalKeyDefs(p.last())
|
||||
}
|
||||
|
||||
private fun splitFunctionalKeyDefs(def: String): List<String> {
|
||||
if (def.isBlank()) return emptyList()
|
||||
return def.split(",").filter { infos.hasShiftKey || !it.trim().startsWith("shift") }
|
||||
}
|
||||
|
||||
private fun getBottomRowAndAdjustBaseKeys(baseKeys: MutableList<List<KeyData>>): ArrayList<KeyParams> {
|
||||
val adjustableKeyCount = when (params.mId.mElementId) {
|
||||
KeyboardId.ELEMENT_SYMBOLS -> 3
|
||||
KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> 4
|
||||
else -> 2 // must be alphabet, parser doesn't work for other elementIds
|
||||
}
|
||||
val adjustedKeys = if (baseKeys.last().size == adjustableKeyCount) baseKeys.last()
|
||||
else null
|
||||
if (adjustedKeys != null)
|
||||
baseKeys.removeLast()
|
||||
val bottomRow = ArrayList<KeyParams>()
|
||||
context.getString(R.string.key_def_bottom_row).split(",").forEach {
|
||||
val key = it.trim().splitOnWhitespace().first()
|
||||
val adjustKey = when (key) {
|
||||
"comma" -> adjustedKeys?.first()
|
||||
"period" -> adjustedKeys?.last()
|
||||
else -> null
|
||||
}
|
||||
val keyParams = getFunctionalKeyParams(it, adjustKey?.label, adjustKey?.popup?.getPopupKeyLabels(params))
|
||||
if (key == "space") { // add the extra keys around space
|
||||
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS) {
|
||||
bottomRow.add(getFunctionalKeyParams(FunctionalKey.NUMPAD))
|
||||
bottomRow.add(keyParams)
|
||||
bottomRow.add(KeyParams(
|
||||
adjustedKeys?.get(1)?.label ?: "/",
|
||||
params,
|
||||
params.mDefaultRelativeKeyWidth,
|
||||
defaultLabelFlags,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
adjustedKeys?.get(1)?.popup
|
||||
))
|
||||
} else if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
|
||||
bottomRow.add(KeyParams(
|
||||
(adjustedKeys?.get(1)?.label ?: "<").rtlLabel(params),
|
||||
params,
|
||||
params.mDefaultRelativeKeyWidth,
|
||||
defaultLabelFlags or Key.LABEL_FLAGS_HAS_POPUP_HINT,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
adjustedKeys?.get(1)?.popup ?: SimplePopups(listOf("!fixedColumnOrder!3", "‹", "≤", "«"))
|
||||
))
|
||||
bottomRow.add(keyParams)
|
||||
bottomRow.add(KeyParams(
|
||||
(adjustedKeys?.get(2)?.label ?: ">").rtlLabel(params),
|
||||
params,
|
||||
params.mDefaultRelativeKeyWidth,
|
||||
defaultLabelFlags or Key.LABEL_FLAGS_HAS_POPUP_HINT,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
adjustedKeys?.get(2)?.popup ?: SimplePopups(listOf("!fixedColumnOrder!3", "›", "≥", "»"))
|
||||
))
|
||||
} else { // alphabet
|
||||
if (params.mId.mLanguageSwitchKeyEnabled)
|
||||
bottomRow.add(getFunctionalKeyParams(FunctionalKey.LANGUAGE_SWITCH))
|
||||
if (params.mId.mEmojiKeyEnabled)
|
||||
bottomRow.add(getFunctionalKeyParams(FunctionalKey.EMOJI))
|
||||
bottomRow.add(keyParams)
|
||||
if (params.mLocaleKeyboardInfos.hasZwnjKey)
|
||||
bottomRow.add(getFunctionalKeyParams(FunctionalKey.ZWNJ))
|
||||
}
|
||||
} else {
|
||||
bottomRow.add(keyParams)
|
||||
}
|
||||
}
|
||||
// set space width
|
||||
val space = bottomRow.first { it.mBackgroundType == Key.BACKGROUND_TYPE_SPACEBAR }
|
||||
space.mRelativeWidth = 1f - bottomRow.filter { it != space }.sumOf { it.mRelativeWidth }
|
||||
return bottomRow
|
||||
}
|
||||
|
||||
private fun getNumberRow(): ArrayList<KeyParams> =
|
||||
params.mLocaleKeyboardInfos.getNumberRow().mapTo(ArrayList()) {
|
||||
it.toKeyParams(params, additionalLabelFlags = Key.LABEL_FLAGS_DISABLE_HINT_LABEL or defaultLabelFlags)
|
||||
}
|
||||
|
||||
private fun getFunctionalKeyParams(def: String, label: String? = null, popupKeys: Collection<String>? = null): KeyParams {
|
||||
val split = def.trim().splitOnWhitespace()
|
||||
val key = FunctionalKey.valueOf(split[0].uppercase())
|
||||
val width = if (split.size == 2) split[1].substringBefore("%").toFloat() / 100f
|
||||
else params.mDefaultRelativeKeyWidth
|
||||
return getFunctionalKeyParams(key, width, label, popupKeys)
|
||||
}
|
||||
|
||||
private fun getFunctionalKeyParams(key: FunctionalKey, relativeWidth: Float? = null, label: String? = null, popupKeys: Collection<String>? = null): KeyParams {
|
||||
// for comma and period: label will override default, popupKeys will be appended
|
||||
val width = relativeWidth ?: params.mDefaultRelativeKeyWidth
|
||||
return when (key) {
|
||||
FunctionalKey.SYMBOL_ALPHA -> KeyParams(
|
||||
if (params.mId.isAlphabetKeyboard) getToSymbolLabel() else params.mLocaleKeyboardInfos.labelAlphabet,
|
||||
KeyCode.ALPHA_SYMBOL,
|
||||
params,
|
||||
width,
|
||||
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
null
|
||||
)
|
||||
FunctionalKey.SYMBOL -> KeyParams(
|
||||
getToSymbolLabel(),
|
||||
KeyCode.SYMBOL,
|
||||
params,
|
||||
width,
|
||||
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
null
|
||||
)
|
||||
FunctionalKey.ALPHA -> KeyParams(
|
||||
params.mLocaleKeyboardInfos.labelAlphabet,
|
||||
KeyCode.ALPHA,
|
||||
params,
|
||||
width,
|
||||
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
null
|
||||
)
|
||||
FunctionalKey.COMMA -> KeyParams(
|
||||
label ?: getCommaLabel(),
|
||||
params,
|
||||
width,
|
||||
Key.LABEL_FLAGS_HAS_POPUP_HINT, // previously only if normal comma, but always is more correct
|
||||
if (label?.first()?.isLetter() == true) Key.BACKGROUND_TYPE_NORMAL // mimic behavior of old dvorak and halmak layouts
|
||||
else Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
SimplePopups(popupKeys?.let { getCommaPopupKeys() + it } ?: getCommaPopupKeys())
|
||||
)
|
||||
FunctionalKey.PERIOD -> KeyParams(
|
||||
label ?: getPeriodLabel(),
|
||||
params,
|
||||
width,
|
||||
Key.LABEL_FLAGS_HAS_POPUP_HINT or defaultLabelFlags,
|
||||
if (label?.first()?.isLetter() == true) Key.BACKGROUND_TYPE_NORMAL
|
||||
else Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
SimplePopups(popupKeys?.let { getPunctuationPopupKeys() + it } ?: getPunctuationPopupKeys())
|
||||
)
|
||||
FunctionalKey.SPACE -> KeyParams(
|
||||
getSpaceLabel(),
|
||||
params,
|
||||
width, // will not be used for normal space (only in number layouts)
|
||||
if (params.mId.isNumberLayout) Key.LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM else 0,
|
||||
Key.BACKGROUND_TYPE_SPACEBAR,
|
||||
null
|
||||
)
|
||||
FunctionalKey.ACTION -> KeyParams(
|
||||
"${getActionKeyLabel()}|${getActionKeyCode()}",
|
||||
params,
|
||||
width,
|
||||
Key.LABEL_FLAGS_PRESERVE_CASE
|
||||
or Key.LABEL_FLAGS_AUTO_X_SCALE
|
||||
or Key.LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO
|
||||
or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR
|
||||
or Key.LABEL_FLAGS_HAS_POPUP_HINT
|
||||
or KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId),
|
||||
Key.BACKGROUND_TYPE_ACTION,
|
||||
getActionKeyPopupKeys()?.let { SimplePopups(it) }
|
||||
)
|
||||
FunctionalKey.DELETE -> KeyParams(
|
||||
"!icon/delete_key|!code/key_delete",
|
||||
params,
|
||||
width,
|
||||
0,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
null
|
||||
)
|
||||
FunctionalKey.SHIFT -> KeyParams(
|
||||
"${getShiftLabel()}|!code/key_shift",
|
||||
params,
|
||||
width,
|
||||
Key.LABEL_FLAGS_PRESERVE_CASE or if (!params.mId.isAlphabetKeyboard) Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR else 0,
|
||||
// todo (later): possibly the whole stickyOn/Off stuff can be removed, currently it should only have a very slight effect in holo
|
||||
if (params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED || params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)
|
||||
Key.BACKGROUND_TYPE_STICKY_ON
|
||||
else Key.BACKGROUND_TYPE_STICKY_OFF,
|
||||
if (params.mId.isAlphabetKeyboard) SimplePopups(listOf("!noPanelAutoPopupKey!", " |!code/key_capslock")) else null // why the alphabet popup keys actually?
|
||||
)
|
||||
FunctionalKey.EMOJI -> KeyParams(
|
||||
"!icon/emoji_normal_key|!code/key_emoji",
|
||||
params,
|
||||
width,
|
||||
KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId),
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
null
|
||||
)
|
||||
// tablet layout has an emoji key that changes to com key in url / mail
|
||||
FunctionalKey.EMOJI_COM -> if (params.mId.mMode == KeyboardId.MODE_URL || params.mId.mMode == KeyboardId.MODE_EMAIL)
|
||||
getFunctionalKeyParams(FunctionalKey.COM, width)
|
||||
else getFunctionalKeyParams(FunctionalKey.EMOJI, width)
|
||||
FunctionalKey.COM -> KeyParams(
|
||||
// todo (later): label and popupKeys could be in localeKeyTexts, handled similar to currency key
|
||||
// better not in the text files, because it should be handled per country
|
||||
".com",
|
||||
params,
|
||||
width,
|
||||
Key.LABEL_FLAGS_AUTO_X_SCALE or Key.LABEL_FLAGS_FONT_NORMAL or Key.LABEL_FLAGS_HAS_POPUP_HINT or Key.LABEL_FLAGS_PRESERVE_CASE,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
SimplePopups(listOf(Key.POPUP_KEYS_HAS_LABELS, ".net", ".org", ".gov", ".edu"))
|
||||
)
|
||||
FunctionalKey.LANGUAGE_SWITCH -> KeyParams(
|
||||
"!icon/language_switch_key|!code/key_language_switch",
|
||||
params,
|
||||
width,
|
||||
0,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
null
|
||||
)
|
||||
FunctionalKey.NUMPAD -> KeyParams(
|
||||
"!icon/numpad_key|!code/key_numpad",
|
||||
params,
|
||||
width,
|
||||
0,
|
||||
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||
null
|
||||
)
|
||||
FunctionalKey.ZWNJ -> KeyParams(
|
||||
"!icon/zwnj_key|\u200C",
|
||||
params,
|
||||
width,
|
||||
Key.LABEL_FLAGS_HAS_POPUP_HINT,
|
||||
// this may not be a good place to make this choice, but probably it's fine (though reading from settings here is not good)
|
||||
if (Settings.getInstance().current.mColors.hasKeyBorders) Key.BACKGROUND_TYPE_SPACEBAR else Key.BACKGROUND_TYPE_NORMAL,
|
||||
SimplePopups(listOf("!icon/zwj_key|\u200D"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getActionKeyLabel(): String {
|
||||
if (params.mId.isMultiLine && (params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED || params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED))
|
||||
return "!icon/enter_key"
|
||||
|
@ -627,7 +525,7 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
}
|
||||
// remove emoji shortcut on enter in tablet mode (like original, because bottom row always has an emoji key)
|
||||
// (probably not necessary, but whatever)
|
||||
if (isTablet() && popupKeys.remove("!icon/emoji_action_key|!code/key_emoji")) {
|
||||
if (Settings.getInstance().isTablet && popupKeys.remove("!icon/emoji_action_key|!code/key_emoji")) {
|
||||
val i = popupKeys.indexOfFirst { it.startsWith(Key.POPUP_KEYS_FIXED_COLUMN_ORDER) }
|
||||
if (i > -1) {
|
||||
val n = popupKeys[i].substringAfter(Key.POPUP_KEYS_FIXED_COLUMN_ORDER).toIntOrNull()
|
||||
|
@ -662,84 +560,6 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
return runInLocale(context, locale) { it.getString(id) }
|
||||
}
|
||||
|
||||
private fun getToSymbolLabel() =
|
||||
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS || params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
|
||||
params.mLocaleKeyboardInfos.labelAlphabet
|
||||
else params.mLocaleKeyboardInfos.labelSymbol
|
||||
|
||||
private fun getShiftLabel(): String {
|
||||
val elementId = params.mId.mElementId
|
||||
if (elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
|
||||
return params.mLocaleKeyboardInfos.labelSymbol
|
||||
if (elementId == KeyboardId.ELEMENT_SYMBOLS)
|
||||
return params.mLocaleKeyboardInfos.getShiftSymbolLabel(isTablet())
|
||||
if (elementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED || elementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED
|
||||
|| elementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED || elementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)
|
||||
return "!icon/shift_key_shifted"
|
||||
return "!icon/shift_key"
|
||||
}
|
||||
|
||||
private fun getPeriodLabel(): String {
|
||||
if (params.mId.isNumberLayout) return "."
|
||||
if (params.mId.isAlphabetKeyboard || params.mId.locale.language in listOf("ar", "fa")) // todo: this exception is not so great...
|
||||
return params.mLocaleKeyboardInfos.labelPeriod
|
||||
return "."
|
||||
}
|
||||
|
||||
private fun getCommaLabel(): String {
|
||||
if (params.mId.mMode == KeyboardId.MODE_URL && params.mId.isAlphabetKeyboard)
|
||||
return "/"
|
||||
if (params.mId.mMode == KeyboardId.MODE_EMAIL && params.mId.isAlphabetKeyboard)
|
||||
return "\\@"
|
||||
if (params.mId.isNumberLayout)
|
||||
return ","
|
||||
return params.mLocaleKeyboardInfos.labelComma
|
||||
}
|
||||
|
||||
private fun getCommaPopupKeys(): List<String> {
|
||||
val keys = mutableListOf<String>()
|
||||
if (!params.mId.mDeviceLocked)
|
||||
keys.add("!icon/clipboard_normal_key|!code/key_clipboard")
|
||||
if (!params.mId.mEmojiKeyEnabled && !params.mId.isNumberLayout)
|
||||
keys.add("!icon/emoji_normal_key|!code/key_emoji")
|
||||
if (!params.mId.mLanguageSwitchKeyEnabled)
|
||||
keys.add("!icon/language_switch_key|!code/key_language_switch")
|
||||
if (!params.mId.mOneHandedModeEnabled)
|
||||
keys.add("!icon/start_onehanded_mode_key|!code/key_start_onehanded")
|
||||
if (!params.mId.mDeviceLocked)
|
||||
keys.add("!icon/settings_key|!code/key_settings")
|
||||
return keys
|
||||
}
|
||||
|
||||
private fun getPunctuationPopupKeys(): List<String> {
|
||||
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS || params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
|
||||
return listOf("…")
|
||||
if (params.mId.isNumberLayout)
|
||||
return listOf(":", "…", ";", "∞", "π", "√", "°", "^")
|
||||
val popupKeys = params.mLocaleKeyboardInfos.getPopupKeys("punctuation")!!.toMutableList()
|
||||
if (params.mId.mSubtype.isRtlSubtype) {
|
||||
for (i in popupKeys.indices)
|
||||
popupKeys[i] = popupKeys[i].rtlLabel(params) // for parentheses
|
||||
}
|
||||
if (isTablet() && popupKeys.contains("!") && popupKeys.contains("?")) {
|
||||
// remove ! and ? keys and reduce number in autoColumnOrder
|
||||
// this makes use of removal of empty popupKeys in PopupKeySpec.insertAdditionalPopupKeys
|
||||
popupKeys[popupKeys.indexOf("!")] = ""
|
||||
popupKeys[popupKeys.indexOf("?")] = ""
|
||||
val columns = popupKeys[0].substringAfter(Key.POPUP_KEYS_AUTO_COLUMN_ORDER).toIntOrNull()
|
||||
if (columns != null)
|
||||
popupKeys[0] = "${Key.POPUP_KEYS_AUTO_COLUMN_ORDER}${columns - 1}"
|
||||
}
|
||||
return popupKeys
|
||||
}
|
||||
|
||||
private fun getSpaceLabel(): String =
|
||||
if (params.mId.mElementId <= KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
|
||||
"!icon/space_key|!code/key_space"
|
||||
else "!icon/space_key_for_number_layout|!code/key_space"
|
||||
|
||||
private fun isTablet() = context.resources.getInteger(R.integer.config_screen_metrics) >= 3
|
||||
|
||||
companion object {
|
||||
private const val TAG = "KeyboardParser"
|
||||
|
||||
|
@ -755,15 +575,15 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
}
|
||||
val layoutFileNames = context.assets.list("layouts")!!
|
||||
if (layoutFileNames.contains("$layoutName.json")) {
|
||||
return JsonKeyboardParser(params, context).parseLayoutString(context.readAssetsFile("layouts${File.separator}$layoutName.json"))
|
||||
return JsonKeyboardParser(params, context).parseLayoutString(context.readAssetsLayoutFile("$layoutName.json"))
|
||||
}
|
||||
if (layoutFileNames.contains("$layoutName.txt")) {
|
||||
return SimpleKeyboardParser(params, context).parseLayoutString(context.readAssetsFile("layouts${File.separator}$layoutName.txt"))
|
||||
return SimpleKeyboardParser(params, context).parseLayoutString(context.readAssetsLayoutFile("$layoutName.txt"))
|
||||
}
|
||||
throw IllegalStateException("can't parse layout $layoutName with id ${params.mId} and elementId ${params.mId.mElementId}")
|
||||
}
|
||||
|
||||
private fun Context.readAssetsFile(name: String) = assets.open(name).reader().readText()
|
||||
private fun Context.readAssetsLayoutFile(name: String) = assets.open("layouts${File.separator}$name").reader().readText()
|
||||
|
||||
private fun getLayoutFileName(params: KeyboardParams, context: Context, overrideElementId: Int? = null): String {
|
||||
var checkForCustom = true
|
||||
|
@ -815,10 +635,6 @@ abstract class KeyboardParser(private val params: KeyboardParams, private val co
|
|||
}
|
||||
}
|
||||
|
||||
protected enum class FunctionalKey {
|
||||
EMOJI, LANGUAGE_SWITCH, COM, EMOJI_COM, ACTION, DELETE, PERIOD, COMMA, SPACE, SHIFT, NUMPAD, SYMBOL, ALPHA, SYMBOL_ALPHA, ZWNJ
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// todo: actually this should be in some separate file
|
||||
|
|
|
@ -14,7 +14,7 @@ object KeyCode {
|
|||
|
||||
const val INTERNAL_FLORIS_MIN = -9999
|
||||
const val INTERNAL_FLORIS_MAX = -1
|
||||
val INTERNAL_FLORIS = INTERNAL_FLORIS_MIN..INTERNAL_FLORIS_MAX
|
||||
val INTERNAL_FLORIS = INTERNAL_FLORIS_MIN..INTERNAL_FLORIS_MAX // do NOT add key codes in this range
|
||||
val INTERNAL_HELI = -19999..-10000 // for keys exclusive to this app
|
||||
val CURRENCY = CURRENCY_SLOT_6..CURRENCY_SLOT_1
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ object KeyCode {
|
|||
const val CJK_SPACE = 12288
|
||||
|
||||
// heliboard only codes
|
||||
const val ALPHA_SYMBOL = -10001
|
||||
const val SYMBOL_ALPHA = -10001
|
||||
const val START_ONE_HANDED_MODE = -10002
|
||||
const val STOP_ONE_HANDED_MODE = -10003
|
||||
const val SWITCH_ONE_HANDED_MODE = -10004
|
||||
|
@ -132,8 +132,8 @@ object KeyCode {
|
|||
// Code value representing the code is not specified.
|
||||
const val NOT_SPECIFIED = -10008 // todo: not sure if there is need to have the "old" unspecified keyCode different, just test it and maybe merge
|
||||
const val CLIPBOARD_COPY_ALL = -10009
|
||||
const val PAGE_UP = -10010
|
||||
const val PAGE_DOWN = -10011
|
||||
const val PAGE_UP = -10010
|
||||
const val PAGE_DOWN = -10011
|
||||
|
||||
/** to make sure a FlorisBoard code works when reading a JSON layout */
|
||||
fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) {
|
||||
|
@ -145,8 +145,8 @@ object KeyCode {
|
|||
SHIFT, CAPS_LOCK, MULTIPLE_CODE_POINTS, UNSPECIFIED,
|
||||
|
||||
// heliboard only
|
||||
ALPHA_SYMBOL, START_ONE_HANDED_MODE, STOP_ONE_HANDED_MODE, SWITCH_ONE_HANDED_MODE, SHIFT_ENTER,
|
||||
ACTION_NEXT, ACTION_PREVIOUS, NOT_SPECIFIED
|
||||
SYMBOL_ALPHA, START_ONE_HANDED_MODE, STOP_ONE_HANDED_MODE, SWITCH_ONE_HANDED_MODE, SHIFT_ENTER,
|
||||
ACTION_NEXT, ACTION_PREVIOUS, NOT_SPECIFIED, CLIPBOARD_COPY_ALL, PAGE_UP, PAGE_DOWN
|
||||
-> this
|
||||
|
||||
// conversion
|
||||
|
@ -156,26 +156,4 @@ object KeyCode {
|
|||
|
||||
else -> throw IllegalStateException("key code $this not yet supported")
|
||||
}
|
||||
|
||||
/** to make sure a FlorisBoard label works when reading a JSON layout */
|
||||
// resulting special labels should be names of FunctionalKey enum, case insensitive
|
||||
fun String.convertFlorisLabel(): String = when (this) {
|
||||
"view_characters" -> "alpha"
|
||||
"view_symbols" -> "symbol"
|
||||
"view_numeric_advanced" -> "numpad"
|
||||
"view_phone" -> "alpha" // phone keyboard is treated like alphabet, just with different layout
|
||||
"view_phone2" -> "symbols" // phone symbols
|
||||
"ime_ui_mode_media" -> "emoji"
|
||||
"ime_ui_mode_clipboard" -> "clipboard" // todo: is this supported? when yes -> add to readme, and add a test
|
||||
"ime_ui_mode_text" -> "alpha"
|
||||
"currency_slot_1" -> "$$$"
|
||||
"currency_slot_2" -> "$$$1"
|
||||
"currency_slot_3" -> "$$$2"
|
||||
"currency_slot_4" -> "$$$3"
|
||||
"currency_slot_5" -> "$$$4"
|
||||
"currency_slot_6" -> "$$$5"
|
||||
"enter" -> "action"
|
||||
"half_space" -> "zwnj"
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package helium314.keyboard.keyboard.internal.keyboard_parser.floris
|
||||
|
||||
/** labels for functional / special keys */
|
||||
object KeyLabel {
|
||||
const val EMOJI = "emoji"
|
||||
const val COM = "com"
|
||||
const val LANGUAGE_SWITCH = "language_switch"
|
||||
const val ACTION = "action"
|
||||
const val DELETE = "delete"
|
||||
const val SHIFT = "shift"
|
||||
const val NUMPAD = "numpad"
|
||||
const val SYMBOL = "symbol"
|
||||
const val ALPHA = "alpha"
|
||||
const val SYMBOL_ALPHA = "symbol_alpha"
|
||||
const val PERIOD = "period"
|
||||
const val COMMA = "comma"
|
||||
const val SPACE = "space"
|
||||
const val ZWNJ = "zwnj"
|
||||
const val CURRENCY = "$$$"
|
||||
const val CURRENCY1 = "$$$1"
|
||||
const val CURRENCY2 = "$$$2"
|
||||
const val CURRENCY3 = "$$$3"
|
||||
const val CURRENCY4 = "$$$4"
|
||||
const val CURRENCY5 = "$$$5"
|
||||
|
||||
/** to make sure a FlorisBoard label works when reading a JSON layout */
|
||||
// resulting special labels should be names of FunctionalKey enum, case insensitive
|
||||
fun String.convertFlorisLabel(): String = when (this) {
|
||||
"view_characters" -> ALPHA
|
||||
"view_symbols" -> SYMBOL
|
||||
"view_numeric_advanced" -> NUMPAD
|
||||
"view_phone" -> ALPHA // phone keyboard is treated like alphabet, just with different layout
|
||||
"view_phone2" -> SYMBOL // phone symbols
|
||||
"ime_ui_mode_media" -> EMOJI
|
||||
"ime_ui_mode_clipboard" -> "clipboard" // todo: is this supported? when yes -> add to readme, and add a test
|
||||
"ime_ui_mode_text" -> ALPHA
|
||||
"currency_slot_1" -> CURRENCY
|
||||
"currency_slot_2" -> CURRENCY1
|
||||
"currency_slot_3" -> CURRENCY2
|
||||
"currency_slot_4" -> CURRENCY3
|
||||
"currency_slot_5" -> CURRENCY4
|
||||
"currency_slot_6" -> CURRENCY5
|
||||
"enter" -> ACTION
|
||||
"half_space" -> ZWNJ
|
||||
else -> this
|
||||
}
|
||||
|
||||
}
|
|
@ -21,18 +21,16 @@ import kotlinx.serialization.encoding.Encoder
|
|||
*/
|
||||
@Serializable(with = KeyTypeSerializer::class)
|
||||
enum class KeyType {
|
||||
// todo: implement the effect on background
|
||||
// also, how to get that specific space bar background?
|
||||
CHARACTER, // default
|
||||
ENTER_EDITING, // enter/insert/delete, gets functional key background (if not action key)
|
||||
ENTER_EDITING, // should be enter/insert/delete, but always gets action key background
|
||||
FUNCTION, // f1..., gets functional key background
|
||||
LOCK, // scroll lock, num lock, caps lock, gets functional key background
|
||||
LOCK, // scroll lock, num lock, caps lock, gets sticky on/off background, which currently is the same as functional background
|
||||
MODIFIER, // alt, ctrl, shift, gets functional key background
|
||||
NAVIGATION, // home, page up, page down, tab, arrows, geta default background
|
||||
SYSTEM_GUI, // esc, print, pause, meta, (keyboard layout switch), geta functional background
|
||||
NUMERIC, // numpad keys, get larger letter and larger width
|
||||
PLACEHOLDER, // other keys go here, e.g. in shift, placeholder, delete the placeholder gets (typically) replaced by the bottom keyboard row
|
||||
UNSPECIFIED; // treated like default
|
||||
NAVIGATION, // home, page up, page down, tab, arrows, gets space background because it'S still the most suitable type
|
||||
SYSTEM_GUI, // esc, print, pause, meta, (keyboard layout switch), gets functional background
|
||||
NUMERIC, // numpad keys, get larger letter and larger width in number layouts, and default background
|
||||
PLACEHOLDER, // spacer, or actual placeholder when used in functional key layouts
|
||||
UNSPECIFIED; // empty background
|
||||
|
||||
override fun toString(): String {
|
||||
return super.toString().lowercase()
|
||||
|
@ -40,7 +38,13 @@ enum class KeyType {
|
|||
|
||||
companion object {
|
||||
fun fromString(string: String): KeyType {
|
||||
return valueOf(string.uppercase())
|
||||
// resolve alternative names
|
||||
return when (string) {
|
||||
"space" -> NAVIGATION
|
||||
"action" -> ENTER_EDITING
|
||||
"shift" -> LOCK
|
||||
else -> valueOf(string.uppercase())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,30 +6,49 @@
|
|||
package helium314.keyboard.keyboard.internal.keyboard_parser.floris
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import helium314.keyboard.keyboard.internal.KeySpecParser
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.latin.utils.addCollections
|
||||
|
||||
// only the constructor and name remain from FlorisBoard
|
||||
// we don't care about the difference between main and relevant (at least for now)
|
||||
@Serializable
|
||||
open class PopupSet<T : AbstractKeyData>(
|
||||
open val main: T? = null,
|
||||
open val relevant: List<T>? = null
|
||||
open val relevant: Collection<T>? = null
|
||||
) {
|
||||
// get labels of all popup keys
|
||||
open fun getPopupKeyLabels(params: KeyboardParams): Collection<String>? {
|
||||
if (main == null && relevant == null) return null
|
||||
val popupKeys = mutableListOf<String>()
|
||||
main?.getPopupLabel(params)?.let { popupKeys.add(it) }
|
||||
relevant?.let { popupKeys.addAll(it.map { it.getPopupLabel(params) }) }
|
||||
main?.compute(params)?.getPopupLabel(params)?.let { popupKeys.add(it) }
|
||||
relevant?.let { popupKeys.addAll(it.mapNotNull { it.compute(params)?.getPopupLabel(params) }) }
|
||||
if (popupKeys.isEmpty()) return null
|
||||
return popupKeys
|
||||
}
|
||||
open fun isEmpty(): Boolean = main == null && relevant.isNullOrEmpty()
|
||||
|
||||
var numberIndex: Int? = null
|
||||
var symbol: String? = null // maybe list of keys?
|
||||
|
||||
fun <U : AbstractKeyData> merge(other: PopupSet<U>?): PopupSet<out AbstractKeyData> {
|
||||
if (other == null || other.isEmpty()) return this
|
||||
if (this.isEmpty()) return other
|
||||
if (this is SimplePopups) {
|
||||
if (other is SimplePopups)
|
||||
return SimplePopups(addCollections(popupKeys, other.popupKeys))
|
||||
return PopupSet(other.main, addCollections(popupKeys?.map { it.toTextKey() }, other.relevant))
|
||||
} else if (other is SimplePopups) {
|
||||
return PopupSet(main, addCollections(relevant, other.popupKeys?.map { it.toTextKey() }))
|
||||
}
|
||||
val newMain = if (main == null) other.main else main
|
||||
val newRelevant = addCollections(relevant, other.relevant)
|
||||
if (main != null && other.main != null)
|
||||
return PopupSet(newMain, addCollections(listOf(other.main!!), newRelevant))
|
||||
return PopupSet(newMain, newRelevant)
|
||||
}
|
||||
}
|
||||
|
||||
class SimplePopups(val popupKeys: Collection<String>?) : PopupSet<AbstractKeyData>() {
|
||||
override fun getPopupKeyLabels(params: KeyboardParams) = popupKeys
|
||||
override fun isEmpty(): Boolean = popupKeys.isNullOrEmpty()
|
||||
}
|
||||
|
|
|
@ -9,12 +9,16 @@ import kotlinx.serialization.SerialName
|
|||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import helium314.keyboard.keyboard.Key
|
||||
import helium314.keyboard.keyboard.KeyboardId
|
||||
import helium314.keyboard.keyboard.KeyboardTheme
|
||||
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
|
||||
import helium314.keyboard.keyboard.internal.KeyboardParams
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.convertFlorisLabel
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyLabel.convertFlorisLabel
|
||||
import helium314.keyboard.keyboard.internal.keyboard_parser.rtlLabel
|
||||
import helium314.keyboard.latin.common.Constants
|
||||
import helium314.keyboard.latin.common.StringUtils
|
||||
import helium314.keyboard.latin.settings.Settings
|
||||
|
||||
// taken from FlorisBoard, small modifications (see also KeyData)
|
||||
// internal keys removed (currently no plan to support them)
|
||||
|
@ -32,13 +36,17 @@ import helium314.keyboard.latin.common.StringUtils
|
|||
* @property popup The popups for ths key. Can also dynamically be provided via popup extensions.
|
||||
*/
|
||||
sealed interface KeyData : AbstractKeyData {
|
||||
val type: KeyType
|
||||
val type: KeyType?
|
||||
val code: Int
|
||||
val label: String
|
||||
val groupId: Int
|
||||
val popup: PopupSet<AbstractKeyData> // not nullable because can't add number otherwise
|
||||
val popup: PopupSet<out AbstractKeyData> // not nullable because can't add number otherwise
|
||||
val width: Float // in percent of keyboard width, 0 is default (depends on key), -1 is fill (like space bar)
|
||||
val labelFlags: Int
|
||||
|
||||
fun copy(newType: KeyType? = type, newCode: Int = code, newLabel: String = label, newGroupId: Int = groupId,
|
||||
newPopup: PopupSet<out AbstractKeyData> = popup, newWidth: Float = width, newLabelFlags: Int = labelFlags): KeyData
|
||||
|
||||
// groups (currently) not supported
|
||||
companion object {
|
||||
/**
|
||||
|
@ -49,15 +57,15 @@ sealed interface KeyData : AbstractKeyData {
|
|||
|
||||
/**
|
||||
* Constant for the Left modifier key group. Any key belonging to this group will get the
|
||||
* popups specified for "~left" in the popup mapping.
|
||||
* popups specified for the comma key.
|
||||
*/
|
||||
const val GROUP_LEFT: Int = 1
|
||||
const val GROUP_COMMA: Int = 1
|
||||
|
||||
/**
|
||||
* Constant for the right modifier key group. Any key belonging to this group will get the
|
||||
* popups specified for "~right" in the popup mapping.
|
||||
* popups specified for the period key.
|
||||
*/
|
||||
const val GROUP_RIGHT: Int = 2
|
||||
const val GROUP_PERIOD: Int = 2
|
||||
|
||||
/**
|
||||
* Constant for the enter modifier key group. Any key belonging to this group will get the
|
||||
|
@ -70,100 +78,276 @@ sealed interface KeyData : AbstractKeyData {
|
|||
* popups specified for "~kana" in the popup mapping.
|
||||
*/
|
||||
const val GROUP_KANA: Int = 97
|
||||
|
||||
private fun getShiftLabel(params: KeyboardParams) = when (params.mId.mElementId) {
|
||||
KeyboardId.ELEMENT_SYMBOLS_SHIFTED -> params.mLocaleKeyboardInfos.labelSymbol
|
||||
KeyboardId.ELEMENT_SYMBOLS -> params.mLocaleKeyboardInfos.getShiftSymbolLabel(Settings.getInstance().isTablet)
|
||||
KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED,
|
||||
KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED, KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED -> "!icon/${KeyboardIconsSet.NAME_SHIFT_KEY_SHIFTED}"
|
||||
else -> "!icon/${KeyboardIconsSet.NAME_SHIFT_KEY}"
|
||||
}
|
||||
|
||||
// todo (later): try avoiding this weirdness
|
||||
// maybe just remove it and if users want it they can use custom functional layouts?
|
||||
// but it has been like this "forever" and actually seems to make sense
|
||||
private fun getPeriodLabel(params: KeyboardParams): String {
|
||||
if (params.mId.isNumberLayout) return "."
|
||||
if (params.mId.isAlphabetKeyboard || params.mId.locale.language in listOf("ar", "fa"))
|
||||
return params.mLocaleKeyboardInfos.labelPeriod
|
||||
return "."
|
||||
}
|
||||
|
||||
private fun getSpaceLabel(params: KeyboardParams): String =
|
||||
if (params.mId.mElementId <= KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
|
||||
"!icon/space_key|!code/key_space"
|
||||
else "!icon/space_key_for_number_layout|!code/key_space"
|
||||
|
||||
private fun getCommaPopupKeys(params: KeyboardParams): List<String> {
|
||||
val keys = mutableListOf<String>()
|
||||
if (!params.mId.mDeviceLocked)
|
||||
keys.add("!icon/clipboard_normal_key|!code/key_clipboard")
|
||||
if (!params.mId.mEmojiKeyEnabled && !params.mId.isNumberLayout)
|
||||
keys.add("!icon/emoji_normal_key|!code/key_emoji")
|
||||
if (!params.mId.mLanguageSwitchKeyEnabled)
|
||||
keys.add("!icon/language_switch_key|!code/key_language_switch")
|
||||
if (!params.mId.mOneHandedModeEnabled)
|
||||
keys.add("!icon/start_onehanded_mode_key|!code/key_start_onehanded")
|
||||
if (!params.mId.mDeviceLocked)
|
||||
keys.add("!icon/settings_key|!code/key_settings")
|
||||
return keys
|
||||
}
|
||||
|
||||
private fun getPunctuationPopupKeys(params: KeyboardParams): List<String> {
|
||||
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS || params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
|
||||
return listOf("…")
|
||||
if (params.mId.isNumberLayout)
|
||||
return listOf(":", "…", ";", "∞", "π", "√", "°", "^")
|
||||
val popupKeys = params.mLocaleKeyboardInfos.getPopupKeys("punctuation")!!.toMutableList()
|
||||
if (params.mId.mSubtype.isRtlSubtype) {
|
||||
for (i in popupKeys.indices)
|
||||
popupKeys[i] = popupKeys[i].rtlLabel(params) // for parentheses
|
||||
}
|
||||
if (Settings.getInstance().isTablet && popupKeys.contains("!") && popupKeys.contains("?")) {
|
||||
// remove ! and ? keys and reduce number in autoColumnOrder
|
||||
// this makes use of removal of empty popupKeys in PopupKeySpec.insertAdditionalPopupKeys
|
||||
popupKeys[popupKeys.indexOf("!")] = ""
|
||||
popupKeys[popupKeys.indexOf("?")] = ""
|
||||
val columns = popupKeys[0].substringAfter(Key.POPUP_KEYS_AUTO_COLUMN_ORDER).toIntOrNull()
|
||||
if (columns != null)
|
||||
popupKeys[0] = "${Key.POPUP_KEYS_AUTO_COLUMN_ORDER}${columns - 1}"
|
||||
}
|
||||
return popupKeys
|
||||
}
|
||||
}
|
||||
|
||||
// make it non-nullable for simplicity, and to reflect current implementations
|
||||
override fun compute(params: KeyboardParams): KeyData {
|
||||
require(groupId <= GROUP_ENTER) { "only groups up to GROUP_ENTER are supported" }
|
||||
require(label.isNotEmpty() || type == KeyType.PLACEHOLDER || code != KeyCode.UNSPECIFIED) { "non-placeholder key has no code and no label" }
|
||||
val newLabel = label.convertFlorisLabel()
|
||||
val newCode = code.checkAndConvertCode()
|
||||
|
||||
// resolve currency keys
|
||||
if (newLabel.startsWith("$$$") || newCode in KeyCode.Spec.CURRENCY) {
|
||||
val currencyKey = params.mLocaleKeyboardInfos.currencyKey
|
||||
val currencyCodeAsString = if (newCode in KeyCode.Spec.CURRENCY) {
|
||||
when (newCode) {
|
||||
KeyCode.CURRENCY_SLOT_1 -> "|" + currencyKey.first
|
||||
KeyCode.CURRENCY_SLOT_2 -> "|" + currencyKey.second[0]
|
||||
KeyCode.CURRENCY_SLOT_3 -> "|" + currencyKey.second[1]
|
||||
KeyCode.CURRENCY_SLOT_4 -> "|" + currencyKey.second[2]
|
||||
KeyCode.CURRENCY_SLOT_5 -> "|" + currencyKey.second[3]
|
||||
KeyCode.CURRENCY_SLOT_6 -> "|" + currencyKey.second[4]
|
||||
else -> ""
|
||||
}
|
||||
} else ""
|
||||
if (newLabel == "$$$") {
|
||||
val finalLabel = currencyKey.first + currencyCodeAsString
|
||||
// the flag is to match old parser, but why is it there for main currency key and not for others?
|
||||
return TextKeyData(type, KeyCode.UNSPECIFIED, finalLabel, groupId, SimplePopups(currencyKey.second), labelFlags or Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO)
|
||||
}
|
||||
val n = newLabel.substringAfter("$$$").toIntOrNull()
|
||||
if (n != null && n <= 5 && n > 0) {
|
||||
val finalLabel = currencyKey.second[n - 1] + currencyCodeAsString
|
||||
return TextKeyData(type, KeyCode.UNSPECIFIED, finalLabel, groupId, popup, labelFlags)
|
||||
}
|
||||
}
|
||||
if (newCode != code || newLabel != label)
|
||||
return TextKeyData(type, newCode, newLabel, groupId, popup, labelFlags).compute(params)
|
||||
return copy(newCode = newCode, newLabel = newLabel)
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
fun isSpaceKey(): Boolean {
|
||||
return type == KeyType.CHARACTER && (code == Constants.CODE_SPACE || code == KeyCode.CJK_SPACE
|
||||
|| code == KeyCode.ZWNJ || code == KeyCode.KESHIDA)
|
||||
return code == Constants.CODE_SPACE || code == KeyCode.CJK_SPACE || code == KeyCode.ZWNJ || code == KeyCode.KESHIDA
|
||||
}
|
||||
|
||||
fun toKeyParams(params: KeyboardParams, width: Float = params.mDefaultRelativeKeyWidth, additionalLabelFlags: Int = 0): Key.KeyParams {
|
||||
// todo: remove checks here, do only when reading json layouts
|
||||
// numeric keys are assigned a higher width in number layouts
|
||||
require(type == KeyType.CHARACTER || type == KeyType.NUMERIC) { "only KeyType CHARACTER or NUMERIC is supported" }
|
||||
// allow GROUP_ENTER negative codes so original florisboard number layouts can be used, bu actually it's ignored
|
||||
require(groupId == GROUP_DEFAULT || groupId == GROUP_ENTER) { "currently only GROUP_DEFAULT or GROUP_ENTER is supported" }
|
||||
require(code != KeyCode.UNSPECIFIED || label.isNotEmpty()) { "key has no code and no label" }
|
||||
fun isKeyPlaceholder() = type == KeyType.PLACEHOLDER && code == KeyCode.UNSPECIFIED && width == 0f
|
||||
|
||||
return if (code == KeyCode.UNSPECIFIED || code == KeyCode.MULTIPLE_CODE_POINTS) {
|
||||
/** this expects that codes and labels are already converted from FlorisBoard values, usually through compute */
|
||||
fun toKeyParams(params: KeyboardParams, additionalLabelFlags: Int = 0): Key.KeyParams {
|
||||
if (type == KeyType.PLACEHOLDER) return Key.KeyParams.newSpacer(params, width)
|
||||
|
||||
val newWidth = if (width == 0f) getDefaultWidth(params) else width
|
||||
val newCode: Int
|
||||
val newLabel: String
|
||||
if (code in KeyCode.Spec.CURRENCY) {
|
||||
// special treatment necessary, because we may need to encode it in the label
|
||||
// (currency is a string, so might have more than 1 codepoint)
|
||||
newCode = 0
|
||||
val l = processLabel(params)
|
||||
newLabel = when (code) {
|
||||
// consider currency codes for label
|
||||
KeyCode.CURRENCY_SLOT_1 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.first}"
|
||||
KeyCode.CURRENCY_SLOT_2 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[0]}"
|
||||
KeyCode.CURRENCY_SLOT_3 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[1]}"
|
||||
KeyCode.CURRENCY_SLOT_4 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[2]}"
|
||||
KeyCode.CURRENCY_SLOT_5 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[3]}"
|
||||
KeyCode.CURRENCY_SLOT_6 -> "$l|${params.mLocaleKeyboardInfos.currencyKey.second[4]}"
|
||||
else -> throw IllegalStateException("code in currency range, but not in currency range?")
|
||||
}
|
||||
} else {
|
||||
newCode = processCode()
|
||||
newLabel = processLabel(params)
|
||||
}
|
||||
val newLabelFlags = labelFlags or additionalLabelFlags or getAdditionalLabelFlags(params)
|
||||
val newPopupKeys = popup.merge(getAdditionalPopupKeys(params))
|
||||
|
||||
val background = when (type) {
|
||||
KeyType.CHARACTER, KeyType.NUMERIC -> Key.BACKGROUND_TYPE_NORMAL
|
||||
KeyType.FUNCTION, KeyType.MODIFIER, KeyType.SYSTEM_GUI -> Key.BACKGROUND_TYPE_FUNCTIONAL
|
||||
KeyType.PLACEHOLDER, KeyType.UNSPECIFIED -> Key.BACKGROUND_TYPE_EMPTY
|
||||
KeyType.NAVIGATION -> Key.BACKGROUND_TYPE_SPACEBAR
|
||||
KeyType.ENTER_EDITING -> Key.BACKGROUND_TYPE_ACTION
|
||||
KeyType.LOCK -> getShiftBackground(params)
|
||||
null -> getDefaultBackground(params)
|
||||
}
|
||||
|
||||
return if (newCode == KeyCode.UNSPECIFIED || newCode == KeyCode.MULTIPLE_CODE_POINTS) {
|
||||
// code will be determined from label if possible (i.e. label is single code point)
|
||||
// but also longer labels should work without issues, also for MultiTextKeyData
|
||||
if (this is MultiTextKeyData) {
|
||||
val outputText = String(codePoints, 0, codePoints.size)
|
||||
Key.KeyParams(
|
||||
"$label|$outputText",
|
||||
code,
|
||||
"$newLabel|$outputText",
|
||||
newCode,
|
||||
params,
|
||||
width,
|
||||
labelFlags or additionalLabelFlags,
|
||||
Key.BACKGROUND_TYPE_NORMAL, // todo (when supported): determine type
|
||||
popup,
|
||||
newWidth,
|
||||
newLabelFlags,
|
||||
background,
|
||||
newPopupKeys,
|
||||
)
|
||||
} else {
|
||||
Key.KeyParams(
|
||||
label.rtlLabel(params), // todo (when supported): convert special labels to keySpec
|
||||
newLabel.rtlLabel(params), // todo (when supported): convert special labels to keySpec
|
||||
params,
|
||||
width,
|
||||
labelFlags or additionalLabelFlags,
|
||||
Key.BACKGROUND_TYPE_NORMAL, // todo (when supported): determine type
|
||||
popup,
|
||||
newWidth,
|
||||
newLabelFlags,
|
||||
background,
|
||||
newPopupKeys,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Key.KeyParams(
|
||||
label.ifEmpty { StringUtils.newSingleCodePointString(code) },
|
||||
code,
|
||||
newLabel.ifEmpty { StringUtils.newSingleCodePointString(newCode) },
|
||||
newCode,
|
||||
params,
|
||||
width,
|
||||
labelFlags or additionalLabelFlags,
|
||||
Key.BACKGROUND_TYPE_NORMAL,
|
||||
popup,
|
||||
newWidth,
|
||||
newLabelFlags,
|
||||
background,
|
||||
newPopupKeys,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultBackground(params: KeyboardParams): Int {
|
||||
// functional keys
|
||||
when (label) { // or use code?
|
||||
KeyLabel.SYMBOL_ALPHA, KeyLabel.SYMBOL, KeyLabel.ALPHA, KeyLabel.COMMA, KeyLabel.PERIOD, KeyLabel.DELETE,
|
||||
KeyLabel.EMOJI, KeyLabel.COM, KeyLabel.LANGUAGE_SWITCH, KeyLabel.NUMPAD -> return Key.BACKGROUND_TYPE_FUNCTIONAL
|
||||
KeyLabel.SPACE, KeyLabel.ZWNJ -> return Key.BACKGROUND_TYPE_SPACEBAR
|
||||
KeyLabel.ACTION -> return Key.BACKGROUND_TYPE_ACTION
|
||||
KeyLabel.SHIFT -> return getShiftBackground(params)
|
||||
}
|
||||
if (type == KeyType.PLACEHOLDER) return Key.BACKGROUND_TYPE_EMPTY
|
||||
return Key.BACKGROUND_TYPE_NORMAL
|
||||
}
|
||||
|
||||
// todo (later): possibly the whole stickyOn/Off stuff can be removed, currently it should only have a very slight effect in holo
|
||||
// but iirc there is some attempt in reviving the sticky thing, right?
|
||||
private fun getShiftBackground(params: KeyboardParams): Int {
|
||||
return if (params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED
|
||||
|| params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) Key.BACKGROUND_TYPE_STICKY_ON
|
||||
else Key.BACKGROUND_TYPE_STICKY_OFF
|
||||
}
|
||||
|
||||
private fun getDefaultWidth(params: KeyboardParams): Float {
|
||||
return if (label == KeyLabel.SPACE && params.mId.isAlphaOrSymbolKeyboard) -1f
|
||||
else if (type == KeyType.NUMERIC && params.mId.isNumberLayout) 0.17f // todo (later) consider making this -1?
|
||||
else params.mDefaultKeyWidth
|
||||
}
|
||||
|
||||
// todo (later): encoding the code in the label should be avoided, because we know it already
|
||||
private fun processLabel(params: KeyboardParams): String = when (label) {
|
||||
KeyLabel.SYMBOL_ALPHA -> if (params.mId.isAlphabetKeyboard) params.mLocaleKeyboardInfos.labelSymbol else params.mLocaleKeyboardInfos.labelAlphabet
|
||||
KeyLabel.SYMBOL -> params.mLocaleKeyboardInfos.labelSymbol
|
||||
KeyLabel.ALPHA -> params.mLocaleKeyboardInfos.labelAlphabet
|
||||
KeyLabel.COMMA -> params.mLocaleKeyboardInfos.labelComma
|
||||
KeyLabel.PERIOD -> getPeriodLabel(params)
|
||||
KeyLabel.SPACE -> getSpaceLabel(params)
|
||||
// KeyLabel.ACTION -> "${getActionKeyLabel(params)}|${getActionKeyCode(params)}" would need context
|
||||
KeyLabel.DELETE -> "!icon/delete_key|!code/key_delete"
|
||||
KeyLabel.SHIFT -> "${getShiftLabel(params)}|!code/key_shift"
|
||||
KeyLabel.EMOJI -> "!icon/emoji_normal_key|!code/key_emoji"
|
||||
// todo (later): label and popupKeys for .com should be in localeKeyTexts, handled similar to currency key
|
||||
KeyLabel.COM -> ".com"
|
||||
KeyLabel.LANGUAGE_SWITCH -> "!icon/language_switch_key|!code/key_language_switch"
|
||||
KeyLabel.NUMPAD -> "!icon/numpad_key|!code/key_numpad"
|
||||
KeyLabel.ZWNJ -> "!icon/zwnj_key|\u200C"
|
||||
KeyLabel.CURRENCY -> params.mLocaleKeyboardInfos.currencyKey.first
|
||||
KeyLabel.CURRENCY1 -> params.mLocaleKeyboardInfos.currencyKey.second[0]
|
||||
KeyLabel.CURRENCY2 -> params.mLocaleKeyboardInfos.currencyKey.second[1]
|
||||
KeyLabel.CURRENCY3 -> params.mLocaleKeyboardInfos.currencyKey.second[2]
|
||||
KeyLabel.CURRENCY4 -> params.mLocaleKeyboardInfos.currencyKey.second[3]
|
||||
KeyLabel.CURRENCY5 -> params.mLocaleKeyboardInfos.currencyKey.second[4]
|
||||
else -> label
|
||||
}
|
||||
|
||||
private fun processCode(): Int {
|
||||
if (code != KeyCode.UNSPECIFIED) return code
|
||||
return when (label) {
|
||||
KeyLabel.SYMBOL_ALPHA -> KeyCode.SYMBOL_ALPHA
|
||||
KeyLabel.SYMBOL -> KeyCode.SYMBOL
|
||||
KeyLabel.ALPHA -> KeyCode.ALPHA
|
||||
else -> code
|
||||
}
|
||||
}
|
||||
|
||||
// todo (later): add explanations / reasoning, often this is just taken from conversion from AOSP layouts
|
||||
private fun getAdditionalLabelFlags(params: KeyboardParams): Int {
|
||||
return when (label) {
|
||||
KeyLabel.ALPHA, KeyLabel.SYMBOL_ALPHA, KeyLabel.SYMBOL -> Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR
|
||||
KeyLabel.PERIOD, KeyLabel.COMMA -> Key.LABEL_FLAGS_HAS_POPUP_HINT // todo: period also has defaultLabelFlags -> when is this relevant?
|
||||
KeyLabel.ACTION -> {
|
||||
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_AUTO_X_SCALE or
|
||||
Key.LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR or
|
||||
Key.LABEL_FLAGS_HAS_POPUP_HINT or KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId)
|
||||
}
|
||||
KeyLabel.SPACE -> if (params.mId.isNumberLayout) Key.LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM else 0
|
||||
KeyLabel.SHIFT -> Key.LABEL_FLAGS_PRESERVE_CASE or if (!params.mId.isAlphabetKeyboard) Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR else 0
|
||||
KeyLabel.EMOJI -> KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId)
|
||||
KeyLabel.COM -> Key.LABEL_FLAGS_AUTO_X_SCALE or Key.LABEL_FLAGS_FONT_NORMAL or Key.LABEL_FLAGS_HAS_POPUP_HINT or Key.LABEL_FLAGS_PRESERVE_CASE
|
||||
KeyLabel.ZWNJ -> Key.LABEL_FLAGS_HAS_POPUP_HINT
|
||||
KeyLabel.CURRENCY -> Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAdditionalPopupKeys(params: KeyboardParams): PopupSet<AbstractKeyData>? {
|
||||
if (groupId == GROUP_COMMA) return SimplePopups(getCommaPopupKeys(params))
|
||||
if (groupId == GROUP_PERIOD) return SimplePopups(getPunctuationPopupKeys(params))
|
||||
// if (groupId == GROUP_ENTER) return getActionKeyPopupKeys(params)?.let { SimplePopups(it) }
|
||||
return when (label) {
|
||||
KeyLabel.COMMA -> SimplePopups(getCommaPopupKeys(params))
|
||||
KeyLabel.PERIOD -> SimplePopups(getPunctuationPopupKeys(params))
|
||||
// KeyLabel.ACTION -> getActionKeyPopupKeys(params)?.let { SimplePopups(it) }
|
||||
KeyLabel.SHIFT -> {
|
||||
if (params.mId.isAlphabetKeyboard) SimplePopups(
|
||||
listOf(
|
||||
"!noPanelAutoPopupKey!",
|
||||
" |!code/key_capslock"
|
||||
)
|
||||
) else null // why the alphabet popup keys actually?
|
||||
}
|
||||
KeyLabel.COM -> SimplePopups(listOf(Key.POPUP_KEYS_HAS_LABELS, ".net", ".org", ".gov", ".edu"))
|
||||
KeyLabel.ZWNJ -> SimplePopups(listOf("!icon/zwj_key|\u200D"))
|
||||
// only add currency popups if there are none defined on the key
|
||||
KeyLabel.CURRENCY -> if (popup.isEmpty()) SimplePopups(params.mLocaleKeyboardInfos.currencyKey.second) else null
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class which describes a single key and its attributes.
|
||||
*
|
||||
* @property type The type of the key. Some actions require both [code] and [type] to match in order
|
||||
* to be successfully executed. Defaults to [KeyType.CHARACTER].
|
||||
* to be successfully executed. Defaults to null.
|
||||
* @property code The UTF-8 encoded code of the character. The code defined here is used as the
|
||||
* data passed to the system. Defaults to 0.
|
||||
* @property label The string used to display the key in the UI. Is not used for the actual data
|
||||
|
@ -173,11 +357,12 @@ sealed interface KeyData : AbstractKeyData {
|
|||
@Serializable
|
||||
@SerialName("text_key")
|
||||
class TextKeyData(
|
||||
override val type: KeyType = KeyType.CHARACTER,
|
||||
override val type: KeyType? = null,
|
||||
override val code: Int = KeyCode.UNSPECIFIED,
|
||||
override val label: String = "",
|
||||
override val groupId: Int = KeyData.GROUP_DEFAULT,
|
||||
override val popup: PopupSet<AbstractKeyData> = PopupSet(),
|
||||
override val popup: PopupSet<out AbstractKeyData> = SimplePopups(null),
|
||||
override val width: Float = 0f,
|
||||
override val labelFlags: Int = 0
|
||||
) : KeyData {
|
||||
override fun asString(isForDisplay: Boolean): String {
|
||||
|
@ -197,6 +382,16 @@ class TextKeyData(
|
|||
return "${TextKeyData::class.simpleName} { type=$type code=$code label=\"$label\" groupId=$groupId }"
|
||||
}
|
||||
|
||||
override fun copy(
|
||||
newType: KeyType?,
|
||||
newCode: Int,
|
||||
newLabel: String,
|
||||
newGroupId: Int,
|
||||
newPopup: PopupSet<out AbstractKeyData>,
|
||||
newWidth: Float,
|
||||
newLabelFlags: Int
|
||||
) = TextKeyData(newType, newCode, newLabel, newGroupId, newPopup, newWidth, newLabelFlags)
|
||||
|
||||
}
|
||||
|
||||
// AutoTextKeyData is just for converting case with shift, which HeliBoard always does anyway
|
||||
|
@ -204,11 +399,12 @@ class TextKeyData(
|
|||
@Serializable
|
||||
@SerialName("auto_text_key")
|
||||
class AutoTextKeyData(
|
||||
override val type: KeyType = KeyType.CHARACTER,
|
||||
override val type: KeyType? = null,
|
||||
override val code: Int = KeyCode.UNSPECIFIED,
|
||||
override val label: String = "",
|
||||
override val groupId: Int = KeyData.GROUP_DEFAULT,
|
||||
override val popup: PopupSet<AbstractKeyData> = PopupSet(),
|
||||
override val popup: PopupSet<out AbstractKeyData> = SimplePopups(null),
|
||||
override val width: Float = 0f,
|
||||
override val labelFlags: Int = 0
|
||||
) : KeyData {
|
||||
|
||||
|
@ -228,16 +424,28 @@ class AutoTextKeyData(
|
|||
override fun toString(): String {
|
||||
return "${AutoTextKeyData::class.simpleName} { type=$type code=$code label=\"$label\" groupId=$groupId }"
|
||||
}
|
||||
|
||||
override fun copy(
|
||||
newType: KeyType?,
|
||||
newCode: Int,
|
||||
newLabel: String,
|
||||
newGroupId: Int,
|
||||
newPopup: PopupSet<out AbstractKeyData>,
|
||||
newWidth: Float,
|
||||
newLabelFlags: Int
|
||||
) = AutoTextKeyData(newType, newCode, newLabel, newGroupId, newPopup, newWidth, newLabelFlags)
|
||||
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("multi_text_key")
|
||||
class MultiTextKeyData(
|
||||
override val type: KeyType = KeyType.CHARACTER,
|
||||
override val type: KeyType? = null,
|
||||
val codePoints: IntArray = intArrayOf(),
|
||||
override val label: String = "",
|
||||
override val groupId: Int = KeyData.GROUP_DEFAULT,
|
||||
override val popup: PopupSet<AbstractKeyData> = PopupSet(),
|
||||
override val popup: PopupSet<out AbstractKeyData> = SimplePopups(null),
|
||||
override val width: Float = 0f,
|
||||
override val labelFlags: Int = 0
|
||||
) : KeyData {
|
||||
@Transient override val code: Int = KeyCode.MULTIPLE_CODE_POINTS
|
||||
|
@ -263,6 +471,17 @@ class MultiTextKeyData(
|
|||
override fun toString(): String {
|
||||
return "${MultiTextKeyData::class.simpleName} { type=$type code=$code label=\"$label\" groupId=$groupId }"
|
||||
}
|
||||
|
||||
override fun copy(
|
||||
newType: KeyType?,
|
||||
newCode: Int,
|
||||
newLabel: String,
|
||||
newGroupId: Int,
|
||||
newPopup: PopupSet<out AbstractKeyData>,
|
||||
newWidth: Float,
|
||||
newLabelFlags: Int
|
||||
) = MultiTextKeyData(newType, codePoints, newLabel, newGroupId, newPopup, newWidth, newLabelFlags)
|
||||
|
||||
}
|
||||
|
||||
fun String.toTextKey(popupKeys: Collection<String>? = null, labelFlags: Int = 0): TextKeyData =
|
||||
|
|
|
@ -201,7 +201,7 @@ public final class Constants {
|
|||
switch (code) {
|
||||
case KeyCode.SHIFT: return "shift";
|
||||
case KeyCode.CAPS_LOCK: return "capslock";
|
||||
case KeyCode.ALPHA_SYMBOL: return "alpha_symbol";
|
||||
case KeyCode.SYMBOL_ALPHA: return "symbol_alpha";
|
||||
case KeyCode.ALPHA: return "alpha";
|
||||
case KeyCode.SYMBOL: return "symbol";
|
||||
case KeyCode.MULTIPLE_CODE_POINTS: return "text";
|
||||
|
|
|
@ -490,7 +490,7 @@ public final class InputLogic {
|
|||
}
|
||||
if (!inputTransaction.didAutoCorrect() && processedEvent.getMKeyCode() != KeyCode.SHIFT
|
||||
&& processedEvent.getMKeyCode() != KeyCode.CAPS_LOCK
|
||||
&& processedEvent.getMKeyCode() != KeyCode.ALPHA_SYMBOL
|
||||
&& processedEvent.getMKeyCode() != KeyCode.SYMBOL_ALPHA
|
||||
&& processedEvent.getMKeyCode() != KeyCode.ALPHA
|
||||
&& processedEvent.getMKeyCode() != KeyCode.SYMBOL)
|
||||
mLastComposedWord.deactivate();
|
||||
|
@ -775,7 +775,7 @@ public final class InputLogic {
|
|||
// We need to switch to the shortcut IME. This is handled by LatinIME since the
|
||||
// input logic has no business with IME switching.
|
||||
case KeyCode.CAPS_LOCK:
|
||||
case KeyCode.ALPHA_SYMBOL:
|
||||
case KeyCode.SYMBOL_ALPHA:
|
||||
case KeyCode.ALPHA:
|
||||
case KeyCode.SYMBOL:
|
||||
case KeyCode.NUMPAD:
|
||||
|
|
|
@ -706,4 +706,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
return wrapper;
|
||||
}
|
||||
|
||||
public boolean isTablet() {
|
||||
return mContext.getResources().getInteger(R.integer.config_screen_metrics) >= 3;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import helium314.keyboard.keyboard.internal.KeyboardParams;
|
|||
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
|
||||
import helium314.keyboard.latin.R;
|
||||
import helium314.keyboard.latin.SuggestedWords;
|
||||
import helium314.keyboard.latin.common.Constants;
|
||||
import helium314.keyboard.latin.utils.TypefaceUtils;
|
||||
|
||||
public final class MoreSuggestions extends Keyboard {
|
||||
|
@ -87,7 +86,7 @@ public final class MoreSuggestions extends Keyboard {
|
|||
mNumRows = row + 1;
|
||||
mBaseWidth = mOccupiedWidth = Math.max(
|
||||
minWidth, calcurateMaxRowWidth(fromIndex, index));
|
||||
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
|
||||
mBaseHeight = mOccupiedHeight = mNumRows * mDefaultAbsoluteRowHeight + mVerticalGap;
|
||||
return index - fromIndex;
|
||||
}
|
||||
|
||||
|
@ -138,7 +137,7 @@ public final class MoreSuggestions extends Keyboard {
|
|||
|
||||
public int getY(final int index) {
|
||||
final int row = mRowNumbers[index];
|
||||
return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
|
||||
return (mNumRows -1 - row) * mDefaultAbsoluteRowHeight + mTopPadding;
|
||||
}
|
||||
|
||||
public int getWidth(final int index) {
|
||||
|
@ -185,7 +184,7 @@ public final class MoreSuggestions extends Keyboard {
|
|||
mParams.mId = parentKeyboard.mId;
|
||||
readAttributes(xmlId);
|
||||
mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
|
||||
mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
|
||||
mPaneView.updateKeyboardGeometry(mParams.mDefaultAbsoluteRowHeight);
|
||||
final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow,
|
||||
mPaneView.newLabelPaint(null /* key */), mResources);
|
||||
mFromIndex = fromIndex;
|
||||
|
@ -218,7 +217,7 @@ public final class MoreSuggestions extends Keyboard {
|
|||
final int numColumnInRow = params.getNumColumnInRow(index);
|
||||
if (columnNumber < numColumnInRow - 1) {
|
||||
final Divider divider = new Divider(params, params.mDivider, x + width, y,
|
||||
params.mDividerWidth, params.mDefaultRowHeight);
|
||||
params.mDividerWidth, params.mDefaultAbsoluteRowHeight);
|
||||
params.onAddKey(divider);
|
||||
}
|
||||
}
|
||||
|
@ -234,7 +233,7 @@ public final class MoreSuggestions extends Keyboard {
|
|||
super(word /* label */, KeyboardIconsSet.ICON_UNDEFINED, KeyCode.MULTIPLE_CODE_POINTS,
|
||||
word /* outputText */, info, 0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL,
|
||||
params.getX(index), params.getY(index), params.getWidth(index),
|
||||
params.mDefaultRowHeight, params.mHorizontalGap, params.mVerticalGap);
|
||||
params.mDefaultAbsoluteRowHeight, params.mHorizontalGap, params.mVerticalGap);
|
||||
mSuggestedWordIndex = index;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,3 +17,40 @@ fun CharSequence.getStringResourceOrName(prefix: String, context: Context): Char
|
|||
val resId = context.resources.getIdentifier(prefix + this, "string", context.packageName)
|
||||
return if (resId == 0) this else context.getString(resId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the collection into a pair of lists on the first match of [condition], discarding the element first matching the condition.
|
||||
* If [condition] is not met, all elements are in the first list.
|
||||
*/
|
||||
fun <T> Collection<T>.splitAt(condition: (T) -> Boolean): Pair<MutableList<T>, MutableList<T>> {
|
||||
var conditionMet = false
|
||||
val first = mutableListOf<T>()
|
||||
val second = mutableListOf<T>()
|
||||
forEach {
|
||||
if (conditionMet) {
|
||||
second.add(it)
|
||||
} else {
|
||||
conditionMet = condition(it)
|
||||
if (!conditionMet)
|
||||
first.add(it)
|
||||
}
|
||||
}
|
||||
return first to second
|
||||
}
|
||||
|
||||
// like plus, but for nullable collections
|
||||
fun <T> addCollections(a: Collection<T>?, b: Collection<T>?): Collection<T>? {
|
||||
if (a.isNullOrEmpty()) return b
|
||||
if (b.isNullOrEmpty()) return a
|
||||
return a + b
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.removeFirst(predicate: (T) -> Boolean) {
|
||||
val i = indexOfFirst(predicate)
|
||||
if (i >= 0) removeAt(i)
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.replaceFirst(predicate: (T) -> Boolean, with: (T) -> T) {
|
||||
val i = indexOfFirst(predicate)
|
||||
if (i >= 0) this[i] = with(this[i])
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="key_def_functional">"
|
||||
;action 10%
|
||||
shift 10%; shift"</string>
|
||||
<string name="key_def_functional_top_row" translatable="false">";delete 10%"</string>
|
||||
<string name="key_def_bottom_row">"symbol_alpha, comma, space, period, emoji_com"</string>
|
||||
</resources>
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!--
|
||||
Functional key definition: use newline to separate rows, keys before semicolon are on
|
||||
the left side, keys after are on the right side. Use comma to separate keys on the same side.
|
||||
Valid key names are at the bottom of SimpleLayoutParser (todo: location may change)
|
||||
Width (in percent of keyboard width) can be appended to the key name, defaults to default key width
|
||||
Applied from bottom
|
||||
-->
|
||||
<string name="key_def_functional" translatable="false">"shift 15%; delete 15%"</string>
|
||||
<!--
|
||||
Functional key definitions for a single row, on top of the keyboard.
|
||||
This is to consider that some keyboards have more than 3 rows (relevant for tablet layout)
|
||||
-->
|
||||
<string name="key_def_functional_top_row" translatable="false">""</string>
|
||||
<!--
|
||||
Bottom row definition is similar to functional key definition, but only one row, and not
|
||||
split into two groups. Space bar will be adjusted in code for language and emoji keys,
|
||||
and other keys in symbol layouts
|
||||
-->
|
||||
<string name="key_def_bottom_row" translatable="false">"symbol_alpha 15%, comma, space, period, action 15%"</string>
|
||||
</resources>
|
|
@ -50,6 +50,12 @@ class ParserTest {
|
|||
ShadowLog.stream = System.out
|
||||
}
|
||||
|
||||
// todo: add more tests
|
||||
// (popup) keys with label and code
|
||||
// (popup) keys with icon
|
||||
// (popup) keys with that are essentially toolbar keys (yes, this should work at some point!)
|
||||
// correct background type, depending on key type and maybe sth else
|
||||
|
||||
@Test fun simpleParser() {
|
||||
val params = KeyboardParams()
|
||||
params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
|
||||
|
@ -124,10 +130,11 @@ f""", // no newline at the end
|
|||
params.mId = KeyboardLayoutSet.getFakeKeyboardId(KeyboardId.ELEMENT_ALPHABET)
|
||||
params.mPopupKeyTypes.add(POPUP_KEYS_LAYOUT)
|
||||
addLocaleKeyTextsToParams(latinIME, params, POPUP_KEYS_NORMAL)
|
||||
data class Expected(val label: String, val text: String?, val code: Int, val popups: List<String>?)
|
||||
data class Expected(val label: String?, val text: String?, val code: Int, val popups: List<String>? = null)
|
||||
val expected = listOf(
|
||||
Expected("a", null, 'a'.code, null),
|
||||
Expected("a", null, 'a'.code, null),
|
||||
Expected("a", null, 'b'.code, listOf("b")), // todo: should also check whether code is "a"
|
||||
Expected("$", null, '$'.code, listOf("£", "€", "¢", "¥", "₱")),
|
||||
Expected("$", null, '¥'.code, listOf("£", "€", "¢", "¥", "₱")),
|
||||
Expected("i", null, 105, null),
|
||||
|
@ -137,11 +144,15 @@ f""", // no newline at the end
|
|||
Expected(".", null, '.'.code, listOf(">")),
|
||||
Expected("'", null, '\''.code, listOf("!", "\"")),
|
||||
Expected("9", null, '9'.code, null), // todo (later): also should have different background or whatever is related to type
|
||||
Expected("", null, -7, null), // todo: expect an icon
|
||||
Expected("?123", null, -207, null),
|
||||
Expected("", null, ' '.code, null),
|
||||
Expected(null, null, -7, null), // todo: expect an icon
|
||||
Expected("?123", "?123", -202, null),
|
||||
Expected(null, null, ' '.code, null),
|
||||
Expected("(", null, '('.code, listOf("<", "[", "{")),
|
||||
Expected("$", null, '$'.code, listOf("£", "₱", "€", "¢", "¥")),
|
||||
Expected("a", null, ' '.code, null),
|
||||
Expected("a", null, ' '.code, null),
|
||||
Expected(null, null, KeyCode.CLIPBOARD, null), // todo: expect an icon
|
||||
Expected(null, null, KeyCode.MULTIPLE_CODE_POINTS, null), // todo: this works here, but crashes on phone
|
||||
Expected("p", null, 'p'.code, null),
|
||||
)
|
||||
val layoutString = """
|
||||
|
@ -149,6 +160,7 @@ f""", // no newline at the end
|
|||
[
|
||||
{ "$": "auto_text_key" "label": "a" },
|
||||
{ "$": "text_key" "label": "a" },
|
||||
{ "$": "text_key" "label": "a|b", "popup": { "main": { "label": "b|a" } } },
|
||||
{ "label": "$$$" },
|
||||
{ "label": "$$$", code: -805 },
|
||||
{ "$": "case_selector",
|
||||
|
@ -228,6 +240,10 @@ f""", // no newline at the end
|
|||
{ "code": -805, "label": "currency_slot_5" }
|
||||
]
|
||||
} },
|
||||
{ "code": 32, "label": "a|!code/key_delete" },
|
||||
{ "code": 32, "label": "a|b" },
|
||||
{ "label": "!icon/clipboard_action_key|!code/key_clipboard" },
|
||||
{ "label": "!icon/clipboard_action_key" },
|
||||
{ "label": "p" }
|
||||
],
|
||||
[
|
||||
|
@ -254,11 +270,9 @@ f""", // no newline at the end
|
|||
""".trimIndent()
|
||||
val keys = JsonKeyboardParser(params, latinIME).parseCoreLayout(layoutString)
|
||||
keys.first().forEachIndexed { index, keyData ->
|
||||
println("key ${keyData.label}: code ${keyData.code}, popups: ${keyData.popup.getPopupKeyLabels(params)}")
|
||||
if (keyData.type == KeyType.ENTER_EDITING || keyData.type == KeyType.SYSTEM_GUI) return@forEachIndexed // todo: currently not accepted, but should be (see below)
|
||||
println("data: key ${keyData.label}: code ${keyData.code}, popups: ${keyData.popup.getPopupKeyLabels(params)}")
|
||||
val keyParams = keyData.toKeyParams(params)
|
||||
println("key ${keyParams.mLabel}: code ${keyParams.mCode}, popups: ${keyParams.mPopupKeys?.toList()}")
|
||||
if (keyParams.outputText == "space") return@forEachIndexed // todo: only works for numeric layouts... idea: parse space anywhere, and otherwise only if special type
|
||||
println("params: key ${keyParams.mLabel}: code ${keyParams.mCode}, popups: ${keyParams.mPopupKeys?.toList()}")
|
||||
assertEquals(expected[index].label, keyParams.mLabel)
|
||||
assertEquals(expected[index].code, keyParams.mCode)
|
||||
assertEquals(expected[index].popups?.sorted(), keyParams.mPopupKeys?.mapNotNull { it.mLabel }?.sorted()) // todo (later): what's wrong with order?
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue