Commit 4f8d9d1f authored by eekkelund's avatar eekkelund

[Keyboard] Move application upwards in case of input area is clicked and keyboard appeares

parent 2862bfd7
/*
* Copyright (C) 2013 Andrea Bernabei <and.bernabei@gmail.com>
* Copyright (C) 2013 Jolla Ltd.
* Copyright (C) 2017 Eetu Kahelin
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
......@@ -28,15 +30,14 @@ import QtQuick.Controls.Styles.Nemo 1.0
NemoWindow {
id: root
width: 320
height: 640
property alias header: toolBar
/*! \internal */
default property alias data: contentArea.data
property alias pageStack: stackView
property alias initialPage: stackView.initialItem
property bool applicationActive: Qt.application.active
property alias orientation: contentArea.uiOrientation
readonly property int isUiPortrait: orientation == Qt.PortraitOrientation || orientation == Qt.InvertedPortraitOrientation
......@@ -140,294 +141,376 @@ NemoWindow {
Item {
id: backgroundItem
anchors.centerIn: parent
// NOTE: Using Screen.height/width will cause issues when the app is not fullscreen (e.g. when testing using Qt desktop)
// because in that case the app window will be smaller but the content will still be for fullscreen size
width: __transpose ? Screen.height : Screen.width
height: __transpose ? Screen.width : Screen.height
anchors.fill: parent
rotation: rotationToTransposeToPortrait()
//This is the rotating item
Item {
id: contentArea
width: parent.width
height: parent.height
anchors.centerIn: parent
property int _horizontalDimension: parent ? parent.width : 0
property int _verticalDimension: parent ? parent.height : 0
property alias defaultOrientationTransition: orientationState.defaultTransition
// This is used for states switching
property int filteredOrientation
//this is the reliable value which changes during the orientation transition
property int uiOrientation
property bool orientationTransitionRunning: false
StackView {
id: stackView
anchors.top: root.isUiPortrait ? toolBar.bottom : parent.top
anchors.right: parent.right
anchors.left: root.isUiPortrait ? parent.left : toolBar.right
anchors.bottom: parent.bottom
clip: true
Component.onCompleted: stackInitialized = true
//IMPORTANT: this property makes it so that at app startup we wait for the initial page to be pushed
//before determining the initial ui orientation (see the states logic below)
//If we don't use this, the orientation will change first time based on NemoWindow's allowedOrientation,
//and the second time based on the allowedOrientations of the initialItem of the stack.
//Using this property avoids that, and make the UI directly start in the correct orientation
//TODO: find a cleaner way to do it (if there's any)
property bool stackInitialized: false
onStackInitializedChanged: if (stackInitialized) {
//set Screen.orientation as default, if allowed
if (root.isOrientationAllowed(Screen.orientation)) {
contentArea.filteredOrientation = Screen.orientation
} else {
//let the window handle it, it will fall back to an allowed orientation
root.fallbackToAnAllowedOrientation()
}
}
//this has to be a function, property won't work inside onCurrentItemChanged, as the property binding hasn't been updated yet there
//(hence we'd be using the old currentItem)
function _isCurrentItemNemoPage()
{
return currentItem && currentItem.hasOwnProperty("__isNemoPage")
}
//update orientation constraints when a Page is pushed/popped
onCurrentItemChanged: {
if (_isCurrentItemNemoPage())
root.orientationConstraintsChanged()
}
id: clipping
//This properties are accessible for free by the Page via Stack.view.<property>
readonly property int orientation: contentArea.uiOrientation
property alias allowedOrientations: root.allowedOrientations
property alias orientationTransitionRunning: contentArea.orientationTransitionRunning
z: 1
width: parent.width - (isUiLandscape ? stackView.panelSize : 0)
height: parent.height - (isUiPortrait ? stackView.panelSize : 0)
clip: stackView.panelSize > 0
Connections {
id: pageConn
target: stackView._isCurrentItemNemoPage() ? stackView.currentItem : null
onAllowedOrientationsChanged: root.orientationConstraintsChanged()
//This is the rotating item
Item {
id: contentArea
anchors.centerIn: parent
transform: Scale {
id: contentScale
property bool animationRunning: xAnim.running || yAnim.running
Behavior on xScale { NumberAnimation { id: xAnim; duration: 100 } }
Behavior on yScale { NumberAnimation { id: yAnim; duration: 100 } }
}
delegate: StackViewDelegate {
pushTransition: Component {
StackViewTransition {
PropertyAnimation {
target: enterItem
property: "x"
from: target.width
to: 0
duration: 500
easing.type: Easing.OutQuad
}
PropertyAnimation {
target: exitItem
property: "x"
from: 0
to: -target.width
duration: 500
easing.type: Easing.OutQuad
}
property int _horizontalDimension: parent ? parent.width : 0
property int _verticalDimension: parent ? parent.height : 0
property alias defaultOrientationTransition: orientationState.defaultTransition
// This is used for states switching
property int filteredOrientation
//this is the reliable value which changes during the orientation transition
property int uiOrientation
property bool orientationTransitionRunning: false
StackView {
id: stackView
anchors.top: root.isUiPortrait ? toolBar.bottom : parent.top
anchors.right: parent.right
anchors.left: root.isUiPortrait ? parent.left : toolBar.right
anchors.bottom: parent.bottom
property real panelSize: 0
property real previousImSize: 0
property real imSize: !root.applicationActive ? 0 : (isUiPortrait ? (root._transpose ? Qt.inputMethod.keyboardRectangle.width
: Qt.inputMethod.keyboardRectangle.height)
: (root._transpose ? Qt.inputMethod.keyboardRectangle.height
: Qt.inputMethod.keyboardRectangle.width))
onImSizeChanged: {
if (imSize <= 0 && previousImSize > 0) {
imShowAnimation.stop()
imHideAnimation.start()
} else if (imSize > 0 && previousImSize <= 0) {
imHideAnimation.stop()
imShowAnimation.to = imSize
imShowAnimation.start()
} else {
panelSize = imSize
}
previousImSize = imSize
}
popTransition: Component {
StackViewTransition {
PropertyAnimation {
target: enterItem
property: "x"
from: -target.width
to: 0
duration: 500
easing.type: Easing.OutQuad
}
PropertyAnimation {
target: exitItem
property: "x"
from: 0
to: target.width
duration: 500
easing.type: Easing.OutQuad
}
}
clip: true
Component.onCompleted: {
stackInitialized = true
}
//IMPORTANT: this property makes it so that at app startup we wait for the initial page to be pushed
//before determining the initial ui orientation (see the states logic below)
//If we don't use this, the orientation will change first time based on NemoWindow's allowedOrientation,
//and the second time based on the allowedOrientations of the initialItem of the stack.
//Using this property avoids that, and make the UI directly start in the correct orientation
//TODO: find a cleaner way to do it (if there's any)
property bool stackInitialized: false
onStackInitializedChanged: if (stackInitialized) {
//set Screen.orientation as default, if allowed
if (root.isOrientationAllowed(Screen.orientation)) {
contentArea.filteredOrientation = Screen.orientation
} else {
//let the window handle it, it will fall back to an allowed orientation
root.fallbackToAnAllowedOrientation()
}
}
}
}
//this has to be a function, property won't work inside onCurrentItemChanged, as the property binding hasn't been updated yet there
//(hence we'd be using the old currentItem)
function _isCurrentItemNemoPage()
{
return currentItem && currentItem.hasOwnProperty("__isNemoPage")
}
Header {
id: toolBar
stackView: root.pageStack
appWindow: root
//update orientation constraints when a Page is pushed/popped
onCurrentItemChanged: {
if (_isCurrentItemNemoPage())
root.orientationConstraintsChanged()
}
//used to animate the dimmer when pages are pushed/popped (see Header's QML code)
property alias __dimmer: headerDimmerContainer
}
//This properties are accessible for free by the Page via Stack.view.<property>
readonly property int orientation: contentArea.uiOrientation
property alias allowedOrientations: root.allowedOrientations
property alias orientationTransitionRunning: contentArea.orientationTransitionRunning
Item {
//This item handles the rotation of the dimmer.
//All this because QML doesn't have a horizontal gradient (unless you import GraphicalEffects)
//and having a container which doesn't rotate but just resizes makes it easier to rotate its inner
//child
id: headerDimmerContainer
//README: Don't use AnchorChanges for this item!
//Reason: state changes disable bindings while the transition from one state to another is running.
//This causes the dimmer not to follow the drawer when the drawer is closed right before the orientation change
anchors.top: isUiPortrait ? toolBar.bottom : parent.top
anchors.left: isUiPortrait ? parent.left : toolBar.right
anchors.right: isUiPortrait ? parent.right : undefined
anchors.bottom: isUiPortrait ? undefined : parent.bottom
//we only set the size in one orientation, the anchors will take care of the other
width: if (!isUiPortrait) Theme.itemHeightExtraSmall/2
height: if (isUiPortrait) Theme.itemHeightExtraSmall/2
//MAKE SURE THAT THE HEIGHT SPECIFIED BY THE THEME IS AN EVEN NUMBER, TO AVOID ROUNDING ERRORS IN THE LAYOUT
Rectangle {
id: headerDimmer
anchors.centerIn: parent
gradient: Gradient {
GradientStop { position: 0; color: Theme.backgroundColor }
GradientStop { position: 1; color: "transparent" }
Connections {
id: pageConn
target: stackView._isCurrentItemNemoPage() ? stackView.currentItem : null
onAllowedOrientationsChanged: root.orientationConstraintsChanged()
}
}
}
Item {
id: orientationState
state: 'Unanimated'
states: [
State {
name: 'Unanimated'
when: !stackView || !stackInitialized
},
State {
name: 'Portrait'
when: contentArea.filteredOrientation === Qt.PortraitOrientation// && stackInitialized
PropertyChanges {
target: contentArea
restoreEntryValues: false
width: _horizontalDimension
height: _verticalDimension
rotation: 0
uiOrientation: Qt.PortraitOrientation
}
PropertyChanges {
target: headerDimmer
height: Theme.itemHeightExtraSmall/2
width: parent.width
rotation: 0
}
},
State {
name: 'Landscape'
when: contentArea.filteredOrientation === Qt.LandscapeOrientation //&& stackInitialized
PropertyChanges {
target: contentArea
restoreEntryValues: false
width: _verticalDimension
height: _horizontalDimension
rotation: 90
uiOrientation: Qt.LandscapeOrientation
}
PropertyChanges {
target: headerDimmer
height: Theme.itemHeightExtraSmall/2
width: parent.height
rotation: -90
}
},
State {
name: 'PortraitInverted'
when: contentArea.filteredOrientation === Qt.InvertedPortraitOrientation //&& stackInitialized
PropertyChanges {
target: contentArea
restoreEntryValues: false
width: _horizontalDimension
height: _verticalDimension
rotation: 180
uiOrientation: Qt.InvertedPortraitOrientation
}
PropertyChanges {
target: headerDimmer
height: Theme.itemHeightExtraSmall/2
width: parent.width
rotation: 0
}
},
State {
name: 'LandscapeInverted'
when: contentArea.filteredOrientation === Qt.InvertedLandscapeOrientation //&& stackInitialized
PropertyChanges {
target: contentArea
restoreEntryValues: false
width: _verticalDimension
height: _horizontalDimension
rotation: 270
uiOrientation: Qt.InvertedLandscapeOrientation
delegate: StackViewDelegate {
pushTransition: Component {
StackViewTransition {
PropertyAnimation {
target: enterItem
property: "x"
from: target.width
to: 0
duration: 500
easing.type: Easing.OutQuad
}
PropertyAnimation {
target: exitItem
property: "x"
from: 0
to: -target.width
duration: 500
easing.type: Easing.OutQuad
}
}
}
PropertyChanges {
target: headerDimmer
height: Theme.itemHeightExtraSmall/2
width: parent.height
rotation: -90
popTransition: Component {
StackViewTransition {
PropertyAnimation {
target: enterItem
property: "x"
from: -target.width
to: 0
duration: 500
easing.type: Easing.OutQuad
}
PropertyAnimation {
target: exitItem
property: "x"
from: 0
to: target.width
duration: 500
easing.type: Easing.OutQuad
}
}
}
}
]
property Transition defaultTransition: Transition {
to: 'Portrait,Landscape,PortraitInverted,LandscapeInverted'
from: 'Portrait,Landscape,PortraitInverted,LandscapeInverted'
}
SequentialAnimation {
PropertyAction {
target: contentArea
property: 'orientationTransitionRunning'
value: true
id: imHideAnimation
PauseAnimation {
duration: 200
}
NumberAnimation {
target: contentArea
property: 'opacity'
target: stackView
property: 'panelSize'
to: 0
duration: 150
duration:200
easing.type: Easing.InOutQuad
}
PropertyAction {
target: contentArea
properties: 'width,height,rotation,uiOrientation'
}
AnchorAnimation {
duration: 0
}
PropertyAction {
target: headerDimmer
properties: 'width,height,rotation'
}
NumberAnimation {
id: imShowAnimation
target: stackView
property: 'panelSize'
duration: 200
easing.type: Easing.InOutQuad
}
}
Header {
id: toolBar
stackView: root.pageStack
appWindow: root
//used to animate the dimmer when pages are pushed/popped (see Header's QML code)
property alias __dimmer: headerDimmerContainer
}
Item {
//This item handles the rotation of the dimmer.
//All this because QML doesn't have a horizontal gradient (unless you import GraphicalEffects)
//and having a container which doesn't rotate but just resizes makes it easier to rotate its inner
//child
id: headerDimmerContainer
//README: Don't use AnchorChanges for this item!
//Reason: state changes disable bindings while the transition from one state to another is running.
//This causes the dimmer not to follow the drawer when the drawer is closed right before the orientation change
anchors.top: isUiPortrait ? toolBar.bottom : parent.top
anchors.left: isUiPortrait ? parent.left : toolBar.right
anchors.right: isUiPortrait ? parent.right : undefined
anchors.bottom: isUiPortrait ? undefined : parent.bottom
//we only set the size in one orientation, the anchors will take care of the other
width: if (!isUiPortrait) Theme.itemHeightExtraSmall/2
height: if (isUiPortrait) Theme.itemHeightExtraSmall/2
//MAKE SURE THAT THE HEIGHT SPECIFIED BY THE THEME IS AN EVEN NUMBER, TO AVOID ROUNDING ERRORS IN THE LAYOUT
Rectangle {
id: headerDimmer
anchors.centerIn: parent
gradient: Gradient {
GradientStop { position: 0; color: Theme.backgroundColor }
GradientStop { position: 1; color: "transparent" }
}
NumberAnimation {
target: contentArea
property: 'opacity'
to: 1
duration: 150
}
}
Item {
id: orientationState
state: 'Unanimated'
states: [
State {
name: 'Unanimated'
when: !stackView || !stackInitialized
},
State {
name: 'Portrait'
when: contentArea.filteredOrientation === Qt.PortraitOrientation// && stackInitialized
PropertyChanges {
target: contentArea
restoreEntryValues: false
width: _horizontalDimension
height: _verticalDimension
rotation: 0
uiOrientation: Qt.PortraitOrientation
}
PropertyChanges {
target: headerDimmer
height: Theme.itemHeightExtraSmall/2
width: parent.width
rotation: 0
}
AnchorChanges {
target: clipping
anchors.top: parent.top
anchors.left: parent.left
anchors.right: undefined
anchors.bottom: undefined
}
},
State {
name: 'Landscape'
when: contentArea.filteredOrientation === Qt.LandscapeOrientation //&& stackInitialized
PropertyChanges {
target: contentArea
restoreEntryValues: false
width: _verticalDimension
height: _horizontalDimension
rotation: 90
uiOrientation: Qt.LandscapeOrientation
}
PropertyChanges {
target: headerDimmer
height: Theme.itemHeightExtraSmall/2
width: parent.height
rotation: -90
}
AnchorChanges {
target: clipping
anchors.top: undefined
anchors.left: undefined
anchors.right: parent.right
anchors.bottom: parent.bottom
}
},
State {
name: 'PortraitInverted'
when: contentArea.filteredOrientation === Qt.InvertedPortraitOrientation //&& stackInitialized
PropertyChanges {
target: contentArea
restoreEntryValues: false
width: _horizontalDimension
height: _verticalDimension
rotation: 180
uiOrientation: Qt.InvertedPortraitOrientation
}
PropertyChanges {
target: headerDimmer
height: Theme.itemHeightExtraSmall/2
width: parent.width
rotation: 0
}
AnchorChanges {
target: clipping
anchors.top: undefined
anchors.left: undefined
anchors.right: parent.right
anchors.bottom: parent.bottom
}
},
State {
name: 'LandscapeInverted'
when: contentArea.filteredOrientation === Qt.InvertedLandscapeOrientation //&& stackInitialized
PropertyChanges {
target: contentArea
restoreEntryValues: false
width: _verticalDimension
height: _horizontalDimension
rotation: 270
uiOrientation: Qt.InvertedLandscapeOrientation
}
PropertyChanges {
target: headerDimmer
height: Theme.itemHeightExtraSmall/2
width: parent.height
rotation: -90
}
AnchorChanges {
target: clipping
anchors.top: undefined
anchors.left: parent.left
anchors.right: undefined
anchors.bottom: parent.bottom
}
}
PropertyAction {
target: contentArea
property: 'orientationTransitionRunning'
value: false
]
property Transition defaultTransition: Transition {
to: 'Portrait,Landscape,PortraitInverted,LandscapeInverted'
from: 'Portrait,Landscape,PortraitInverted,LandscapeInverted'
SequentialAnimation {
PropertyAction {
target: contentArea
property: 'orientationTransitionRunning'
value: true
}
NumberAnimation {
target: contentArea
property: 'opacity'
to: 0
duration: 150
}
PropertyAction {
target: contentArea
properties: 'width,height,rotation,uiOrientation'
}
AnchorAnimation {
duration: 0
}
PropertyAction {
target: headerDimmer
properties: 'width,height,rotation'
}
NumberAnimation {
target: contentArea
property: 'opacity'
to: 1
duration: 150
}
PropertyAction {
target: contentArea
property: 'orientationTransitionRunning'
value: false
}
}
}
}
Component.onCompleted: {
if (transitions.length === 0) {
transitions = [ defaultTransition ]
Component.onCompleted: {
if (transitions.length === 0) {
transitions = [ defaultTransition ]
}
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment