summaryrefslogtreecommitdiffstats
path: root/basicsuite/ebike-ui/NaviPage.qml
diff options
context:
space:
mode:
Diffstat (limited to 'basicsuite/ebike-ui/NaviPage.qml')
-rw-r--r--basicsuite/ebike-ui/NaviPage.qml691
1 files changed, 691 insertions, 0 deletions
diff --git a/basicsuite/ebike-ui/NaviPage.qml b/basicsuite/ebike-ui/NaviPage.qml
new file mode 100644
index 0000000..e6c8805
--- /dev/null
+++ b/basicsuite/ebike-ui/NaviPage.qml
@@ -0,0 +1,691 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the E-Bike demo project.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+import QtPositioning 5.3
+import QtLocation 5.9
+import QtQuick.VirtualKeyboard 2.1
+
+import "./BikeStyle"
+
+Page {
+ id: mapContainer
+ property var startCoordinate: QtPositioning.coordinate(36.131961, -115.153048)
+ property var destinationCoordinate: QtPositioning.coordinate(90, 0)
+ property var targetPlace
+ property real totalDistance
+ property real metersPerSecond // This is calculated at the start of a trip
+ property real totalTravelTime: totalDistance / metersPerSecond
+ property real naviGuideSegmentDistance: 0
+ property string naviGuideArrowSource: "images/nav_nodir.png"
+ property string naviGuideDistance
+ property string naviGuideAddress: "-"
+ property alias routeQuery: routeQuery
+ property alias routeModel: routeModel
+ property alias targetEdit: targetEdit
+ property var routeSegmentList
+ property var currentSegment
+ property int routeSegment
+ property int pathSegment
+
+ function resetDemo() {
+ // Clear/reset everything
+ naviGuideArrowSource = "images/nav_nodir.png"
+ naviGuideDistance = "-"
+ naviGuideAddress = "-"
+ navigation.active = false
+ navigationArrowAnimation.stop()
+ targetEdit.clear()
+
+ map.focus = true
+ routeQuery.clearWaypoints()
+ routeModel.reset()
+ navigationArrowItem.coordinate = navigation.coordinate
+ destinationCoordinate = QtPositioning.coordinate(90, 0)
+ map.center = navigation.coordinate
+ }
+
+ TextField {
+ id: targetEdit
+ width: UILayout.naviPageLocationWidth
+ height: UILayout.naviPageLocationHeight
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ top: parent.top
+ topMargin: UILayout.naviPageLocationTopMargin
+ }
+ background: Rectangle {
+ radius: UILayout.naviPageLocationRadius
+ implicitWidth: UILayout.naviPageLocationWidth
+ implicitHeight: UILayout.naviPageLocationHeight
+ border.color: Colors.naviPageSuggestionBorder
+ border.width: targetEdit.activeFocus ? 2 : 0
+ }
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.naviPageSearchTextSize
+ }
+ cursorVisible: !navigation.active
+ inputMethodHints: Qt.ImhNoPredictiveText // This should disable lookup on the device
+
+ leftPadding: UILayout.naviPageLocationLeftPadding
+ rightPadding: UILayout.naviPageSearchIconMargin + UILayout.naviPageSearchIconWidth
+ placeholderText: qsTr("<i>Where do you want to go?</i>")
+ z: 1
+ autoScroll: false
+
+ // Update search text whenever text is edited
+ onTextEdited: suggest.search = text
+
+ // If in navigation mode, disable editing
+ readOnly: navigation.active
+
+ Image {
+ source: navigation.active ? "images/search_cancel.png" : "images/search.png"
+ width: UILayout.naviPageSearchIconWidth
+ height: UILayout.naviPageSearchIconHeight
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ right: parent.right
+ rightMargin: UILayout.naviPageSearchIconMargin
+ }
+ visible: !suggest.loading
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: navigation.active
+ onClicked: {
+ naviGuideArrowSource = "images/nav_nodir.png"
+ naviGuideDistance = "-"
+ naviGuideAddress = "-"
+ navigation.active = false
+ navigationArrowAnimation.stop()
+ targetEdit.clear()
+ }
+ }
+ }
+
+ // Show a busy indicator whenever suggestions are loading
+ BusyIndicator {
+ width: height
+ anchors {
+ top: targetEdit.top
+ bottom: targetEdit.bottom
+ right: targetEdit.right
+ rightMargin: UILayout.naviPageSearchIconMargin
+ }
+ running: suggest.loading
+ }
+
+ Image {
+ id: naviInputShadow
+ source: "images/small_input_box_shadow.png"
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ horizontalCenterOffset: 1
+ verticalCenterOffset: 1
+ }
+ visible: false
+ z: -2
+ }
+ }
+
+ ListView {
+ id: targetList
+ anchors {
+ top: targetEdit.bottom;
+ topMargin: UILayout.naviPageSuggestionsOffset
+ horizontalCenter: targetEdit.horizontalCenter
+ }
+ width: UILayout.naviPageLocationWidth
+ height: 3 * UILayout.naviPageSuggestionHeight
+ model: suggestions
+ visible: targetEdit.activeFocus && !navigation.active
+ z: 1
+ currentIndex: -1
+
+ delegate: Component {
+ Rectangle {
+ width: parent.width
+ height: UILayout.naviPageSuggestionHeight
+ color: "white"
+ border.color: Colors.naviPageSuggestionsDivider
+ border.width: 1
+
+ Text {
+ width: parent.width
+ height: parent.height
+ verticalAlignment: Text.AlignVCenter
+ leftPadding: 10
+ elide: Text.ElideRight
+ text: placename
+ color: Colors.naviPageSuggestionText
+ font {
+ family: "Montserrat, Medium"
+ weight: Font.Medium
+ pixelSize: UILayout.naviPageSuggestionTextSize
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: targetList.currentIndex = index
+ }
+ }
+ }
+
+ onCurrentIndexChanged: {
+ // Called when the currentIndex is reset, ignore that
+ if (targetList.currentIndex == -1)
+ return;
+
+ // Get current place name
+ targetPlace = model.get(targetList.currentIndex)
+ suggestions.addToMostRecent(targetPlace)
+
+ // Reset search
+ targetList.currentIndex = -1
+ // Update current text
+ targetEdit.text = targetPlace.place_name
+ // Clear the model and stop any search
+ suggestions.clear()
+ suggest.stopSuggest()
+
+ targetEdit.cursorPosition = 0
+ targetEdit.ensureVisible(1)
+ navigation.active = true
+ map.focus = true
+
+ destinationCoordinate = QtPositioning.coordinate(targetPlace.center[1], targetPlace.center[0]);
+ routeQuery.clearWaypoints()
+ routeQuery.addWaypoint(map.center)
+ routeQuery.addWaypoint(destinationCoordinate)
+ routeQuery.travelModes = RouteQuery.BicycleTravel
+ routeQuery.routeOptimizations = RouteQuery.ShortestRoute
+ routeModel.update()
+ }
+ }
+
+ // Zoom and location icons
+ NaviButton {
+ id: gpsCenter
+ anchors {
+ top: parent.top
+ topMargin: UILayout.naviPageIconTopMargin
+ right: parent.right
+ rightMargin: UILayout.naviPageIconRightMargin
+ }
+ iconSource: "images/map_locate.png"
+ onClicked: {
+ navigationArrowItem.coordinate = navigation.coordinate
+ map.center = navigation.coordinate
+ }
+ }
+
+ NaviButton {
+ id: zoomIn
+ anchors {
+ top: gpsCenter.bottom
+ topMargin: UILayout.naviPageIconSpacing
+ right: parent.right
+ rightMargin: UILayout.naviPageIconRightMargin
+ }
+ iconSource: "images/map_zoomin.png"
+ autoRepeat: true
+ onClicked: navigation.zoomlevel += 0.1
+ }
+
+ NaviButton {
+ id: zoomOut
+ anchors {
+ top: zoomIn.bottom
+ topMargin: UILayout.naviPageIconSpacing
+ right: parent.right
+ rightMargin: UILayout.naviPageIconRightMargin
+ }
+ iconSource: "images/map_zoomout.png"
+ autoRepeat: true
+ onClicked: navigation.zoomlevel -= 0.1
+ }
+
+ NaviGuide {
+ id: naviGuide
+ anchors {
+ right: parent.right
+ bottom: parent.bottom
+ rightMargin: UILayout.naviPageGuideRightMargin
+ bottomMargin: UILayout.naviPageGuideBottomMargin
+ }
+ arrowSource: naviGuideArrowSource
+ distance: naviGuideDistance
+ address: naviGuideAddress
+ visible: navigation.active
+ }
+
+ NaviTripInfo {
+ id: totalTripInfo
+ z: 1
+ visible: navigation.active
+ anchors {
+ bottom: parent.bottom
+ bottomMargin: UILayout.naviPageTripBottomMargin
+ horizontalCenter: parent.horizontalCenter
+ }
+
+ remainingDistance: totalDistance
+ remainingTravelTime: totalTravelTime
+ }
+
+ RouteQuery {
+ id: routeQuery
+ numberAlternativeRoutes: 0
+ }
+
+ function updateNaviGuide(segment, nextsegment) {
+ var maneuver = segment.maneuver;
+ naviGuideSegmentDistance = maneuver.distanceToNextInstruction;
+ navigationArrowAnimation.pathDistance = naviGuideSegmentDistance;
+ naviGuideDistance = maneuver.distanceToNextInstruction;
+
+ if (nextsegment) {
+ var nextmaneuver = nextsegment.maneuver;
+ naviGuideAddress = nextmaneuver.instructionText;
+ switch (nextmaneuver.direction) {
+ case RouteManeuver.NoDirection:
+ naviGuideArrowSource = "images/nav_nodir.png";
+ break;
+ case RouteManeuver.DirectionForward:
+ naviGuideArrowSource = "images/nav_straight.png";
+ break;
+ case RouteManeuver.DirectionBearRight:
+ naviGuideArrowSource = "images/nav_bear_r.png";
+ break;
+ case RouteManeuver.DirectionLightRight:
+ naviGuideArrowSource = "images/nav_light_right.png";
+ break;
+ case RouteManeuver.DirectionRight:
+ naviGuideArrowSource = "images/nav_right.png";
+ break;
+ case RouteManeuver.DirectionHardRight:
+ naviGuideArrowSource = "images/nav_hard_r.png";
+ break;
+ case RouteManeuver.DirectionUTurnRight:
+ naviGuideArrowSource = "images/nav_uturn_r.png";
+ break;
+ case RouteManeuver.DirectionUTurnLeft:
+ naviGuideArrowSource = "images/nav_uturn_l.png";
+ break;
+ case RouteManeuver.DirectionHardLeft:
+ naviGuideArrowSource = "images/nav_hard_l.png";
+ break;
+ case RouteManeuver.DirectionLeft:
+ naviGuideArrowSource = "images/nav_left.png";
+ break;
+ case RouteManeuver.DirectionLightLeft:
+ naviGuideArrowSource = "images/nav_light_left.png";
+ break;
+ case RouteManeuver.DirectionBearLeft:
+ naviGuideArrowSource = "images/nav_bear_l.png";
+ break;
+ }
+ } else {
+ naviGuideAddress = "-";
+ naviGuideArrowSource = "images/nav_nodir.png";
+ }
+ }
+
+ function setNextAnimation() {
+ if (!navigation.active)
+ return;
+ var position = QtPositioning.coordinate(currentSegment.maneuver.position.longitude,
+ currentSegment.maneuver.position.latitude);
+
+ // Update the navigation instructions
+ if (pathSegment === 0) {
+ var nextSegment = routeSegmentList[routeSegment + 1];
+ updateNaviGuide(currentSegment, nextSegment);
+ }
+
+ var startPos = navigationArrowItem.coordinate;
+ var path = currentSegment.path;
+ pathSegment += 1;
+ if (pathSegment >= path.length) {
+ routeSegment += 1;
+ currentSegment = routeSegmentList[routeSegment];
+ if (!currentSegment) {
+ naviGuideArrowSource = "images/nav_nodir.png";
+ navigation.active = false;
+ targetEdit.clear();
+ return;
+ }
+ pathSegment = 0;
+ setNextAnimation();
+ return;
+ }
+ var endPos = path[pathSegment];
+
+ // Calculate new direction
+ var oldDir = navigationArrowAnimation.rotationDirection;
+ var newDir = startPos.azimuthTo(endPos);
+
+ // Calculate the duration of the animation
+ var diff = oldDir - newDir;
+ if (Math.abs(diff) < 15)
+ navigationArrowAnimation.rotationDuration = 0;
+ else if (diff < -180)
+ navigationArrowAnimation.rotationDuration = (diff + 360) * 5;
+ else if (diff > 180)
+ navigationArrowAnimation.rotationDuration = (360 - diff) * 5;
+ else
+ navigationArrowAnimation.rotationDuration = Math.abs(diff) * 5;
+
+ // Set animation details
+ var pathDistance = startPos.distanceTo(endPos);
+ var nextDistance = navigationArrowAnimation.pathDistance - pathDistance;
+ var nextRemainingDistance = navigationArrowAnimation.remainingDistance - pathDistance;
+ navigationArrowAnimation.coordinateDuration = pathDistance * 40;
+ navigationArrowAnimation.rotationDirection = startPos.azimuthTo(endPos);
+ navigationArrowAnimation.pathDistance = nextDistance;
+ navigationArrowAnimation.remainingDistance = nextRemainingDistance;
+ navigationArrowAnimation.sourceCoordinate = startPos;
+ navigationArrowAnimation.targetCoordinate = endPos;
+ navigationArrowAnimation.start();
+ }
+
+ RouteModel {
+ id: routeModel
+ plugin: map.plugin
+ query: routeQuery
+ onStatusChanged: {
+ if (status === RouteModel.Ready) {
+ switch (count) {
+ case 0:
+ // technically not an error
+ console.log('mapping error', errorString)
+ break
+ case 1:
+ break
+ }
+ } else if (status === RouteModel.Error) {
+ console.log('mapping error', errorString)
+ }
+ }
+ onRoutesChanged: {
+ if (count === 0)
+ return;
+ var route = routeModel.get(0);
+
+ totalDistance = route.distance;
+ metersPerSecond = route.distance / route.travelTime;
+ navigationArrowAnimation.remainingDistance = route.distance;
+
+ routeManeuverModel.clear();
+ currentSegment = route.segments[0];
+ routeSegmentList = route.segments;
+
+ routeSegment = 0;
+ pathSegment = 0;
+ setNextAnimation();
+ }
+ }
+
+ // Model that is used to display individual instructions
+ ListModel {
+ id: routeManeuverModel
+ }
+
+ Map {
+ id: map
+ gesture.enabled: true
+ anchors.fill: parent
+ plugin: mapboxgl
+
+ center: navigation.coordinate
+ zoomLevel: navigation.zoomlevel
+ bearing: navigation.direction
+
+ onCenterChanged: {
+ suggest.center = map.center
+ }
+
+ Behavior on bearing {
+ RotationAnimation {
+ duration: 250
+ direction: RotationAnimation.Shortest
+ }
+ }
+
+ Behavior on center {
+ id: centerBehavior
+ enabled: true
+ CoordinateAnimation { duration: 1500 }
+ }
+
+ MapQuickItem {
+ id: navigationArrowItem
+ z: 3
+ coordinate: navigation.routePosition
+ anchorPoint.x: navigationArrowImage.width / 2
+ anchorPoint.y: navigationArrowImage.height / 2
+ sourceItem: Image {
+ id: navigationArrowImage
+ source: "images/map_location_arrow.png"
+ width: 30
+ height: 30
+ }
+ }
+
+ MapQuickItem {
+ id: navigationCircleItem
+ z: 2
+ coordinate: navigation.routePosition
+ anchorPoint.x: navigationCircleImage.width / 2
+ anchorPoint.y: navigationCircleImage.height / 2
+ sourceItem: Image {
+ id: navigationCircleImage
+ source: "images/blue_circle_gps_area.png"
+ width: 100
+ height: 100
+ }
+ }
+
+ MapQuickItem {
+ id: destinationQuickItem
+ z: 3
+ coordinate: destinationCoordinate
+ anchorPoint.x: destinationImage.width / 2
+ anchorPoint.y: destinationImage.height - 10
+ sourceItem: Image {
+ id: destinationImage
+ source: "images/map_destination.png"
+ width: 40
+ height: 40
+ }
+ visible: navigation.active
+ }
+
+ MapItemView {
+ model: routeModel
+ delegate: routeDelegate
+ }
+
+ Component {
+ id: routeDelegate
+
+ MapRoute {
+ id: route
+ route: routeData
+ line.color: "#3698e8"
+ line.width: 6
+ smooth: true
+ visible: index === 0 // Show only one route (numberAlternativeRoutes not respected yet)
+ }
+ }
+
+ SequentialAnimation {
+ id: navigationArrowAnimation
+ property real rotationDuration: 0;
+ property real rotationDirection: 0;
+ property real coordinateDuration: 0;
+ property real pathDistance: 0;
+ property real remainingDistance: 0;
+ property var sourceCoordinate: navigation.routePosition;
+ property var targetCoordinate: startCoordinate;
+
+ RotationAnimation {
+ target: map
+ property: "bearing"
+ duration: navigationArrowAnimation.rotationDuration
+ to: navigationArrowAnimation.rotationDirection
+ direction: RotationAnimation.Shortest
+ }
+
+ ParallelAnimation {
+ CoordinateAnimation {
+ target: map
+ property: "center"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.targetCoordinate
+ }
+
+ CoordinateAnimation {
+ target: navigationArrowItem
+ property: "coordinate"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.targetCoordinate
+ }
+
+ CoordinateAnimation {
+ target: navigationCircleItem
+ property: "coordinate"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.targetCoordinate
+ }
+
+ NumberAnimation {
+ target: mapContainer
+ property: "naviGuideDistance"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.pathDistance
+ }
+
+ NumberAnimation {
+ target: mapContainer
+ property: "totalDistance"
+ duration: navigationArrowAnimation.coordinateDuration
+ to: navigationArrowAnimation.remainingDistance
+ }
+ }
+
+ onStopped: setNextAnimation()
+ }
+
+ MouseArea {
+ // Whenever the user taps on the map, move focus to it
+ anchors.fill: parent
+ onClicked: map.focus = true
+ }
+ }
+
+ Plugin {
+ id: mapboxgl
+ name: "mapbox"
+ PluginParameter {
+ name: "mapbox.access_token"
+ value: "pk.eyJ1IjoibWFwYm94NHF0IiwiYSI6ImNpd3J3eDE0eDEzdm8ydHM3YzhzajlrN2oifQ.keEkjqm79SiFDFjnesTcgQ"
+ }
+ PluginParameter {
+ name: "mapbox.mapping.map_id"
+ value: "mapbox.run-bike-hike"
+ }
+ }
+
+ states: [
+ State {
+ name: "";
+ when: !navigation.active
+ PropertyChanges {
+ target: targetEdit
+ width: UILayout.naviPageLocationWidth
+ anchors.topMargin: UILayout.naviPageLocationTopMargin
+ anchors.bottomMargin: 0
+ }
+ AnchorChanges {
+ target: targetEdit
+ anchors.top: parent.top
+ anchors.bottom: undefined
+ }
+ PropertyChanges {
+ target: naviInputShadow
+ visible: false
+ }
+ },
+ State {
+ name: "NAVIGATING";
+ when: navigation.active
+ PropertyChanges {
+ target: targetEdit
+ width: UILayout.naviPageTripWidth
+ anchors.topMargin: 0
+ anchors.bottomMargin: UILayout.naviPageTripSearchMargin
+ }
+ AnchorChanges {
+ target: targetEdit
+ anchors.top: undefined
+ anchors.bottom: totalTripInfo.top
+ }
+ PropertyChanges {
+ target: naviInputShadow
+ visible: true
+ }
+ }
+ ]
+
+ transitions: Transition {
+ NumberAnimation {
+ properties: "width"
+ easing.type: Easing.InOutQuad
+ duration: 250
+ }
+ AnchorAnimation {
+ duration: 250
+ }
+ }
+}