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.
This commit is contained in:
parent
aa6c4c758a
commit
40c0710f2b
8 changed files with 174 additions and 107 deletions
|
@ -1,5 +1,4 @@
|
||||||
import QtQuick 2.4
|
import QtQuick 2.4
|
||||||
import QtQuick.Window 2.0
|
|
||||||
import FlightGear 1.0
|
import FlightGear 1.0
|
||||||
import FlightGear.Launcher 1.0
|
import FlightGear.Launcher 1.0
|
||||||
import "."
|
import "."
|
||||||
|
@ -66,7 +65,9 @@ FocusScope
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if (activeFocus) {
|
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 {
|
} else {
|
||||||
OverlayShared.globalOverlay.dismissOverlay()
|
OverlayShared.globalOverlay.dismissOverlay()
|
||||||
searchCompleter.clear();
|
searchCompleter.clear();
|
||||||
|
|
|
@ -167,6 +167,4 @@ Item {
|
||||||
OverlayShared.globalOverlay = this
|
OverlayShared.globalOverlay = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ Item {
|
||||||
width: parent.width - 2
|
width: parent.width - 2
|
||||||
|
|
||||||
// helper function called by BaseMenuItem on its parent, i.e,
|
// helper function called by BaseMenuItem on its parent, i.e,
|
||||||
// us, to requestc closing the menu
|
// us, to request closing the menu
|
||||||
function requestClose()
|
function requestClose()
|
||||||
{
|
{
|
||||||
root.close();
|
root.close();
|
||||||
|
@ -75,4 +75,4 @@ Item {
|
||||||
// menu items get inserted here
|
// menu items get inserted here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import QtQuick 2.4
|
import QtQuick 2.4
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property bool dismissOnClickOutside: activeOverlayLoader.item && activeOverlayLoader.item.hasOwnProperty("dismissOnClickOutside") ?
|
||||||
|
activeOverlayLoader.item.dismissOnClickOutside : false
|
||||||
|
|
||||||
function showOverlay(comp)
|
function showOverlay(comp)
|
||||||
{
|
{
|
||||||
|
@ -23,9 +27,40 @@ Item {
|
||||||
activeOverlayLoader.visible = false;
|
activeOverlayLoader.visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
visible: activeOverlayLoader.visible && root.dismissOnClickOutside
|
||||||
|
onClicked: root.dismissOverlay()
|
||||||
|
}
|
||||||
|
|
||||||
Loader
|
Loader
|
||||||
{
|
{
|
||||||
id: activeOverlayLoader
|
id: activeOverlayLoader
|
||||||
// no size, size to the component
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
100
src/GUI/qml/OverlayMenu.qml
Normal file
100
src/GUI/qml/OverlayMenu.qml
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import QtQuick 2.4
|
import QtQuick 2.4
|
||||||
import QtQuick.Window 2.0
|
|
||||||
import FlightGear.Launcher 1.0
|
import FlightGear.Launcher 1.0
|
||||||
|
import FlightGear 1.0
|
||||||
import "."
|
import "."
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
@ -123,109 +122,20 @@ Item {
|
||||||
hoverEnabled: root.enabled
|
hoverEnabled: root.enabled
|
||||||
enabled: root.enabled
|
enabled: root.enabled
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var screenPos = _launcher.mapToGlobal(currentChoiceText, Qt.point(0, -currentChoiceText.height * currentIndex))
|
OverlayShared.globalOverlay.showOverlayAtItemOffset(menu, root,
|
||||||
if (screenPos.y < 0) {
|
Qt.point(currentChoiceFrame.x, root.height))
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PopupWindowTracker {
|
Component {
|
||||||
id: tracker
|
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
|
|
||||||
}
|
}
|
||||||
|
|
21
src/GUI/qml/PopupChoiceItem.qml
Normal file
21
src/GUI/qml/PopupChoiceItem.qml
Normal file
|
@ -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
|
|
@ -121,6 +121,8 @@
|
||||||
<file>qml/PlanAirportView.qml</file>
|
<file>qml/PlanAirportView.qml</file>
|
||||||
<file>qml/PlanRouteDetails.qml</file>
|
<file>qml/PlanRouteDetails.qml</file>
|
||||||
<file>qml/RouteLegsView.qml</file>
|
<file>qml/RouteLegsView.qml</file>
|
||||||
|
<file>qml/OverlayMenu.qml</file>
|
||||||
|
<file>qml/PopupChoiceItem.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/preview">
|
<qresource prefix="/preview">
|
||||||
<file alias="close-icon">preview-close.png</file>
|
<file alias="close-icon">preview-close.png</file>
|
||||||
|
|
Loading…
Add table
Reference in a new issue