1
0
Fork 0

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:
James Turner 2018-09-18 17:33:46 +01:00
parent aa6c4c758a
commit 40c0710f2b
8 changed files with 174 additions and 107 deletions

View file

@ -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();

View file

@ -167,6 +167,4 @@ Item {
OverlayShared.globalOverlay = this
}
}
}

View file

@ -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();

View file

@ -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();
}
}
}

100
src/GUI/qml/OverlayMenu.qml Normal file
View 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
}
}

View file

@ -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
}
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
Component {
id: menu
OverlayMenu {
currentIndex: root.currentIndex
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)
headerText: root.headerText
onSelect: root.select(index)
}
}
} // of Text delegate
} // text repeater
} // text column
} // of popup Window
}

View 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

View file

@ -121,6 +121,8 @@
<file>qml/PlanAirportView.qml</file>
<file>qml/PlanRouteDetails.qml</file>
<file>qml/RouteLegsView.qml</file>
<file>qml/OverlayMenu.qml</file>
<file>qml/PopupChoiceItem.qml</file>
</qresource>
<qresource prefix="/preview">
<file alias="close-icon">preview-close.png</file>