- Replaced libswipetype with MornsGestureEngine — ported from FlorisBoard's StatisticalGlideTypingClassifier (Apache 2.0) - Uses combined shape + location Gaussian probability scoring (SHAPE_STD=22.08, LOCATION_STD=0.5109) with 200-point sampling - Gesture activates instantly on key change with velocity-based detection - Trail draws from initial touch through entire glide - Added Kotlin plugin to build (v2.1.20) - 2000 word dictionary from cracklib-small - Removed libswipetype dependency from glide flow (libswipetype still present as submodule but unused)
633 lines
20 KiB
Java
633 lines
20 KiB
Java
package juloo.keyboard2;
|
|
|
|
import android.content.Context;
|
|
import android.content.ContextWrapper;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Insets;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.inputmethodservice.InputMethodService;
|
|
import android.os.Build.VERSION;
|
|
import android.util.AttributeSet;
|
|
import android.util.DisplayMetrics;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.Window;
|
|
import android.view.WindowInsets;
|
|
import android.view.WindowManager;
|
|
import android.view.WindowMetrics;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
public class Keyboard2View extends View
|
|
implements View.OnTouchListener, Pointers.IPointerEventHandler
|
|
{
|
|
private KeyboardData _keyboard;
|
|
|
|
/** The key holding the shift key is used to set shift state from
|
|
autocapitalisation. */
|
|
private KeyboardData.Key _shift_key;
|
|
|
|
/** Used to add fake pointers. */
|
|
private KeyboardData.Key _compose_key;
|
|
|
|
private Pointers _pointers;
|
|
private MornsGlideAdapter _glideAdapter;
|
|
/** Glide gesture tracking. */
|
|
private int _glideLastKey = -1;
|
|
private int[] _glideKeys = new int[32];
|
|
private int _glideKeyCount = 0;
|
|
private boolean _inGlide = false;
|
|
/** Trail points for the swipe gesture (pixels). */
|
|
private List<float[]> _trailPoints = new ArrayList<>();
|
|
private Paint _trailPaint;
|
|
|
|
private Pointers.Modifiers _mods;
|
|
|
|
private static int _currentWhat = 0;
|
|
|
|
private Config _config;
|
|
|
|
private float _keyWidth;
|
|
private float _mainLabelSize;
|
|
private float _subLabelSize;
|
|
private float _marginRight;
|
|
private float _marginLeft;
|
|
private float _marginBottom;
|
|
private int _insets_left = 0;
|
|
private int _insets_right = 0;
|
|
private int _insets_bottom = 0;
|
|
|
|
private Theme _theme;
|
|
private Theme.Computed _tc;
|
|
|
|
private static RectF _tmpRect = new RectF();
|
|
|
|
enum Vertical
|
|
{
|
|
TOP,
|
|
CENTER,
|
|
BOTTOM
|
|
}
|
|
|
|
public Keyboard2View(Context context, AttributeSet attrs)
|
|
{
|
|
super(context, attrs);
|
|
_theme = new Theme(getContext(), attrs);
|
|
_config = Config.globalConfig();
|
|
_pointers = new Pointers(this, _config);
|
|
_trailPaint = new Paint();
|
|
_trailPaint.setColor(0x8044AAFF);
|
|
_trailPaint.setStyle(Paint.Style.STROKE);
|
|
_trailPaint.setStrokeWidth(6f);
|
|
_trailPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
_trailPaint.setStrokeJoin(Paint.Join.ROUND);
|
|
_trailPaint.setAntiAlias(true);
|
|
refresh_navigation_bar(context);
|
|
setOnTouchListener(this);
|
|
int layout_id = (attrs == null) ? 0 :
|
|
attrs.getAttributeResourceValue(null, "layout", 0);
|
|
if (layout_id == 0)
|
|
reset();
|
|
else
|
|
setKeyboard(KeyboardData.load(getResources(), layout_id));
|
|
}
|
|
|
|
public void setGlideAdapter(MornsGlideAdapter a) {
|
|
_glideAdapter = a;
|
|
}
|
|
|
|
private Window getParentWindow(Context context)
|
|
{
|
|
if (context instanceof InputMethodService)
|
|
return ((InputMethodService)context).getWindow().getWindow();
|
|
if (context instanceof ContextWrapper)
|
|
return getParentWindow(((ContextWrapper)context).getBaseContext());
|
|
return null;
|
|
}
|
|
|
|
public void refresh_navigation_bar(Context context)
|
|
{
|
|
if (VERSION.SDK_INT < 21)
|
|
return;
|
|
// The intermediate Window is a [Dialog].
|
|
Window w = getParentWindow(context);
|
|
w.setNavigationBarColor(_theme.colorNavBar);
|
|
if (VERSION.SDK_INT < 26)
|
|
return;
|
|
int uiFlags = getSystemUiVisibility();
|
|
if (_theme.isLightNavBar)
|
|
uiFlags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
|
else
|
|
uiFlags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
|
setSystemUiVisibility(uiFlags);
|
|
}
|
|
|
|
public void setKeyboard(KeyboardData kw)
|
|
{
|
|
_keyboard = kw;
|
|
_shift_key = _keyboard.findKeyWithValue(KeyValue.SHIFT);
|
|
_compose_key = _keyboard.findKeyWithValue(KeyValue.COMPOSE);
|
|
KeyModifier.set_modmap(_keyboard.modmap);
|
|
reset();
|
|
}
|
|
|
|
public void reset()
|
|
{
|
|
_mods = Pointers.Modifiers.EMPTY;
|
|
_pointers.clear();
|
|
requestLayout();
|
|
invalidate();
|
|
}
|
|
|
|
void set_fake_ptr_latched(KeyboardData.Key key, KeyValue kv, boolean latched,
|
|
boolean lock)
|
|
{
|
|
if (_keyboard == null || key == null)
|
|
return;
|
|
_pointers.set_fake_pointer_state(key, kv, latched, lock);
|
|
}
|
|
|
|
/** Called by auto-capitalisation. */
|
|
public void set_shift_state(boolean latched, boolean lock)
|
|
{
|
|
set_fake_ptr_latched(_shift_key, KeyValue.SHIFT, latched, lock);
|
|
}
|
|
|
|
/** Called from [KeyEventHandler]. */
|
|
public void set_compose_pending(boolean pending)
|
|
{
|
|
set_fake_ptr_latched(_compose_key, KeyValue.COMPOSE, pending, false);
|
|
}
|
|
|
|
/** Called from [Keybard2.onUpdateSelection]. */
|
|
public void set_selection_state(boolean selection_state)
|
|
{
|
|
if (_config.editor_config.selection_mode_enabled)
|
|
set_fake_ptr_latched(KeyboardData.Key.EMPTY,
|
|
KeyValue.SELECTION_MODE, selection_state, true);
|
|
}
|
|
|
|
public KeyValue modifyKey(KeyValue k, Pointers.Modifiers mods)
|
|
{
|
|
return KeyModifier.modify(k, mods);
|
|
}
|
|
|
|
public void onPointerDown(KeyValue k, boolean isSwipe)
|
|
{
|
|
updateFlags();
|
|
_config.handler.key_down(k, isSwipe);
|
|
invalidate();
|
|
vibrate();
|
|
}
|
|
|
|
public void onPointerUp(KeyValue k, Pointers.Modifiers mods)
|
|
{
|
|
// [key_up] must be called before [updateFlags]. The latter might disable
|
|
// flags.
|
|
_config.handler.key_up(k, mods);
|
|
updateFlags();
|
|
invalidate();
|
|
}
|
|
|
|
public void onPointerHold(KeyValue k, Pointers.Modifiers mods)
|
|
{
|
|
_config.handler.key_up(k, mods);
|
|
updateFlags();
|
|
}
|
|
|
|
public void onPointerFlagsChanged(boolean shouldVibrate)
|
|
{
|
|
updateFlags();
|
|
invalidate();
|
|
if (shouldVibrate)
|
|
vibrate();
|
|
}
|
|
|
|
private void updateFlags()
|
|
{
|
|
_mods = _pointers.getModifiers();
|
|
_config.handler.mods_changed(_mods);
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouch(View v, MotionEvent event)
|
|
{
|
|
int p;
|
|
switch (event.getActionMasked())
|
|
{
|
|
case MotionEvent.ACTION_DOWN:
|
|
case MotionEvent.ACTION_POINTER_DOWN:
|
|
p = event.getActionIndex();
|
|
float tx = event.getX(p);
|
|
float ty = event.getY(p);
|
|
KeyboardData.Key key = getKeyAtPosition(tx, ty);
|
|
if (key != null)
|
|
_pointers.onTouchDown(tx, ty, event.getPointerId(p), key);
|
|
// Reset glide tracking
|
|
_glideKeyCount = 0;
|
|
_glideKeys[0] = -1;
|
|
_inGlide = false;
|
|
_glideLastKey = -1;
|
|
_trailPoints.clear();
|
|
if (_glideAdapter != null) _glideAdapter.cancelGesture();
|
|
if (key != null && key.keys[0] != null
|
|
&& key.keys[0].getKind() == KeyValue.Kind.Char)
|
|
{
|
|
_glideLastKey = key.keys[0].getChar();
|
|
_trailPoints.add(new float[]{tx, ty});
|
|
}
|
|
break;
|
|
case MotionEvent.ACTION_MOVE:
|
|
for (p = 0; p < event.getPointerCount(); p++)
|
|
{
|
|
float mx = event.getX(p);
|
|
float my = event.getY(p);
|
|
_pointers.onTouchMove(mx, my, event.getPointerId(p));
|
|
// Collect trail + detect glide in one pass
|
|
if (_glideAdapter != null)
|
|
{
|
|
KeyboardData.Key k = getKeyAtPosition(mx, my);
|
|
_trailPoints.add(new float[]{mx, my});
|
|
invalidate();
|
|
if (k != null && k.keys[0] != null
|
|
&& k.keys[0].getKind() == KeyValue.Kind.Char)
|
|
{
|
|
int cp = k.keys[0].getChar();
|
|
if (cp != _glideLastKey)
|
|
{
|
|
_glideLastKey = cp;
|
|
if (!_inGlide)
|
|
{
|
|
_inGlide = true;
|
|
_glideAdapter.startGesture();
|
|
_pointers.cancelPointer(event.getPointerId(p));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Feed points during glide (convert to dp for the engine)
|
|
if (_inGlide && _glideAdapter != null)
|
|
{
|
|
float density = getResources().getDisplayMetrics().density;
|
|
_glideAdapter.addPoint(mx / density, my / density);
|
|
_trailPoints.add(new float[]{mx, my});
|
|
invalidate(); // trigger redraw to show trail
|
|
}
|
|
}
|
|
break;
|
|
case MotionEvent.ACTION_UP:
|
|
case MotionEvent.ACTION_POINTER_UP:
|
|
if (_inGlide && _glideAdapter != null)
|
|
_glideAdapter.finishGesture();
|
|
_inGlide = false;
|
|
_trailPoints.clear();
|
|
invalidate();
|
|
_pointers.onTouchUp(event.getPointerId(event.getActionIndex()));
|
|
break;
|
|
case MotionEvent.ACTION_CANCEL:
|
|
_inGlide = false;
|
|
_trailPoints.clear();
|
|
invalidate();
|
|
_pointers.onTouchCancel();
|
|
break;
|
|
default:
|
|
return (false);
|
|
}
|
|
return (true);
|
|
}
|
|
|
|
private KeyboardData.Row getRowAtPosition(float ty)
|
|
{
|
|
float y = _config.marginTop;
|
|
if (ty < y)
|
|
return null;
|
|
for (KeyboardData.Row row : _keyboard.rows)
|
|
{
|
|
y += (row.shift + row.height) * _tc.row_height;
|
|
if (ty < y)
|
|
return row;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private KeyboardData.Key getKeyAtPosition(float tx, float ty)
|
|
{
|
|
KeyboardData.Row row = getRowAtPosition(ty);
|
|
float x = _marginLeft;
|
|
if (row == null || tx < x)
|
|
return null;
|
|
for (KeyboardData.Key key : row.keys)
|
|
{
|
|
float xLeft = x + key.shift * _keyWidth;
|
|
float xRight = xLeft + key.width * _keyWidth;
|
|
if (tx < xLeft)
|
|
return null;
|
|
if (tx < xRight)
|
|
return key;
|
|
x = xRight;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void vibrate()
|
|
{
|
|
VibratorCompat.vibrate(this, _config);
|
|
}
|
|
|
|
@Override
|
|
public void onMeasure(int wSpec, int hSpec)
|
|
{
|
|
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
|
|
int width = dm.widthPixels;
|
|
_marginLeft = Math.max(_config.horizontal_margin, _insets_left);
|
|
_marginRight = Math.max(_config.horizontal_margin, _insets_right);
|
|
_marginBottom = _config.margin_bottom + _insets_bottom;
|
|
_keyWidth = (width - _marginLeft - _marginRight) / _keyboard.keysWidth;
|
|
_tc = new Theme.Computed(_theme, _config, _keyWidth, _keyboard);
|
|
// Compute the size of labels based on the width or the height of keys. The
|
|
// margin around keys is taken into account. Keys normal aspect ratio is
|
|
// assumed to be 3/2 for a 10 columns layout. It's generally more, the
|
|
// width computation is useful when the keyboard is unusually high.
|
|
float labelBaseSize = Math.min(
|
|
_tc.row_height - _tc.vertical_margin,
|
|
(width / 10 - _tc.horizontal_margin) * 3/2
|
|
) * _config.characterSize;
|
|
_mainLabelSize = labelBaseSize * _config.labelTextSize;
|
|
_subLabelSize = labelBaseSize * _config.sublabelTextSize;
|
|
int height =
|
|
(int)(_tc.row_height * _keyboard.keysHeight
|
|
+ _config.marginTop + _marginBottom);
|
|
setMeasuredDimension(width, height);
|
|
// Pass real key positions to the glide adapter
|
|
if (_glideAdapter != null && _keyboard != null)
|
|
{
|
|
float density = getResources().getDisplayMetrics().density;
|
|
List<MornsGlideAdapter.KeyInfo> keys = new ArrayList<>();
|
|
float yy = _tc.margin_top / density;
|
|
for (KeyboardData.Row row : _keyboard.rows)
|
|
{
|
|
yy += row.shift * _tc.row_height / density;
|
|
float xx = _marginLeft / density;
|
|
float keyH = (row.height * _tc.row_height - _tc.vertical_margin) / density;
|
|
for (KeyboardData.Key k : row.keys)
|
|
{
|
|
xx += k.shift * _keyWidth / density;
|
|
float keyW = (_keyWidth * k.width - _tc.horizontal_margin) / density;
|
|
if (k.keys != null && k.keys.length > 0 && k.keys[0] != null
|
|
&& k.keys[0].getKind() == KeyValue.Kind.Char)
|
|
{
|
|
char c = k.keys[0].getChar();
|
|
MornsGlideAdapter.KeyInfo ki = new MornsGlideAdapter.KeyInfo();
|
|
ki.codePoint = (int)c;
|
|
ki.centerX = xx + keyW/2;
|
|
ki.centerY = yy + keyH/2;
|
|
ki.width = keyW;
|
|
ki.height = keyH;
|
|
keys.add(ki);
|
|
}
|
|
xx += (k.width * _keyWidth) / density;
|
|
}
|
|
yy += row.height * _tc.row_height / density;
|
|
}
|
|
_glideAdapter.setKeys(keys, loadGestureWords());
|
|
}
|
|
}
|
|
|
|
private List<String> loadGestureWords()
|
|
{
|
|
List<String> words = new ArrayList<>();
|
|
try (java.io.BufferedReader br = new java.io.BufferedReader(
|
|
new java.io.InputStreamReader(
|
|
getResources().openRawResource(R.raw.gesture_words))))
|
|
{
|
|
String line;
|
|
while ((line = br.readLine()) != null)
|
|
{
|
|
line = line.trim();
|
|
if (line.length() >= 2) words.add(line);
|
|
}
|
|
}
|
|
catch (Exception e) { /* ignore */ }
|
|
return words;
|
|
}
|
|
|
|
Rect _cached_exclusion_rect = new Rect();
|
|
List<Rect> _cached_exclusion_rects = Arrays.asList(_cached_exclusion_rect);
|
|
@Override
|
|
public void onLayout(boolean changed, int left, int top, int right, int bottom)
|
|
{
|
|
if (!changed)
|
|
return;
|
|
// Since SDK 30, this is done automatically:
|
|
// https://android.googlesource.com/platform/frameworks/base/+/android11-release/core/java/android/inputmethodservice/InputMethodService.java#852
|
|
if (VERSION.SDK_INT == 29)
|
|
{
|
|
// Disable the back-gesture on the keyboard area
|
|
_cached_exclusion_rect.set(
|
|
left + (int)_marginLeft,
|
|
top + (int)_config.marginTop,
|
|
right - (int)_marginRight,
|
|
bottom - (int)_marginBottom);
|
|
setSystemGestureExclusionRects(_cached_exclusion_rects);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public WindowInsets onApplyWindowInsets(WindowInsets wi)
|
|
{
|
|
// LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS is set in [Keyboard2#updateSoftInputWindowLayoutParams] for SDK_INT >= 35.
|
|
if (VERSION.SDK_INT < 35)
|
|
return wi;
|
|
int insets_types =
|
|
WindowInsets.Type.systemBars()
|
|
| WindowInsets.Type.displayCutout();
|
|
Insets insets = wi.getInsets(insets_types);
|
|
_insets_left = insets.left;
|
|
_insets_right = insets.right;
|
|
_insets_bottom = insets.bottom;
|
|
return WindowInsets.CONSUMED;
|
|
}
|
|
|
|
/** Horizontal and vertical position of the 9 indexes. */
|
|
static final Paint.Align[] LABEL_POSITION_H = new Paint.Align[]{
|
|
Paint.Align.CENTER, Paint.Align.LEFT, Paint.Align.RIGHT, Paint.Align.LEFT,
|
|
Paint.Align.RIGHT, Paint.Align.LEFT, Paint.Align.RIGHT,
|
|
Paint.Align.CENTER, Paint.Align.CENTER
|
|
};
|
|
|
|
static final Vertical[] LABEL_POSITION_V = new Vertical[]{
|
|
Vertical.CENTER, Vertical.TOP, Vertical.TOP, Vertical.BOTTOM,
|
|
Vertical.BOTTOM, Vertical.CENTER, Vertical.CENTER, Vertical.TOP,
|
|
Vertical.BOTTOM
|
|
};
|
|
|
|
@Override
|
|
protected void onDraw(Canvas canvas)
|
|
{
|
|
float y = _tc.margin_top;
|
|
for (KeyboardData.Row row : _keyboard.rows)
|
|
{
|
|
y += row.shift * _tc.row_height;
|
|
float x = _marginLeft + _tc.margin_left;
|
|
float keyH = row.height * _tc.row_height - _tc.vertical_margin;
|
|
for (KeyboardData.Key k : row.keys)
|
|
{
|
|
x += k.shift * _keyWidth;
|
|
float keyW = _keyWidth * k.width - _tc.horizontal_margin;
|
|
boolean isKeyDown = _pointers.isKeyDown(k);
|
|
Theme.Computed.Key tc_key;
|
|
if (isKeyDown)
|
|
tc_key = _tc.key_activated;
|
|
else
|
|
switch (k.role)
|
|
{
|
|
case Action: tc_key = _tc.key_action; break;
|
|
case Space_bar: tc_key = _tc.key_space_bar; break;
|
|
case Suggestion: tc_key = _tc.key_suggestion; break;
|
|
default:
|
|
case Normal: tc_key = _tc.key; break;
|
|
}
|
|
drawKeyFrame(canvas, x, y, keyW, keyH, tc_key);
|
|
if (k.keys[0] != null)
|
|
drawLabel(canvas, k.keys[0], keyW / 2f + x, y, keyH, isKeyDown, tc_key);
|
|
for (int i = 1; i < 9; i++)
|
|
{
|
|
if (k.keys[i] != null)
|
|
drawSubLabel(canvas, k.keys[i], x, y, keyW, keyH, i, isKeyDown, tc_key);
|
|
}
|
|
drawIndication(canvas, k, x, y, keyW, keyH, _tc);
|
|
x += _keyWidth * k.width;
|
|
}
|
|
y += row.height * _tc.row_height;
|
|
}
|
|
// Draw swipe gesture trail
|
|
if (_trailPoints != null && _trailPoints.size() >= 2)
|
|
{
|
|
android.graphics.Path trail = new android.graphics.Path();
|
|
trail.moveTo(_trailPoints.get(0)[0], _trailPoints.get(0)[1]);
|
|
for (int i = 1; i < _trailPoints.size(); i++)
|
|
trail.lineTo(_trailPoints.get(i)[0], _trailPoints.get(i)[1]);
|
|
canvas.drawPath(trail, _trailPaint);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDetachedFromWindow()
|
|
{
|
|
super.onDetachedFromWindow();
|
|
}
|
|
|
|
/** Draw borders and background of the key. */
|
|
void drawKeyFrame(Canvas canvas, float x, float y, float keyW, float keyH,
|
|
Theme.Computed.Key tc)
|
|
{
|
|
float r = tc.border_radius;
|
|
float w = tc.border_width;
|
|
float padding = w / 2.f;
|
|
_tmpRect.set(x + padding, y + padding, x + keyW - padding, y + keyH - padding);
|
|
canvas.drawRoundRect(_tmpRect, r, r, tc.bg_paint);
|
|
if (w > 0.f)
|
|
{
|
|
float overlap = r - r * 0.85f + w; // sin(45°)
|
|
drawBorder(canvas, x, y, x + overlap, y + keyH, tc.border_left_paint, tc);
|
|
drawBorder(canvas, x + keyW - overlap, y, x + keyW, y + keyH, tc.border_right_paint, tc);
|
|
drawBorder(canvas, x, y, x + keyW, y + overlap, tc.border_top_paint, tc);
|
|
drawBorder(canvas, x, y + keyH - overlap, x + keyW, y + keyH, tc.border_bottom_paint, tc);
|
|
}
|
|
}
|
|
|
|
/** Clip to draw a border at a time. This allows to call [drawRoundRect]
|
|
several time with the same parameters but a different Paint. */
|
|
void drawBorder(Canvas canvas, float clipl, float clipt, float clipr,
|
|
float clipb, Paint paint, Theme.Computed.Key tc)
|
|
{
|
|
float r = tc.border_radius;
|
|
canvas.save();
|
|
canvas.clipRect(clipl, clipt, clipr, clipb);
|
|
canvas.drawRoundRect(_tmpRect, r, r, paint);
|
|
canvas.restore();
|
|
}
|
|
|
|
private int labelColor(KeyValue k, boolean isKeyDown, boolean sublabel)
|
|
{
|
|
if (isKeyDown)
|
|
{
|
|
int flags = _pointers.getKeyFlags(k);
|
|
if (flags != -1)
|
|
{
|
|
if ((flags & Pointers.FLAG_P_LOCKED) != 0)
|
|
return _theme.lockedColor;
|
|
return _theme.activatedColor;
|
|
}
|
|
return _theme.pressedColor;
|
|
}
|
|
if (k.hasFlagsAny(KeyValue.FLAG_SECONDARY | KeyValue.FLAG_GREYED))
|
|
{
|
|
if (k.hasFlagsAny(KeyValue.FLAG_GREYED))
|
|
return _theme.greyedLabelColor;
|
|
return _theme.secondaryLabelColor;
|
|
}
|
|
return sublabel ? _theme.subLabelColor : _theme.labelColor;
|
|
}
|
|
|
|
private void drawLabel(Canvas canvas, KeyValue kv, float x, float y,
|
|
float keyH, boolean isKeyDown, Theme.Computed.Key tc)
|
|
{
|
|
kv = modifyKey(kv, _mods);
|
|
if (kv == null)
|
|
return;
|
|
float textSize = scaleTextSize(kv, true);
|
|
Paint p = tc.label_paint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT), labelColor(kv, isKeyDown, false), textSize);
|
|
canvas.drawText(kv.getString(), x, (keyH - p.ascent() - p.descent()) / 2f + y, p);
|
|
}
|
|
|
|
private void drawSubLabel(Canvas canvas, KeyValue kv, float x, float y,
|
|
float keyW, float keyH, int sub_index, boolean isKeyDown,
|
|
Theme.Computed.Key tc)
|
|
{
|
|
Paint.Align a = LABEL_POSITION_H[sub_index];
|
|
Vertical v = LABEL_POSITION_V[sub_index];
|
|
kv = modifyKey(kv, _mods);
|
|
if (kv == null)
|
|
return;
|
|
float textSize = scaleTextSize(kv, false);
|
|
Paint p = tc.sublabel_paint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT), labelColor(kv, isKeyDown, true), textSize, a);
|
|
float subPadding = _config.keyPadding;
|
|
if (v == Vertical.CENTER)
|
|
y += (keyH - p.ascent() - p.descent()) / 2f;
|
|
else
|
|
y += (v == Vertical.TOP) ? subPadding - p.ascent() : keyH - subPadding - p.descent();
|
|
if (a == Paint.Align.CENTER)
|
|
x += keyW / 2f;
|
|
else
|
|
x += (a == Paint.Align.LEFT) ? subPadding : keyW - subPadding;
|
|
String label = kv.getString();
|
|
int label_len = label.length();
|
|
// Limit the label of string keys to 3 characters
|
|
if (label_len > 3 && kv.getKind() == KeyValue.Kind.String)
|
|
label_len = 3;
|
|
canvas.drawText(label, 0, label_len, x, y, p);
|
|
}
|
|
|
|
private void drawIndication(Canvas canvas, KeyboardData.Key k, float x,
|
|
float y, float keyW, float keyH, Theme.Computed tc)
|
|
{
|
|
if (k.indication == null || k.indication.equals(""))
|
|
return;
|
|
Paint p = tc.indication_paint;
|
|
p.setTextSize(_subLabelSize);
|
|
canvas.drawText(k.indication, 0, k.indication.length(),
|
|
x + keyW / 2f, (keyH - p.ascent() - p.descent()) * 4/5 + y, p);
|
|
}
|
|
|
|
private float scaleTextSize(KeyValue k, boolean main_label)
|
|
{
|
|
float smaller_font = k.hasFlagsAny(KeyValue.FLAG_SMALLER_FONT) ? 0.75f : 1.f;
|
|
float label_size = main_label ? _mainLabelSize : _subLabelSize;
|
|
return label_size * smaller_font;
|
|
}
|
|
}
|