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.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();
|
||||
|
|
|
@ -167,6 +167,4 @@ Item {
|
|||
OverlayShared.globalOverlay = this
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
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.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
|
||||
}
|
||||
|
|
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/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>
|
||||
|
|
Loading…
Reference in a new issue