Skip to content

Instantly share code, notes, and snippets.

@jbache
Created December 1, 2014 15:02
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save jbache/2b625d40efd4c344ab20 to your computer and use it in GitHub Desktop.
Save jbache/2b625d40efd4c344ab20 to your computer and use it in GitHub Desktop.
Qt Quick Navigation Drawer
/*
Copyright (c) 2014 Cutehacks A/S
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
About:
This is a Qt Quick implementation of the Android Navigation drawer
Questions, suggestions or requests can be directed to jens@cutehacks.com
www.cutehacks.com
*/
import QtQuick 2.2
Rectangle {
id: panel
property bool open: false // The open or close state of the drawer
property int position: Qt.LeftEdge // Which side of the screen the drawer is on, can be Qt.LeftEdge or Qt.RightEdge
property Item visualParent: parent // The item the drawer should cover, by default the parent of the Drawer
// The fraction showing how open the drawer is
readonly property real panelProgress: (panel.x - _minimumX) / (_maximumX - _minimumX)
function show() { open = true; }
function hide() { open = false; }
function toggle() {
if (open) open = false;
else open = true;
}
// Internal
default property alias data: contentItem.data
readonly property real expandedFraction: 0.78 // How big fraction of the screen realesatate that is covered by an open menu
readonly property real _scaleFactor: _rootItem.width / 320 // Note, this should really be application global
readonly property int _pullThreshold: panel.width/2
readonly property int _slideDuration: 260
readonly property int _collapsedX: _rightEdge ? _rootItem.width : - panel.width
readonly property int _expandedWidth: expandedFraction * _rootItem.width
readonly property int _expandedX: _rightEdge ? _rootItem.width - width : 0
readonly property bool _rightEdge: position === Qt.RightEdge
readonly property int _minimumX: _rightEdge ? _rootItem.width - panel.width : -panel.width
readonly property int _maximumX: _rightEdge ? _rootItem.width : 0
readonly property int _openMarginSize: 20 * _scaleFactor
property real _velocity: 0
property real _oldMouseX: -1
function _findRootItem() {
var p = panel;
while (p.parent != null)
p = p.parent;
return p;
}
property Item _rootItem: _findRootItem()
height: parent.height
on_RightEdgeChanged: _setupAnchors()
onOpenChanged: completeSlideDirection()
width: _expandedWidth
x: _collapsedX
z: 10
function _setupAnchors() { // Note that we can't reliably apply anchors using bindings
_rootItem = _findRootItem();
shadow.anchors.right = undefined;
shadow.anchors.left = undefined;
mouse.anchors.left = undefined;
mouse.anchors.right = undefined;
if (_rightEdge) {
mouse.anchors.right = mouse.parent.right;
shadow.anchors.right = panel.left;
} else {
mouse.anchors.left = mouse.parent.left;
shadow.anchors.left = panel.right;
}
slideAnimation.enabled = false;
panel.x =_rightEdge ? _rootItem.width : - panel.width;
slideAnimation.enabled = true;
}
function completeSlideDirection() {
if (open) {
panel.x = _expandedX;
} else {
panel.x = _collapsedX;
Qt.inputMethod.hide();
}
}
function handleRelease() {
var velocityThreshold = 5 * _scaleFactor;
if ((_rightEdge && _velocity > velocityThreshold) ||
(!_rightEdge && _velocity < -velocityThreshold)) {
panel.open = false;
completeSlideDirection()
} else if ((_rightEdge && _velocity < -velocityThreshold) ||
(!_rightEdge && _velocity > velocityThreshold)) {
panel.open = true;
completeSlideDirection()
} else if ((_rightEdge && panel.x < _expandedX + _pullThreshold) ||
(!_rightEdge && panel.x > _expandedX - _pullThreshold) ) {
panel.open = true;
panel.x = _expandedX;
} else {
panel.open = false;
panel.x = _collapsedX;
}
}
function handleClick(mouse) {
if ((_rightEdge && mouse.x < panel.x ) || mouse.x > panel.width) {
open = false;
}
}
onPositionChanged: {
if (! (position === Qt.RightEdge || position === Qt.LeftEdge ) ) {
console.warn("SlidePanel: Unsupported position.")
}
}
Behavior on x {
id: slideAnimation
enabled: !mouse.drag.active
NumberAnimation {
duration: _slideDuration
easing.type: Easing.OutCubic
}
}
Connections {
target: _rootItem
onWidthChanged: {
slideAnimation.enabled = false
panel.completeSlideDirection()
slideAnimation.enabled = true
}
}
NumberAnimation on x {
id: holdAnimation
to: _collapsedX + (_openMarginSize * (_rightEdge ? -1 : 1))
running : false
easing.type: Easing.OutCubic
duration: 200
}
MouseArea {
id: mouse
parent: _rootItem
y: visualParent.y
width: open ? _rootItem.width : _openMarginSize
height: visualParent.height
onPressed: if (!open) holdAnimation.restart();
onClicked: handleClick(mouse)
drag.target: panel
drag.minimumX: _minimumX
drag.maximumX: _maximumX
drag.axis: Qt.Horizontal
drag.onActiveChanged: if (active) holdAnimation.stop()
onReleased: handleRelease()
z: open ? 1 : 0
onMouseXChanged: {
_velocity = (mouse.x - _oldMouseX);
_oldMouseX = mouse.x;
}
}
Rectangle {
id: backgroundDimmer
parent: visualParent
anchors.fill: parent
opacity: 0.5 * Math.min(1, Math.abs(panel.x - _collapsedX) / _rootItem.width/2)
color: "black"
}
Item {
id: contentItem
parent: _rootItem
width: panel.width
height: panel.height
x: panel.x
y: panel.y
z: open ? 5 : 0
clip: true
}
Item {
id: shadow
anchors.left: panel.right
anchors.leftMargin: _rightEdge ? 0 : 4 * _scaleFactor
height: parent.height
Rectangle {
height: 4 * _scaleFactor
width: panel.height
rotation: 90
opacity: Math.min(1, Math.abs(panel.x - _collapsedX)/_openMarginSize)
transformOrigin: Item.TopLeft
gradient: Gradient{
GradientStop { position: _rightEdge ? 1 : 0 ; color: "#00000000"}
GradientStop { position: _rightEdge ? 0 : 1 ; color: "#2c000000"}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment