From 0239d5ef44688be43dac9c4066e24e3a9c032ff9 Mon Sep 17 00:00:00 2001 From: James Turner Date: Wed, 7 Nov 2018 09:23:17 +0100 Subject: [PATCH] Grid-view for the launcher --- src/GUI/LauncherController.cxx | 21 +++- src/GUI/LauncherController.hxx | 11 ++ src/GUI/assets/icons8-grid-view.svg | 6 + src/GUI/assets/icons8-menu.svg | 6 + src/GUI/qml/AircraftGridDelegate.qml | 96 ++++++++++++++++ src/GUI/qml/AircraftGridView.qml | 77 +++++++++++++ src/GUI/qml/AircraftList.qml | 155 +++++++++++--------------- src/GUI/qml/AircraftListView.qml | 80 +++++++++++++ src/GUI/qml/AircraftRatingsPanel.qml | 5 +- src/GUI/qml/AircraftVariantChoice.qml | 74 +++++++----- src/GUI/qml/GridToggleButton.qml | 43 +++++++ src/GUI/qml/ListHeaderBox.qml | 3 +- src/GUI/resources.qrc | 6 + 13 files changed, 462 insertions(+), 121 deletions(-) create mode 100644 src/GUI/assets/icons8-grid-view.svg create mode 100644 src/GUI/assets/icons8-menu.svg create mode 100644 src/GUI/qml/AircraftGridDelegate.qml create mode 100644 src/GUI/qml/AircraftGridView.qml create mode 100644 src/GUI/qml/AircraftListView.qml create mode 100644 src/GUI/qml/GridToggleButton.qml diff --git a/src/GUI/LauncherController.cxx b/src/GUI/LauncherController.cxx index 5d482fcb2..cfa5a29fe 100644 --- a/src/GUI/LauncherController.cxx +++ b/src/GUI/LauncherController.cxx @@ -106,6 +106,8 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) : LocalAircraftCache::instance()->scanDirs(); m_aircraftModel->setPackageRoot(globals->packageRoot()); + m_aircraftGridMode = settings.value("aircraftGridMode").toBool(); + m_subsystemIdleTimer = new QTimer(this); m_subsystemIdleTimer->setInterval(10); connect(m_subsystemIdleTimer, &QTimer::timeout, []() @@ -821,13 +823,24 @@ void LauncherController::saveConfigAs() m_config->saveConfigToFile(file); } +void LauncherController::setAircraftGridMode(bool aircraftGridMode) +{ + if (m_aircraftGridMode == aircraftGridMode) + return; + + QSettings settings; + settings.setValue("aircraftGridMode", aircraftGridMode); + m_aircraftGridMode = aircraftGridMode; + emit aircraftGridModeChanged(m_aircraftGridMode); +} + void LauncherController::setMinWindowSize(QSize sz) { - if (sz == m_minWindowSize) - return; + if (sz == m_minWindowSize) + return; - m_window->setMinimumSize(sz); - emit minWindowSizeChanged(); + m_window->setMinimumSize(sz); + emit minWindowSizeChanged(); } QUrl LauncherController::flyIconUrl() const diff --git a/src/GUI/LauncherController.hxx b/src/GUI/LauncherController.hxx index 1195304eb..29264c006 100644 --- a/src/GUI/LauncherController.hxx +++ b/src/GUI/LauncherController.hxx @@ -86,6 +86,7 @@ class LauncherController : public QObject Q_PROPERTY(QUrl flyIconUrl READ flyIconUrl NOTIFY selectedAircraftChanged) + Q_PROPERTY(bool aircraftGridMode READ aircraftGridMode WRITE setAircraftGridMode NOTIFY aircraftGridModeChanged) public: explicit LauncherController(QObject *parent, QWindow* win); @@ -187,6 +188,11 @@ public: QUrl flyIconUrl() const; + bool aircraftGridMode() const + { + return m_aircraftGridMode; + } + signals: void selectedAircraftChanged(QUrl selectedAircraft); @@ -199,6 +205,8 @@ signals: void viewCommandLine(); + void aircraftGridModeChanged(bool aircraftGridMode); + public slots: void setSelectedAircraft(QUrl selectedAircraft); @@ -217,6 +225,8 @@ public slots: void openConfig(); void saveConfigAs(); + void setAircraftGridMode(bool aircraftGridMode); + private slots: void onAircraftInstalledCompleted(QModelIndex index); @@ -271,6 +281,7 @@ private: bool m_inAppMode = false; bool m_keepRunningInAppMode = false; bool m_appModeResult = true; + bool m_aircraftGridMode; }; #endif // LAUNCHERCONTROLLER_HXX diff --git a/src/GUI/assets/icons8-grid-view.svg b/src/GUI/assets/icons8-grid-view.svg new file mode 100644 index 000000000..79c925334 --- /dev/null +++ b/src/GUI/assets/icons8-grid-view.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/GUI/assets/icons8-menu.svg b/src/GUI/assets/icons8-menu.svg new file mode 100644 index 000000000..89b9570c2 --- /dev/null +++ b/src/GUI/assets/icons8-menu.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/GUI/qml/AircraftGridDelegate.qml b/src/GUI/qml/AircraftGridDelegate.qml new file mode 100644 index 000000000..99ad7eb33 --- /dev/null +++ b/src/GUI/qml/AircraftGridDelegate.qml @@ -0,0 +1,96 @@ +import QtQuick 2.4 +import FlightGear.Launcher 1.0 +import "." + +Item { + id: root + + signal select(var uri); + signal showDetails(var uri) + + readonly property bool __isSelected: (_launcher.selectedAircraft === model.uri) + + Rectangle { + anchors.centerIn: parent + width: parent.width - 4 + height: parent.height - 4 + color: "transparent" + border.width: 1 + border.color: "#dfdfdf" + } + + MouseArea { + id: mouse + anchors.fill: parent + onClicked: { + if (__isSelected) { + root.showDetails(model.uri) + } else { + root.select(model.uri) + } + } + } + + Column { + id: contentBox + width: parent.width + y: Style.margin + spacing: 2 // + + Item { + id: thumbnailBox + width: 172 + height: 128 + anchors.horizontalCenter: parent.horizontalCenter + + Rectangle { + anchors.centerIn: parent + border.width: 1 + border.color: "#7f7f7f" + width: thumbnail.width + height: thumbnail.height + + ThumbnailImage { + id: thumbnail + aircraftUri: model.uri + maximumSize.width: 172 + maximumSize.height: 128 + } + } + + Button { + visible: hover.containsMouse && (model.packageStatus === LocalAircraftCache.PackageNotInstalled) + text: qsTr("Install") + onClicked: { + // drill down and also start the install + _launcher.requestInstallUpdate(model.uri) + root.showDetails(model.uri) + } + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + } + } + } + + AircraftVariantChoice { + id: titleBox + + width: parent.width + popupFontPixelSize: Style.baseFontPixelSize + aircraft: model.uri; + currentIndex: model.activeVariant + onSelected: { + model.activeVariant = index + root.select(model.uri) + } + } + } + + MouseArea { + id: hover + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + } +} // of root item diff --git a/src/GUI/qml/AircraftGridView.qml b/src/GUI/qml/AircraftGridView.qml new file mode 100644 index 000000000..5af2cf11a --- /dev/null +++ b/src/GUI/qml/AircraftGridView.qml @@ -0,0 +1,77 @@ +import QtQuick 2.0 +import FlightGear.Launcher 1.0 as FG + +Item { + id: root + + property alias model: view.model + property alias header: view.header + + signal showDetails(var uri); + + function updateSelectionFromLauncher() + { + var row = model.indexForURI(_launcher.selectedAircraft); + if (row >= 0) { + view.currentIndex = row; + } else { + // clear selection in view, so we don't show something + // erroneous such as the previous value + view.currentIndex = -1; + } + } + + onModelChanged: updateSelectionFromLauncher(); + + Component { + id: highlight + Rectangle { + gradient: Gradient { + GradientStop { position: 0.0; color: "#98A3B4" } + GradientStop { position: 1.0; color: "#5A6B83" } + } + } + } + + GridView { + id: view + cellWidth: width / colCount + cellHeight: 128 + Style.strutSize + highlightMoveDuration: 0 + + readonly property int baseCellWidth: 172 + (Style.strutSize * 2) + readonly property int colCount: Math.floor(width / baseCellWidth) + + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + right: scrollbar.left + topMargin: Style.margin + } + + delegate: AircraftGridDelegate { + width: view.cellWidth + height: view.cellHeight + + onSelect: { + view.currentIndex = model.index; + _launcher.selectedAircraft = uri; + } + + onShowDetails: root.showDetails(uri) + } + + clip: true + focus: true + highlight: highlight + } + + Scrollbar { + id: scrollbar + anchors.right: parent.right + anchors.top: parent.top + height: view.height + flickable: view + } +} diff --git a/src/GUI/qml/AircraftList.qml b/src/GUI/qml/AircraftList.qml index 8ca4ff13d..1dbf327e2 100644 --- a/src/GUI/qml/AircraftList.qml +++ b/src/GUI/qml/AircraftList.qml @@ -6,8 +6,15 @@ FocusScope { id: root - Component.onCompleted: { - aircraftList.updateSelectionFromLauncher(); + property var __model: null + property Component __header: null + property string __lastState: "installed" + + function updateSelectionFromLauncher() + { + if (aircraftContent.item) { + aircraftContent.item.updateSelectionFromLauncher(); + } } Rectangle @@ -16,6 +23,14 @@ FocusScope height: searchButton.height + (Style.margin * 2) width: parent.width + GridToggleButton { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: Style.margin + gridMode: !_launcher.aircraftGridMode + onClicked: _launcher.aircraftGridMode = !_launcher.aircraftGridMode + } + Row { anchors.centerIn: parent spacing: Style.margin @@ -25,7 +40,7 @@ FocusScope text: qsTr("Installed Aircraft") onClicked: { root.state = "installed" - aircraftList.updateSelectionFromLauncher(); + root.updateSelectionFromLauncher(); } active: root.state == "installed" } @@ -35,7 +50,7 @@ FocusScope text: qsTr("Browse") onClicked: { root.state = "browse" - aircraftList.updateSelectionFromLauncher(); + root.updateSelectionFromLauncher(); } active: root.state == "browse" } @@ -46,7 +61,7 @@ FocusScope text: qsTr("Updates") onClicked: { root.state = "updates" - aircraftList.updateSelectionFromLauncher(); + root.updateSelectionFromLauncher(); } active: root.state == "updates" } @@ -64,7 +79,7 @@ FocusScope onSearch: { _launcher.searchAircraftModel.setAircraftFilterString(term) root.state = "search" - aircraftList.updateSelectionFromLauncher(); + root.updateSelectionFromLauncher(); } active: root.state == "search" @@ -80,90 +95,65 @@ FocusScope anchors.top: tabBar.bottom } - Component { - id: highlight - Rectangle { - gradient: Gradient { - GradientStop { position: 0.0; color: "#98A3B4" } - GradientStop { position: 1.0; color: "#5A6B83" } - } - } - } - Component { id: ratingsHeader AircraftRatingsPanel { - width: aircraftList.width - Style.strutSize * 2 - x: (aircraftList.width - width) / 2 - theList: aircraftList + width: aircraftContent.width + onClearSelection: { + _launcher.selectedAircraft = ""; + root.updateSelectionFromLauncher() + } } } Component { id: noDefaultCatalogHeader NoDefaultCatalogPanel { - width: aircraftList.width - Style.strutSize * 2 - x: (aircraftList.width - width) / 2 + width: aircraftContent.width } } Component { id: updateAllHeader UpdateAllPanel { - width: aircraftList.width - Style.strutSize * 2 - x: (aircraftList.width - width) / 2 + width: aircraftContent.width } } - ListView { - id: aircraftList + Component { + id: emptyHeader + Item { + } + } + + Loader { + id: aircraftContent + source: _launcher.aircraftGridMode ? "qrc:///qml/AircraftGridView.qml" + : "qrc:///qml/AircraftListView.qml" anchors { left: parent.left top: tabBarDivider.bottom bottom: parent.bottom - right: scrollbar.left + right: parent.right topMargin: Style.margin } - delegate: AircraftCompactDelegate { - onSelect: { - aircraftList.currentIndex = model.index; - _launcher.selectedAircraft = uri; - } - - onShowDetails: root.showDetails(uri) + Binding { + target: aircraftContent.item + property: "model" + value: root.__model } - clip: true - focus: true + Binding { + target: aircraftContent.item + property: "header" + value: root.__header + } - // prevent mouse wheel interactions when the details view is - // visible, since it has its own flickable - enabled: !detailsView.visible - - highlight: highlight - highlightMoveDuration: __realHighlightMoveDuration - - // saved here becuase we need to reset highlightMoveDuration - // when doing a progrmatic set - readonly property int __realHighlightMoveDuration: 200 - - function updateSelectionFromLauncher() - { - model.selectVariantForAircraftURI(_launcher.selectedAircraft); - var row = model.indexForURI(_launcher.selectedAircraft); - if (row >= 0) { - // sequence here is necessary so progrommatic moves - // are instant - highlightMoveDuration = 0; - currentIndex = row; - highlightMoveDuration = __realHighlightMoveDuration; - } else { - // clear selection in view, so we don't show something - // erroneous such as the previous value - currentIndex = -1; - } + Connections { + target: aircraftContent.item + onShowDetails: root.showDetails(uri) } } @@ -173,7 +163,7 @@ FocusScope left: parent.left top: tabBar.bottom bottom: parent.bottom - right: scrollbar.left + right: parent.right } horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -182,49 +172,42 @@ FocusScope visible: (root.state == "updates") && (_launcher.aircraftWithUpdatesModel.count == 0) } - Scrollbar { - id: scrollbar - anchors.right: parent.right - anchors.top: tabBar.bottom - height: aircraftList.height - flickable: aircraftList - } - state: "installed" states: [ State { name: "installed" PropertyChanges { - target: aircraftList - model: _launcher.installedAircraftModel + target: root + __model: _launcher.installedAircraftModel + __header: emptyHeader } }, State { name: "search" PropertyChanges { - target: aircraftList - model: _launcher.searchAircraftModel - header: null + target: root + __model: _launcher.searchAircraftModel + __header: emptyHeader } }, State { name: "browse" PropertyChanges { - target: aircraftList - model: _launcher.browseAircraftModel - header: _addOns.showNoOfficialHangar ? noDefaultCatalogHeader : ratingsHeader + target: root + __model: _launcher.browseAircraftModel + __header: _addOns.showNoOfficialHangar ? noDefaultCatalogHeader : ratingsHeader } }, State { name: "updates" PropertyChanges { - target: aircraftList - model: _launcher.aircraftWithUpdatesModel - header: (_launcher.aircraftWithUpdatesModel.count > 0) ? updateAllHeader : null + target: root + __model: _launcher.aircraftWithUpdatesModel + __header: (_launcher.aircraftWithUpdatesModel.count > 0) ? updateAllHeader : emptyHeader } } ] @@ -239,6 +222,8 @@ FocusScope function goBack() { + // details view can change the aircraft URI / variant + updateSelectionFromLauncher(); detailsView.visible = false; } @@ -250,15 +235,9 @@ FocusScope Button { anchors { left: parent.left; top: parent.top; margins: Style.margin } width: Style.strutSize - id: backButton text: "< Back" - onClicked: { - // ensure that if the variant was changed inside the detailsView, - // that we update our selection correctly - aircraftList.updateSelectionFromLauncher(); - root.goBack(); - } + onClicked: root.goBack(); } } } diff --git a/src/GUI/qml/AircraftListView.qml b/src/GUI/qml/AircraftListView.qml new file mode 100644 index 000000000..ed0958084 --- /dev/null +++ b/src/GUI/qml/AircraftListView.qml @@ -0,0 +1,80 @@ +import QtQuick 2.0 +import FlightGear.Launcher 1.0 as FG +import "." + +Item { + id: root + + property alias model: aircraftList.model + property alias header: aircraftList.header + + signal showDetails(var uri); + + function updateSelectionFromLauncher() + { + model.selectVariantForAircraftURI(_launcher.selectedAircraft); + var row = model.indexForURI(_launcher.selectedAircraft); + if (row >= 0) { + // sequence here is necessary so progrommatic moves + // are instant + aircraftList.highlightMoveDuration = 0; + aircraftList.currentIndex = row; + aircraftList.highlightMoveDuration = aircraftList.__realHighlightMoveDuration; + } else { + // clear selection in view, so we don't show something + // erroneous such as the previous value + aircraftList.currentIndex = -1; + } + } + + onModelChanged: updateSelectionFromLauncher() + + Component { + id: highlight + Rectangle { + gradient: Gradient { + GradientStop { position: 0.0; color: "#98A3B4" } + GradientStop { position: 1.0; color: "#5A6B83" } + } + } + } + + ListView { + id: aircraftList + + anchors { + left: parent.left + top: parent.top + bottom: parent.bottom + right: scrollbar.left + topMargin: Style.margin + } + + delegate: AircraftCompactDelegate { + onSelect: { + aircraftList.currentIndex = model.index; + _launcher.selectedAircraft = uri; + } + + onShowDetails: root.showDetails(uri) + } + + clip: true + focus: true + + highlight: highlight + highlightMoveDuration: __realHighlightMoveDuration + + // saved here becuase we need to reset highlightMoveDuration + // when doing a progrmatic set + readonly property int __realHighlightMoveDuration: 200 + } + + Scrollbar { + id: scrollbar + anchors.right: parent.right + anchors.top: parent.top + height: aircraftList.height + flickable: aircraftList + } +} diff --git a/src/GUI/qml/AircraftRatingsPanel.qml b/src/GUI/qml/AircraftRatingsPanel.qml index 8e7afd521..3e6f46463 100644 --- a/src/GUI/qml/AircraftRatingsPanel.qml +++ b/src/GUI/qml/AircraftRatingsPanel.qml @@ -4,7 +4,8 @@ import "." ListHeaderBox { - property ListView theList: null + id: root + signal clearSelection(); contents: [ @@ -39,7 +40,7 @@ ListHeaderBox onClicked: { // clear selection so we don't jump to the selected item // each time the proxy model changes. - theList.currentIndex = -1; + root.clearSelection(); editRatingsPanel.visible = true } diff --git a/src/GUI/qml/AircraftVariantChoice.qml b/src/GUI/qml/AircraftVariantChoice.qml index d15951215..72366387f 100644 --- a/src/GUI/qml/AircraftVariantChoice.qml +++ b/src/GUI/qml/AircraftVariantChoice.qml @@ -10,11 +10,13 @@ Rectangle { implicitHeight: title.implicitHeight - radius: 4 + radius: Style.roundRadius border.color: Style.frameColor border.width: headingMouseArea.containsMouse ? 1 : 0 color: headingMouseArea.containsMouse ? "#7fffffff" : "transparent" + readonly property int centerX: width / 2 + readonly property bool __enabled: aircraftInfo.numVariants > 1 property alias aircraft: aircraftInfo.uri @@ -28,33 +30,53 @@ Rectangle { signal selected(var index) - Text { - id: title - - anchors.verticalCenter: parent.verticalCenter - anchors.right: upDownIcon.left - anchors.left: parent.left - anchors.leftMargin: 4 - anchors.rightMargin: 4 - - horizontalAlignment: Text.AlignHCenter - font.pixelSize: Style.baseFontPixelSize * 2 - text: aircraftInfo.name - fontSizeMode: Text.Fit - - elide: Text.ElideRight - maximumLineCount: 1 - color: headingMouseArea.containsMouse ? Style.themeColor : Style.baseTextColor - } - Image { - id: upDownIcon - source: "qrc:///up-down-arrow" - anchors.right: parent.right - anchors.rightMargin: 8 - anchors.verticalCenter: parent.verticalCenter - visible: __enabled + +// Image { +// id: upDownIcon +// source: "qrc:///up-down-arrow" +// x: root.centerX + Math.min(title.implicitWidth * 0.5, title.width * 0.5) +// anchors.verticalCenter: parent.verticalCenter +// visible: __enabled +// } + + Row { + anchors.centerIn: parent + height: title.implicitHeight + width: childrenRect.width + + Text { + id: title + + anchors.verticalCenter: parent.verticalCenter + + // this ugly logic is to keep the up-down arrow at the right + // hand side of the text, but allow the font scaling to work if + // we're short on horizontal space + width: Math.min(implicitWidth, + root.width - (Style.margin * 2) - (__enabled ? upDownIcon.implicitWidth : 0)) + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + font.pixelSize: Style.baseFontPixelSize * 2 + text: aircraftInfo.name + fontSizeMode: Text.Fit + + elide: Text.ElideRight + maximumLineCount: 1 + color: headingMouseArea.containsMouse ? Style.themeColor : Style.baseTextColor + } + + Image { + id: upDownIcon + source: "qrc:///up-down-arrow" + // x: root.centerX + Math.min(title.implicitWidth * 0.5, title.width * 0.5) + anchors.verticalCenter: parent.verticalCenter + visible: __enabled + width: __enabled ? implicitWidth : 0 + } } MouseArea { diff --git a/src/GUI/qml/GridToggleButton.qml b/src/GUI/qml/GridToggleButton.qml new file mode 100644 index 000000000..8c4e655c7 --- /dev/null +++ b/src/GUI/qml/GridToggleButton.qml @@ -0,0 +1,43 @@ +import QtQuick 2.0 +import "." + +Rectangle { + id: root + radius: Style.roundRadius + border.width: 1 + border.color: Style.themeColor + width: height + height: Style.baseFontPixelSize + Style.margin * 2 + color: mouse.containsMouse ? Style.minorFrameColor : "white" + + property bool gridMode: false + + signal clicked(); + + Image { + id: icon + width: parent.width - Style.margin + height: parent.height - Style.margin + anchors.centerIn: parent + source: root.gridMode ? "qrc:///svg/icon-grid-view" + : "qrc:///svg/icon-list-view" + } + + MouseArea { + id: mouse + hoverEnabled: true + onClicked: root.clicked(); + anchors.fill: parent + } + + Text { + anchors.left: root.right + anchors.leftMargin: Style.margin + anchors.verticalCenter: root.verticalCenter + visible: mouse.containsMouse + color: Style.baseTextColor + font.pixelSize: Style.baseFontPixelSize + text: root.gridMode ? qsTr("Switch to grid view") + : qsTr("Switch to list view") + } +} diff --git a/src/GUI/qml/ListHeaderBox.qml b/src/GUI/qml/ListHeaderBox.qml index 32159a3b2..664dbacea 100644 --- a/src/GUI/qml/ListHeaderBox.qml +++ b/src/GUI/qml/ListHeaderBox.qml @@ -11,7 +11,8 @@ Item { Rectangle { id: contentBox - width: parent.width + width: parent.width - Style.strutSize * 2 + x: Style.strutSize height: Style.strutSize y: Style.margin color: "white" diff --git a/src/GUI/resources.qrc b/src/GUI/resources.qrc index 00b56e314..30c954827 100644 --- a/src/GUI/resources.qrc +++ b/src/GUI/resources.qrc @@ -123,6 +123,10 @@ qml/RouteLegsView.qml qml/OverlayMenu.qml qml/PopupChoiceItem.qml + qml/AircraftGridDelegate.qml + qml/AircraftGridView.qml + qml/AircraftListView.qml + qml/GridToggleButton.qml preview-close.png @@ -139,5 +143,7 @@ assets/icons8-aircraft.svg assets/icons8-rocket.svg assets/icons8-helicopter.svg + assets/icons8-grid-view.svg + assets/icons8-menu.svg