mirror of
https://github.com/Julow/Unexpected-Keyboard.git
synced 2025-02-13 16:19:49 +01:00
On API 30 to 34, the status bar changes color when the keyboard appears and disappears. A ghost of the changed status bar is animated by the same animation used for the keyboard, which is unexpected.
511 lines
16 KiB
Java
511 lines
16 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.Arrays;
|
|
|
|
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 KeyValue _shift_kv;
|
|
private KeyboardData.Key _shift_key;
|
|
|
|
/** Used to add fake pointers. */
|
|
private KeyValue _compose_kv;
|
|
private KeyboardData.Key _compose_key;
|
|
|
|
private Pointers _pointers;
|
|
|
|
private Pointers.Modifiers _mods;
|
|
|
|
private static int _currentWhat = 0;
|
|
|
|
private Config _config;
|
|
|
|
private float _keyWidth;
|
|
private float _marginRight;
|
|
private float _marginLeft;
|
|
private float _marginBottom;
|
|
|
|
private Theme _theme;
|
|
|
|
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);
|
|
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));
|
|
}
|
|
|
|
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_kv = KeyValue.getKeyByName("shift");
|
|
_shift_key = _keyboard.findKeyWithValue(_shift_kv);
|
|
if (_shift_key == null)
|
|
{
|
|
_shift_kv = _shift_kv.withFlags(_shift_kv.getFlags() | KeyValue.FLAG_LOCK);
|
|
_shift_key = _keyboard.findKeyWithValue(_shift_kv);
|
|
}
|
|
_compose_kv = KeyValue.getKeyByName("compose");
|
|
_compose_key = _keyboard.findKeyWithValue(_compose_kv);
|
|
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, _shift_kv, latched, lock);
|
|
}
|
|
|
|
/** Called from [KeyEventHandler]. */
|
|
public void set_compose_pending(boolean pending)
|
|
{
|
|
set_fake_ptr_latched(_compose_key, _compose_kv, pending, false);
|
|
}
|
|
|
|
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_UP:
|
|
case MotionEvent.ACTION_POINTER_UP:
|
|
_pointers.onTouchUp(event.getPointerId(event.getActionIndex()));
|
|
break;
|
|
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);
|
|
break;
|
|
case MotionEvent.ACTION_MOVE:
|
|
for (p = 0; p < event.getPointerCount(); p++)
|
|
_pointers.onTouchMove(event.getX(p), event.getY(p), event.getPointerId(p));
|
|
break;
|
|
case MotionEvent.ACTION_CANCEL:
|
|
_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) * _config.keyHeight;
|
|
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)
|
|
{
|
|
int width;
|
|
int insets_left = 0;
|
|
int insets_right = 0;
|
|
int insets_bottom = 0;
|
|
// LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS is set in [Keyboard2#updateSoftInputWindowLayoutParams].
|
|
// and keyboard is allowed do draw behind status/navigation bars
|
|
if (VERSION.SDK_INT >= 35)
|
|
{
|
|
WindowMetrics metrics =
|
|
((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE))
|
|
.getCurrentWindowMetrics();
|
|
width = metrics.getBounds().width();
|
|
WindowInsets wi = metrics.getWindowInsets();
|
|
int insets_types =
|
|
WindowInsets.Type.statusBars()
|
|
| WindowInsets.Type.displayCutout()
|
|
| WindowInsets.Type.mandatorySystemGestures()
|
|
| WindowInsets.Type.navigationBars();
|
|
Insets insets = wi.getInsets(insets_types);
|
|
insets_left = insets.left;
|
|
insets_right = insets.right;
|
|
// On API 35, the keyboard is allowed to draw under the
|
|
// button-navigation bar but on lower APIs, it must be discounted from
|
|
// the width.
|
|
if (VERSION.SDK_INT < 35)
|
|
{
|
|
Insets nav_insets = wi.getInsets(WindowInsets.Type.navigationBars());
|
|
width -= nav_insets.left + nav_insets.right;
|
|
insets_left -= nav_insets.left;
|
|
insets_right -= nav_insets.right;
|
|
}
|
|
// [insets.bottom] doesn't take into account the buttons that appear in
|
|
// the gesture navigation bar when the IME is showing so ensure a minimum
|
|
// of margin is added.
|
|
if (VERSION.SDK_INT >= 35)
|
|
insets_bottom = Math.max(insets.bottom, _config.bottomInsetMin);
|
|
}
|
|
else
|
|
{
|
|
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
|
|
width = dm.widthPixels;
|
|
}
|
|
int height =
|
|
(int)(_config.keyHeight * _keyboard.keysHeight
|
|
+ _config.marginTop + _marginBottom);
|
|
setMeasuredDimension(width, height);
|
|
_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;
|
|
}
|
|
|
|
@Override
|
|
public void onLayout(boolean changed, int left, int top, int right, int bottom)
|
|
{
|
|
if (!changed)
|
|
return;
|
|
if (VERSION.SDK_INT >= 29)
|
|
{
|
|
// Disable the back-gesture on the keyboard area
|
|
Rect keyboard_area = new Rect(
|
|
left + (int)_marginLeft,
|
|
top + (int)_config.marginTop,
|
|
right - (int)_marginRight,
|
|
bottom - (int)_marginBottom);
|
|
setSystemGestureExclusionRects(Arrays.asList(keyboard_area));
|
|
}
|
|
}
|
|
|
|
/** 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)
|
|
{
|
|
// Set keyboard background opacity
|
|
getBackground().setAlpha(_config.keyboardOpacity);
|
|
// Set keys opacity
|
|
_theme.keyBgPaint.setAlpha(_config.keyOpacity);
|
|
_theme.keyDownBgPaint.setAlpha(_config.keyActivatedOpacity);
|
|
_theme.keyBorderPaint.setAlpha(_config.keyOpacity);
|
|
float key_vertical_margin = _config.key_vertical_margin * _config.keyHeight;
|
|
float key_horizontal_margin = _config.key_horizontal_margin * _keyWidth;
|
|
// Add half of the key margin on the left and on the top as it's then added
|
|
// on the right and on the bottom of every keys.
|
|
float y = _config.marginTop + key_vertical_margin / 2;
|
|
for (KeyboardData.Row row : _keyboard.rows)
|
|
{
|
|
y += row.shift * _config.keyHeight;
|
|
float x = _marginLeft + key_horizontal_margin / 2;
|
|
float keyH = row.height * _config.keyHeight - key_vertical_margin;
|
|
for (KeyboardData.Key k : row.keys)
|
|
{
|
|
x += k.shift * _keyWidth;
|
|
float keyW = _keyWidth * k.width - key_horizontal_margin;
|
|
boolean isKeyDown = _pointers.isKeyDown(k);
|
|
drawKeyFrame(canvas, x, y, keyW, keyH, isKeyDown);
|
|
if (k.keys[0] != null)
|
|
drawLabel(canvas, k.keys[0], keyW / 2f + x, y, keyH, isKeyDown);
|
|
for (int i = 1; i < 9; i++)
|
|
{
|
|
if (k.keys[i] != null)
|
|
drawSubLabel(canvas, k.keys[i], x, y, keyW, keyH, i, isKeyDown);
|
|
}
|
|
drawIndication(canvas, k, x, y, keyW, keyH);
|
|
x += _keyWidth * k.width;
|
|
}
|
|
y += row.height * _config.keyHeight;
|
|
}
|
|
}
|
|
|
|
@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,
|
|
boolean isKeyDown)
|
|
{
|
|
float r = _theme.keyBorderRadius;
|
|
if (_config.borderConfig)
|
|
r = _config.customBorderRadius * _keyWidth;
|
|
float w = (_config.borderConfig) ? _config.customBorderLineWidth : _theme.keyBorderWidth;
|
|
float padding = w / 2.f;
|
|
if (isKeyDown)
|
|
w = _theme.keyBorderWidthActivated;
|
|
_tmpRect.set(x + padding, y + padding, x + keyW - padding, y + keyH - padding);
|
|
canvas.drawRoundRect(_tmpRect, r, r,
|
|
isKeyDown ? _theme.keyDownBgPaint : _theme.keyBgPaint);
|
|
if (w > 0.f)
|
|
{
|
|
_theme.keyBorderPaint.setStrokeWidth(w);
|
|
float overlap = r - r * 0.85f + w; // sin(45°)
|
|
drawBorder(canvas, x, y, x + overlap, y + keyH, _theme.keyBorderColorLeft);
|
|
drawBorder(canvas, x + keyW - overlap, y, x + keyW, y + keyH, _theme.keyBorderColorRight);
|
|
drawBorder(canvas, x, y, x + keyW, y + overlap, _theme.keyBorderColorTop);
|
|
drawBorder(canvas, x, y + keyH - overlap, x + keyW, y + keyH, _theme.keyBorderColorBottom);
|
|
}
|
|
}
|
|
|
|
/** 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, int color)
|
|
{
|
|
Paint p = _theme.keyBorderPaint;
|
|
float r = _theme.keyBorderRadius;
|
|
if (_config.borderConfig)
|
|
r = _config.customBorderRadius * _keyWidth;
|
|
canvas.save();
|
|
canvas.clipRect(clipl, clipt, clipr, clipb);
|
|
p.setColor(color);
|
|
canvas.drawRoundRect(_tmpRect, r, r, p);
|
|
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;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
kv = modifyKey(kv, _mods);
|
|
if (kv == null)
|
|
return;
|
|
float textSize = scaleTextSize(kv, _config.labelTextSize, keyH);
|
|
Paint p = _theme.labelPaint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT));
|
|
p.setColor(labelColor(kv, isKeyDown, false));
|
|
p.setAlpha(_config.labelBrightness);
|
|
p.setTextSize(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)
|
|
{
|
|
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, _config.sublabelTextSize, keyH);
|
|
Paint p = _theme.subLabelPaint(kv.hasFlagsAny(KeyValue.FLAG_KEY_FONT), a);
|
|
p.setColor(labelColor(kv, isKeyDown, true));
|
|
p.setAlpha(_config.labelBrightness);
|
|
p.setTextSize(textSize);
|
|
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)
|
|
{
|
|
if (k.indication == null || k.indication.equals(""))
|
|
return;
|
|
Paint p = _theme.indicationPaint(false);
|
|
p.setColor(_theme.subLabelColor);
|
|
p.setTextSize(keyH * _config.sublabelTextSize * _config.characterSize);
|
|
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, float rel_size, float keyH)
|
|
{
|
|
float smaller_font = k.hasFlagsAny(KeyValue.FLAG_SMALLER_FONT) ? 0.75f : 1.f;
|
|
return keyH * rel_size * smaller_font * _config.characterSize;
|
|
}
|
|
}
|