250 lines
8.9 KiB
QML
250 lines
8.9 KiB
QML
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 2.0 or (at your option) the GNU General
|
|
** Public license version 3 or any later version approved by the KDE Free
|
|
** Qt Foundation. The licenses are as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
import QtQuick 2.2
|
|
import QtQuick.Controls 1.2
|
|
import QtQuick.Controls.Private 1.0
|
|
|
|
Item {
|
|
id: content
|
|
|
|
property Component menuItemDelegate
|
|
property Component scrollIndicatorStyle
|
|
property Component scrollerStyle
|
|
property var itemsModel
|
|
property int minWidth: 100
|
|
property real maxHeight: 800
|
|
readonly property bool mousePressed: hoverArea.pressed
|
|
|
|
signal triggered(var item)
|
|
|
|
function menuItemAt(index) {
|
|
list.currentIndex = index
|
|
return list.currentItem
|
|
}
|
|
|
|
width: Math.max(list.contentWidth, minWidth)
|
|
height: Math.min(list.contentHeight, fittedMaxHeight)
|
|
|
|
readonly property int currentIndex: __menu.__currentIndex
|
|
property Item currentItem: null
|
|
property int itemHeight: 23
|
|
|
|
Component.onCompleted: {
|
|
var children = list.contentItem.children
|
|
for (var i = 0; i < list.count; i++) {
|
|
var child = children[i]
|
|
if (child.visible && child.styleData.type === MenuItemType.Item) {
|
|
itemHeight = children[i].height
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
readonly property int fittingItems: Math.floor((maxHeight - downScroller.height) / itemHeight)
|
|
readonly property real fittedMaxHeight: itemHeight * fittingItems + downScroller.height
|
|
readonly property bool shouldUseScrollers: scrollView.style === emptyScrollerStyle && itemsModel.length > fittingItems
|
|
readonly property real upScrollerHeight: upScroller.visible ? upScroller.height : 0
|
|
readonly property real downScrollerHeight: downScroller.visible ? downScroller.height : 0
|
|
property var oldMousePos: undefined
|
|
property var openedSubmenu: null
|
|
|
|
function updateCurrentItem(mouse) {
|
|
var pos = mapToItem(list.contentItem, mouse.x, mouse.y)
|
|
var dx = 0
|
|
var dy = 0
|
|
var dist = 0
|
|
if (openedSubmenu && oldMousePos !== undefined) {
|
|
dx = mouse.x - oldMousePos.x
|
|
dy = mouse.y - oldMousePos.y
|
|
dist = Math.sqrt(dx * dx + dy * dy)
|
|
}
|
|
oldMousePos = mouse
|
|
if (openedSubmenu && dist > 5) {
|
|
var menuRect = __menu.__popupGeometry
|
|
var submenuRect = openedSubmenu.__popupGeometry
|
|
var angle = Math.atan2(dy, dx)
|
|
var ds = 0
|
|
if (submenuRect.x > menuRect.x) {
|
|
ds = menuRect.width - oldMousePos.x
|
|
} else {
|
|
angle = Math.PI - angle
|
|
ds = oldMousePos.x
|
|
}
|
|
var above = submenuRect.y - menuRect.y - oldMousePos.y
|
|
var below = submenuRect.height - above
|
|
var minAngle = Math.atan2(above, ds)
|
|
var maxAngle = Math.atan2(below, ds)
|
|
// This tests that the current mouse position is in
|
|
// the triangle defined by the previous mouse position
|
|
// and the submenu's top-left and bottom-left corners.
|
|
if (minAngle < angle && angle < maxAngle) {
|
|
sloppyTimer.start()
|
|
return
|
|
}
|
|
}
|
|
|
|
if (!currentItem || !currentItem.contains(Qt.point(pos.x - currentItem.x, pos.y - currentItem.y))) {
|
|
if (currentItem && !hoverArea.pressed
|
|
&& currentItem.styleData.type === MenuItemType.Menu) {
|
|
currentItem.__closeSubMenu()
|
|
openedSubmenu = null
|
|
}
|
|
currentItem = list.itemAt(pos.x, pos.y)
|
|
if (currentItem) {
|
|
__menu.__currentIndex = currentItem.__menuItemIndex
|
|
if (currentItem.styleData.type === MenuItemType.Menu) {
|
|
showCurrentItemSubMenu(false)
|
|
}
|
|
} else {
|
|
__menu.__currentIndex = -1
|
|
}
|
|
}
|
|
}
|
|
|
|
function showCurrentItemSubMenu(immediately) {
|
|
if (!currentItem.__menuItem.__popupVisible) {
|
|
currentItem.__showSubMenu(immediately)
|
|
openedSubmenu = currentItem.__menuItem
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: sloppyTimer
|
|
interval: 1000
|
|
|
|
// Stop timer as soon as we hover one of the submenu items
|
|
property int currentIndex: openedSubmenu ? openedSubmenu.__currentIndex : -1
|
|
onCurrentIndexChanged: if (currentIndex !== -1) stop()
|
|
|
|
onTriggered: {
|
|
if (openedSubmenu && openedSubmenu.__currentIndex === -1)
|
|
updateCurrentItem(oldMousePos)
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: emptyScrollerStyle
|
|
Style {
|
|
padding { left: 0; right: 0; top: 0; bottom: 0 }
|
|
property bool scrollToClickedPosition: false
|
|
property Component frame: Item { visible: false }
|
|
property Component corner: Item { visible: false }
|
|
property Component __scrollbar: Item { visible: false }
|
|
}
|
|
}
|
|
|
|
ScrollView {
|
|
id: scrollView
|
|
anchors {
|
|
fill: parent
|
|
topMargin: upScrollerHeight
|
|
bottomMargin: downScrollerHeight
|
|
}
|
|
|
|
style: scrollerStyle || emptyScrollerStyle
|
|
__wheelAreaScrollSpeed: itemHeight
|
|
|
|
ListView {
|
|
id: list
|
|
model: itemsModel
|
|
delegate: menuItemDelegate
|
|
snapMode: ListView.SnapToItem
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
highlightFollowsCurrentItem: true
|
|
highlightMoveDuration: 0
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: hoverArea
|
|
anchors.left: scrollView.left
|
|
width: scrollView.width - scrollView.__verticalScrollBar.width
|
|
height: parent.height
|
|
|
|
hoverEnabled: Settings.hoverEnabled
|
|
acceptedButtons: Qt.AllButtons
|
|
|
|
onPositionChanged: updateCurrentItem({ "x": mouse.x, "y": mouse.y })
|
|
onPressed: updateCurrentItem({ "x": mouse.x, "y": mouse.y })
|
|
onReleased: {
|
|
if (currentItem && currentItem.__menuItem.enabled) {
|
|
if (currentItem.styleData.type === MenuItemType.Menu) {
|
|
showCurrentItemSubMenu(true)
|
|
} else {
|
|
content.triggered(currentItem)
|
|
}
|
|
}
|
|
}
|
|
onExited: {
|
|
if (currentItem && !currentItem.__menuItem.__popupVisible) {
|
|
currentItem = null
|
|
__menu.__currentIndex = -1
|
|
}
|
|
}
|
|
|
|
MenuContentScroller {
|
|
id: upScroller
|
|
direction: Qt.UpArrow
|
|
visible: shouldUseScrollers && !list.atYBeginning
|
|
function scrollABit() { list.contentY -= itemHeight }
|
|
}
|
|
|
|
MenuContentScroller {
|
|
id: downScroller
|
|
direction: Qt.DownArrow
|
|
visible: shouldUseScrollers && !list.atYEnd
|
|
function scrollABit() { list.contentY += itemHeight }
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
interval: 1
|
|
running: true
|
|
repeat: false
|
|
onTriggered: list.positionViewAtIndex(currentIndex, !scrollView.__style
|
|
? ListView.Center : ListView.Beginning)
|
|
}
|
|
|
|
Binding {
|
|
target: scrollView.__verticalScrollBar
|
|
property: "singleStep"
|
|
value: itemHeight
|
|
}
|
|
}
|