mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-17 07:22:45 +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;
|
||||
/** Flags of the label */
|
||||
private final int mLabelFlags;
|
||||
private static final int LABEL_FLAGS_ALIGN_HINT_LABEL_TO_BOTTOM = 0x02;
|
||||
private 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_HINT_LABEL_TO_BOTTOM = 0x02;
|
||||
public static final int LABEL_FLAGS_ALIGN_ICON_TO_BOTTOM = 0x04;
|
||||
public static final int LABEL_FLAGS_ALIGN_LABEL_OFF_CENTER = 0x08;
|
||||
// Font typeface specification.
|
||||
private static final int LABEL_FLAGS_FONT_MASK = 0x30;
|
||||
private static final int LABEL_FLAGS_FONT_NORMAL = 0x10;
|
||||
private 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_NORMAL = 0x10;
|
||||
public static final int LABEL_FLAGS_FONT_MONO_SPACE = 0x20;
|
||||
public static final int LABEL_FLAGS_FONT_DEFAULT = 0x30;
|
||||
// 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_LARGE_LETTER_RATIO = 0x40;
|
||||
private static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
|
||||
private 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_LARGE_LETTER_RATIO = 0x40;
|
||||
public static final int LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO = 0x80;
|
||||
public static final int LABEL_FLAGS_FOLLOW_KEY_LABEL_RATIO = 0xC0;
|
||||
public static final int LABEL_FLAGS_FOLLOW_KEY_HINT_LABEL_RATIO = 0x140;
|
||||
// End of key text ratio mask enum values
|
||||
private static final int LABEL_FLAGS_HAS_POPUP_HINT = 0x200;
|
||||
private 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_POPUP_HINT = 0x200;
|
||||
public static final int LABEL_FLAGS_HAS_SHIFTED_LETTER_HINT = 0x400;
|
||||
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
|
||||
// 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.
|
||||
private static final int LABEL_FLAGS_AUTO_X_SCALE = 0x4000;
|
||||
private 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_X_SCALE = 0x4000;
|
||||
public static final int LABEL_FLAGS_AUTO_Y_SCALE = 0x8000;
|
||||
public static final int LABEL_FLAGS_AUTO_SCALE = LABEL_FLAGS_AUTO_X_SCALE
|
||||
| LABEL_FLAGS_AUTO_Y_SCALE;
|
||||
private static final int LABEL_FLAGS_PRESERVE_CASE = 0x10000;
|
||||
private static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
|
||||
private static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
|
||||
private static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000;
|
||||
private static final int LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO = 0x100000;
|
||||
private 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_PRESERVE_CASE = 0x10000;
|
||||
public static final int LABEL_FLAGS_SHIFTED_LETTER_ACTIVATED = 0x20000;
|
||||
public static final int LABEL_FLAGS_FROM_CUSTOM_ACTION_LABEL = 0x40000;
|
||||
public static final int LABEL_FLAGS_FOLLOW_FUNCTIONAL_TEXT_COLOR = 0x80000;
|
||||
public static final int LABEL_FLAGS_KEEP_BACKGROUND_ASPECT_RATIO = 0x100000;
|
||||
public static final int LABEL_FLAGS_DISABLE_HINT_LABEL = 0x40000000;
|
||||
public static final int LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS = 0x80000000;
|
||||
|
||||
/** Icon to display instead of a label. Icon takes precedence over a label */
|
||||
private final int mIconId;
|
||||
|
@ -282,9 +282,9 @@ public class Key implements Comparable<Key> {
|
|||
mEnabled = keyParams.mEnabled;
|
||||
|
||||
// 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
|
||||
final float horizontalGapFloat = isSpacer() ? 0 : keyParams.mKeyboardParams.mHorizontalGap;
|
||||
|
||||
// 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.mRelativeHorizontalGap * keyParams.mKeyboardParams.mOccupiedWidth);
|
||||
mHorizontalGap = Math.round(horizontalGapFloat);
|
||||
mVerticalGap = Math.round(keyParams.mKeyboardParams.mVerticalGap);
|
||||
mWidth = Math.round(keyParams.mFullWidth - horizontalGapFloat);
|
||||
|
@ -435,16 +435,16 @@ public class Key implements Comparable<Key> {
|
|||
}
|
||||
|
||||
private static String backgroundName(final int backgroundType) {
|
||||
switch (backgroundType) {
|
||||
case BACKGROUND_TYPE_EMPTY: return "empty";
|
||||
case BACKGROUND_TYPE_NORMAL: return "normal";
|
||||
case BACKGROUND_TYPE_FUNCTIONAL: return "functional";
|
||||
case BACKGROUND_TYPE_STICKY_OFF: return "stickyOff";
|
||||
case BACKGROUND_TYPE_STICKY_ON: return "stickyOn";
|
||||
case BACKGROUND_TYPE_ACTION: return "action";
|
||||
case BACKGROUND_TYPE_SPACEBAR: return "spacebar";
|
||||
default: return null;
|
||||
}
|
||||
return switch (backgroundType) {
|
||||
case BACKGROUND_TYPE_EMPTY -> "empty";
|
||||
case BACKGROUND_TYPE_NORMAL -> "normal";
|
||||
case BACKGROUND_TYPE_FUNCTIONAL -> "functional";
|
||||
case BACKGROUND_TYPE_STICKY_OFF -> "stickyOff";
|
||||
case BACKGROUND_TYPE_STICKY_ON -> "stickyOn";
|
||||
case BACKGROUND_TYPE_ACTION -> "action";
|
||||
case BACKGROUND_TYPE_SPACEBAR -> "spacebar";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
|
@ -985,8 +985,10 @@ public class Key implements Comparable<Key> {
|
|||
}
|
||||
|
||||
public void setDimensionsFromRelativeSize(final float newX, final float newY) {
|
||||
if (mRelativeHeight == 0 || mRelativeWidth == 0)
|
||||
throw new IllegalStateException("can't use setUsingRelativeHeight, not all fields are set");
|
||||
if (mRelativeHeight == 0)
|
||||
mRelativeHeight = mKeyboardParams.mDefaultRelativeRowHeight;
|
||||
if (mRelativeWidth == 0)
|
||||
mRelativeWidth = mKeyboardParams.mDefaultRelativeKeyWidth;
|
||||
if (mRelativeHeight < 0)
|
||||
// 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");
|
||||
|
@ -996,6 +998,30 @@ public class Key implements Comparable<Key> {
|
|||
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
|
||||
* 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();
|
||||
int actionFlags = style.getFlags(keyAttr, R.styleable.Keyboard_Key_keyActionFlags);
|
||||
String[] moreKeys = style.getStringArray(keyAttr, R.styleable.Keyboard_Key_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;
|
||||
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlags(params, moreKeys);
|
||||
|
||||
final String[] additionalMoreKeys;
|
||||
if ((mLabelFlags & LABEL_FLAGS_DISABLE_ADDITIONAL_MORE_KEYS) != 0) {
|
||||
|
@ -1151,7 +1154,151 @@ public class Key implements Comparable<Key> {
|
|||
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,
|
||||
@Nullable final String hintLabel, @Nullable final String moreKeySpecs,
|
||||
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) {
|
||||
String[] moreKeys = MoreKeySpec.splitKeySpecs(moreKeySpecs);
|
||||
// Get maximum column order number and set a relevant mode value.
|
||||
int moreKeysColumnAndFlags = MORE_KEYS_MODE_MAX_COLUMN_WITH_AUTO_ORDER | params.mMaxMoreKeysKeyboardColumn;
|
||||
int value;
|
||||
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
|
||||
// Override with fixed column order number and set a relevant mode value.
|
||||
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_AUTO_ORDER
|
||||
| (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
||||
}
|
||||
if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
|
||||
// Override with fixed column order number and set a relevant mode value.
|
||||
moreKeysColumnAndFlags = MORE_KEYS_MODE_FIXED_COLUMN_WITH_FIXED_ORDER | (value & MORE_KEYS_COLUMN_NUMBER_MASK);
|
||||
}
|
||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
|
||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_HAS_LABELS;
|
||||
}
|
||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
|
||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
|
||||
}
|
||||
if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
|
||||
moreKeysColumnAndFlags |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
|
||||
}
|
||||
mMoreKeysColumnAndFlags = moreKeysColumnAndFlags;
|
||||
mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlags(params, moreKeys);
|
||||
|
||||
moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, null);
|
||||
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.common.Colors;
|
||||
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 java.util.Arrays;
|
||||
|
@ -137,29 +136,41 @@ public final class KeyboardTheme implements Comparable<KeyboardTheme> {
|
|||
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) {
|
||||
final boolean hasBorders = prefs.getBoolean(Settings.PREF_THEME_KEY_BORDERS, false);
|
||||
switch (themeColors) {
|
||||
case THEME_USER:
|
||||
final int accent = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, false);
|
||||
final int keyBgColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, false);
|
||||
final int functionalKeyBgColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, false);
|
||||
final int spaceBarBgColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, false);
|
||||
final int keyTextColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, false);
|
||||
final int hintTextColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, false);
|
||||
final int spaceBarTextColor = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX, false);
|
||||
final int background = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, false);
|
||||
return new Colors(themeStyle, hasBorders, accent, background, keyBgColor, functionalKeyBgColor, spaceBarBgColor, keyTextColor, hintTextColor, spaceBarTextColor);
|
||||
return new Colors(
|
||||
themeStyle,
|
||||
hasBorders,
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, false),
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, false),
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, false),
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, false),
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, false),
|
||||
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:
|
||||
final int accent2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, true);
|
||||
final int keyBgColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, true);
|
||||
final int functionalKeyBgColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, true);
|
||||
final int spaceBarBgColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, true);
|
||||
final int keyTextColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_TEXT_SUFFIX, true);
|
||||
final int hintTextColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_HINT_TEXT_SUFFIX, true);
|
||||
final int spaceBarTextColor2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_TEXT_SUFFIX, true);
|
||||
final int background2 = Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, true);
|
||||
return new Colors(themeStyle, hasBorders, accent2, background2, keyBgColor2, functionalKeyBgColor2, spaceBarBgColor2, keyTextColor2, hintTextColor2, spaceBarTextColor2);
|
||||
return new Colors(
|
||||
themeStyle,
|
||||
hasBorders,
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_ACCENT_SUFFIX, true),
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_BACKGROUND_SUFFIX, true),
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_KEYS_SUFFIX, true),
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_FUNCTIONAL_KEYS_SUFFIX, true),
|
||||
Settings.readUserColor(prefs, context, Settings.PREF_COLOR_SPACEBAR_SUFFIX, true),
|
||||
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:
|
||||
return new Colors(
|
||||
themeStyle,
|
||||
|
|
|
@ -420,8 +420,7 @@ public class KeyboardView extends View {
|
|||
paint.setTextAlign(Align.CENTER);
|
||||
}
|
||||
if (key.needsAutoXScale()) {
|
||||
final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) /
|
||||
TypefaceUtils.getStringWidth(label, paint));
|
||||
final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / TypefaceUtils.getStringWidth(label, paint));
|
||||
if (key.needsAutoScale()) {
|
||||
final float autoSize = paint.getTextSize() * ratio;
|
||||
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.Keyboard
|
||||
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.latin.R
|
||||
import org.dslul.openboard.inputmethod.latin.common.Constants
|
||||
import org.dslul.openboard.inputmethod.latin.settings.Settings
|
||||
import org.dslul.openboard.inputmethod.latin.utils.sumOf
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import java.io.IOException
|
||||
|
||||
|
@ -42,6 +44,120 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
|||
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> {
|
||||
mParams.mId = id
|
||||
// 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
|
||||
// keep it around for a while, for testing
|
||||
private fun useRelative() {
|
||||
var currentY = mParams.mTopPadding.toFloat()
|
||||
for (row in keysInRows) {
|
||||
if (row.isEmpty()) continue
|
||||
fillGapsWithSpacers(row)
|
||||
val y = row.first().yPos
|
||||
assert(row.all { it.yPos == y })
|
||||
var currentX = 0f
|
||||
row.forEach {
|
||||
it.setDimensionsFromRelativeSize(currentX, y)
|
||||
it.setDimensionsFromRelativeSize(currentX, currentY)
|
||||
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>) {
|
||||
if (mParams.mId.mElementId !in KeyboardId.ELEMENT_ALPHABET..KeyboardId.ELEMENT_SYMBOLS_SHIFTED) return
|
||||
if (row.isEmpty()) return
|
||||
if (row.all { it.xPos == 0f }) return // need existing xPos to determine gaps
|
||||
var currentX = 0f + mParams.mLeftPadding
|
||||
var i = 0
|
||||
while (i < row.size) {
|
||||
|
@ -258,6 +378,9 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
|||
startRow()
|
||||
for (keyParams in row) {
|
||||
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()
|
||||
}
|
||||
|
@ -268,12 +391,3 @@ open class KeyboardBuilder<KP : KeyboardParams>(protected val mContext: Context,
|
|||
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