iOS: Center IM cursor rectangle within available space when showing keyboard

The cursor rectangle is translated into screen coordinates, and compared
against the screen geometry after subtracting the future keyboard rect
(which is already in screen coordinates). If the two do not overlap
completely, the root view is shifted accordingly so that the cursor
rectangle is placed in the center of the available space.

A future improvement would be to first check if centering the input
item's clip rectangle would bring the cursor within the available
geometry, before falling back to using the cursor rectangle. This
would look better for multi-line text inputs where the cursor is
not in the center.

Task-number: QTBUG-46747
Change-Id: If9b551b4d297e2a1f6d7f84b81628fa65c08edfd
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
Tor Arne Vestbø 2017-01-03 18:03:17 +01:00
parent 34f82b8abc
commit 3c99bddb84

View File

@ -498,15 +498,32 @@ void QIOSInputContext::scrollToCursor()
return; return;
} }
const int margin = 20; QWindow *focusWindow = qApp->focusWindow();
QRectF translatedCursorPos = qApp->inputMethod()->cursorRectangle(); QRect cursorRect = qApp->inputMethod()->cursorRectangle().translated(focusWindow->geometry().topLeft()).toRect();
translatedCursorPos.translate(focusView().qwindow->geometry().topLeft()); if (cursorRect.isNull()) {
scroll(0);
return;
}
qreal keyboardY = [rootView convertRect:m_keyboardState.keyboardEndRect fromView:nil].origin.y; // Add some padding so that the cusor does not end up directly above the keyboard
int statusBarY = qGuiApp->primaryScreen()->availableGeometry().y(); static const int kCursorRectPadding = 20;
cursorRect.adjust(0, -kCursorRectPadding, 0, kCursorRectPadding);
scroll((translatedCursorPos.bottomLeft().y() < keyboardY - margin) ? 0 // We explicitly ask for the geometry of the screen instead of the availableGeometry,
: qMin(rootView.bounds.size.height - keyboardY, translatedCursorPos.y() - statusBarY - margin)); // as we hide the statusbar when scrolling the screen, so the available geometry will
// include the space taken by the status bar at the moment.
QRect screenGeometry = focusWindow->screen()->geometry();
QRect keyboardGeometry = QRectF::fromCGRect(m_keyboardState.keyboardEndRect).toRect();
QRect availableGeometry = (QRegion(screenGeometry) - keyboardGeometry).boundingRect();
if (!availableGeometry.contains(cursorRect, true)) {
qImDebug() << "cursor rect" << cursorRect << "not fully within" << availableGeometry;
int scrollToCenter = -(availableGeometry.center() - cursorRect.center()).y();
int scrollToBottom = focusWindow->screen()->geometry().bottom() - availableGeometry.bottom();
scroll(qMin(scrollToCenter, scrollToBottom));
} else {
scroll(0);
}
} }
void QIOSInputContext::scroll(int y) void QIOSInputContext::scroll(int y)
@ -519,6 +536,8 @@ void QIOSInputContext::scroll(int y)
if (CATransform3DEqualToTransform(translationTransform, rootView.layer.sublayerTransform)) if (CATransform3DEqualToTransform(translationTransform, rootView.layer.sublayerTransform))
return; return;
qImDebug() << "scrolling root view to y =" << -y;
QPointer<QIOSInputContext> self = this; QPointer<QIOSInputContext> self = this;
[UIView animateWithDuration:m_keyboardState.animationDuration delay:0 [UIView animateWithDuration:m_keyboardState.animationDuration delay:0
options:(m_keyboardState.animationCurve << 16) | UIViewAnimationOptionBeginFromCurrentState options:(m_keyboardState.animationCurve << 16) | UIViewAnimationOptionBeginFromCurrentState