Skip to content

Commit

Permalink
Guided tour of the map canvas (#5465)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohsenD98 authored Jul 26, 2024
1 parent ef469aa commit afd8eaf
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 58 deletions.
2 changes: 1 addition & 1 deletion scripts/build-for-linux.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash

cmake -S . -B $(git rev-parse --show-toplevel)/build-x64-linux -GNinja -DWITH_VCPKG=ON -DENABLE_TESTS=ON
cmake -S . -B $(git rev-parse --show-toplevel)/build-x64-linux -GNinja -DWITH_VCPKG=ON -DWITH_SPIX=ON -DENABLE_TESTS=ON
cmake --build $(git rev-parse --show-toplevel)/build-x64-linux
109 changes: 52 additions & 57 deletions src/qml/QFieldGuide.qml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import Theme

Popup {
id: guide

required property var baseRoot
property bool enablePanelAnimation: false
property bool allowedToShow: true
property var steps: []
property int targetMargins: 5
property int index: 0
Expand Down Expand Up @@ -38,6 +38,15 @@ Popup {
enablePanelAnimation = true;
}

function blockGuide() {
allowedToShow = false;
}

function runTour() {
if (allowedToShow)
open();
}

Item {
id: internalObject
property var window: Window.window
Expand Down Expand Up @@ -122,14 +131,13 @@ Popup {

Rectangle {
id: hintPanel

property color color: Theme.mainBackgroundColor
property int dir: {
if (y < internalObject.pos.y)
return 1;
return 0;
}

color: Theme.mainBackgroundColor
objectName: "hintPanel"
width: Math.max(250, baseRoot.width / 2)
height: 88 + description.height + (animatedHint.visible ? animatedHint.height + 8 : 20)
Expand All @@ -140,13 +148,15 @@ Popup {
enabled: enablePanelAnimation

NumberAnimation {
id: panelXAnimation
duration: 150
}
}
Behavior on y {
enabled: enablePanelAnimation

NumberAnimation {
id: panelYAnimation
duration: 150
}
}
Expand All @@ -168,6 +178,17 @@ Popup {
return 0;
}

QfProgrerssRing {
id: progerss
value: (guide.index + 1) / steps.length
size: nextButton.height
anchors {
verticalCenter: nextButton.verticalCenter
left: parent.left
leftMargin: 15
}
}

Text {
objectName: "guideInternalTitle"
font.bold: true
Expand All @@ -178,6 +199,7 @@ Popup {
}
return "";
}
color: Theme.mainColor
anchors {
top: parent.top
left: parent.left
Expand All @@ -191,9 +213,8 @@ Popup {
Text {
id: description
objectName: "guideInternalDescription"
wrapMode: Text.WrapAnywhere
maximumLineCount: 4
elide: Text.ElideRight
wrapMode: Text.WordWrap
color: Theme.mainTextColor
text: {
if (internalObject.step) {
return internalObject.step.description;
Expand Down Expand Up @@ -257,7 +278,7 @@ Popup {
verticalPadding: 0
horizontalPadding: 12
bgcolor: "#00000000"
color: Theme.gray
color: Theme.mainColor
height: 32
radius: 5
visible: guide.index !== 0
Expand All @@ -272,78 +293,52 @@ Popup {
}
}

QfButton {
QfToolButton {
anchors {
right: parent.right
top: parent.top
margins: 10
margins: 2
}
width: 20
width: 30
height: width
bgcolor: Theme.darkRed
icon.color: Theme.mainTextColor
icon.source: Theme.getThemeIcon('ic_close_black_24dp')
icon.width: 45
icon.height: 45
padding: 0
bgcolor: "transparent"

onClicked: {
guide.close();
}
}
}

Image {
Shape {
id: pointerToItem
width: 48
height: width
source: hintPanel.dir ? Theme.getThemeVectorIcon("ic_arrow_drop_down_48dp") : Theme.getThemeVectorIcon("ic_arrow_drop_up_48dp")

Behavior on x {
enabled: enablePanelAnimation

NumberAnimation {
duration: 150
}
}
Behavior on y {
enabled: enablePanelAnimation

NumberAnimation {
duration: 150
}
}
rotation: hintPanel.dir ? 0 : 180
visible: panelXAnimation.running || panelYAnimation.running ? 0 : 1

x: {
if (internalObject.target[0]) {
return internalObject.pos.x < 10 ? 16 : internalObject.pos.x - 12;
return internalObject.pos.x + internalObject.target[0].width / 4;
}
return 0;
}
y: {
if (internalObject.target[0]) {
return internalObject.pos.y + (hintPanel.dir ? -(height - 14) : internalObject.target[0].height - 14);
return internalObject.pos.y + (hintPanel.dir ? -(height + 4) : internalObject.target[0].height + 4);
}
return 0;
}
}
}

/** Example how to use:
QFieldGuide {
id: startupTour
baseRoot: mainWindow
steps: [{
"title": qsTr("Zoom Toolbar"),
"description": qsTr("Main responsibility is described."),
"target": () => [zoomToolbar]
}, {
"title": qsTr("Menu Button"),
"description": qsTr("Main responsibility is described."),
"target": () => [menuButton, zoomToolbar]
}, {
"title": qsTr("GNSS Button"),
"description": qsTr("Main responsibility is described."),
"target": () => [gnssButton]
}, {
"title": qsTr("SearchBar"),
"description": qsTr("Main responsibility is described."),
"target": () => [locatorItem]
},]
ShapePath {
fillColor: Theme.mainBackgroundColor
strokeWidth: 1
strokeColor: Theme.mainBackgroundColor
PathSvg {
path: "M 0 0 L 20 0 L 10 10 Z"
}
}
}
*/
}
1 change: 1 addition & 0 deletions src/qml/WelcomeScreen.qml
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,7 @@ Page {
if (firstRun) {
welcomeText.text = qsTr("Welcome to QField. First time using this application? Try the sample projects listed below.");
settings.setValue("/QField/FirstRunDone", true);
settings.setValue("/QField/showMapCanvasGuide", true);
} else {
welcomeText.text = qsTr("Welcome back to QField.");
}
Expand Down
48 changes: 48 additions & 0 deletions src/qml/imports/Theme/QfProgrerssRing.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import QtQuick
import QtQuick.Controls

ProgressBar {
id: control

property int size: 56
property real strokeWidth: 6
property color color: Theme.mainColor
property color backgroundColor: Theme.lightGray

background: Rectangle {
implicitWidth: control.size
implicitHeight: control.size
radius: control.width / 2
color: "transparent"
border.color: control.backgroundColor
border.width: control.strokeWidth
}

onVisualPositionChanged: {
canvas.requestPaint();
}

contentItem: Item {
Canvas {
id: canvas
anchors.fill: parent
antialiasing: true
renderTarget: Canvas.Image
property real radius: control.width / 2 - control.strokeWidth / 2

onPaint: {
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.lineWidth = control.strokeWidth;
ctx.strokeStyle = control.color;
ctx.lineCap = "round";
ctx.beginPath();
ctx.arc(width / 2, height / 2, radius, -0.5 * Math.PI, -0.5 * Math.PI + control.visualPosition * 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.restore();
}
}
}
}
1 change: 1 addition & 0 deletions src/qml/imports/Theme/qmldir
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ QfPageHeader 1.0 QfPageHeader.qml
QfOverlayContainer 1.0 QfOverlayContainer.qml
QfTabBar 1.0 QfTabBar.qml
QfSwipeAnimator 1.0 QfSwipeAnimator.qml
QfProgrerssRing 1.0 QfProgrerssRing.qml
41 changes: 41 additions & 0 deletions src/qml/qgismobileapp.qml
Original file line number Diff line number Diff line change
Expand Up @@ -3374,6 +3374,7 @@ ApplicationWindow {
layoutListInstantiator.model.reloadModel();
geofencer.applyProjectSettings(qgisProject);
positioningSettings.geofencingPreventDigitizingDuringAlert = iface.readProjectBoolEntry("qfieldsync", "/geofencingShouldPreventDigitizing", false);
mapCanvasTour.startOnFreshRun();
}

function onSetMapExtent(extent) {
Expand Down Expand Up @@ -3931,4 +3932,44 @@ ApplicationWindow {
pluginPermissionDialog.open();
}
}

QFieldGuide {
id: mapCanvasTour
baseRoot: mainWindow
objectName: 'mapCanvasTour'

steps: [{
"title": qsTr("Dashboard"),
"description": qsTr("This button opens the dashboard. With the dashboard you can interact with the legend and map theme, or start digitizing by activating the editing mode. Long-pressing the button gives you immediate access to the main menu."),
"target": () => [menuButton]
}, {
"title": qsTr("Positioning"),
"description": qsTr("This button toggles the positioning system. When enabled, a position marker will appear top of the map. Long-pressing the button will open the positioning menu where additional functionalities can be explored."),
"target": () => [gnssButton]
}, {
"title": qsTr("Search"),
"description": qsTr("The search bar provides you with a quick way to find features within your project, jump to a typed latitude and longitude point, and much more."),
"target": () => [locatorItem]
}, {
"title": qsTr("Zoom"),
"description": qsTr("In addition to the pinch gesture, these buttons help you quickly zoom in and out."),
"target": () => [zoomToolbar]
}]

function startOnFreshRun() {
const startupGuide = settings.valueBool("/QField/showMapCanvasGuide", true);
if (startupGuide) {
runTour();
}
settings.setValue("/QField/showMapCanvasGuide", false);
}
}

Item {
objectName: 'toursController'

function blockGuides() {
mapCanvasTour.blockGuide();
}
}
}
1 change: 1 addition & 0 deletions src/qml/qml.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,6 @@
<file>PluginManagerSettings.qml</file>
<file>InformationDrawer.qml</file>
<file>QFieldGuide.qml</file>
<file>imports/Theme/QfProgrerssRing.qml</file>
</qresource>
</RCC>
2 changes: 2 additions & 0 deletions test/spix/smoke_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ def test_wms_layer(app, screenshot_path, screenshot_check, extra, process_alive)
"""
assert app.existsAndVisible("mainWindow")

app.invokeMethod("mainWindow/toursController", "blockGuides", [])

# Arbitrary wait period to insure project fully loaded and rendered
time.sleep(4)

Expand Down

1 comment on commit afd8eaf

@qfield-fairy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.