From 40c0710f2b223bc80d29221e6c974f50c3b5ddc1 Mon Sep 17 00:00:00 2001 From: James Turner Date: Tue, 18 Sep 2018 17:33:46 +0100 Subject: [PATCH] Change launcher pop-up to support scrolling Use an internal window (instead of a real OS window) for popup choices, and cap the maximum size. Use a scrollbar when the number of items is too large, and adjust the position to fit in the window. --- src/GUI/qml/AirportEntry.qml | 5 +- src/GUI/qml/Launcher.qml | 2 - src/GUI/qml/Menu.qml | 4 +- src/GUI/qml/Overlay.qml | 35 ++++++++++ src/GUI/qml/OverlayMenu.qml | 100 ++++++++++++++++++++++++++++ src/GUI/qml/PopupChoice.qml | 112 ++++---------------------------- src/GUI/qml/PopupChoiceItem.qml | 21 ++++++ src/GUI/resources.qrc | 2 + 8 files changed, 174 insertions(+), 107 deletions(-) create mode 100644 src/GUI/qml/OverlayMenu.qml create mode 100644 src/GUI/qml/PopupChoiceItem.qml diff --git a/src/GUI/qml/AirportEntry.qml b/src/GUI/qml/AirportEntry.qml index 527235a36..c5d9f43ab 100644 --- a/src/GUI/qml/AirportEntry.qml +++ b/src/GUI/qml/AirportEntry.qml @@ -1,5 +1,4 @@ import QtQuick 2.4 -import QtQuick.Window 2.0 import FlightGear 1.0 import FlightGear.Launcher 1.0 import "." @@ -66,7 +65,9 @@ FocusScope onActiveFocusChanged: { if (activeFocus) { - OverlayShared.globalOverlay.showOverlayAtItemOffset(overlay, root, Qt.point(xOffsetForEditFrame, root.height + Style.margin)) + OverlayShared.globalOverlay.showOverlayAtItemOffset(overlay, root, + Qt.point(xOffsetForEditFrame, root.height + Style.margin), + false /* don't offset */); } else { OverlayShared.globalOverlay.dismissOverlay() searchCompleter.clear(); diff --git a/src/GUI/qml/Launcher.qml b/src/GUI/qml/Launcher.qml index 644924dbb..61c60d3bd 100644 --- a/src/GUI/qml/Launcher.qml +++ b/src/GUI/qml/Launcher.qml @@ -167,6 +167,4 @@ Item { OverlayShared.globalOverlay = this } } - - } diff --git a/src/GUI/qml/Menu.qml b/src/GUI/qml/Menu.qml index 783223489..dc0e26b2a 100644 --- a/src/GUI/qml/Menu.qml +++ b/src/GUI/qml/Menu.qml @@ -62,7 +62,7 @@ Item { width: parent.width - 2 // helper function called by BaseMenuItem on its parent, i.e, -// us, to requestc closing the menu +// us, to request closing the menu function requestClose() { root.close(); @@ -75,4 +75,4 @@ Item { // menu items get inserted here } } -} \ No newline at end of file +} diff --git a/src/GUI/qml/Overlay.qml b/src/GUI/qml/Overlay.qml index 537c2246c..611559406 100644 --- a/src/GUI/qml/Overlay.qml +++ b/src/GUI/qml/Overlay.qml @@ -1,6 +1,10 @@ import QtQuick 2.4 Item { + id: root + + readonly property bool dismissOnClickOutside: activeOverlayLoader.item && activeOverlayLoader.item.hasOwnProperty("dismissOnClickOutside") ? + activeOverlayLoader.item.dismissOnClickOutside : false function showOverlay(comp) { @@ -23,9 +27,40 @@ Item { activeOverlayLoader.visible = false; } + MouseArea { + anchors.fill: parent + hoverEnabled: true + visible: activeOverlayLoader.visible && root.dismissOnClickOutside + onClicked: root.dismissOverlay() + } + Loader { id: activeOverlayLoader // no size, size to the component + + onStatusChanged: { + if (status == Loader.Ready) { + if (item.hasOwnProperty("adjustYPosition")) { + // setting position here doesn't work, so we use a 0-interval + // timer to do it shortly afterwards + adjustPosition.start(); + } + } + } + + function adjustY() + { + var overflowHeight = (y + item.height) - root.height; + if (overflowHeight > 0) { + activeOverlayLoader.y = Math.max(0, y - overflowHeight) + } + } + + Timer { + id: adjustPosition + interval: 0 + onTriggered: activeOverlayLoader.adjustY(); + } } } diff --git a/src/GUI/qml/OverlayMenu.qml b/src/GUI/qml/OverlayMenu.qml new file mode 100644 index 000000000..fbe293631 --- /dev/null +++ b/src/GUI/qml/OverlayMenu.qml @@ -0,0 +1,100 @@ +import QtQuick 2.4 +import FlightGear 1.0 +import "." + +Rectangle { + id: root + + property var model: undefined + property int currentIndex: 0 + property string headerText: "" + property int maximumPermittedHeight: 450 + + color: "white" + border.width: 1 + border.color: Style.minorFrameColor + width: flick.width + Style.margin + height: flick.height + Style.margin + clip: true + + // Overlay control properties + property bool dismissOnClickOutside: true + property bool adjustYPosition: true + + signal select(var index); + + Component.onCompleted: { + computeMenuWidth(); + } + + function close() + { + OverlayShared.globalOverlay.dismissOverlay() + } + + function doSelect(index) + { + select(index); + close(); + } + + function computeMenuWidth() + { + var minWidth = 0; + for (var i = 0; i < choicesRepeater.count; i++) { + minWidth = Math.max(minWidth, choicesRepeater.itemAt(i).implicitWidth); + } + if (root.haveHeader()) + minWidth = Math.max(minWidth, header.width); + flick.width = minWidth + scroller.width + } + + function haveHeader() + { + return headerText !== ""; + } + + Flickable { + id: flick + anchors.centerIn: parent + + height: Math.min(root.maximumPermittedHeight, contentHeight); + contentHeight: itemsColumn.childrenRect.height + + Column { + id: itemsColumn + width: flick.width + spacing: Style.margin + + PopupChoiceItem { + id: header + text: root.headerText + visible: root.haveHeader() + onSelect: root.doSelect(-1); + } + + // main item repeater + Repeater { + id: choicesRepeater + model: root.model + delegate: PopupChoiceItem { + isCurrentIndex: root.currentIndex === model.index + onSelect: root.doSelect(index) + text: model && model.hasOwnProperty(displayRole) ? model[displayRole] // Qml ListModel and QAbstractItemModel + : modelData && modelData.hasOwnProperty(displayRole) ? modelData[displayRole] // QObjectList / QObject + : modelData != undefined ? modelData : "" // Models without role + } + } // menu item repeater + } // of menu contents column + } + + Scrollbar { + id: scroller + flickable: flick + visible: flick.contentHeight > flick.height + anchors.right: root.right + anchors.rightMargin: 1 + anchors.verticalCenter: parent.verticalCenter + height: flick.height + } +} diff --git a/src/GUI/qml/PopupChoice.qml b/src/GUI/qml/PopupChoice.qml index fadc2b67e..4e596dfaf 100644 --- a/src/GUI/qml/PopupChoice.qml +++ b/src/GUI/qml/PopupChoice.qml @@ -1,7 +1,6 @@ import QtQuick 2.4 -import QtQuick.Window 2.0 import FlightGear.Launcher 1.0 - +import FlightGear 1.0 import "." Item { @@ -123,109 +122,20 @@ Item { hoverEnabled: root.enabled enabled: root.enabled onClicked: { - var screenPos = _launcher.mapToGlobal(currentChoiceText, Qt.point(0, -currentChoiceText.height * currentIndex)) - if (screenPos.y < 0) { - // if the popup would appear off the screen, use the first item - // position instead - screenPos = _launcher.mapToGlobal(currentChoiceText, Qt.point(0, 0)) - } - - popupFrame.x = screenPos.x; - popupFrame.y = screenPos.y; - popupFrame.visible = true - tracker.window = popupFrame + OverlayShared.globalOverlay.showOverlayAtItemOffset(menu, root, + Qt.point(currentChoiceFrame.x, root.height)) } } - PopupWindowTracker { - id: tracker + Component { + id: menu + OverlayMenu { + currentIndex: root.currentIndex + model: root.model + headerText: root.headerText + onSelect: root.select(index) + } } - Window { - id: popupFrame - flags: Qt.Popup - height: choicesColumn.childrenRect.height + Style.margin * 2 - width: choicesColumn.width + Style.margin * 2 - visible: false - color: "white" - - Rectangle { - border.width: 1 - border.color: Style.minorFrameColor - anchors.fill: parent - } - - // choice layout column - Column { - id: choicesColumn - spacing: Style.margin - x: Style.margin - y: Style.margin - width: menuWidth - - // optional header component: - StyledText { - id: header - text: root.headerText - visible: root.haveHeader(); - height: implicitHeight + Style.margin - - // essentially the same mouse area as normal items - MouseArea { - width: popupFrame.width // full width of the popup - height: parent.height - z: -1 // so header can do other mouse behaviours - onClicked: { - popupFrame.visible = false - root.select(-1); - } - } - } - - function calculateMenuWidth() - { - var minWidth = 0; - for (var i = 0; i < choicesRepeater.count; i++) { - minWidth = Math.max(minWidth, choicesRepeater.itemAt(i).implicitWidth); - } - if (root.haveHeader()) - minWidth = Math.max(minWidth, header.width); - return minWidth; - } - - readonly property int menuWidth: calculateMenuWidth() - - // main item repeater - Repeater { - id: choicesRepeater - model: root.model - delegate: - Text { - id: choiceText - readonly property bool selected: root.currentIndex === model.index - - // Taken from TableViewItemDelegateLoader.qml to follow QML role conventions - text: model && model.hasOwnProperty(displayRole) ? model[displayRole] // Qml ListModel and QAbstractItemModel - : modelData && modelData.hasOwnProperty(displayRole) ? modelData[displayRole] // QObjectList / QObject - : modelData != undefined ? modelData : "" // Models without role - height: implicitHeight + Style.margin - font.pixelSize: Style.baseFontPixelSize - color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor - - MouseArea { - id: choiceArea - width: popupFrame.width // full width of the popup - height: parent.height - hoverEnabled: true - - onClicked: { - popupFrame.visible = false - root.select(model.index) - } - } - } // of Text delegate - } // text repeater - } // text column - } // of popup Window } diff --git a/src/GUI/qml/PopupChoiceItem.qml b/src/GUI/qml/PopupChoiceItem.qml new file mode 100644 index 000000000..d783b616e --- /dev/null +++ b/src/GUI/qml/PopupChoiceItem.qml @@ -0,0 +1,21 @@ +import QtQuick 2.4 +import "." + +Text { + id: choiceText + property bool isCurrentIndex: false + + signal select(var index); + + height: implicitHeight + Style.margin + font.pixelSize: Style.baseFontPixelSize + color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor + + MouseArea { + id: choiceArea + width: flick.width // full width of the popup + height: parent.height + hoverEnabled: true + onClicked: choiceText.select(model.index) + } +} // of Text delegate diff --git a/src/GUI/resources.qrc b/src/GUI/resources.qrc index 42c97fb5a..4be553cbc 100644 --- a/src/GUI/resources.qrc +++ b/src/GUI/resources.qrc @@ -121,6 +121,8 @@ qml/PlanAirportView.qml qml/PlanRouteDetails.qml qml/RouteLegsView.qml + qml/OverlayMenu.qml + qml/PopupChoiceItem.qml preview-close.png