Page MenuHomePhorge

D367.1791546030.diff
No OneTemporary

Size
8 KB
Referenced Files
None
Subscribers
None

D367.1791546030.diff

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -243,6 +243,8 @@
ReactToEventPopup.qml
FileHandler.qml
media/PlayButton.qml
+ media/MediaViewerPage.qml
+ media/ZoomableImage.qml
ActionSettingsPage.qml
CreateRoomPage.qml
@@ -285,6 +287,7 @@
actions/RoomListActions.qml
actions/DialogActions.qml
actions/SettingsActions.qml
+ actions/MediaViewerActions.qml
actions/AllActions.qml
MessageNotification.qml
diff --git a/src/contents/ui/Main.qml b/src/contents/ui/Main.qml
--- a/src/contents/ui/Main.qml
+++ b/src/contents/ui/Main.qml
@@ -327,6 +327,10 @@
pageStack.layers.push(Qt.resolvedUrl("JoinRoomPage.qml"), {});
}
+ function pushMediaViewerPage(event) {
+ pageStack.layers.push(Qt.resolvedUrl("media/MediaViewerPage.qml"), { event });
+ }
+
function sessionNameFor(userId, deviceId) {
return userId + '/' + deviceId;
}
diff --git a/src/contents/ui/actions/AllActions.qml b/src/contents/ui/actions/AllActions.qml
--- a/src/contents/ui/actions/AllActions.qml
+++ b/src/contents/ui/actions/AllActions.qml
@@ -16,6 +16,7 @@
KazvActions.BubbleActions {},
KazvActions.RoomListActions {},
KazvActions.DialogActions {},
- KazvActions.SettingsActions {}
+ KazvActions.SettingsActions {},
+ KazvActions.MediaViewerActions {},
]
}
diff --git a/src/contents/ui/actions/MediaViewerActions.qml b/src/contents/ui/actions/MediaViewerActions.qml
new file mode 100644
--- /dev/null
+++ b/src/contents/ui/actions/MediaViewerActions.qml
@@ -0,0 +1,22 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQml
+
+import org.kde.kirigami as Kirigami
+
+import '../shortcuts' as Shortcuts
+
+Shortcuts.ActionCollection {
+ actionGroupName: l10n.get('action-group-media-viewer-actions')
+ actions: [
+ Kirigami.Action {
+ objectName: 'toOriginalSize'
+ icon.name: 'zoom-original-symbolic'
+ text: l10n.get('media-viewer-to-original-size')
+ }
+ ]
+}
diff --git a/src/contents/ui/event-types/Image.qml b/src/contents/ui/event-types/Image.qml
--- a/src/contents/ui/event-types/Image.qml
+++ b/src/contents/ui/event-types/Image.qml
@@ -70,19 +70,22 @@
visible: !compactMode
}
- property var image: Image {
- id: image
- objectName: 'mainImage'
- Layout.fillWidth: true
- property double imageWidth: upper.thumbnailWidth || upper.imageWidth || image.implicitWidth || 1
- property double imageHeight: upper.thumbnailHeight || upper.imageHeight || image.implicitHeight || 1
- property double privHeight: Math.min(imageMaxHeight, imageHeight)
- property double privWidth: privHeight / imageHeight * imageWidth
- Layout.preferredWidth: privWidth
- Layout.preferredHeight: width < privWidth ? privHeight / privWidth * width : privHeight
- source: fileHandler.kazvIOJob ? "" : fileHandler.cachedFile
- horizontalAlignment: Image.AlignLeft
- fillMode: Image.PreserveAspectFit
+ property var image: AbstractButton {
+ onClicked: pushMediaViewerPage(event)
+ contentItem: Image {
+ id: image
+ objectName: 'mainImage'
+ Layout.fillWidth: true
+ property double imageWidth: upper.thumbnailWidth || upper.imageWidth || image.implicitWidth || 1
+ property double imageHeight: upper.thumbnailHeight || upper.imageHeight || image.implicitHeight || 1
+ property double privHeight: Math.min(imageMaxHeight, imageHeight)
+ property double privWidth: privHeight / imageHeight * imageWidth
+ Layout.preferredWidth: privWidth
+ Layout.preferredHeight: width < privWidth ? privHeight / privWidth * width : privHeight
+ source: fileHandler.kazvIOJob ? "" : fileHandler.cachedFile
+ horizontalAlignment: Image.AlignLeft
+ fillMode: Image.PreserveAspectFit
+ }
}
property var canAddSticker: !!event.content.url
diff --git a/src/contents/ui/media/MediaViewerPage.qml b/src/contents/ui/media/MediaViewerPage.qml
new file mode 100644
--- /dev/null
+++ b/src/contents/ui/media/MediaViewerPage.qml
@@ -0,0 +1,47 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import moe.kazv.mxc.kazv as MK
+
+import org.kde.kirigami as Kirigami
+import '..' as Kazv
+
+Kirigami.Page {
+ id: mediaViewerPage
+ required property var event
+ property var jobManager: kazvIOManager
+ property var session: matrixSession
+
+ actions: [
+ Kazv.KazvAction {
+ objectName: 'toOriginalSize'
+ onTriggered: loader.item?.toOriginalSize()
+ }
+ ]
+
+ Kazv.FileHandler {
+ id: fileHandler
+ // Remove thumbnail info from content, so that FileHandler only downloads the original.
+ eventContent: ({ file: event.content?.file, url: event.content?.url })
+ autoCache: true
+ kazvIOManager: mediaViewerPage.jobManager
+ matrixSession: mediaViewerPage.session
+ }
+
+ property Component imageComp: ZoomableImage {
+ imageUrl: fileHandler.kazvIOJob ? '' : fileHandler.cachedFile
+ imageAlt: event.content?.body
+ }
+
+ Loader {
+ id: loader
+ anchors.fill: parent
+ sourceComponent: fileHandler.kazvIOJob ? undefined : mediaViewerPage.imageComp
+ }
+}
diff --git a/src/contents/ui/media/ZoomableImage.qml b/src/contents/ui/media/ZoomableImage.qml
new file mode 100644
--- /dev/null
+++ b/src/contents/ui/media/ZoomableImage.qml
@@ -0,0 +1,110 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import moe.kazv.mxc.kazv as MK
+
+import org.kde.kirigami as Kirigami
+import '..' as Kazv
+
+Item {
+ id: imageCont
+ anchors.fill: parent
+
+ required property string imageUrl
+ required property string imageAlt
+ property var fitScale: Math.min(1, width/image.implicitWidth, height/image.implicitHeight)
+
+ // image holds the unscaled original image.
+ // imageItem is used for scaling.
+ // We do not scale image directly because we want the position 0,0 to be
+ // such that the image is centered in the page.
+ Item {
+ id: imageItem
+ width: parent.width
+ height: parent.height
+ scale: imageCont.fitScale
+ property bool returning: false
+
+ Behavior on scale {
+ id: scaleRule
+ enabled: imageItem.returning
+ NumberAnimation {
+ duration: Kirigami.Units.shortDuration
+ }
+ }
+ Behavior on x {
+ id: xRule
+ enabled: imageItem.returning
+ NumberAnimation {
+ duration: Kirigami.Units.shortDuration
+ }
+ }
+ Behavior on y {
+ id: yRule
+ enabled: imageItem.returning
+ NumberAnimation {
+ duration: Kirigami.Units.shortDuration
+ }
+ }
+ function returnBounds() {
+ if (scale <= imageCont.fitScale) {
+ returning = true;
+ scale = imageCont.fitScale;
+ x = 0;
+ y = 0;
+ returning = false;
+ }
+ }
+ Image {
+ id: image
+ width: implicitWidth
+ height: implicitHeight
+ anchors.centerIn: parent
+ source: imageCont.imageUrl
+ fillMode: Image.Pad
+ Accessible.name: imageCont.imageAlt
+ }
+ }
+
+ PinchHandler {
+ target: imageItem
+ rotationAxis.enabled: false
+ scaleAxis.minimum: imageCont.fitScale
+ onActiveChanged: {
+ if (!active) { imageItem.returnBounds() }
+ }
+ }
+
+ DragHandler {
+ id: dragHandler
+ // If set to imageItem, it will cause translation to be reset whenever
+ // the pointer clicks somewhere out of imageItem's bounds.
+ // But imageItem is smaller than the original image, so this behaviour
+ // is not reasonable.
+ target: null
+ xAxis.onActiveValueChanged: delta => imageItem.x += delta
+ yAxis.onActiveValueChanged: delta => imageItem.y += delta
+ onActiveChanged: {
+ if (!active) { imageItem.returnBounds() }
+ }
+ }
+
+ WheelHandler {
+ target: imageItem
+ property: 'scale'
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
+ onActiveChanged: {
+ if (!active) { imageItem.returnBounds() }
+ }
+ }
+
+ function toOriginalSize() {
+ imageItem.scale = 1;
+ }
+}
diff --git a/src/l10n/en/100-ui.ftl b/src/l10n/en/100-ui.ftl
--- a/src/l10n/en/100-ui.ftl
+++ b/src/l10n/en/100-ui.ftl
@@ -24,6 +24,7 @@
action-group-room-list-context-menu-actions = Room List Actions
action-group-dialog-actions = Dialog Actions
action-group-settings-actions = Settings Actions
+action-group-media-viewer-actions = Media Viewer Actions
dialog-accept-action = Confirm
dialog-reject-action = Cancel
@@ -513,3 +514,5 @@
verification-action-deny-sas-match = They do not match
verification-show-numbers = Show numbers instead
verification-show-emojis = Show emojis instead
+
+media-viewer-to-original-size = Original size

File Metadata

Mime Type
text/plain
Expires
Fri, Oct 9, 4:40 AM (13 h, 11 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784994
Default Alt Text
D367.1791546030.diff (8 KB)

Event Timeline