diff --git a/app/qml/CMakeLists.txt b/app/qml/CMakeLists.txt index d9662b019..6eef31f76 100644 --- a/app/qml/CMakeLists.txt +++ b/app/qml/CMakeLists.txt @@ -44,6 +44,7 @@ set(MM_QML components/MMColorButton.qml components/MMPopup.qml components/MMPhoto.qml + components/MMPhotoCard.qml components/MMProgressBar.qml components/MMRadioButton.qml components/MMRoundButton.qml diff --git a/app/qml/components/MMPhoto.qml b/app/qml/components/MMPhoto.qml index c3857a41a..68b2f31f8 100644 --- a/app/qml/components/MMPhoto.qml +++ b/app/qml/components/MMPhoto.qml @@ -6,13 +6,10 @@ * (at your option) any later version. * * * ***************************************************************************/ +pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Controls -import Qt5Compat.GraphicalEffects - -import "../components" as MMComponents -import "." +import QtQuick.Effects Image { id: root @@ -20,33 +17,34 @@ Image { property url photoUrl property bool isLocalFile: true - signal clicked( var path ) + signal clicked( url path ) height: width source: root.photoUrl asynchronous: true autoTransform: true layer.enabled: true - layer { - effect: OpacityMask { - maskSource: Item { - width: root.width - height: root.height - Rectangle { - anchors.centerIn: parent - width: parent.width - height: parent.height - radius: 20 * __dp - } - } - } + layer.effect: MultiEffect { + maskEnabled: true + maskSource: maskRect + autoPaddingEnabled: false + } + + // The mask shape + Rectangle { + id: maskRect + width: root.width + height: root.height + radius: 20 * __dp + visible: false + layer.enabled: true } Rectangle { - anchors.fill: parent + anchors.fill: root color: __style.polarColor z: -1 - visible: root.photoUrl == '' || root.status === Image.Error // if image has transparent background, we would still see it + visible: root.photoUrl.toString() === "" || root.status === Image.Error // if image has transparent background, we would still see it MMIcon { anchors.centerIn: parent @@ -57,13 +55,13 @@ Image { } MMSingleClickMouseArea { - anchors.fill: parent + anchors.fill: root onSingleClicked: root.clicked(root.photoUrl) } - MMComponents.MMBusyIndicator { + MMBusyIndicator { id: busyIndicator - anchors.centerIn: parent + anchors.centerIn: root visible: root.status === Image.Loading } diff --git a/app/qml/components/MMPhotoCard.qml b/app/qml/components/MMPhotoCard.qml new file mode 100644 index 000000000..d92f45ae9 --- /dev/null +++ b/app/qml/components/MMPhotoCard.qml @@ -0,0 +1,103 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Effects + +Item { + id: root + + property alias imageSource: bngImage.photoUrl + property string text: "" + property bool textVisible: false + property int size: 120 * __dp + + signal clicked(url path) + + height: size + width: size + + layer.enabled: true + layer.effect: MultiEffect { + maskEnabled: true + maskSource: photoMask + autoPaddingEnabled: false + } + + Rectangle { + id: photoMask + width: root.width + height: root.height + radius: 20 * __dp + visible: false + layer.enabled: true + } + + MMPhoto { + id: bngImage + anchors.fill: root + fillMode: Image.PreserveAspectCrop + } + + MultiEffect{ + id: blurFooter + height: root.height * 0.33 + visible: root.textVisible + anchors { + left: root.left + right: root.right + bottom: root.bottom + } + + source: ShaderEffectSource { + sourceItem: bngImage + sourceRect: Qt.rect(0, bngImage.height - bngImage.height * 0.33, bngImage.width, + bngImage.height * 0.33) + recursive: false + } + + autoPaddingEnabled: false + blurEnabled: true + blur: 0.8 + } + + // tint overlay + Rectangle { + anchors.fill: blurFooter + color: __style.nightColor + opacity: 0.35 + visible: root.textVisible + } + + Text { + visible: root.textVisible + text: root.text + width: root.width - 2 * (root.size * 0.15) + anchors.centerIn: blurFooter + + color: __style.polarColor + font: __style.t5 + + lineHeightMode: Text.FixedHeight + lineHeight: __style.margin16 + maximumLineCount: 2 + + horizontalAlignment: Text.AlignHCenter | Text.AlignJustify + verticalAlignment: Text.AlignVCenter + + wrapMode: Text.WordWrap + elide: Text.ElideRight + } + + MMSingleClickMouseArea { + anchors.fill: root + onSingleClicked: root.clicked(root.imageSource) + } +} diff --git a/app/qml/form/editors/MMFormGalleryEditor.qml b/app/qml/form/editors/MMFormGalleryEditor.qml index bfa98db84..5fa27803a 100644 --- a/app/qml/form/editors/MMFormGalleryEditor.qml +++ b/app/qml/form/editors/MMFormGalleryEditor.qml @@ -48,12 +48,10 @@ MMPrivateComponents.MMBaseInput { homePath: root._fieldActiveProject.homePath } - delegate: MMComponents.MMPhoto { - width: rowView.height + delegate: MMComponents.MMPhotoCard{ + size: rowView.height - fillMode: Image.PreserveAspectCrop - - photoUrl: { + imageSource: { let absolutePath = model.PhotoPath if ( absolutePath !== '' && __inputUtils.fileExists( absolutePath ) ) { @@ -62,6 +60,10 @@ MMPrivateComponents.MMBaseInput { return '' } + textVisible: false + + text: model.FeatureTitle + onClicked: function( path ) { root.openLinkedFeature( model.FeaturePair ) } diff --git a/gallery/qml.qrc b/gallery/qml.qrc index 9d82ad2d8..d709e4bc5 100644 --- a/gallery/qml.qrc +++ b/gallery/qml.qrc @@ -5,6 +5,7 @@ qml/components/EditorItem.qml qml/components/TextItem.qml qml/components/ColorBox.qml + qml/components/FormPhotoViewer.qml qml/pages/ComponentsPage.qml qml/pages/SandboxPage.qml qml/pages/IconsPage.qml @@ -106,6 +107,7 @@ ../app/qml/form/components/calendar/MMDateTumbler.qml ../app/qml/form/components/photo/MMPhotoAttachment.qml ../app/qml/form/components/photo/MMPhotoPreview.qml + ../app/qml/form/components/MMFormPhotoSketchingPageDialog.qml ../app/qml/form/editors/MMFormCalendarEditor.qml ../app/qml/form/editors/MMFormComboboxBaseEditor.qml ../app/qml/form/editors/MMFormGalleryEditor.qml diff --git a/gallery/qml/components/FormPhotoViewer.qml b/gallery/qml/components/FormPhotoViewer.qml new file mode 100644 index 000000000..27cf54506 --- /dev/null +++ b/gallery/qml/components/FormPhotoViewer.qml @@ -0,0 +1,170 @@ + + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +import QtQuick + +import mm 1.0 as MM + +import "../../app/qml/components" as MMComponents +import "../../app/qml/components/private" as MMPrivateComponents +import "../../app/qml/form/components/photo" as MMPhotoComponents +import "../../app/qml/form/components" as MMFormComponents + + +/* + * Photo viewer mock for the gallery feature form. + * Its purpose is to show image based on the provided URL. + * It is not combined with MMPhotoFormEditor as it would be not possible to use it in the gallery then. + * + * Serves as a base class for MMPhotoFormEditor. + */ +MMPrivateComponents.MMBaseInput { + id: root + + property string photoUrl: "" + property bool hasCameraCapability: true + + property var photoComponent: photo + property alias photoState: photoStateGroup.state + + signal trashClicked + signal capturePhotoClicked + signal chooseFromGalleryClicked + signal drawOnPhotoClicked + + StateGroup { + id: photoStateGroup + + states: [ + State { + name: "valid" + }, + State { + name: "notSet" + }, + State { + name: "notAvailable" + } + ] + + state: "notSet" + } + + inputContent: Rectangle { + width: parent.width + height: __style.row160 + + color: __style.polarColor + radius: __style.radius20 + + MMComponents.MMPhoto { + id: photo + + width: parent.width + height: parent.height + + visible: photoStateGroup.state !== "notSet" + + photoUrl: root.photoUrl + isLocalFile: root.photoUrl.startsWith("file://") + cache: false + + fillMode: Image.PreserveAspectCrop + + onStatusChanged: { + if (status === Image.Error) { + __inputUtils.log( + "Image Loading", + "Could not load the image. It may be missing or invalid, the URL might be incorrect, or there may be no network connection: " + root.photoUrl) + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (photo.status === Image.Ready) { + previewLoader.active = true + previewLoader.focus = true + } + } + } + + MMComponents.MMRoundButton { + anchors { + right: parent.right + bottom: parent.bottom + rightMargin: __style.margin10 + bottomMargin: __style.margin10 + } + + bgndColor: __style.negativeColor + iconSource: __style.deleteIcon + iconColor: __style.grapeColor + + visible: root.editState === "enabled" + && photoStateGroup.state !== "notSet" + + onClicked: root.trashClicked() + } + + MMComponents.MMRoundButton { + anchors { + right: parent.right + top: parent.top + rightMargin: __style.margin10 + topMargin: __style.margin10 + } + + bgndColor: __style.lightGreenColor + iconSource: __style.drawIcon + iconColor: __style.forestColor + + visible: root.editState === "enabled" + && photoStateGroup.state !== "notSet" + && __activeProject.photoSketchingEnabled + && root.photoUrl.startsWith("file://") + + onClicked: { + sketchingLoader.active = true + sketchingLoader.focus = true + } + } + } + + MMPhotoComponents.MMPhotoAttachment { + width: parent.width + height: parent.height + + visible: photoStateGroup.state === "notSet" + enabled: root.editState === "enabled" + + hasCameraCapability: root.hasCameraCapability + + onCapturePhotoClicked: root.capturePhotoClicked() + onChooseFromGalleryClicked: root.chooseFromGalleryClicked() + } + } + + Loader { + id: previewLoader + + asynchronous: true + active: false + sourceComponent: previewComponent + } + + Component { + id: previewComponent + + MMPhotoComponents.MMPhotoPreview { + photoUrl: root.photoUrl + } + } +} diff --git a/gallery/qml/pages/PhotosPage.qml b/gallery/qml/pages/PhotosPage.qml index 65f06f7fc..bab3c15a5 100644 --- a/gallery/qml/pages/PhotosPage.qml +++ b/gallery/qml/pages/PhotosPage.qml @@ -14,6 +14,7 @@ import QtQuick.Controls.Basic import "../../app/qml/components" import "../../app/qml/form/editors" +import "../components" as GalleryComponents Page { @@ -22,40 +23,148 @@ Page { color: __style.lightGreenColor } - Column { - width: parent.width - spacing: 20 - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 20 - anchors.left: parent.left - anchors.leftMargin: 20 + ScrollView { + id: scrollView + anchors.fill: parent + + contentWidth: availableWidth - MMFormPhotoViewer { + Column { width: parent.width + spacing: 20 + topPadding: 20 + rightPadding: 20 + leftPadding: 20 - onCapturePhotoClicked: console.log("onCapturePhotoClicked") - onChooseFromGalleryClicked: console.log("onChooseFromGalleryClicked") - } + GalleryComponents.FormPhotoViewer { + width: parent.width - parent.leftPadding - parent.rightPadding - MMFormPhotoViewer { - width: parent.width + onCapturePhotoClicked: console.log("onCapturePhotoClicked") + onChooseFromGalleryClicked: console.log("onChooseFromGalleryClicked") + } - photoUrl: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" - photoState: "valid" - } + GalleryComponents.FormPhotoViewer { + width: parent.width - parent.leftPadding - parent.rightPadding - MMFormPhotoViewer { - width: 200 + photoUrl: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" + photoState: "valid" + } - photoUrl: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" - photoState: "valid" - } + GalleryComponents.FormPhotoViewer { + width: 200 + + photoUrl: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" + photoState: "valid" + } + + GalleryComponents.FormPhotoViewer { + width: 200 + + photoState: "notAvailable" + } + + ScrollView { + spacing: 20 + width: parent.width - parent.leftPadding - parent.rightPadding + height: contentHeight + + ScrollBar.vertical.policy: ScrollBar.AlwaysOff + ScrollBar.horizontal.policy: ScrollBar.AlwaysOn + + ScrollBar.horizontal.visible: false + + Row { + spacing: 10 + + MMPhotoCard { + size: 120 + imageSource: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" + text: "This is my feature" + textVisible: true + } + + MMPhotoCard { + size: 120 + imageSource: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" + text: "This is my feature and added longer text to check how it looks" + textVisible: true + } + + MMPhotoCard { + size: 120 + imageSource: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" + text: "This is my feature" + textVisible: false + } + } + } + + MMListSpacer { + height: __style.margin20 + } + + ScrollView { + spacing: 20 + width: parent.width - parent.leftPadding - parent.rightPadding + height: contentHeight + + ScrollBar.vertical.policy: ScrollBar.AlwaysOff + ScrollBar.horizontal.policy: ScrollBar.AlwaysOn + ScrollBar.horizontal.visible: false + + Row { + spacing: 10 + + MMPhotoCard { + size: 180 + imageSource: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" + text: "This is my feature" + textVisible: true + } + + MMPhotoCard { + size: 180 + imageSource: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" + text: "This is my feature and added longer text to check how it looks" + textVisible: true + } + } + } + + MMListSpacer { + height: __style.margin20 + } + + ScrollView { + spacing: 20 + width: parent.width - parent.leftPadding - parent.rightPadding + + ScrollBar.vertical.policy: ScrollBar.AlwaysOff + ScrollBar.horizontal.policy: ScrollBar.AlwaysOn + ScrollBar.horizontal.visible: false + + Row { + spacing: 10 + + MMPhotoCard { + size: (scrollView.width - scrollView.leftPadding - scrollView.rightPadding) / 2 + imageSource: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" + text: "This is my feature" + textVisible: true + } - MMFormPhotoViewer { - width: 200 + MMPhotoCard { + size: (scrollView.width - scrollView.leftPadding - scrollView.rightPadding) / 2 + imageSource: "https://images.pexels.com/photos/615348/forest-fog-sunny-nature-615348.jpeg" + text: "This is my feature and added longer text to check how it looks,and again this my feature and added longer text to check how it looks and again is my feature and added longer text to check how it looks" + textVisible: true + } + } + } - photoState: "notAvailable" + MMListSpacer { + height: __style.margin20 + } } } }