diff --git a/src/gui/Pages/Chat/Timeline/EventList.qml b/src/gui/Pages/Chat/Timeline/EventList.qml index f7dd49e9..e84302fb 100644 --- a/src/gui/Pages/Chat/Timeline/EventList.qml +++ b/src/gui/Pages/Chat/Timeline/EventList.qml @@ -366,7 +366,7 @@ Rectangle { forLink || (isAnimated(event) ? "" : event.media_url) utils.makePopup( - "Popups/ImageViewerPopup.qml", + "Popups/ImageViewerPopup/ImageViewerPopup.qml", { thumbnailTitle: getThumbnailTitle(event), thumbnailMxc: event.thumbnail_url, diff --git a/src/gui/Popups/ImageViewerPopup.qml b/src/gui/Popups/ImageViewerPopup.qml deleted file mode 100644 index d5b6fef3..00000000 --- a/src/gui/Popups/ImageViewerPopup.qml +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later - -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Window 2.12 -import "../Base" - -HPopup { - id: popup - - property alias thumbnailTitle: thumbnail.title - property alias thumbnailMxc: thumbnail.mxc - property alias thumbnailPath: thumbnail.cachedPath // optional - property alias thumbnailCryptDict: thumbnail.cryptDict - property alias fullTitle: full.title - property alias fullMxc: full.mxc - property alias fullCryptDict: full.cryptDict - property size overallSize - - property bool alternateScaling: false - property bool activedFullScreen: false - - readonly property bool imageLargerThanWindow: - overallSize.width > window.width || overallSize.height > window.height - - readonly property bool imageEqualToWindow: - overallSize.width == window.width && - overallSize.height == window.height - - readonly property int paintedWidth: - full.status === Image.Ready ? - full.animatedPaintedWidth || full.paintedWidth : - thumbnail.animatedPaintedWidth || thumbnail.paintedWidth - - readonly property int paintedHeight: - full.status === Image.Ready ? - full.animatedPaintedHeight || full.paintedHeight : - thumbnail.animatedPaintedHeight || thumbnail.paintedHeight - - signal openExternallyRequested() - - function showFullScreen() { - if (activedFullScreen) return - - window.showFullScreen() - popup.activedFullScreen = true - if (! imageLargerThanWindow) popup.alternateScaling = true - } - - function exitFullScreen() { - if (! activedFullScreen) return - - window.showNormal() - popup.activedFullScreen = false - if (! imageLargerThanWindow) popup.alternateScaling = false - } - - function toggleFulLScreen() { - const isFull = window.visibility === Window.FullScreen - return isFull ? exitFullScreen() : showFullScreen() - } - - - margins: 0 - background: null - - onAboutToHide: exitFullScreen() - - HFlickable { - id: flickable - implicitWidth: window.width - implicitHeight: window.height - contentWidth: - Math.max(window.width, popup.paintedWidth * thumbnail.scale) - contentHeight: - Math.max(window.height, popup.paintedHeight * thumbnail.scale) - - ScrollBar.vertical: null - - TapHandler { - acceptedButtons: Qt.LeftButton | Qt.RightButton - onTapped: popup.close() - gesturePolicy: TapHandler.ReleaseWithinBounds - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - onWheel: { - if (wheel.modifiers !== Qt.ControlModifier) { - wheel.accepted = false - return - } - - wheel.accepted = true - const add = wheel.angleDelta.y / 120 / 5 - thumbnail.scale = Math.max( - 0.1, Math.min(10, thumbnail.scale + add), - ) - } - } - - HMxcImage { - id: thumbnail - anchors.centerIn: parent - width: - popup.alternateScaling && popup.imageLargerThanWindow ? - popup.overallSize.width : - - popup.alternateScaling ? - window.width : - - Math.min(window.width, popup.overallSize.width) - - height: - popup.alternateScaling && popup.imageLargerThanWindow ? - popup.overallSize.height : - - popup.alternateScaling ? - window.height : - - Math.min(window.height, popup.overallSize.height) - - fillMode: HMxcImage.PreserveAspectFit - // Use only cachedPath if set, don't waste time refetching thumb - canUpdate: ! cachedPath - - Behavior on width { - HNumberAnimation { overshoot: popup.alternateScaling? 2 : 3 } - } - - Behavior on height { - HNumberAnimation { overshoot: popup.alternateScaling? 2 : 3 } - } - - Binding on showProgressBar { - value: false - when: ! thumbnail.show - } - - HNumberAnimation { - id: resetScaleAnimation - target: thumbnail - property: "scale" - from: thumbnail.scale - to: 1 - overshoot: 2 - } - - Timer { - // Timer to not disappear before full image is done rendering - interval: 1000 - running: full.status === HMxcImage.Ready - onTriggered: thumbnail.show = false - } - - HMxcImage { - id: full - anchors.fill: parent - thumbnail: false - fillMode: parent.fillMode - // Image never loads at 0 opacity or with visible: false - opacity: status === HMxcImage.Ready ? 1 : 0.01 - - Behavior on opacity { HNumberAnimation {} } - } - - Item { - anchors.centerIn: parent - width: popup.paintedWidth - height: popup.paintedHeight - - TapHandler { - gesturePolicy: TapHandler.ReleaseWithinBounds - onTapped: { - thumbnail.scale === 1 ? - popup.alternateScaling = ! popup.alternateScaling : - resetScaleAnimation.start() - } - onDoubleTapped: popup.toggleFulLScreen() - } - - TapHandler { - acceptedButtons: Qt.MiddleButton - gesturePolicy: TapHandler.ReleaseWithinBounds - onTapped: popup.openExternallyRequested() - } - - TapHandler { - acceptedButtons: Qt.RightButton - gesturePolicy: TapHandler.ReleaseWithinBounds - onTapped: popup.close() - } - } - } - } -} diff --git a/src/gui/Popups/ImageViewerPopup/ImageViewerPopup.qml b/src/gui/Popups/ImageViewerPopup/ImageViewerPopup.qml new file mode 100644 index 00000000..0a4add2b --- /dev/null +++ b/src/gui/Popups/ImageViewerPopup/ImageViewerPopup.qml @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Window 2.12 +import "../../Base" + +HPopup { + id: popup + + property string thumbnailTitle + property string thumbnailMxc + property string thumbnailPath: "" + property var thumbnailCryptDict + property string fullTitle + property string fullMxc + property var fullCryptDict + property size overallSize + + property bool alternateScaling: false + property bool activedFullScreen: false + + readonly property alias canvas: canvas + + readonly property bool imageLargerThanWindow: + overallSize.width > window.width || overallSize.height > window.height + + readonly property bool imageEqualToWindow: + overallSize.width == window.width && + overallSize.height == window.height + + readonly property int paintedWidth: + canvas.full.status === Image.Ready ? + canvas.full.animatedPaintedWidth || canvas.full.paintedWidth : + canvas.thumbnail.animatedPaintedWidth || canvas.thumbnail.paintedWidth + + readonly property int paintedHeight: + canvas.full.status === Image.Ready ? + canvas.full.animatedPaintedHeight || canvas.full.paintedHeight : + canvas.thumbnail.animatedPaintedHeight || canvas.thumbnail.paintedHeight + + signal openExternallyRequested() + + function showFullScreen() { + if (activedFullScreen) return + + window.showFullScreen() + popup.activedFullScreen = true + if (! imageLargerThanWindow) popup.alternateScaling = true + } + + function exitFullScreen() { + if (! activedFullScreen) return + + window.showNormal() + popup.activedFullScreen = false + if (! imageLargerThanWindow) popup.alternateScaling = false + } + + function toggleFulLScreen() { + const isFull = window.visibility === Window.FullScreen + return isFull ? exitFullScreen() : showFullScreen() + } + + + margins: 0 + background: null + + onAboutToHide: exitFullScreen() + + Item { + implicitWidth: window.width + implicitHeight: window.height + + ViewerCanvas { + id: canvas + anchors.fill: parent + viewer: popup + } + } +} diff --git a/src/gui/Popups/ImageViewerPopup/ViewerCanvas.qml b/src/gui/Popups/ImageViewerPopup/ViewerCanvas.qml new file mode 100644 index 00000000..cc409aaf --- /dev/null +++ b/src/gui/Popups/ImageViewerPopup/ViewerCanvas.qml @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import "../../Base" + +HFlickable { + property HPopup viewer + + readonly property alias thumbnail: thumbnail + readonly property alias full: full + + + contentWidth: + Math.max(window.width, viewer.paintedWidth * thumbnail.scale) + contentHeight: + Math.max(window.height, viewer.paintedHeight * thumbnail.scale) + + ScrollBar.vertical: null + + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onTapped: viewer.close() + gesturePolicy: TapHandler.ReleaseWithinBounds + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: { + if (wheel.modifiers !== Qt.ControlModifier) { + wheel.accepted = false + return + } + + wheel.accepted = true + const add = wheel.angleDelta.y / 120 / 5 + thumbnail.scale = Math.max( + 0.1, Math.min(10, thumbnail.scale + add), + ) + } + } + + HMxcImage { + id: thumbnail + anchors.centerIn: parent + width: + viewer.alternateScaling && viewer.imageLargerThanWindow ? + viewer.overallSize.width : + + viewer.alternateScaling ? + window.width : + + Math.min(window.width, viewer.overallSize.width) + + height: + viewer.alternateScaling && viewer.imageLargerThanWindow ? + viewer.overallSize.height : + + viewer.alternateScaling ? + window.height : + + Math.min(window.height, viewer.overallSize.height) + + fillMode: HMxcImage.PreserveAspectFit + title: viewer.thumbnailTitle + mxc: viewer.thumbnailMxc + cachedPath: viewer.thumbnailPath + cryptDict: viewer.thumbnailCryptDict + // Use only cachedPath if set, don't waste time refetching thumb + canUpdate: ! cachedPath + + Behavior on width { + HNumberAnimation { overshoot: viewer.alternateScaling? 2 : 3 } + } + + Behavior on height { + HNumberAnimation { overshoot: viewer.alternateScaling? 2 : 3 } + } + + Binding on showProgressBar { + value: false + when: ! thumbnail.show + } + + HNumberAnimation { + id: resetScaleAnimation + target: thumbnail + property: "scale" + from: thumbnail.scale + to: 1 + overshoot: 2 + } + + Timer { + // Timer to not disappear before full image is done rendering + interval: 1000 + running: full.status === HMxcImage.Ready + onTriggered: thumbnail.show = false + } + + HMxcImage { + id: full + anchors.fill: parent + thumbnail: false + fillMode: parent.fillMode + title: viewer.fullTitle + mxc: viewer.fullMxc + cryptDict: viewer.fullCryptDict + // Image never loads at 0 opacity or with visible: false + opacity: status === HMxcImage.Ready ? 1 : 0.01 + + Behavior on opacity { HNumberAnimation {} } + } + + Item { + anchors.centerIn: parent + width: viewer.paintedWidth + height: viewer.paintedHeight + + TapHandler { + gesturePolicy: TapHandler.ReleaseWithinBounds + onTapped: { + thumbnail.scale === 1 ? + viewer.alternateScaling = ! viewer.alternateScaling : + resetScaleAnimation.start() + } + onDoubleTapped: viewer.toggleFulLScreen() + } + + TapHandler { + acceptedButtons: Qt.MiddleButton + gesturePolicy: TapHandler.ReleaseWithinBounds + onTapped: viewer.openExternallyRequested() + } + + TapHandler { + acceptedButtons: Qt.RightButton + gesturePolicy: TapHandler.ReleaseWithinBounds + onTapped: viewer.close() + } + } + } +}