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 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 * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public * modify it under the terms of the GNU Library General Public
...@@ -28,15 +30,14 @@ import QtQuick.Controls.Styles.Nemo 1.0 ...@@ -28,15 +30,14 @@ import QtQuick.Controls.Styles.Nemo 1.0
NemoWindow { NemoWindow {
id: root id: root
width: 320
height: 640
property alias header: toolBar property alias header: toolBar
/*! \internal */ /*! \internal */
default property alias data: contentArea.data default property alias data: contentArea.data
property alias pageStack: stackView property alias pageStack: stackView
property alias initialPage: stackView.initialItem property alias initialPage: stackView.initialItem
property bool applicationActive: Qt.application.active
property alias orientation: contentArea.uiOrientation property alias orientation: contentArea.uiOrientation
readonly property int isUiPortrait: orientation == Qt.PortraitOrientation || orientation == Qt.InvertedPortraitOrientation readonly property int isUiPortrait: orientation == Qt.PortraitOrientation || orientation == Qt.InvertedPortraitOrientation
...@@ -140,294 +141,376 @@ NemoWindow { ...@@ -140,294 +141,376 @@ NemoWindow {
Item { Item {
id: backgroundItem id: backgroundItem
anchors.fill: parent
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
rotation: rotationToTransposeToPortrait() rotation: rotationToTransposeToPortrait()
//This is the rotating item
Item { Item {
id: contentArea id: clipping
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()
}
//This properties are accessible for free by the Page via Stack.view.<property> z: 1
readonly property int orientation: contentArea.uiOrientation width: parent.width - (isUiLandscape ? stackView.panelSize : 0)
property alias allowedOrientations: root.allowedOrientations height: parent.height - (isUiPortrait ? stackView.panelSize : 0)
property alias orientationTransitionRunning: contentArea.orientationTransitionRunning clip: stackView.panelSize > 0
Connections { //This is the rotating item
id: pageConn Item {
target: stackView._isCurrentItemNemoPage() ? stackView.currentItem : null id: contentArea
onAllowedOrientationsChanged: root.orientationConstraintsChanged() 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 { property int _horizontalDimension: parent ? parent.width : 0
pushTransition: Component { property int _verticalDimension: parent ? parent.height : 0
StackViewTransition {
PropertyAnimation { property alias defaultOrientationTransition: orientationState.defaultTransition
target: enterItem
property: "x" // This is used for states switching
from: target.width property int filteredOrientation
to: 0
duration: 500 //this is the reliable value which changes during the orientation transition
easing.type: Easing.OutQuad property int uiOrientation
}
PropertyAnimation { property bool orientationTransitionRunning: false
target: exitItem
property: "x" StackView {
from: 0 id: stackView
to: -target.width anchors.top: root.isUiPortrait ? toolBar.bottom : parent.top
duration: 500 anchors.right: parent.right
easing.type: Easing.OutQuad 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 { clip: true
PropertyAnimation { Component.onCompleted: {
target: enterItem stackInitialized = true
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
}
}
} }
//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 { //update orientation constraints when a Page is pushed/popped
id: toolBar onCurrentItemChanged: {
stackView: root.pageStack if (_isCurrentItemNemoPage())
appWindow: root root.orientationConstraintsChanged()
}
//used to animate the dimmer when pages are pushed/popped (see Header's QML code) //This properties are accessible for free by the Page via Stack.view.<property>
property alias __dimmer: headerDimmerContainer readonly property int orientation: contentArea.uiOrientation
} property alias allowedOrientations: root.allowedOrientations
property alias orientationTransitionRunning: contentArea.orientationTransitionRunning
Item { Connections {
//This item handles the rotation of the dimmer. id: pageConn
//All this because QML doesn't have a horizontal gradient (unless you import GraphicalEffects) target: stackView._isCurrentItemNemoPage() ? stackView.currentItem : null
//and having a container which doesn't rotate but just resizes makes it easier to rotate its inner onAllowedOrientationsChanged: root.orientationConstraintsChanged()
//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" }
} }
}
}
Item { delegate: StackViewDelegate {
id: orientationState pushTransition: Component {
StackViewTransition {
state: 'Unanimated' PropertyAnimation {
target: enterItem
states: [ property: "x"
State { from: target.width
name: 'Unanimated' to: 0
when: !stackView || !stackInitialized duration: 500
}, easing.type: Easing.OutQuad
State { }
name: 'Portrait' PropertyAnimation {
when: contentArea.filteredOrientation === Qt.PortraitOrientation// && stackInitialized target: exitItem
PropertyChanges { property: "x"
target: contentArea from: 0
restoreEntryValues: false to: -target.width
width: _horizontalDimension duration: 500
height: _verticalDimension easing.type: Easing.OutQuad
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
} }
PropertyChanges { popTransition: Component {
target: headerDimmer StackViewTransition {
height: Theme.itemHeightExtraSmall/2 PropertyAnimation {
width: parent.height target: enterItem
rotation: -90 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 { SequentialAnimation {
PropertyAction { id: imHideAnimation
target: contentArea PauseAnimation {
property: 'orientationTransitionRunning' duration: 200
value: true
} }
NumberAnimation { NumberAnimation {
target: contentArea target: stackView
property: 'opacity' property: 'panelSize'
to: 0 to: 0
duration: 150 duration:200
easing.type: Easing.InOutQuad
} }
PropertyAction { }
target: contentArea
properties: 'width,height,rotation,uiOrientation' NumberAnimation {
} id: imShowAnimation
AnchorAnimation { target: stackView
duration: 0 property: 'panelSize'
} duration: 200
PropertyAction { easing.type: Easing.InOutQuad
target: headerDimmer }
properties: 'width,height,rotation'
}
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 Item {
duration: 150 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' property Transition defaultTransition: Transition {
value: false 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: { Component.onCompleted: {
if (transitions.length === 0) { if (transitions.length === 0) {
transitions = [ defaultTransition ] 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