From 65db3070fc53cdfe96694f5b6cfd175a49abe552 Mon Sep 17 00:00:00 2001 From: Asaf Mahlev Date: Sat, 6 Jun 2026 15:27:19 +0300 Subject: [PATCH 1/2] fix(shortcut-row): align down-swipe popup below source key (#36) The below-key (down-swipe) shortcut popup reused the above-key placement formula in PopupKeysKeyboardView.showPopupKeysPanelInternal, compensated by a +container.getMeasuredHeight() hack in MainKeyboardView. That left the bottom popup with the wrong vertical gap/overlap vs the top one. Make the placement below-aware: setShowBelowAnchor() selects a mirrored y-formula that anchors the panel's visible content top to pointY (vs bottom for the above case), and MainKeyboardView passes pointY = key bottom directly. Horizontal anchoring is unchanged (already symmetric). Closes #36. --- .../keyboard/keyboard/MainKeyboardView.java | 3 ++- .../keyboard/keyboard/PopupKeysKeyboardView.java | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java index f1ff26dfa..c5981475e 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java @@ -675,8 +675,9 @@ private PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key, // {@code mPreviewVisibleOffset} has been set appropriately in // {@link KeyboardView#showKeyPreview(PointerTracker)}. final int pointY = belowSourceKey - ? key.getY() + key.getHeight() + container.getMeasuredHeight() + ? key.getY() + key.getHeight() : key.getY() + mKeyPreviewDrawParams.getVisibleOffset(); + popupKeysKeyboardView.setShowBelowAnchor(belowSourceKey); popupKeysKeyboardView.showPopupKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); return popupKeysKeyboardView; } diff --git a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java index ef5b80352..d4686cbc1 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java @@ -44,6 +44,9 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane private int mOriginX; private int mOriginY; private Key mCurrentKey; + // When true, the panel is anchored below the source key (its top aligned to pointY) + // instead of the default above-key placement (its bottom aligned to pointY). + private boolean mShowBelowAnchor; private int mActivePointerId; @@ -137,6 +140,10 @@ public void showPopupKeysPanel(final View parentView, final Controller controlle mEmojiViewCallback = emojiViewCallback; showPopupKeysPanelInternal(parentView, controller, pointX, pointY); } + // Must be called before showPopupKeysPanel(...) for a given show; resets per show. + public void setShowBelowAnchor(final boolean below) { + mShowBelowAnchor = below; + } @SuppressLint("RtlHardcoded") // a key on the left is on the left, independent of layout direction private void showPopupKeysPanelInternal(final View parentView, final Controller controller, @@ -146,8 +153,13 @@ private void showPopupKeysPanelInternal(final View parentView, final Controller // The coordinates of panel's left-top corner in parentView's coordinate system. // We need to consider background drawable paddings. final int x = pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft(); - final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom() - + getPaddingBottom(); + // Above-anchor (default): panel bottom aligned to pointY (popup sits above the key). + // Below-anchor: panel top aligned to pointY (popup sits below the key), mirroring the + // padding compensation so the visible content edge lands on pointY either way. + final int y = mShowBelowAnchor + ? pointY - container.getPaddingTop() - getPaddingTop() + : pointY - container.getMeasuredHeight() + container.getPaddingBottom() + + getPaddingBottom(); parentView.getLocationInWindow(mCoordinates); final int containerY = y + CoordinateUtils.y(mCoordinates); From 05a48bd8fab3fbaaa3924bc23875348b71a4eee8 Mon Sep 17 00:00:00 2001 From: Asaf Mahlev Date: Sun, 7 Jun 2026 07:57:46 +0300 Subject: [PATCH 2/2] fix(shortcut-row): align popup icons to the source letter row (#36) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The real misalignment was horizontal, not vertical: the popup re-anchored on the swiped key via PopupKeysKeyboard's left/right split, which keys the split off the key's absolute x in the full keyboard. The bottom letter row is inset by the shift key (and bounded by backspace), so the leftmost letter (z) sits well right of x=0 and the icons got placed over the shift area — swiping down on z landed on a middle/right icon instead of the far-left one. The top row starts at x~0 so it looked fine, producing the top/bottom asymmetry. Fix: pin the shortcut popup's visible content left edge to the source letter row's left edge (leftmost normal key), so the 7 icons tile across the row and each letter maps to the icon directly above it (z->1st ... m->7th), draggable sideways. Normal long-press popups are unchanged (NO_ROW_ALIGN sentinel; they still anchor on the key). Verified on-device (S24+). --- .../keyboard/keyboard/MainKeyboardView.java | 19 ++++++++++++++++--- .../keyboard/PopupKeysKeyboardView.java | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java index c5981475e..c642688db 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java @@ -619,12 +619,13 @@ protected void onDetachedFromWindow() { @Nullable public PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key, @NonNull final PointerTracker tracker) { - return showPopupKeysKeyboard(key, tracker, false); + return showPopupKeysKeyboard(key, tracker, false, PopupKeysKeyboardView.NO_ROW_ALIGN); } @Nullable private PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key, - @NonNull final PointerTracker tracker, final boolean belowSourceKey) { + @NonNull final PointerTracker tracker, final boolean belowSourceKey, + final int rowAlignedLeftX) { final PopupKeySpec[] popupKeys = key.getPopupKeys(); if (popupKeys == null) { return null; @@ -678,6 +679,7 @@ private PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key, ? key.getY() + key.getHeight() : key.getY() + mKeyPreviewDrawParams.getVisibleOffset(); popupKeysKeyboardView.setShowBelowAnchor(belowSourceKey); + popupKeysKeyboardView.setRowAlignedLeftX(rowAlignedLeftX); popupKeysKeyboardView.showPopupKeysPanel(this, this, pointX, pointY, mKeyboardActionListener); return popupKeysKeyboardView; } @@ -696,7 +698,18 @@ public PopupKeysPanel showShortcutRowKeyboard(@NonNull final Key key, if (popupParentKey == null) { return null; } - return showPopupKeysKeyboard(popupParentKey, tracker, belowSourceKey); + // Align the shortcut popup's icons to the source letter row so the swiped key maps to the + // icon directly above it, instead of re-anchoring on the swiped key (which the irregular + // shift/backspace keys at the row edges throw off). Row left = leftmost normal key in the row. + int rowLeftX = Integer.MAX_VALUE; + for (final Key rowKey : keyboard.getSortedKeys()) { + if (rowKey.getY() == key.getY() && rowKey.getBackgroundType() == Key.BACKGROUND_TYPE_NORMAL + && !rowKey.isModifier() && !rowKey.isSpacer()) { + rowLeftX = Math.min(rowLeftX, rowKey.getX()); + } + } + if (rowLeftX == Integer.MAX_VALUE) rowLeftX = key.getX(); + return showPopupKeysKeyboard(popupParentKey, tracker, belowSourceKey, rowLeftX); } public boolean isInDraggingFinger() { diff --git a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java index d4686cbc1..20e1bf47c 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java @@ -47,6 +47,11 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane // When true, the panel is anchored below the source key (its top aligned to pointY) // instead of the default above-key placement (its bottom aligned to pointY). private boolean mShowBelowAnchor; + // Shortcut-row popup: when set (not NO_ROW_ALIGN), the panel's visible content left edge is + // pinned to this x (the source letter row's left edge) so the icons tile across the row and the + // swiped key maps to the icon directly above it — instead of re-anchoring on the swiped key. + public static final int NO_ROW_ALIGN = Integer.MIN_VALUE; + private int mRowAlignedLeftX = NO_ROW_ALIGN; private int mActivePointerId; @@ -145,6 +150,11 @@ public void setShowBelowAnchor(final boolean below) { mShowBelowAnchor = below; } + // Must be called before showPopupKeysPanel(...) for a given show; NO_ROW_ALIGN resets it. + public void setRowAlignedLeftX(final int rowLeftX) { + mRowAlignedLeftX = rowLeftX; + } + @SuppressLint("RtlHardcoded") // a key on the left is on the left, independent of layout direction private void showPopupKeysPanelInternal(final View parentView, final Controller controller, final int pointX, final int pointY) { @@ -152,7 +162,12 @@ private void showPopupKeysPanelInternal(final View parentView, final Controller final View container = getContainerView(); // The coordinates of panel's left-top corner in parentView's coordinate system. // We need to consider background drawable paddings. - final int x = pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft(); + // Default: anchor the panel so its default column sits under pointX (centered on the key). + // Row-aligned (shortcut-row popup): pin the visible content left edge to the source row's + // left edge so the icons tile across the row and the swiped key maps to the icon above it. + final int x = (mRowAlignedLeftX != NO_ROW_ALIGN) + ? mRowAlignedLeftX - container.getPaddingLeft() - getPaddingLeft() + : pointX - getDefaultCoordX() - container.getPaddingLeft() - getPaddingLeft(); // Above-anchor (default): panel bottom aligned to pointY (popup sits above the key). // Below-anchor: panel top aligned to pointY (popup sits below the key), mirroring the // padding compensation so the visible content edge lands on pointY either way.