mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-17 15:32:48 +00:00
Add keyboard parser for simple layout definitions (#270)
and one example layout and some todos containing the further plan
This commit is contained in:
parent
1e79e3e2f6
commit
9f67113216
9 changed files with 971 additions and 118 deletions
28
app/src/main/assets/layouts/qwerty.txt
Normal file
28
app/src/main/assets/layouts/qwerty.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
q %
|
||||||
|
w \
|
||||||
|
e |
|
||||||
|
r =
|
||||||
|
t [
|
||||||
|
y ]
|
||||||
|
u <
|
||||||
|
i >
|
||||||
|
o {
|
||||||
|
p }
|
||||||
|
|
||||||
|
a @
|
||||||
|
s #
|
||||||
|
d $ €
|
||||||
|
f _
|
||||||
|
g &
|
||||||
|
h -
|
||||||
|
j +
|
||||||
|
k (
|
||||||
|
l )
|
||||||
|
|
||||||
|
z *
|
||||||
|
x "
|
||||||
|
c '
|
||||||
|
v :
|
||||||
|
b ;
|
||||||
|
n !
|
||||||
|
m ?
|
|
@ -51,38 +51,38 @@ public class Key implements Comparable<Key> {
|
||||||
private final String mHintLabel;
|
private final String mHintLabel;
|
||||||
/** Flags of the label */
|
/** Flags of the label */
|
||||||
private final int mLabelFlags;
|
private final int mLabelFlags;
|
||||||
private static final int LABEL_FLAGS_ALIGN_HINT_LABEL_TO_BOTTOM = 0x02;
|
public static final int LABEL_FLAGS_ALIGN_HINT_LABEL_TO_BOTTOM = 0x02;
|
||||||
private static final int LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM = 0x04;
|
public static final int LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM = 0x04;
|
||||||
private static final int LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER = 0x08;
|
public static final int LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER = 0x08;
|
||||||
// Font typeface specification.
|
// Font typeface specification.
|
||||||
private static final int LABEL_FLAGS_FONT_MASK = 0x30;
|
private static final int LABEL_FLAGS_FONT_MASK = 0x30;
|
||||||
private static final int LABEL_FLAGS_FONT_NORMAL = 0x10;
|
public static final int LABEL_FLAGS_FONT_NORMAL = 0x10;
|
||||||
private static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20;
|
public static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20;
|
||||||
private static final int LABEL_FLAGS_FONT_DEFAULT = 0x30;
|
public static final int LABEL_FLAGS_FONT_DEFAULT = 0x30;
|
||||||
// Start of key text ratio enum values
|
// Start of key text ratio enum values
|
||||||
private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0;
|
private static final int LABEL_FLAGS_FOLLOW_KEY_TEXT_RATIO_MASK = 0x1C0;
|
||||||
private static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40;
|
public static final int LABEL_FLAGS_FOLLOW_KEY_LARGE_LETTER_RATIO = 0x40;
|
||||||
private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
|
public static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
|
||||||
private static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0;
|
public static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0;
|
||||||
private static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140;
|
public static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140;
|
||||||
// End of key text ratio mask enum values
|
// End of key text ratio mask enum values
|
||||||
private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200;
|
public static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200;
|
||||||
private static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400;
|
public static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400;
|
||||||
private static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
|
public static final int LABEL_FLAGS_HAS_HINT_LABEL = 0x800;
|
||||||
// The bit to calculate the ratio of key label width against key width. If autoXScale bit is on
|
// The bit to calculate the ratio of key label width against key width. If autoXScale bit is on
|
||||||
// and autoYScale bit is off, the key label may be shrunk only for X-direction.
|
// and autoYScale bit is off, the key label may be shrunk only for X-direction.
|
||||||
// If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled.
|
// If both autoXScale and autoYScale bits are on, the key label text size may be auto scaled.
|
||||||
private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
|
public static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
|
||||||
private static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000;
|
public static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000;
|
||||||
private static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE
|
public static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE
|
||||||
| LABEL_FLAGS_AUTO_Y_SCALE;
|
| LABEL_FLAGS_AUTO_Y_SCALE;
|
||||||
private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000;
|
public static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000;
|
||||||
private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
|
public static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
|
||||||
private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
|
public static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
|
||||||
private static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000;
|
public static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000;
|
||||||
private static final int LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO = 0x100000;
|
public static final int LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO = 0x100000;
|
||||||
private static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
|
public static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
|
||||||
private static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
|
public static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
|
||||||
|
|
||||||
/** Icon to display instead of a label. Icon takes precedence over a label */
|
/** Icon to display instead of a label. Icon takes precedence over a label */
|
||||||
private final int mIconId;
|
private final int mIconId;
|
||||||
|
@ -282,9 +282,9 @@ public class Key implements Comparable<Key> {
|
||||||
mEnabled = keyParams.mEnabled;
|
mEnabled = keyParams.mEnabled;
|
||||||
|
|
||||||
// stuff to create
|
// stuff to create
|
||||||
// interestingly it looks a little better when rounding horizontalGap to int immediately instead of using horizontalGapFloat
|
|
||||||
// but using float for determining mX and mWidth is more correct and keyboard ends up looking exactly like before introduction of KeyParams
|
// get the "correct" float gap: may shift keys by one pixel, but results in more uniform gaps between keys
|
||||||
final float horizontalGapFloat = isSpacer() ? 0 : keyParams.mKeyboardParams.mHorizontalGap;
|
final float horizontalGapFloat = isSpacer() ? 0 : (keyParams.mKeyboardParams.mRelativeHorizontalGap * keyParams.mKeyboardParams.mOccupiedWidth);
|
||||||
mHorizontalGap = Math.round(horizontalGapFloat);
|
mHorizontalGap = Math.round(horizontalGapFloat);
|
||||||
mVerticalGap = Math.round(keyParams.mKeyboardParams.mVerticalGap);
|
mVerticalGap = Math.round(keyParams.mKeyboardParams.mVerticalGap);
|
||||||
mWidth = Math.round(keyParams.mFullWidth - horizontalGapFloat);
|
mWidth = Math.round(keyParams.mFullWidth - horizontalGapFloat);
|
||||||
|
@ -435,16 +435,16 @@ public class Key implements Comparable<Key> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String backgroundName(final int backgroundType) {
|
private static String backgroundName(final int backgroundType) {
|
||||||
switch (backgroundType) {
|
return switch (backgroundType) {
|
||||||
case BACKGROUND_TYPE_EMPTY: return "empty";
|
case BACKGROUND_TYPE_EMPTY -> "empty";
|
||||||
case BACKGROUND_TYPE_NORMAL: return "normal";
|
case BACKGROUND_TYPE_NORMAL -> "normal";
|
||||||
case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
|
case BACKGROUND_TYPE_FUNCTIONAL -> "functional";
|
||||||
case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff";
|
case BACKGROUND_TYPE_STICKY_OFF -> "stickyOff";
|
||||||
case BACKGROUND_TYPE_STICKY_ON: return "stickyOn";
|
case BACKGROUND_TYPE_STICKY_ON -> "stickyOn";
|
||||||
case BACKGROUND_TYPE_ACTION: return "action";
|
case BACKGROUND_TYPE_ACTION -> "action";
|
||||||
case BACKGROUND_TYPE_SPACEBAR: return "spacebar";
|
case BACKGROUND_TYPE_SPACEBAR -> "spacebar";
|
||||||
default: return null;
|
default -> null;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCode() {
|
public int getCode() {
|
||||||
|
@ -985,8 +985,10 @@ public class Key implements Comparable<Key> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDimensionsFromRelativeSize(final float newX, final float newY) {
|
public void setDimensionsFromRelativeSize(final float newX, final float newY) {
|
||||||
if (mRelativeHeight == 0 || mRelativeWidth == 0)
|
if (mRelativeHeight == 0)
|
||||||
throw new IllegalStateException("can't use setUsingRelativeHeight, not all fields are set");
|
mRelativeHeight = mKeyboardParams.mDefaultRelativeRowHeight;
|
||||||
|
if (mRelativeWidth == 0)
|
||||||
|
mRelativeWidth = mKeyboardParams.mDefaultRelativeKeyWidth;
|
||||||
if (mRelativeHeight < 0)
|
if (mRelativeHeight < 0)
|
||||||
// todo (later): deal with it properly when it needs to be adjusted, i.e. when changing moreKeys or moreSuggestions
|
// todo (later): deal with it properly when it needs to be adjusted, i.e. when changing moreKeys or moreSuggestions
|
||||||
throw new IllegalStateException("can't (yet) deal with absolute height");
|
throw new IllegalStateException("can't (yet) deal with absolute height");
|
||||||
|
@ -996,6 +998,30 @@ public class Key implements Comparable<Key> {
|
||||||
mFullHeight = mRelativeHeight * mKeyboardParams.mBaseHeight;
|
mFullHeight = mRelativeHeight * mKeyboardParams.mBaseHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getMoreKeysColumnAndFlags(final KeyboardParams params, final String[] moreKeys) {
|
||||||
|
// Get maximum column order number and set a relevant mode value.
|
||||||
|
int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER | params.mMaxMoreKeysKeyboardColumn;
|
||||||
|
int value;
|
||||||
|
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
|
||||||
|
// Override with fixed column order number and set a relevant mode value.
|
||||||
|
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER | (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
||||||
|
}
|
||||||
|
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
|
||||||
|
// Override with fixed column order number and set a relevant mode value.
|
||||||
|
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER | (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
||||||
|
}
|
||||||
|
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
|
||||||
|
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS;
|
||||||
|
}
|
||||||
|
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
|
||||||
|
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
|
||||||
|
}
|
||||||
|
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
|
||||||
|
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
|
||||||
|
}
|
||||||
|
return moreKeysColumnAndFlags;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create keyParams with the given top-left coordinate and extract its attributes from a key
|
* Create keyParams with the given top-left coordinate and extract its attributes from a key
|
||||||
* specification string, Key attribute array, key style, and etc.
|
* specification string, Key attribute array, key style, and etc.
|
||||||
|
@ -1040,30 +1066,7 @@ public class Key implements Comparable<Key> {
|
||||||
final Locale localeForUpcasing = params.mId.getLocale();
|
final Locale localeForUpcasing = params.mId.getLocale();
|
||||||
int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
|
int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
|
||||||
String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
|
String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_moreKeys);
|
||||||
|
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlags(params, moreKeys);
|
||||||
// Get maximum column order number and set a relevant mode value.
|
|
||||||
int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER
|
|
||||||
| style.getInt(keyAttr, R.styleable.Keyboard_Key_maxMoreKeysColumn,
|
|
||||||
params.mMaxMoreKeysKeyboardColumn);
|
|
||||||
int value;
|
|
||||||
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
|
|
||||||
// Override with fixed column order number and set a relevant mode value.
|
|
||||||
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER | (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
|
||||||
}
|
|
||||||
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
|
|
||||||
// Override with fixed column order number and set a relevant mode value.
|
|
||||||
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER | (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
|
||||||
}
|
|
||||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
|
|
||||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS;
|
|
||||||
}
|
|
||||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
|
|
||||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
|
|
||||||
}
|
|
||||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
|
|
||||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
|
|
||||||
}
|
|
||||||
mMoreKeysColumnAndFlags = moreKeysColumnAndFlags;
|
|
||||||
|
|
||||||
final String[] additionalMoreKeys;
|
final String[] additionalMoreKeys;
|
||||||
if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
|
if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
|
||||||
|
@ -1151,7 +1154,151 @@ public class Key implements Comparable<Key> {
|
||||||
mEnabled = true;
|
mEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** for <GridRows/> */
|
/**
|
||||||
|
* constructor that does not require attrs, style or absolute key dimension / position
|
||||||
|
* setDimensionsFromRelativeSize needs to be called before creating the key
|
||||||
|
*/
|
||||||
|
public KeyParams(
|
||||||
|
// todo (much later): replace keySpec? these encoded icons and codes are not really great
|
||||||
|
@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)
|
||||||
|
@NonNull final KeyboardParams params,
|
||||||
|
final float relativeWidth,
|
||||||
|
final int labelFlags,
|
||||||
|
final int backgroundType,
|
||||||
|
@Nullable final String[] layoutMoreKeys // same style as current moreKeys (relevant for the special keys)
|
||||||
|
) {
|
||||||
|
mKeyboardParams = params;
|
||||||
|
mBackgroundType = backgroundType;
|
||||||
|
mLabelFlags = labelFlags;
|
||||||
|
mRelativeWidth = relativeWidth;
|
||||||
|
mRelativeHeight = params.mDefaultRelativeRowHeight;
|
||||||
|
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlags(params, layoutMoreKeys);
|
||||||
|
mIconId = KeySpecParser.getIconId(keySpec);
|
||||||
|
|
||||||
|
final boolean needsToUpcase = needsToUpcase(mLabelFlags, params.mId.mElementId);
|
||||||
|
final Locale localeForUpcasing = params.mId.getLocale();
|
||||||
|
int actionFlags = 0;
|
||||||
|
|
||||||
|
final String[] languageMoreKeys;
|
||||||
|
if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
|
||||||
|
languageMoreKeys = null;
|
||||||
|
} else {
|
||||||
|
// same style as additionalMoreKeys (i.e. moreKeys with the % placeholder(s))
|
||||||
|
// todo: read from assets or xml, and cache the results for quick reading again
|
||||||
|
languageMoreKeys = null; // todo: getLanguageMoreKeys(keySpec, mKeyboardParams.mId.getLocale());
|
||||||
|
}
|
||||||
|
final String[] finalMoreKeys = MoreKeySpec.insertAdditionalMoreKeys(languageMoreKeys, layoutMoreKeys);
|
||||||
|
if (finalMoreKeys != null) {
|
||||||
|
actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
|
||||||
|
mMoreKeys = new MoreKeySpec[finalMoreKeys.length];
|
||||||
|
for (int i = 0; i < finalMoreKeys.length; i++) {
|
||||||
|
mMoreKeys[i] = new MoreKeySpec(finalMoreKeys[i], needsToUpcase, localeForUpcasing);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mMoreKeys = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int code = KeySpecParser.getCode(keySpec);
|
||||||
|
if ((mLabelFlags & LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL) != 0) {
|
||||||
|
mLabel = params.mId.mCustomActionLabel;
|
||||||
|
} else if (code >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
|
||||||
|
// This is a workaround to have a key that has a supplementary code point in its label.
|
||||||
|
// Because we can put a string in resource neither as a XML entity of a supplementary
|
||||||
|
// code point nor as a surrogate pair.
|
||||||
|
mLabel = new StringBuilder().appendCodePoint(code).toString();
|
||||||
|
} else {
|
||||||
|
final String label = KeySpecParser.getLabel(keySpec);
|
||||||
|
mLabel = needsToUpcase
|
||||||
|
? StringUtils.toTitleCaseOfKeyLabel(label, localeForUpcasing)
|
||||||
|
: label;
|
||||||
|
}
|
||||||
|
if ((mLabelFlags & LABEL_FLAGS_DISABLE_HINT_LABEL) != 0) {
|
||||||
|
mHintLabel = null;
|
||||||
|
} else {
|
||||||
|
// maybe also always null for comma and period keys
|
||||||
|
final boolean hintLabelAlwaysFromFirstLongPressKey = false; // todo (later): add the setting, and use it (store in params?)
|
||||||
|
String hintLabel;
|
||||||
|
if (hintLabelAlwaysFromFirstLongPressKey) {
|
||||||
|
hintLabel = mMoreKeys == null ? null : mMoreKeys[0].mLabel;
|
||||||
|
} else {
|
||||||
|
hintLabel = layoutMoreKeys == null ? null : layoutMoreKeys[0];
|
||||||
|
if (hintLabel != null && hintLabel.length() > 1 && hintLabel.startsWith("!")) // this is not great, but other than removing com key label this is definitely ok
|
||||||
|
hintLabel = null;
|
||||||
|
if (hintLabel != null && hintLabel.length() == 2 && hintLabel.startsWith("\\"))
|
||||||
|
hintLabel = hintLabel.replace("\\", "");
|
||||||
|
}
|
||||||
|
mHintLabel = needsToUpcase
|
||||||
|
? StringUtils.toTitleCaseOfKeyLabel(hintLabel, localeForUpcasing)
|
||||||
|
: hintLabel;
|
||||||
|
}
|
||||||
|
String outputText = KeySpecParser.getOutputText(keySpec);
|
||||||
|
if (needsToUpcase) {
|
||||||
|
outputText = StringUtils.toTitleCaseOfKeyLabel(outputText, localeForUpcasing);
|
||||||
|
}
|
||||||
|
// Choose the first letter of the label as primary code if not specified.
|
||||||
|
if (code == CODE_UNSPECIFIED && TextUtils.isEmpty(outputText) && !TextUtils.isEmpty(mLabel)) {
|
||||||
|
if (StringUtils.codePointCount(mLabel) == 1) {
|
||||||
|
// Use the first letter of the hint label if shiftedLetterActivated flag is
|
||||||
|
// specified.
|
||||||
|
if ((mLabelFlags & LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT) != 0 && (mLabelFlags & LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED) != 0
|
||||||
|
&& !TextUtils.isEmpty(mHintLabel)) {
|
||||||
|
mCode = mHintLabel.codePointAt(0);
|
||||||
|
} else {
|
||||||
|
mCode = mLabel.codePointAt(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In some locale and case, the character might be represented by multiple code
|
||||||
|
// points, such as upper case Eszett of German alphabet.
|
||||||
|
outputText = mLabel;
|
||||||
|
mCode = CODE_OUTPUT_TEXT;
|
||||||
|
}
|
||||||
|
} else if (code == CODE_UNSPECIFIED && outputText != null) {
|
||||||
|
if (StringUtils.codePointCount(outputText) == 1) {
|
||||||
|
mCode = outputText.codePointAt(0);
|
||||||
|
outputText = null;
|
||||||
|
} else {
|
||||||
|
mCode = CODE_OUTPUT_TEXT;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mCode = needsToUpcase ? StringUtils.toTitleCaseOfKeyCode(code, localeForUpcasing) : code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// action flags don't need to be specified, they can be deduced from the key
|
||||||
|
if (backgroundType == BACKGROUND_TYPE_SPACEBAR || mCode == Constants.CODE_LANGUAGE_SWITCH)
|
||||||
|
actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
|
||||||
|
if (mCode <= Constants.CODE_SPACE && mCode != CODE_OUTPUT_TEXT)
|
||||||
|
actionFlags |= ACTION_FLAGS_NO_KEY_PREVIEW;
|
||||||
|
if (mCode == Constants.CODE_DELETE)
|
||||||
|
actionFlags |= ACTION_FLAGS_IS_REPEATABLE;
|
||||||
|
if (mCode == Constants.CODE_SETTINGS || mCode == Constants.CODE_LANGUAGE_SWITCH)
|
||||||
|
actionFlags |= ACTION_FLAGS_ALT_CODE_WHILE_TYPING;
|
||||||
|
mActionFlags = actionFlags;
|
||||||
|
|
||||||
|
// todo: for what it is actually used? maybe it could be removed?
|
||||||
|
final int altCodeInAttr; // settings and language switch keys have alt code space, all others nothing
|
||||||
|
if (mCode == Constants.CODE_SETTINGS || mCode == Constants.CODE_LANGUAGE_SWITCH)
|
||||||
|
altCodeInAttr = Constants.CODE_SPACE;
|
||||||
|
else
|
||||||
|
altCodeInAttr = CODE_UNSPECIFIED;
|
||||||
|
final int altCode = needsToUpcase
|
||||||
|
? StringUtils.toTitleCaseOfKeyCode(altCodeInAttr, localeForUpcasing)
|
||||||
|
: altCodeInAttr;
|
||||||
|
mOptionalAttributes = OptionalAttributes.newInstance(outputText, altCode,
|
||||||
|
// disabled icon only ever for old version of shortcut key, visual insets can be replaced with spacer
|
||||||
|
// todo (much later): can the 3 below be removed completely?
|
||||||
|
KeyboardIconsSet.ICON_UNDEFINED, 0, 0);
|
||||||
|
// KeyVisualAttributes for a key essentially are what the theme has, but on a per-key base
|
||||||
|
// could be used e.g. for having a color gradient on key color
|
||||||
|
// where is it used / which attribute?
|
||||||
|
// keyLetterSize in some keyboards
|
||||||
|
// keyShiftedLetterHintRatio same
|
||||||
|
// keyHintLabelVerticalAdjustment same
|
||||||
|
// todo (later): make sure these keys look ok when migrating the non-latin layouts (+pc qwerty)
|
||||||
|
mKeyVisualAttributes = null;
|
||||||
|
mEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** constructor for <GridRows/> */
|
||||||
public KeyParams(@Nullable final String label, final int code, @Nullable final String outputText,
|
public KeyParams(@Nullable final String label, final int code, @Nullable final String outputText,
|
||||||
@Nullable final String hintLabel, @Nullable final String moreKeySpecs,
|
@Nullable final String hintLabel, @Nullable final String moreKeySpecs,
|
||||||
final int labelFlags, final int backgroundType, final int x, final int y,
|
final int labelFlags, final int backgroundType, final int x, final int y,
|
||||||
|
@ -1167,28 +1314,7 @@ public class Key implements Comparable<Key> {
|
||||||
|
|
||||||
if (moreKeySpecs != null) {
|
if (moreKeySpecs != null) {
|
||||||
String[] moreKeys = MoreKeySpec.splitKeySpecs(moreKeySpecs);
|
String[] moreKeys = MoreKeySpec.splitKeySpecs(moreKeySpecs);
|
||||||
// Get maximum column order number and set a relevant mode value.
|
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlags(params, moreKeys);
|
||||||
int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER | params.mMaxMoreKeysKeyboardColumn;
|
|
||||||
int value;
|
|
||||||
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
|
|
||||||
// Override with fixed column order number and set a relevant mode value.
|
|
||||||
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER
|
|
||||||
| (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
|
||||||
}
|
|
||||||
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
|
|
||||||
// Override with fixed column order number and set a relevant mode value.
|
|
||||||
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER | (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
|
||||||
}
|
|
||||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
|
|
||||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS;
|
|
||||||
}
|
|
||||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
|
|
||||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
|
|
||||||
}
|
|
||||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
|
|
||||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
|
|
||||||
}
|
|
||||||
mMoreKeysColumnAndFlags = moreKeysColumnAndFlags;
|
|
||||||
|
|
||||||
moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, null);
|
moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, null);
|
||||||
int actionFlags = 0;
|
int actionFlags = 0;
|
||||||
|
|
|
@ -16,7 +16,6 @@ import androidx.core.content.ContextCompat;
|
||||||
import org.dslul.openboard.inputmethod.latin.R;
|
import org.dslul.openboard.inputmethod.latin.R;
|
||||||
import org.dslul.openboard.inputmethod.latin.common.Colors;
|
import org.dslul.openboard.inputmethod.latin.common.Colors;
|
||||||
import org.dslul.openboard.inputmethod.latin.settings.Settings;
|
import org.dslul.openboard.inputmethod.latin.settings.Settings;
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.ColorUtilKt;
|
|
||||||
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
|
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -137,29 +136,41 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
|
||||||
return KEYBOARD_THEMES[DEFAULT_THEME_ID];
|
return KEYBOARD_THEMES[DEFAULT_THEME_ID];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getThemeActionAndEmojiKeyLabelFlags(final int themeId) {
|
||||||
|
if (themeId == THEME_ID_LXX_BASE || themeId == THEME_ID_ROUNDED_BASE)
|
||||||
|
return Key.LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static Colors getThemeColors(final String themeColors, final String themeStyle, final Context context, final SharedPreferences prefs) {
|
public static Colors getThemeColors(final String themeColors, final String themeStyle, final Context context, final SharedPreferences prefs) {
|
||||||
final boolean hasBorders = prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false);
|
final boolean hasBorders = prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false);
|
||||||
switch (themeColors) {
|
switch (themeColors) {
|
||||||
case THEME_USER:
|
case THEME_USER:
|
||||||
final int accent = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, false);
|
return new Colors(
|
||||||
final int keyBgColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, false);
|
themeStyle,
|
||||||
final int functionalKeyBgColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, false);
|
hasBorders,
|
||||||
final int spaceBarBgColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, false);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, false),
|
||||||
final int keyTextColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, false);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, false),
|
||||||
final int hintTextColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, false);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, false),
|
||||||
final int spaceBarTextColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX, false);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, false),
|
||||||
final int background = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, false);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, false),
|
||||||
return new Colors(themeStyle, hasBorders, accent, background, keyBgColor, functionalKeyBgColor, spaceBarBgColor, keyTextColor, hintTextColor, spaceBarTextColor);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, false),
|
||||||
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, false),
|
||||||
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX, false)
|
||||||
|
);
|
||||||
case THEME_USER_NIGHT:
|
case THEME_USER_NIGHT:
|
||||||
final int accent2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, true);
|
return new Colors(
|
||||||
final int keyBgColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, true);
|
themeStyle,
|
||||||
final int functionalKeyBgColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, true);
|
hasBorders,
|
||||||
final int spaceBarBgColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, true);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, true),
|
||||||
final int keyTextColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, true);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, true),
|
||||||
final int hintTextColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, true);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, true),
|
||||||
final int spaceBarTextColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX, true);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, true),
|
||||||
final int background2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, true);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, true),
|
||||||
return new Colors(themeStyle, hasBorders, accent2, background2, keyBgColor2, functionalKeyBgColor2, spaceBarBgColor2, keyTextColor2, hintTextColor2, spaceBarTextColor2);
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, true),
|
||||||
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, true),
|
||||||
|
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX, true)
|
||||||
|
);
|
||||||
case THEME_DARK:
|
case THEME_DARK:
|
||||||
return new Colors(
|
return new Colors(
|
||||||
themeStyle,
|
themeStyle,
|
||||||
|
|
|
@ -420,8 +420,7 @@ public class KeyboardView extends View {
|
||||||
paint.setTextAlign(Align.CENTER);
|
paint.setTextAlign(Align.CENTER);
|
||||||
}
|
}
|
||||||
if (key.needsAutoXScale()) {
|
if (key.needsAutoXScale()) {
|
||||||
final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) /
|
final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / TypefaceUtils.getStringWidth(label, paint));
|
||||||
TypefaceUtils.getStringWidth(label, paint));
|
|
||||||
if (key.needsAutoScale()) {
|
if (key.needsAutoScale()) {
|
||||||
final float autoSize = paint.getTextSize() * ratio;
|
final float autoSize = paint.getTextSize() * ratio;
|
||||||
paint.setTextSize(autoSize);
|
paint.setTextSize(autoSize);
|
||||||
|
|
|
@ -13,10 +13,12 @@ import org.dslul.openboard.inputmethod.keyboard.Key
|
||||||
import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams
|
import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams
|
||||||
import org.dslul.openboard.inputmethod.keyboard.Keyboard
|
import org.dslul.openboard.inputmethod.keyboard.Keyboard
|
||||||
import org.dslul.openboard.inputmethod.keyboard.KeyboardId
|
import org.dslul.openboard.inputmethod.keyboard.KeyboardId
|
||||||
|
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.SimpleKeyboardParser
|
||||||
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.XmlKeyboardParser
|
import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.XmlKeyboardParser
|
||||||
import org.dslul.openboard.inputmethod.latin.R
|
import org.dslul.openboard.inputmethod.latin.R
|
||||||
import org.dslul.openboard.inputmethod.latin.common.Constants
|
import org.dslul.openboard.inputmethod.latin.common.Constants
|
||||||
import org.dslul.openboard.inputmethod.latin.settings.Settings
|
import org.dslul.openboard.inputmethod.latin.settings.Settings
|
||||||
|
import org.dslul.openboard.inputmethod.latin.utils.sumOf
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
@ -42,6 +44,120 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
||||||
mParams.mAllowRedundantMoreKeys = enabled
|
mParams.mAllowRedundantMoreKeys = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadSimpleKeyboard(id: KeyboardId): KeyboardBuilder<KP> {
|
||||||
|
mParams.mId = id
|
||||||
|
keysInRows = SimpleKeyboardParser(mParams, mContext).parseFromAssets("qwerty")
|
||||||
|
useRelative()
|
||||||
|
|
||||||
|
// todo: further plan to make is actually useful
|
||||||
|
// create languageMoreKeys list from stuff in keyboard-text tools
|
||||||
|
// probably use files in assets, and cache them in a weak hash map with localestring as key
|
||||||
|
// or better 2 letter code, and join codes when combining languageMoreKeys for multiple locales
|
||||||
|
// or maybe locale tag, but that's super annoying for api < 24(?)
|
||||||
|
// or no caching if loading and combining is fast anyway (need to test)
|
||||||
|
// the locale morekeys then should be a map label -> moreKeys
|
||||||
|
// the whole moreKeys map for the current keyboard could be in mParams to simplify access when creating keys
|
||||||
|
// file format? it's easy to switch, but still... text like above? json?
|
||||||
|
// or use resources? could look like donottranslate-more-keys files
|
||||||
|
// should be possible with configuration and contextThemeWrapper, but probably more complicated than simple files
|
||||||
|
// also would be a bit annoying as it would require to have empty base strings for all possible keys
|
||||||
|
// test first whether something like morekeys_ဂ, or morekeys_ø or better morekeys_ø actually works
|
||||||
|
// if not, definitely don't use resources
|
||||||
|
// consider the % placeholder, this should still be used and documented
|
||||||
|
// though maybe has issues when merging languages?
|
||||||
|
// how to deal with unnecessary moreKeys?
|
||||||
|
// e.g. german should have ö as moreKey on o, but swiss german layout has ö as separate key
|
||||||
|
// still have ö on o (like now), or remove it? or make it optional?
|
||||||
|
// is this handled by KeyboardParams.removeRedundantMoreKeys?
|
||||||
|
// not only moreKeys, also currency key and some labels keys should be translated, though not necessarily in that map
|
||||||
|
// need some placeholder for currency key, like $$$
|
||||||
|
// have an explicit all-more-keys definition, which is created from a script merging all available moreKeys
|
||||||
|
// only letter forms and nothing else, right?
|
||||||
|
// maybe some most-but-not-all? e.g. only all that occur for more than one language
|
||||||
|
// migrate latin layouts to this style (need to make exception for pcqwerty!)
|
||||||
|
// finalize simple layout format
|
||||||
|
// keep like now: nice, because simple and allows defining any number of moreKeys
|
||||||
|
// rows of letters, separated with space: very straightforward, but moreKeys are annoying and only one possible
|
||||||
|
// consider the current layout maybe doesn't have the correct moreKeys
|
||||||
|
// where to actually get the current keyboard layout name, so it can be used to select the correct file?
|
||||||
|
// maybe KeyboardLayoutSet will need to be replaced
|
||||||
|
// need to solve the scaling issue with number row and 5 row keyboards
|
||||||
|
// allow users to switch to old style (keep it until all layouts are switched)
|
||||||
|
// really helps to find differences
|
||||||
|
// add a text that issues / unwanted differences should be reported, as the setting will be removed at some point
|
||||||
|
// label flags to do (top part is for latin!)
|
||||||
|
// allow users to define their own layouts
|
||||||
|
// write up how things work for users, also regarding language more keys
|
||||||
|
// readme, maybe also some "help" button in a dialog
|
||||||
|
// some sort of proper UI, or simply text input?
|
||||||
|
// better text import for the start because of much work
|
||||||
|
// ui follows later (consider that users need to be able to start from existing layouts!)
|
||||||
|
// some warning if more than 2 or 3 characters on a single label
|
||||||
|
// currently can't resize keys, but could set autoXScale (does only decrease size, never increase)
|
||||||
|
// careful about moreKeys: if moreKeys don't fit on screen, parser throws an exception!
|
||||||
|
// need to somehow test for this
|
||||||
|
// is that autoColumnOrder thing a workaround for that?
|
||||||
|
// still would crash for a single huge label
|
||||||
|
// popup and (single key) long press preview rescale the label on x only, which may deform emojis
|
||||||
|
// migrate symbol layouts to this style
|
||||||
|
// maybe allow users to define their own symbol and shift-symbol layouts
|
||||||
|
// migrate emoji layouts to this style
|
||||||
|
// emojis are defined in that string array, should be simple to handle
|
||||||
|
// parsing could be done into a single row, which is then split as needed
|
||||||
|
// this might help with split layout (no change in key size, but in number of rows!)
|
||||||
|
// write another parser, it should already consider split
|
||||||
|
// more dynamic / lazy way for loading the 10 emoji keyboards?
|
||||||
|
// use recyclerView instead of a keyboard?
|
||||||
|
// or recyclerView with one keyboardView per row?
|
||||||
|
// could be possible if creating the keyboards is fast enough... but also need to check whether it's ok for memory use and stuff
|
||||||
|
// migrate keypad layouts to this style
|
||||||
|
// will need more configurable layout definition -> another parser
|
||||||
|
// migrate moreKeys and moreSuggestions to this style?
|
||||||
|
// at least they should not make use of the KeyTextsSet/Table and of the XmlKeyboardParser
|
||||||
|
// migrate other languages to this style
|
||||||
|
// may be difficult in some cases, like additional row, or no shift key, or pc qwerty layout
|
||||||
|
// also the (integrated) number row might cause issues
|
||||||
|
// at least some of these layouts will need more complicated definition, not just a simple text file
|
||||||
|
// remove all the keyboard layout related xmls if possible
|
||||||
|
// rows_, rowkeys_, row_, kbd_ maybe keyboard_layout_set, keys_, keystyle_, key_
|
||||||
|
// and the texts_table and its source tools
|
||||||
|
|
||||||
|
// todo: label flags
|
||||||
|
// alignHintLabelToBottom -> what does it do?
|
||||||
|
// fontNormal -> check / compare turkish layout
|
||||||
|
// fontDefault -> check exclamation and question keys
|
||||||
|
// hasShiftedLetterHint, shiftedLetterActivated -> what is the effect on period key?
|
||||||
|
// labelFlags should be set correctly
|
||||||
|
// alignHintLabelToBottom: on lxx and rounded themes
|
||||||
|
// alignIconToBottom: space_key_for_number_layout
|
||||||
|
// alignLabelOffCenter: number keys in phone layout
|
||||||
|
// fontNormal: turkish (rows 1 and 2 only), .com, emojis, numModeKeyStyle, a bunch of non-latin languages
|
||||||
|
// fontMonoSpace: unused (not really: fontDefault is monospace + normal)
|
||||||
|
// fontDefault: keyExclamationQuestion, a bunch of "normal" keys in fontNormal layouts like thai
|
||||||
|
// followKeyLargeLetterRatio: number keys in number/phone/numpad layouts
|
||||||
|
// followKeyLetterRatio: mode keys in number layouts, some keys in some non-latin layouts
|
||||||
|
// followKeyLabelRatio: enter key, some keys in phone layout (same as followKeyLetterRatio + followKeyLargeLetterRatio)
|
||||||
|
// followKeyHintLabelRatio: unused directly (but includes some others)
|
||||||
|
// hasPopupHint: basically the long-pressable functional keys
|
||||||
|
// hasShiftedLetterHint: period key and some keys on pcqwerty
|
||||||
|
// hasHintLabel: number keys in number layouts
|
||||||
|
// autoXScale: com key, action keys, some on phone layout, some non-latin languages
|
||||||
|
// autoScale: only one single letter in khmer layout (includes autoXScale)
|
||||||
|
// preserveCase: action key + more keys, com key, shift keys
|
||||||
|
// shiftedLetterActivated: period and some keys on pcqwerty, tablet only
|
||||||
|
// fromCustomActionLabel: action key with customLabelActionKeyStyle -> check parser where to get this info
|
||||||
|
// followFunctionalTextColor: number mode keys, action key
|
||||||
|
// keepBackgroundAspectRatio: lxx and rounded action more keys, lxx no-border action and emoji, moreKeys keyboard view
|
||||||
|
// disableKeyHintLabel: keys in pcqwerty row 1 and number row
|
||||||
|
// disableAdditionalMoreKeys: keys in pcqwerty row 1
|
||||||
|
// -> probably can't define the related layouts in a simple way, better use some json or xml or anything more reasonable than the simple text format
|
||||||
|
// maybe remove some of the flags? or keep supporting them?
|
||||||
|
// for pcqwerty: hasShiftedLetterHint -> hasShiftedLetterHint|shiftedLetterActivated when shift is enabled, need to consider if the flag is used
|
||||||
|
// actually period key also has shifted letter hint
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
fun loadFromXml(xmlId: Int, id: KeyboardId): KeyboardBuilder<KP> {
|
fun loadFromXml(xmlId: Int, id: KeyboardId): KeyboardBuilder<KP> {
|
||||||
mParams.mId = id
|
mParams.mId = id
|
||||||
// loading a keyboard should set default params like mParams.readAttributes(mContext, attrs);
|
// loading a keyboard should set default params like mParams.readAttributes(mContext, attrs);
|
||||||
|
@ -84,16 +200,19 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
||||||
// still should not be more than a pixel difference
|
// still should not be more than a pixel difference
|
||||||
// keep it around for a while, for testing
|
// keep it around for a while, for testing
|
||||||
private fun useRelative() {
|
private fun useRelative() {
|
||||||
|
var currentY = mParams.mTopPadding.toFloat()
|
||||||
for (row in keysInRows) {
|
for (row in keysInRows) {
|
||||||
if (row.isEmpty()) continue
|
if (row.isEmpty()) continue
|
||||||
fillGapsWithSpacers(row)
|
fillGapsWithSpacers(row)
|
||||||
val y = row.first().yPos
|
|
||||||
assert(row.all { it.yPos == y })
|
|
||||||
var currentX = 0f
|
var currentX = 0f
|
||||||
row.forEach {
|
row.forEach {
|
||||||
it.setDimensionsFromRelativeSize(currentX, y)
|
it.setDimensionsFromRelativeSize(currentX, currentY)
|
||||||
currentX += it.mFullWidth
|
currentX += it.mFullWidth
|
||||||
}
|
}
|
||||||
|
// need to truncate to int here, otherwise it may end up one pixel lower than original
|
||||||
|
// though actually not truncating would be more correct... but that's already an y / height issue somewhere in Key
|
||||||
|
// todo (later): round, and do the change together with the some thing in Key(KeyParams keyParams)
|
||||||
|
currentY += row.first().mFullHeight.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +222,7 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
||||||
private fun fillGapsWithSpacers(row: MutableList<KeyParams>) {
|
private fun fillGapsWithSpacers(row: MutableList<KeyParams>) {
|
||||||
if (mParams.mId.mElementId !in KeyboardId.ELEMENT_ALPHABET..KeyboardId.ELEMENT_SYMBOLS_SHIFTED) return
|
if (mParams.mId.mElementId !in KeyboardId.ELEMENT_ALPHABET..KeyboardId.ELEMENT_SYMBOLS_SHIFTED) return
|
||||||
if (row.isEmpty()) return
|
if (row.isEmpty()) return
|
||||||
|
if (row.all { it.xPos == 0f }) return // need existing xPos to determine gaps
|
||||||
var currentX = 0f + mParams.mLeftPadding
|
var currentX = 0f + mParams.mLeftPadding
|
||||||
var i = 0
|
var i = 0
|
||||||
while (i < row.size) {
|
while (i < row.size) {
|
||||||
|
@ -258,6 +378,9 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
||||||
startRow()
|
startRow()
|
||||||
for (keyParams in row) {
|
for (keyParams in row) {
|
||||||
endKey(keyParams.createKey())
|
endKey(keyParams.createKey())
|
||||||
|
// todo (later): markAsBottomKey if in bottom row?
|
||||||
|
// this is not done in original parsing style, but why not?
|
||||||
|
// just test it (with different bottom paddings)
|
||||||
}
|
}
|
||||||
endRow()
|
endRow()
|
||||||
}
|
}
|
||||||
|
@ -268,12 +391,3 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
||||||
private const val BUILDER_TAG = "Keyboard.Builder"
|
private const val BUILDER_TAG = "Keyboard.Builder"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// adapted from Kotlin source: https://github.com/JetBrains/kotlin/blob/7a7d392b3470b38d42f80c896b7270678d0f95c3/libraries/stdlib/common/src/generated/_Collections.kt#L3004
|
|
||||||
private inline fun <T> Iterable<T>.sumOf(selector: (T) -> Float): Float {
|
|
||||||
var sum = 0f
|
|
||||||
for (element in this) {
|
|
||||||
sum += selector(element)
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,540 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
package org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import org.dslul.openboard.inputmethod.keyboard.Key
|
||||||
|
import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams
|
||||||
|
import org.dslul.openboard.inputmethod.keyboard.KeyboardId
|
||||||
|
import org.dslul.openboard.inputmethod.keyboard.KeyboardTheme
|
||||||
|
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardIconsSet
|
||||||
|
import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardParams
|
||||||
|
import org.dslul.openboard.inputmethod.latin.R
|
||||||
|
import org.dslul.openboard.inputmethod.latin.utils.InputTypeUtils
|
||||||
|
import org.dslul.openboard.inputmethod.latin.utils.sumOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for simple layouts like qwerty or symbol, defined only as rows of (normal) keys with moreKeys.
|
||||||
|
* Functional keys are pre-defined and can't be changed, with exception of comma, period and similar
|
||||||
|
* keys in symbol layouts.
|
||||||
|
* There may be a short "extra row" for the configurable keys in the bottom row. This is two keys
|
||||||
|
* for alphabet, 3 keys for symbols and 4 keys for shift symbols. MoreKeys on period and comma get
|
||||||
|
* merged with defaults.
|
||||||
|
* All normal keys have the same width and flags, which likely makes the simple layout definitions
|
||||||
|
* incompatible with the requirements of certain (non-latin) languages. These languages need to use
|
||||||
|
* a different (more configurable) layout definition style, and therefore a different parser.
|
||||||
|
* Also number, phone and numpad layouts are not compatible with this parser.
|
||||||
|
*/
|
||||||
|
class SimpleKeyboardParser(private val params: KeyboardParams, private val context: Context) {
|
||||||
|
|
||||||
|
fun parseFromAssets(layoutName: String) =
|
||||||
|
parse(context.assets.open("layouts/$layoutName.txt").reader().readText())
|
||||||
|
|
||||||
|
fun parse(layoutContent: String): ArrayList<ArrayList<KeyParams>> {
|
||||||
|
params.readAttributes(context, null)
|
||||||
|
val keysInRows = ArrayList<ArrayList<KeyParams>>()
|
||||||
|
|
||||||
|
val baseKeys: MutableList<List<BaseKey>> = parseAdjustablePartOfLayout(layoutContent)
|
||||||
|
if (!params.mId.mNumberRowEnabled) {
|
||||||
|
// todo (later): not all layouts have numbers on first row, so maybe have some layout flag to switch it off (or an option)
|
||||||
|
// but for latin it's fine, so don't care now
|
||||||
|
val newFirstRow = baseKeys.first().mapIndexed { index, baseKey ->
|
||||||
|
if (index < numbers.size)
|
||||||
|
BaseKey(baseKey.label, baseKey.moreKeys?.let { arrayOf(numbers[index], *it) })
|
||||||
|
else baseKey
|
||||||
|
}
|
||||||
|
baseKeys[0] = newFirstRow
|
||||||
|
}
|
||||||
|
val functionalKeysReversed = parseFunctionalKeys().reversed()
|
||||||
|
|
||||||
|
// keyboard parsed bottom-up because the number of rows is not fixed, but the functional keys
|
||||||
|
// are always added to the rows near the bottom
|
||||||
|
keysInRows.add(getBottomRowAndAdjustBaseKeys(baseKeys))
|
||||||
|
|
||||||
|
baseKeys.reversed().forEachIndexed { i, row ->
|
||||||
|
// parse functional keys for this row (if any)
|
||||||
|
val functionalKeysDefs = if (i < functionalKeysReversed.size) functionalKeysReversed[i]
|
||||||
|
else emptyList<String>() to emptyList()
|
||||||
|
val functionalKeysLeft = functionalKeysDefs.first.map { getFunctionalKeyParams(it) }
|
||||||
|
val functionalKeysRight = functionalKeysDefs.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 availableWidth = 1f - (functionalKeysLeft.sumOf { it.mRelativeWidth }) - (functionalKeysRight.sumOf { it.mRelativeWidth })
|
||||||
|
val width: Float
|
||||||
|
val spacerWidth: Float
|
||||||
|
if (availableWidth - usedKeyWidth > 0.0001f) { // don't add spacers if only a tiny bit is empty
|
||||||
|
// width available, add spacer
|
||||||
|
width = params.mDefaultRelativeKeyWidth
|
||||||
|
spacerWidth = (availableWidth - usedKeyWidth) / 2
|
||||||
|
} else {
|
||||||
|
// need more width, re-scale
|
||||||
|
spacerWidth = 0f
|
||||||
|
width = availableWidth / row.size
|
||||||
|
}
|
||||||
|
if (spacerWidth != 0f) {
|
||||||
|
paramsRow.add(KeyParams.newSpacer(params).apply { mRelativeWidth = spacerWidth })
|
||||||
|
}
|
||||||
|
|
||||||
|
for (key in row) {
|
||||||
|
paramsRow.add(KeyParams(
|
||||||
|
key.label,
|
||||||
|
params,
|
||||||
|
width, // any reasonable way to scale width if there is a long text? might be allowed in user-defined layout
|
||||||
|
0, // todo: maybe autoScale / autoXScale if label has more than 2 characters (exception for emojis?)
|
||||||
|
Key.BACKGROUND_TYPE_NORMAL,
|
||||||
|
key.moreKeys
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if (spacerWidth != 0f) {
|
||||||
|
paramsRow.add(KeyParams.newSpacer(params).apply { mRelativeWidth = spacerWidth })
|
||||||
|
}
|
||||||
|
functionalKeysRight.forEach { paramsRow.add(it) }
|
||||||
|
keysInRows.add(0, paramsRow) // we're doing it backwards, so add on top
|
||||||
|
}
|
||||||
|
// rescale height if we have more than 4 rows
|
||||||
|
val heightRescale = if (keysInRows.size > 4) 4f / keysInRows.size else 1f
|
||||||
|
if (params.mId.mNumberRowEnabled)
|
||||||
|
keysInRows.add(0, getNumberRow())
|
||||||
|
if (heightRescale != 1f)
|
||||||
|
// rescale all keys, so number row doesn't look weird (this is done like in current parsing)
|
||||||
|
// todo: in symbols view, number row is not rescaled
|
||||||
|
// so the symbols keyboard is higher than the normal one
|
||||||
|
// not a new issue, but should be solved in this migration
|
||||||
|
// how? possibly scale all keyboards to height of main alphabet? (consider suggestion strip)
|
||||||
|
keysInRows.forEach { it.forEach { it.mRelativeHeight *= heightRescale } }
|
||||||
|
|
||||||
|
return keysInRows
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseAdjustablePartOfLayout(layoutContent: String) =
|
||||||
|
layoutContent.split("\n\n").mapTo(mutableListOf()) { row -> row.split("\n").mapNotNull {
|
||||||
|
if (it.isBlank()) return@mapNotNull null
|
||||||
|
val split = it.split(" ")
|
||||||
|
val moreKeys = if (split.size == 1) null else Array(split.size - 1) { split[it + 1] }
|
||||||
|
BaseKey(split.first(), moreKeys)
|
||||||
|
} }
|
||||||
|
|
||||||
|
private fun parseFunctionalKeys(): List<Pair<List<String>, List<String>>> =
|
||||||
|
context.getString(R.string.key_def_functional).split("\n").mapNotNull { line ->
|
||||||
|
if (line.isBlank()) return@mapNotNull null
|
||||||
|
val p = line.split(";")
|
||||||
|
p.first().let { if (it.isBlank()) emptyList() else it.split(",") } to
|
||||||
|
p.last().let { if (it.isBlank()) emptyList() else it.split(",") }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBottomRowAndAdjustBaseKeys(baseKeys: MutableList<List<BaseKey>>): 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().split(" ").first()
|
||||||
|
val adjustKey = when (key) {
|
||||||
|
KEY_COMMA -> adjustedKeys?.first()
|
||||||
|
KEY_PERIOD -> adjustedKeys?.last()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
val keyParams = getFunctionalKeyParams(it, adjustKey?.label, adjustKey?.moreKeys)
|
||||||
|
if (key == KEY_SPACE) { // add the extra keys around space
|
||||||
|
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS) {
|
||||||
|
bottomRow.add(getFunctionalKeyParams(KEY_NUMPAD))
|
||||||
|
bottomRow.add(keyParams)
|
||||||
|
bottomRow.add(KeyParams(
|
||||||
|
adjustedKeys?.get(1)?.label ?: "/",
|
||||||
|
params,
|
||||||
|
params.mDefaultRelativeKeyWidth,
|
||||||
|
0,
|
||||||
|
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
adjustedKeys?.get(1)?.moreKeys
|
||||||
|
))
|
||||||
|
} else if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
|
||||||
|
bottomRow.add(KeyParams(
|
||||||
|
adjustedKeys?.get(1)?.label ?: "<",
|
||||||
|
params,
|
||||||
|
params.mDefaultRelativeKeyWidth,
|
||||||
|
0,
|
||||||
|
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
adjustedKeys?.get(1)?.moreKeys
|
||||||
|
))
|
||||||
|
bottomRow.add(keyParams)
|
||||||
|
bottomRow.add(KeyParams(
|
||||||
|
adjustedKeys?.get(2)?.label ?: ">",
|
||||||
|
params,
|
||||||
|
params.mDefaultRelativeKeyWidth,
|
||||||
|
0,
|
||||||
|
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
adjustedKeys?.get(2)?.moreKeys
|
||||||
|
))
|
||||||
|
} else { // alphabet
|
||||||
|
if (params.mId.mLanguageSwitchKeyEnabled)
|
||||||
|
bottomRow.add(getFunctionalKeyParams(KEY_LANGUAGE_SWITCH))
|
||||||
|
if (params.mId.mEmojiKeyEnabled)
|
||||||
|
bottomRow.add(getFunctionalKeyParams(KEY_EMOJI))
|
||||||
|
bottomRow.add(keyParams)
|
||||||
|
// todo (later): add zwnj if necessary (where to get that info? layout file? then likely will not happen in this parser)
|
||||||
|
}
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: everything below here likely can and should be shared with the planned parser for more complicated layouts
|
||||||
|
// abstract class?
|
||||||
|
// interface?
|
||||||
|
// utils file?
|
||||||
|
|
||||||
|
private fun getNumberRow(): ArrayList<KeyParams> {
|
||||||
|
val row = ArrayList<KeyParams>()
|
||||||
|
numbers.forEachIndexed { i, n ->
|
||||||
|
row.add(KeyParams(
|
||||||
|
n,
|
||||||
|
params,
|
||||||
|
params.mDefaultRelativeKeyWidth,
|
||||||
|
Key.LABEL_FLAGS_DISABLE_HINT_LABEL, // todo (later): maybe optional or enable (but then all numbers should have hints)
|
||||||
|
Key.BACKGROUND_TYPE_NORMAL,
|
||||||
|
numbersMoreKeys[i] // todo (later, non-latin): language may add some (either alt numbers, or latin numbers if they are replaced above, see number todo)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
// for comma and period: label will override default, moreKeys will be appended
|
||||||
|
private fun getFunctionalKeyParams(def: String, label: String? = null, moreKeys: Array<String>? = null): KeyParams {
|
||||||
|
val split = def.trim().split(" ")
|
||||||
|
val key = split[0]
|
||||||
|
val width = if (split.size == 2) split[1].substringBefore("%").toFloat() / 100f
|
||||||
|
else params.mDefaultRelativeKeyWidth
|
||||||
|
return when (key) {
|
||||||
|
KEY_SYMBOL -> KeyParams(
|
||||||
|
"${getSymbolLabel()}|!code/key_switch_alpha_symbol", // todo (later): in numpad the code is key_symbolNumpad
|
||||||
|
params,
|
||||||
|
width,
|
||||||
|
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR,
|
||||||
|
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
KEY_COMMA -> KeyParams(
|
||||||
|
label ?: getDefaultCommaLabel(),
|
||||||
|
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 else Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
moreKeys?.let { getCommaMoreKeys() + it } ?: getCommaMoreKeys()
|
||||||
|
)
|
||||||
|
KEY_SPACE -> KeyParams(
|
||||||
|
"!icon/space_key|!code/key_space", // !icon/space_key_for_number_layout in number layout, but not on tablet
|
||||||
|
params,
|
||||||
|
width, // will not be used for normal space (only in number layouts)
|
||||||
|
0, // todo (later): alignIconToBottom for non-tablet number layout
|
||||||
|
Key.BACKGROUND_TYPE_SPACEBAR,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
KEY_PERIOD -> KeyParams(
|
||||||
|
label ?: ".",
|
||||||
|
params,
|
||||||
|
width,
|
||||||
|
Key.LABEL_FLAGS_HAS_POPUP_HINT or Key.LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT, // todo (later): check what LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT does, maybe remove the flag here
|
||||||
|
if (label?.first()?.isLetter() == true) Key.BACKGROUND_TYPE_NORMAL else Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
moreKeys?.let { getPeriodMoreKeys() + it } ?: getPeriodMoreKeys()
|
||||||
|
)
|
||||||
|
KEY_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 KeyboardTheme.getThemeActionAndEmojiKeyLabelFlags(params.mThemeId),
|
||||||
|
Key.BACKGROUND_TYPE_ACTION,
|
||||||
|
getActionKeyMoreKeys()
|
||||||
|
)
|
||||||
|
KEY_DELETE -> KeyParams(
|
||||||
|
"!icon/delete_key|!code/key_delete",
|
||||||
|
params,
|
||||||
|
width,
|
||||||
|
0,
|
||||||
|
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
KEY_SHIFT -> KeyParams(
|
||||||
|
"${getShiftLabel()}|!code/key_shift",
|
||||||
|
params,
|
||||||
|
width,
|
||||||
|
Key.LABEL_FLAGS_PRESERVE_CASE,
|
||||||
|
// todo (later): possibly the whole stickOn/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,
|
||||||
|
arrayOf("!noPanelAutoMoreKey!", " |!code/key_capslock")
|
||||||
|
)
|
||||||
|
KEY_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
|
||||||
|
KEY_EMOJI_COM -> if (params.mId.mMode == KeyboardId.MODE_URL || params.mId.mMode == KeyboardId.MODE_EMAIL)
|
||||||
|
getFunctionalKeyParams(KEY_COM)
|
||||||
|
else getFunctionalKeyParams(KEY_EMOJI)
|
||||||
|
KEY_COM -> KeyParams(
|
||||||
|
".com", // todo: should depend on language
|
||||||
|
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,
|
||||||
|
arrayOf("!hasLabels!", ".net", ".org", ".gov", ".edu") // todo: maybe should be in languageMoreKeys
|
||||||
|
)
|
||||||
|
KEY_LANGUAGE_SWITCH -> KeyParams(
|
||||||
|
"!icon/language_switch_key|!code/key_language_switch",
|
||||||
|
params,
|
||||||
|
width,
|
||||||
|
0,
|
||||||
|
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
KEY_ALPHA -> KeyParams(
|
||||||
|
"${getAlphabetLabel()}|!code/key_switch_alpha_symbol", // todo (later): in numpad the code is key_alphaNumpad
|
||||||
|
params,
|
||||||
|
width,
|
||||||
|
Key.LABEL_FLAGS_PRESERVE_CASE or Key.LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR,
|
||||||
|
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
KEY_NUMPAD -> KeyParams(
|
||||||
|
"!icon/numpad_key|!code/key_numpad",
|
||||||
|
params,
|
||||||
|
width,
|
||||||
|
0,
|
||||||
|
Key.BACKGROUND_TYPE_FUNCTIONAL,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
KEY_EXCLAMATION -> KeyParams(
|
||||||
|
"!",
|
||||||
|
params,
|
||||||
|
width,
|
||||||
|
Key.LABEL_FLAGS_FONT_DEFAULT,
|
||||||
|
Key.BACKGROUND_TYPE_NORMAL,
|
||||||
|
arrayOf("¡") // todo (later) may depend on language
|
||||||
|
)
|
||||||
|
KEY_QUESTION -> KeyParams(
|
||||||
|
"\\?",
|
||||||
|
params,
|
||||||
|
width,
|
||||||
|
Key.LABEL_FLAGS_FONT_DEFAULT,
|
||||||
|
Key.BACKGROUND_TYPE_NORMAL,
|
||||||
|
arrayOf("¿") // todo (later) may depend on language
|
||||||
|
)
|
||||||
|
else -> throw IllegalArgumentException("unknown key definition \"$key\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
val iconName = when (params.mId.imeAction()) {
|
||||||
|
EditorInfo.IME_ACTION_GO -> KeyboardIconsSet.NAME_GO_KEY
|
||||||
|
EditorInfo.IME_ACTION_SEARCH -> KeyboardIconsSet.NAME_SEARCH_KEY
|
||||||
|
EditorInfo.IME_ACTION_SEND -> KeyboardIconsSet.NAME_SEND_KEY
|
||||||
|
EditorInfo.IME_ACTION_NEXT -> KeyboardIconsSet.NAME_NEXT_KEY
|
||||||
|
EditorInfo.IME_ACTION_DONE -> KeyboardIconsSet.NAME_DONE_KEY
|
||||||
|
EditorInfo.IME_ACTION_PREVIOUS -> KeyboardIconsSet.NAME_PREVIOUS_KEY
|
||||||
|
InputTypeUtils.IME_ACTION_CUSTOM_LABEL -> return params.mId.mCustomActionLabel
|
||||||
|
else -> return "!icon/enter_key"
|
||||||
|
}
|
||||||
|
val replacement = iconName.replaceIconWithLabelIfNoDrawable()
|
||||||
|
return if (iconName == replacement) // i.e. icon exists
|
||||||
|
"!icon/$iconName"
|
||||||
|
else
|
||||||
|
replacement
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getActionKeyCode() =
|
||||||
|
if (params.mId.isMultiLine && (params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED || params.mId.mElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED))
|
||||||
|
"!code/key_shift_enter"
|
||||||
|
else "!code/key_enter"
|
||||||
|
|
||||||
|
|
||||||
|
private fun getActionKeyMoreKeys(): Array<String>? {
|
||||||
|
val action = params.mId.imeAction()
|
||||||
|
val navigatePrev = params.mId.navigatePrevious()
|
||||||
|
val navigateNext = params.mId.navigateNext()
|
||||||
|
return when {
|
||||||
|
params.mId.passwordInput() -> when {
|
||||||
|
navigatePrev && action == EditorInfo.IME_ACTION_NEXT -> createMoreKeysArray(MORE_KEYS_NAVIGATE_PREVIOUS)
|
||||||
|
action == EditorInfo.IME_ACTION_NEXT -> null
|
||||||
|
navigateNext && action == EditorInfo.IME_ACTION_PREVIOUS -> createMoreKeysArray(MORE_KEYS_NAVIGATE_NEXT)
|
||||||
|
action == EditorInfo.IME_ACTION_PREVIOUS -> null
|
||||||
|
navigateNext && navigatePrev -> createMoreKeysArray(MORE_KEYS_NAVIGATE_PREVIOUS_NEXT)
|
||||||
|
navigateNext -> createMoreKeysArray(MORE_KEYS_NAVIGATE_NEXT)
|
||||||
|
navigatePrev -> createMoreKeysArray(MORE_KEYS_NAVIGATE_PREVIOUS)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
// could change definition of numbers to query a range, or have a pre-defined list, but not that crucial
|
||||||
|
params.mId.mMode in listOf(KeyboardId.MODE_URL, KeyboardId.MODE_EMAIL, KeyboardId.ELEMENT_PHONE, KeyboardId.ELEMENT_NUMBER, KeyboardId.MODE_DATE, KeyboardId.MODE_TIME, KeyboardId.MODE_DATETIME) -> when {
|
||||||
|
action == EditorInfo.IME_ACTION_NEXT && navigatePrev -> createMoreKeysArray(MORE_KEYS_NAVIGATE_PREVIOUS)
|
||||||
|
action == EditorInfo.IME_ACTION_NEXT -> null
|
||||||
|
action == EditorInfo.IME_ACTION_PREVIOUS && navigateNext -> createMoreKeysArray(MORE_KEYS_NAVIGATE_NEXT)
|
||||||
|
action == EditorInfo.IME_ACTION_PREVIOUS -> null
|
||||||
|
navigateNext && navigatePrev -> createMoreKeysArray(MORE_KEYS_NAVIGATE_PREVIOUS_NEXT)
|
||||||
|
navigateNext -> createMoreKeysArray(MORE_KEYS_NAVIGATE_NEXT)
|
||||||
|
navigatePrev -> createMoreKeysArray(MORE_KEYS_NAVIGATE_PREVIOUS)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
action == EditorInfo.IME_ACTION_NEXT && navigatePrev -> createMoreKeysArray(MORE_KEYS_NAVIGATE_EMOJI_PREVIOUS)
|
||||||
|
action == EditorInfo.IME_ACTION_NEXT -> createMoreKeysArray(MORE_KEYS_NAVIGATE_EMOJI)
|
||||||
|
action == EditorInfo.IME_ACTION_PREVIOUS && navigateNext -> createMoreKeysArray(MORE_KEYS_NAVIGATE_EMOJI_NEXT)
|
||||||
|
action == EditorInfo.IME_ACTION_PREVIOUS -> createMoreKeysArray(MORE_KEYS_NAVIGATE_EMOJI)
|
||||||
|
navigateNext && navigatePrev -> createMoreKeysArray(MORE_KEYS_NAVIGATE_EMOJI_PREVIOUS_NEXT)
|
||||||
|
navigateNext -> createMoreKeysArray(MORE_KEYS_NAVIGATE_EMOJI_NEXT)
|
||||||
|
navigatePrev -> createMoreKeysArray(MORE_KEYS_NAVIGATE_EMOJI_PREVIOUS)
|
||||||
|
else -> createMoreKeysArray(MORE_KEYS_NAVIGATE_EMOJI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMoreKeysArray(moreKeysDef: String): Array<String> {
|
||||||
|
val moreKeys = mutableListOf<String>()
|
||||||
|
for (moreKey in moreKeysDef.split(",")) {
|
||||||
|
val iconPrefixRemoved = moreKey.substringAfter("!icon/")
|
||||||
|
if (iconPrefixRemoved == moreKey) { // i.e. there is no !icon/
|
||||||
|
moreKeys.add(moreKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val iconName = iconPrefixRemoved.substringBefore("|")
|
||||||
|
val replacementText = iconName.replaceIconWithLabelIfNoDrawable()
|
||||||
|
if (replacementText == iconName) { // i.e. we have the drawable
|
||||||
|
moreKeys.add(moreKey)
|
||||||
|
} else {
|
||||||
|
moreKeys.add("!hasLabels!") // test what it actually does, but it's probably necessary
|
||||||
|
moreKeys.add(replacementText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return moreKeys.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.replaceIconWithLabelIfNoDrawable(): String {
|
||||||
|
if (params.mIconsSet.getIconDrawable(KeyboardIconsSet.getIconId(this)) != null) return this
|
||||||
|
val id = context.resources.getIdentifier("label_$this", "string", context.packageName)
|
||||||
|
return context.getString(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: may depend on language
|
||||||
|
private fun getAlphabetLabel(): String {
|
||||||
|
return "ABC"
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: may depend on language
|
||||||
|
private fun getSymbolLabel(): String {
|
||||||
|
return "\\?123"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getShiftLabel(): String {
|
||||||
|
val elementId = params.mId.mElementId
|
||||||
|
if (elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
|
||||||
|
return "=\\<" // todo: may depend on language
|
||||||
|
if (elementId == KeyboardId.ELEMENT_SYMBOLS)
|
||||||
|
return getSymbolLabel()
|
||||||
|
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 getDefaultCommaLabel(): String {
|
||||||
|
if (params.mId.mMode == KeyboardId.MODE_URL)
|
||||||
|
return "/"
|
||||||
|
if (params.mId.mMode == KeyboardId.MODE_EMAIL)
|
||||||
|
return "\\@"
|
||||||
|
return ","
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCommaMoreKeys(): Array<String> {
|
||||||
|
val keys = mutableListOf("!icon/clipboard_normal_key|!code/key_clipboard")
|
||||||
|
if (!params.mId.mEmojiKeyEnabled)
|
||||||
|
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")
|
||||||
|
keys.add("!icon/settings_key|!code/key_settings")
|
||||||
|
return keys.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPeriodMoreKeys(): Array<String> {
|
||||||
|
if (params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS || params.mId.mElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED)
|
||||||
|
return arrayOf("…")
|
||||||
|
// todo: language-dependent, also influences the number after autoColumnOrder
|
||||||
|
// there is a weird messup with morekeys_punctuation and morekeys_period
|
||||||
|
// by default, morekeys_period is taken from morekeys_punctuation, but some languages override this
|
||||||
|
// morekeys_period is also changed by some languages
|
||||||
|
// period key always uses morekeys_period, except for dvorak layout which is the only user of morekeys_punctuation
|
||||||
|
// -> clean it up when implementing the language-dependent moreKeys
|
||||||
|
return arrayOf("!autoColumnOrder!8", "\\,", "?", "!", "#", ")", "(", "/", ";", "'", "@", ":", "-", "\"", "+", "\\%", "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// class for holding a parsed key of the simple layout
|
||||||
|
private class BaseKey(
|
||||||
|
val label: String,
|
||||||
|
val moreKeys: Array<String>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
// todo (later): may depend on language for non-latin layouts... or should the number row always be latin?
|
||||||
|
private val numbers = (1..9).map { it.toString() } + "0"
|
||||||
|
|
||||||
|
// moreKeys for numbers, order is 1-9 and then 0
|
||||||
|
// todo (later): like numbers, for non-latin layouts this depends on language and therefore should not be in the parser
|
||||||
|
private val numbersMoreKeys = arrayOf(
|
||||||
|
arrayOf("¹", "½", "⅓","¼", "⅛"),
|
||||||
|
arrayOf("²", "⅔"),
|
||||||
|
arrayOf("³", "¾", "⅜"),
|
||||||
|
arrayOf("⁴"),
|
||||||
|
arrayOf("⅝"),
|
||||||
|
null,
|
||||||
|
arrayOf("⅞"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
arrayOf("ⁿ", "∅"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// could use 1 string per key, and make arrays right away
|
||||||
|
private const val MORE_KEYS_NAVIGATE_PREVIOUS = "!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard"
|
||||||
|
private const val MORE_KEYS_NAVIGATE_NEXT = "!icon/clipboard_action_key|!code/key_clipboard,!icon/next_key|!code/key_action_next"
|
||||||
|
private const val MORE_KEYS_NAVIGATE_PREVIOUS_NEXT = "!fixedColumnOrder!3,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/next_key|!code/key_action_next"
|
||||||
|
private const val MORE_KEYS_NAVIGATE_EMOJI_PREVIOUS = "!fixedColumnOrder!3,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji"
|
||||||
|
private const val MORE_KEYS_NAVIGATE_EMOJI = "!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji"
|
||||||
|
private const val MORE_KEYS_NAVIGATE_EMOJI_NEXT = "!fixedColumnOrder!3,!needsDividers!,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji,!icon/next_key|!code/key_action_next"
|
||||||
|
private const val MORE_KEYS_NAVIGATE_EMOJI_PREVIOUS_NEXT = "!fixedColumnOrder!4,!needsDividers!,!icon/previous_key|!code/key_action_previous,!icon/clipboard_action_key|!code/key_clipboard,!icon/emoji_action_key|!code/key_emoji,!icon/next_key|!code/key_action_next"
|
||||||
|
|
||||||
|
private const val KEY_EMOJI = "emoji"
|
||||||
|
private const val KEY_LANGUAGE_SWITCH = "language"
|
||||||
|
private const val KEY_COM = "com"
|
||||||
|
private const val KEY_EMOJI_COM = "emoji_com"
|
||||||
|
private const val KEY_DELETE = "delete"
|
||||||
|
private const val KEY_ACTION = "action"
|
||||||
|
private const val KEY_PERIOD = "period"
|
||||||
|
private const val KEY_COMMA = "comma"
|
||||||
|
private const val KEY_SPACE = "space"
|
||||||
|
private const val KEY_SHIFT = "shift"
|
||||||
|
private const val KEY_NUMPAD = "numpad"
|
||||||
|
private const val KEY_SYMBOL = "symbol"
|
||||||
|
private const val KEY_ALPHA = "alphabet"
|
||||||
|
private const val KEY_QUESTION = "question"
|
||||||
|
private const val KEY_EXCLAMATION = "exclamation"
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.dslul.openboard.inputmethod.latin.utils
|
||||||
|
|
||||||
|
// generic extension functions
|
||||||
|
|
||||||
|
// adapted from Kotlin source: https://github.com/JetBrains/kotlin/blob/7a7d392b3470b38d42f80c896b7270678d0f95c3/libraries/stdlib/common/src/generated/_Collections.kt#L3004
|
||||||
|
inline fun <T> Iterable<T>.sumOf(selector: (T) -> Float): Float {
|
||||||
|
var sum = 0f
|
||||||
|
for (element in this) {
|
||||||
|
sum += selector(element)
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
7
app/src/main/res/values-sw600dp/functional-keys.xml
Normal file
7
app/src/main/res/values-sw600dp/functional-keys.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="key_def_functional">";delete 10%
|
||||||
|
;action 10%
|
||||||
|
shift 10%; exclamation, question, shift"</string>
|
||||||
|
<string name="key_def_bottom_row">"symbol, comma, space, period, emoji_com"</string>
|
||||||
|
</resources>
|
16
app/src/main/res/values/functional-keys.xml
Normal file
16
app/src/main/res/values/functional-keys.xml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?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
|
||||||
|
-->
|
||||||
|
<string name="key_def_functional" translatable="false">"shift 15%; delete 15%"</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 15%, comma, space, period, action 15%"</string>
|
||||||
|
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue