From 7fe799e96560660a026c7509bf9378c78d3b1f92 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Fri, 10 Nov 2023 11:56:37 +0700 Subject: [PATCH] Add a grid pattern overlay feature to QField's QML camera --- images/images.qrc | 1 + .../qfield/nodpi/ic_3x3_grid_white_24dp.svg | 4 + .../QFieldControls/+Qt5/QFieldCamera.qml | 301 ++++++++++------ .../QFieldControls/+Qt6/QFieldCamera.qml | 334 +++++++++++------- 4 files changed, 405 insertions(+), 235 deletions(-) create mode 100644 images/themes/qfield/nodpi/ic_3x3_grid_white_24dp.svg diff --git a/images/images.qrc b/images/images.qrc index 517d4761cb..22031a3d50 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -11,6 +11,7 @@ pictures/qfield-love.png + themes/qfield/nodpi/ic_3x3_grid_white_24dp.svg themes/qfield/nodpi/ic_flash_auto_black_24dp.svg themes/qfield/nodpi/ic_flash_on_black_24dp.svg themes/qfield/nodpi/ic_flash_off_black_24dp.svg diff --git a/images/themes/qfield/nodpi/ic_3x3_grid_white_24dp.svg b/images/themes/qfield/nodpi/ic_3x3_grid_white_24dp.svg new file mode 100644 index 0000000000..1d09ee79a5 --- /dev/null +++ b/images/themes/qfield/nodpi/ic_3x3_grid_white_24dp.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/qml/imports/QFieldControls/+Qt5/QFieldCamera.qml b/src/qml/imports/QFieldControls/+Qt5/QFieldCamera.qml index fb1cd10aff..12e86fd86b 100644 --- a/src/qml/imports/QFieldControls/+Qt5/QFieldCamera.qml +++ b/src/qml/imports/QFieldControls/+Qt5/QFieldCamera.qml @@ -1,5 +1,6 @@ import QtQuick 2.14 import QtQuick.Controls 2.14 +import QtQuick.Shapes 1.14 import QtQuick.Window 2.14 import QtMultimedia 5.14 import Qt.labs.settings 1.0 @@ -45,6 +46,7 @@ Popup { Settings { id: settings property bool geoTagging: true + property bool showGrid: false } Page { @@ -120,6 +122,7 @@ Popup { } VideoOutput { + id: videoOutput anchors.fill: parent visible: cameraItem.state == "PhotoCapture" || cameraItem.state == "VideoCapture" @@ -130,6 +133,55 @@ Popup { autoOrientation: true } + Shape { + id: grid + visible: settings.showGrid + anchors.centerIn: parent + + property bool isLandscape: (mainWindow.width / mainWindow.height) > (videoOutput.contentRect.width / videoOutput.contentRect.height) + + width: isLandscape + ? videoOutput.contentRect.width * mainWindow.height / videoOutput.contentRect.height + : mainWindow.width + height: isLandscape + ? mainWindow.height + : videoOutput.contentRect.height * mainWindow.width / videoOutput.contentRect.width + + ShapePath { + strokeColor: "#99000000" + strokeWidth: 3 + fillColor: "transparent" + + startX: grid.width / 3 + startY: 0 + + PathLine { x: grid.width / 3; y: grid.height } + PathMove { x: grid.width / 3 * 2; y: 0 } + PathLine { x: grid.width / 3 * 2; y: grid.height } + PathMove { x: 0; y: grid.height / 3 } + PathLine { x: grid.width; y: grid.height / 3 } + PathMove { x: 0; y: grid.height / 3 * 2 } + PathLine { x: grid.width; y: grid.height / 3 * 2 } + } + + ShapePath { + strokeColor: "#AAFFFFFF" + strokeWidth: 1 + fillColor: "transparent" + + startX: grid.width / 3 + startY: 0 + + PathLine { x: grid.width / 3; y: grid.height } + PathMove { x: grid.width / 3 * 2; y: 0 } + PathLine { x: grid.width / 3 * 2; y: grid.height } + PathMove { x: 0; y: grid.height / 3 } + PathLine { x: grid.width; y: grid.height / 3 } + PathMove { x: 0; y: grid.height / 3 * 2 } + PathLine { x: grid.width; y: grid.height / 3 * 2 } + } + } + MouseArea { anchors.fill: parent @@ -209,97 +261,103 @@ Popup { Rectangle { x: cameraItem.isPortraitMode ? 0 : parent.width - 100 - y: cameraItem.isPortraitMode ? parent.height - 100 : 0 + y: cameraItem.isPortraitMode ? parent.height - 100 - mainWindow.sceneBottomMargin : 0 width: cameraItem.isPortraitMode ? parent.width : 100 - height: cameraItem.isPortraitMode ? 100 : parent.height + height: cameraItem.isPortraitMode ? 100 + mainWindow.sceneBottomMargin : parent.height color: Theme.darkGraySemiOpaque Rectangle { - id: captureRing - anchors.centerIn: parent - width: 64 - height: 64 - radius: 32 - color: Theme.darkGraySemiOpaque - border.color: cameraItem.state == "VideoCapture" && camera.videoRecorder.recorderState != CameraRecorder.StoppedState - ? "red" - : "white" - border.width: 2 - - QfToolButton { - id: captureButton + anchors.top: parent.top + width: parent.width + height: cameraItem.isPortraitMode ? parent.height - mainWindow.sceneBottomMargin : parent.height + color: "transparent" + Rectangle { + id: captureRing anchors.centerIn: parent - visible: camera.cameraStatus == Camera.ActiveStatus || - camera.cameraStatus == Camera.LoadedStatus || - camera.cameraStatus == Camera.StandbyStatus - - round: true - roundborder: true - iconSource: cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview" - ? Theme.getThemeIcon("ic_check_white_48dp") - : '' - bgcolor: cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview" - ? Theme.mainColor - : cameraItem.state == "VideoCapture" ? "red" : "white" - - onClicked: { - if (cameraItem.state == "PhotoCapture") { - camera.imageCapture.captureToLocation(qgisProject.homePath+ '/DCIM/') - currentPosition = positionSource.positionInformation - } else if (cameraItem.state == "VideoCapture") { - if (camera.videoRecorder.recorderState == CameraRecorder.StoppedState) { - camera.videoRecorder.record() - } else { - camera.videoRecorder.stop() - videoPreview.source = camera.videoRecorder.actualLocation - var path = camera.videoRecorder.actualLocation.toString() - var filePos = path.indexOf('file://') - currentPath = filePos === 0 ? path.substring(7) : path - cameraItem.state = "VideoPreview" - } - } else if (cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview") { - if (cameraItem.state == "PhotoPreview") { - if (settings.geoTagging && positionSource.active) { - FileUtils.addImageMetadata(currentPath, currentPosition) + width: 64 + height: 64 + radius: 32 + color: Theme.darkGraySemiOpaque + border.color: cameraItem.state == "VideoCapture" && camera.videoRecorder.recorderState != CameraRecorder.StoppedState + ? "red" + : "white" + border.width: 2 + + QfToolButton { + id: captureButton + + anchors.centerIn: parent + visible: camera.cameraStatus == Camera.ActiveStatus || + camera.cameraStatus == Camera.LoadedStatus || + camera.cameraStatus == Camera.StandbyStatus + + round: true + roundborder: true + iconSource: cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview" + ? Theme.getThemeIcon("ic_check_white_48dp") + : '' + bgcolor: cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview" + ? Theme.mainColor + : cameraItem.state == "VideoCapture" ? "red" : "white" + + onClicked: { + if (cameraItem.state == "PhotoCapture") { + camera.imageCapture.captureToLocation(qgisProject.homePath+ '/DCIM/') + currentPosition = positionSource.positionInformation + } else if (cameraItem.state == "VideoCapture") { + if (camera.videoRecorder.recorderState == CameraRecorder.StoppedState) { + camera.videoRecorder.record() + } else { + camera.videoRecorder.stop() + videoPreview.source = camera.videoRecorder.actualLocation + var path = camera.videoRecorder.actualLocation.toString() + var filePos = path.indexOf('file://') + currentPath = filePos === 0 ? path.substring(7) : path + cameraItem.state = "VideoPreview" + } + } else if (cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview") { + if (cameraItem.state == "PhotoPreview") { + if (settings.geoTagging && positionSource.active) { + FileUtils.addImageMetadata(currentPath, currentPosition) + } } + cameraItem.finished(currentPath) } - cameraItem.finished(currentPath) } } } - } - QfToolButton { - id: zoomButton - visible: cameraItem.isCapturing + QfToolButton { + id: zoomButton + visible: cameraItem.isCapturing - x: cameraItem.isPortraitMode ? (parent.width / 4) - (width / 2) : (parent.width - width) / 2 - y: cameraItem.isPortraitMode ? (parent.height - height) / 2 : (parent.height / 4) * 3 - (height / 2) + x: cameraItem.isPortraitMode ? (parent.width / 4) - (width / 2) : (parent.width - width) / 2 + y: cameraItem.isPortraitMode ? (parent.height - height) / 2 : (parent.height / 4) * 3 - (height / 2) - iconColor: "white" - bgcolor: Theme.darkGraySemiOpaque - round: true + iconColor: "white" + bgcolor: Theme.darkGraySemiOpaque + round: true - text: (camera.digitalZoom * camera.opticalZoom).toFixed(1) +'X' - font: Theme.tinyFont + text: (camera.digitalZoom * camera.opticalZoom).toFixed(1) +'X' + font: Theme.tinyFont - onClicked: { - camera.opticalZoom = 1; - camera.digitalZoom = 1; + onClicked: { + camera.opticalZoom = 1; + camera.digitalZoom = 1; + } } - } - QfToolButton { - id: flashButton - visible: cameraItem.isCapturing && camera.flash.supportedModes.length > 1 + QfToolButton { + id: flashButton + visible: cameraItem.isCapturing && camera.flash.supportedModes.length > 1 - x: cameraItem.isPortraitMode ? (parent.width / 4) * 3 - (width / 2) : (parent.width - width) / 2 - y: cameraItem.isPortraitMode ? (parent.height - height) / 2 : (parent.height / 4) - (height / 2) + x: cameraItem.isPortraitMode ? (parent.width / 4) * 3 - (width / 2) : (parent.width - width) / 2 + y: cameraItem.isPortraitMode ? (parent.height - height) / 2 : (parent.height / 4) - (height / 2) - iconSource: { - switch(camera.flash.mode) { + iconSource: { + switch(camera.flash.mode) { case Camera.FlashAuto: return Theme.getThemeVectorIcon('ic_flash_auto_black_24dp'); case Camera.FlashOn: @@ -308,56 +366,58 @@ Popup { return Theme.getThemeVectorIcon('ic_flash_off_black_24dp'); default: return''; + } } - } - iconColor: "white" - bgcolor: Qt.hsla(Theme.darkGray.hslHue, Theme.darkGray.hslSaturation, Theme.darkGray.hslLightness, 0.5) - round: true + iconColor: "white" + bgcolor: Qt.hsla(Theme.darkGray.hslHue, Theme.darkGray.hslSaturation, Theme.darkGray.hslLightness, 0.5) + round: true - onClicked: { - if (camera.flash.mode == Camera.FlashOff) { - camera.flash.mode = Camera.FlashOn; - } else { - camera.flash.mode = Camera.FlashOff + onClicked: { + if (camera.flash.mode == Camera.FlashOff) { + camera.flash.mode = Camera.FlashOn; + } else { + camera.flash.mode = Camera.FlashOff + } } } - } - - Rectangle { - visible: cameraItem.state == "VideoCapture" && camera.videoRecorder.recorderState != CameraRecorder.StoppedState - - x: cameraItem.isPortraitMode ? captureRing.x + captureRing.width / 2 - width / 2 : captureRing.x + captureRing.width / 2 - width / 2 - y: cameraItem.isPortraitMode ? captureRing.y - height - 20 : captureRing.y - height - 20 - - width: durationLabelMetrics.boundingRect('00:00:00').width + 20 - height: durationLabelMetrics.boundingRect('00:00:00').height + 10 - radius: 6 - - color: 'red' - Text { - id: durationLabel - anchors.centerIn: parent - text: { - if (camera.videoRecorder.duration > 0) { - var seconds = Math.ceil(camera.videoRecorder.duration / 1000); - var hours = Math.floor(seconds / 60 / 60) + ''; - seconds -= hours * 60 * 60; - var minutes = Math.floor(seconds / 60) + ''; - seconds = (seconds - minutes * 60) + ''; - return hours.padStart(2,'0') + ':' + minutes.padStart(2,'0') + ':' + seconds.padStart(2,'0'); - } else { - // tiny bit of a cheat here as the first second isn't triggered - return '00:00:01'; + Rectangle { + visible: cameraItem.state == "VideoCapture" && camera.videoRecorder.recorderState != CameraRecorder.StoppedState + + x: cameraItem.isPortraitMode ? captureRing.x + captureRing.width / 2 - width / 2 : captureRing.x + captureRing.width / 2 - width / 2 + y: cameraItem.isPortraitMode ? captureRing.y - height - 20 : captureRing.y - height - 20 + + width: durationLabelMetrics.boundingRect('00:00:00').width + 20 + height: durationLabelMetrics.boundingRect('00:00:00').height + 10 + radius: 6 + + color: 'red' + + Text { + id: durationLabel + anchors.centerIn: parent + text: { + if (camera.videoRecorder.duration > 0) { + var seconds = Math.ceil(camera.videoRecorder.duration / 1000); + var hours = Math.floor(seconds / 60 / 60) + ''; + seconds -= hours * 60 * 60; + var minutes = Math.floor(seconds / 60) + ''; + seconds = (seconds - minutes * 60) + ''; + return hours.padStart(2,'0') + ':' + minutes.padStart(2,'0') + ':' + seconds.padStart(2,'0'); + } else { + // tiny bit of a cheat here as the first second isn't triggered + return '00:00:01'; + } } + color: 'white' } - color: 'white' - } - FontMetrics { - id: durationLabelMetrics - font: durationLabel.font + FontMetrics { + id: durationLabelMetrics + font: durationLabel.font + } } + } } @@ -407,5 +467,24 @@ Popup { } } } + + QfToolButton { + id: gridButton + + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.top: geotagButton.bottom + anchors.topMargin: 4 + + iconSource: Theme.getThemeVectorIcon("ic_3x3_grid_white_24dp") + iconColor: settings.showGrid ? Theme.mainColor : "white" + bgcolor: Theme.darkGraySemiOpaque + round: true + + onClicked: { + settings.showGrid = !settings.showGrid + displayToast(settings.showGrid ? qsTr("Grid enabled") : qsTr("Grid disabled")) + } + } } } diff --git a/src/qml/imports/QFieldControls/+Qt6/QFieldCamera.qml b/src/qml/imports/QFieldControls/+Qt6/QFieldCamera.qml index f0534fcb4f..7cbae6751d 100644 --- a/src/qml/imports/QFieldControls/+Qt6/QFieldCamera.qml +++ b/src/qml/imports/QFieldControls/+Qt6/QFieldCamera.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Controls +import QtQuick.Shapes import QtQuick.Window import QtMultimedia import Qt.labs.settings @@ -43,6 +44,7 @@ Popup { Settings { id: settings property bool geoTagging: true + property bool showGrid: false } Page { @@ -107,6 +109,55 @@ Popup { visible: cameraItem.state == "PhotoCapture" || cameraItem.state == "VideoCapture" } + Shape { + id: grid + visible: settings.showGrid + anchors.centerIn: parent + + property bool isLandscape: (mainWindow.width / mainWindow.height) > (videoOutput.contentRect.width / videoOutput.contentRect.height) + + width: isLandscape + ? videoOutput.contentRect.width * mainWindow.height / videoOutput.contentRect.height + : mainWindow.width + height: isLandscape + ? mainWindow.height + : videoOutput.contentRect.height * mainWindow.width / videoOutput.contentRect.width + + ShapePath { + strokeColor: "#99000000" + strokeWidth: 3 + fillColor: "transparent" + + startX: grid.width / 3 + startY: 0 + + PathLine { x: grid.width / 3; y: grid.height } + PathMove { x: grid.width / 3 * 2; y: 0 } + PathLine { x: grid.width / 3 * 2; y: grid.height } + PathMove { x: 0; y: grid.height / 3 } + PathLine { x: grid.width; y: grid.height / 3 } + PathMove { x: 0; y: grid.height / 3 * 2 } + PathLine { x: grid.width; y: grid.height / 3 * 2 } + } + + ShapePath { + strokeColor: "#AAFFFFFF" + strokeWidth: 1 + fillColor: "transparent" + + startX: grid.width / 3 + startY: 0 + + PathLine { x: grid.width / 3; y: grid.height } + PathMove { x: grid.width / 3 * 2; y: 0 } + PathLine { x: grid.width / 3 * 2; y: grid.height } + PathMove { x: 0; y: grid.height / 3 } + PathLine { x: grid.width; y: grid.height / 3 } + PathMove { x: 0; y: grid.height / 3 * 2 } + PathLine { x: grid.width; y: grid.height / 3 * 2 } + } + } + PinchHandler { enabled: cameraItem.visible && cameraItem.isCapturing target: null @@ -180,146 +231,162 @@ Popup { color: Theme.darkGraySemiOpaque Rectangle { - id: captureRing - anchors.centerIn: parent - width: 64 - height: 64 - radius: 32 + x: cameraItem.isPortraitMode ? 0 : parent.width - 100 + y: cameraItem.isPortraitMode ? parent.height - 100 - mainWindow.sceneBottomMargin : 0 + width: cameraItem.isPortraitMode ? parent.width : 100 + height: cameraItem.isPortraitMode ? 100 + mainWindow.sceneBottomMargin : parent.height + color: Theme.darkGraySemiOpaque - border.color: cameraItem.state == "VideoCapture" && captureSession.recorder.recorderState !== MediaRecorder.StoppedState - ? "red" - : "white" - border.width: 2 - - QfToolButton { - id: captureButton - - anchors.centerIn: parent - visible: camera.cameraStatus == Camera.ActiveStatus || - camera.cameraStatus == Camera.LoadedStatus || - camera.cameraStatus == Camera.StandbyStatus - - round: true - roundborder: true - iconSource: cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview" - ? Theme.getThemeIcon("ic_check_white_48dp") - : '' - bgcolor: cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview" - ? Theme.mainColor - : cameraItem.state == "VideoCapture" ? "red" : "white" - - onClicked: { - if (cameraItem.state == "PhotoCapture") { - captureSession.imageCapture.captureToFile(qgisProject.homePath+ '/DCIM/') - currentPosition = positionSource.positionInformation - } else if (cameraItem.state == "VideoCapture") { - if (captureSession.recorder.recorderState === MediaRecorder.StoppedState) { - captureSession.recorder.record() - } else { - captureSession.recorder.stop() - videoPreview.source = captureSession.recorder.actualLocation - var path = captureSession.recorder.actualLocation.toString() - var filePos = path.indexOf('file://') - currentPath = filePos === 0 ? path.substring(7) : path - cameraItem.state = "VideoPreview" - } - } else if (cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview") { - if (cameraItem.state == "PhotoPreview") { - if (settings.geoTagging && positionSource.active) { - FileUtils.addImageMetadata(currentPath, currentPosition) + + Rectangle { + anchors.top: parent.top + width: parent.width + height: cameraItem.isPortraitMode ? parent.height - mainWindow.sceneBottomMargin : parent.height + color: "transparent" + + Rectangle { + id: captureRing + anchors.centerIn: parent + width: 64 + height: 64 + radius: 32 + color: Theme.darkGraySemiOpaque + border.color: cameraItem.state == "VideoCapture" && captureSession.recorder.recorderState !== MediaRecorder.StoppedState + ? "red" + : "white" + border.width: 2 + + QfToolButton { + id: captureButton + + anchors.centerIn: parent + visible: camera.cameraStatus == Camera.ActiveStatus || + camera.cameraStatus == Camera.LoadedStatus || + camera.cameraStatus == Camera.StandbyStatus + + round: true + roundborder: true + iconSource: cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview" + ? Theme.getThemeIcon("ic_check_white_48dp") + : '' + bgcolor: cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview" + ? Theme.mainColor + : cameraItem.state == "VideoCapture" ? "red" : "white" + + onClicked: { + if (cameraItem.state == "PhotoCapture") { + captureSession.imageCapture.captureToFile(qgisProject.homePath+ '/DCIM/') + currentPosition = positionSource.positionInformation + } else if (cameraItem.state == "VideoCapture") { + if (captureSession.recorder.recorderState === MediaRecorder.StoppedState) { + captureSession.recorder.record() + } else { + captureSession.recorder.stop() + videoPreview.source = captureSession.recorder.actualLocation + var path = captureSession.recorder.actualLocation.toString() + var filePos = path.indexOf('file://') + currentPath = filePos === 0 ? path.substring(7) : path + cameraItem.state = "VideoPreview" + } + } else if (cameraItem.state == "PhotoPreview" || cameraItem.state == "VideoPreview") { + if (cameraItem.state == "PhotoPreview") { + if (settings.geoTagging && positionSource.active) { + FileUtils.addImageMetadata(currentPath, currentPosition) + } + } + cameraItem.finished(currentPath) } } - cameraItem.finished(currentPath) } } - } - } - QfToolButton { - id: zoomButton - visible: cameraItem.isCapturing + QfToolButton { + id: zoomButton + visible: cameraItem.isCapturing - x: cameraItem.isPortraitMode ? (parent.width / 4) - (width / 2) : (parent.width - width) / 2 - y: cameraItem.isPortraitMode ? (parent.height - height) / 2 : (parent.height / 4) * 3 - (height / 2) + x: cameraItem.isPortraitMode ? (parent.width / 4) - (width / 2) : (parent.width - width) / 2 + y: cameraItem.isPortraitMode ? (parent.height - height) / 2 : (parent.height / 4) * 3 - (height / 2) - iconColor: "white" - bgcolor: Theme.darkGraySemiOpaque - round: true + iconColor: "white" + bgcolor: Theme.darkGraySemiOpaque + round: true - text: camera.zoomFactor.toFixed(1) +'X' - font: Theme.tinyFont + text: camera.zoomFactor.toFixed(1) +'X' + font: Theme.tinyFont - onClicked: { - camera.zoomFactor = 1; - } - } - - QfToolButton { - id: flashButton - visible: cameraItem.isCapturing && camera.isFlashModeSupported(Camera.FlashOn) - - x: cameraItem.isPortraitMode ? (parent.width / 4) * 3 - (width / 2) : (parent.width - width) / 2 - y: cameraItem.isPortraitMode ? (parent.height - height) / 2 : (parent.height / 4) - (height / 2) - - iconSource: { - switch(camera.flashMode) { - case Camera.FlashAuto: - return Theme.getThemeVectorIcon('ic_flash_auto_black_24dp'); - case Camera.FlashOn: - return Theme.getThemeVectorIcon('ic_flash_on_black_24dp'); - case Camera.FlashOff: - return Theme.getThemeVectorIcon('ic_flash_off_black_24dp'); - default: - return''; + onClicked: { + camera.zoomFactor = 1; + } } - } - iconColor: "white" - bgcolor: Qt.hsla(Theme.darkGray.hslHue, Theme.darkGray.hslSaturation, Theme.darkGray.hslLightness, 0.5) - round: true - onClicked: { - if (camera.flashMode === Camera.FlashOff) { - camera.flashMode = Camera.FlashOn; - } else { - camera.flashMode = Camera.FlashOff - } - } - } + QfToolButton { + id: flashButton + visible: cameraItem.isCapturing && camera.isFlashModeSupported(Camera.FlashOn) + + x: cameraItem.isPortraitMode ? (parent.width / 4) * 3 - (width / 2) : (parent.width - width) / 2 + y: cameraItem.isPortraitMode ? (parent.height - height) / 2 : (parent.height / 4) - (height / 2) + + iconSource: { + switch(camera.flashMode) { + case Camera.FlashAuto: + return Theme.getThemeVectorIcon('ic_flash_auto_black_24dp'); + case Camera.FlashOn: + return Theme.getThemeVectorIcon('ic_flash_on_black_24dp'); + case Camera.FlashOff: + return Theme.getThemeVectorIcon('ic_flash_off_black_24dp'); + default: + return''; + } + } + iconColor: "white" + bgcolor: Qt.hsla(Theme.darkGray.hslHue, Theme.darkGray.hslSaturation, Theme.darkGray.hslLightness, 0.5) + round: true - Rectangle { - visible: cameraItem.state == "VideoCapture" && captureSession.recorder.recorderState !== MediaRecorder.StoppedState - - x: cameraItem.isPortraitMode ? captureRing.x + captureRing.width / 2 - width / 2 : captureRing.x + captureRing.width / 2 - width / 2 - y: cameraItem.isPortraitMode ? captureRing.y - height - 20 : captureRing.y - height - 20 - - width: durationLabelMetrics.boundingRect('00:00:00').width + 20 - height: durationLabelMetrics.boundingRect('00:00:00').height + 10 - radius: 6 - - color: 'red' - - Text { - id: durationLabel - anchors.centerIn: parent - text: { - if (captureSession.recorder.duration > 0) { - var seconds = Math.ceil(captureSession.recorder.duration / 1000); - var hours = Math.floor(seconds / 60 / 60) + ''; - seconds -= hours * 60 * 60; - var minutes = Math.floor(seconds / 60) + ''; - seconds = (seconds - minutes * 60) + ''; - return hours.padStart(2,'0') + ':' + minutes.padStart(2,'0') + ':' + seconds.padStart(2,'0'); - } else { - // tiny bit of a cheat here as the first second isn't triggered - return '00:00:01'; + onClicked: { + if (camera.flashMode === Camera.FlashOff) { + camera.flashMode = Camera.FlashOn; + } else { + camera.flashMode = Camera.FlashOff + } } } - color: 'white' - } - FontMetrics { - id: durationLabelMetrics - font: durationLabel.font + Rectangle { + visible: cameraItem.state == "VideoCapture" && captureSession.recorder.recorderState !== MediaRecorder.StoppedState + + x: cameraItem.isPortraitMode ? captureRing.x + captureRing.width / 2 - width / 2 : captureRing.x + captureRing.width / 2 - width / 2 + y: cameraItem.isPortraitMode ? captureRing.y - height - 20 : captureRing.y - height - 20 + + width: durationLabelMetrics.boundingRect('00:00:00').width + 20 + height: durationLabelMetrics.boundingRect('00:00:00').height + 10 + radius: 6 + + color: 'red' + + Text { + id: durationLabel + anchors.centerIn: parent + text: { + if (captureSession.recorder.duration > 0) { + var seconds = Math.ceil(captureSession.recorder.duration / 1000); + var hours = Math.floor(seconds / 60 / 60) + ''; + seconds -= hours * 60 * 60; + var minutes = Math.floor(seconds / 60) + ''; + seconds = (seconds - minutes * 60) + ''; + return hours.padStart(2,'0') + ':' + minutes.padStart(2,'0') + ':' + seconds.padStart(2,'0'); + } else { + // tiny bit of a cheat here as the first second isn't triggered + return '00:00:01'; + } + } + color: 'white' + } + + FontMetrics { + id: durationLabelMetrics + font: durationLabel.font + } + } } } } @@ -370,5 +437,24 @@ Popup { } } } + + QfToolButton { + id: gridButton + + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.top: geotagButton.bottom + anchors.topMargin: 4 + + iconSource: Theme.getThemeVectorIcon("ic_3x3_grid_white_24dp") + iconColor: settings.showGrid ? Theme.mainColor : "white" + bgcolor: Theme.darkGraySemiOpaque + round: true + + onClicked: { + settings.showGrid = !settings.showGrid + displayToast(settings.showGrid ? qsTr("Grid enabled") : qsTr("Grid disabled")) + } + } } }