Rework fullscreen image

As discussed in network/neochat#161, when clicking the image it now only covers the neochat window. A modal popup that covers the neochat window is now used. The app behind get dimmed.

Left clicking anywhere closes the preview as well as the using the close button. Right clicking on the image itself still gives the image's context menu.

Before
![fullscreenimage_before](/uploads/f7a64ab2f0b75405f3f0a16f32c029f3/fullscreenimage_before.png)

After
![fullscreenimage_updated2](/uploads/8feb6c79891019203a6a0a8439c71b70/fullscreenimage_updated2.png)

Latest
![fullscreenimage_updated_final](/uploads/61ca4c1251b914ae3a6bdd158f4dc396/fullscreenimage_updated_final.png)

Closes network/neochat#161
This commit is contained in:
James Graham
2022-10-09 16:27:51 +00:00
parent a761d36abd
commit d00e122d88
3 changed files with 292 additions and 62 deletions

View File

@@ -3,10 +3,12 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
ApplicationWindow { Popup {
id: root id: root
property alias source: image.source property alias source: image.source
@@ -16,74 +18,291 @@ ApplicationWindow {
property int imageHeight: -1 property int imageHeight: -1
property var modelData property var modelData
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground parent: Overlay.overlay
closePolicy: Popup.CloseOnEscape
width: parent.width
height: parent.height
modal: true
padding: 0
background: null
title: i18n("Image View - %1", filename) ColumnLayout {
anchors.fill: parent
spacing: Kirigami.Units.largeSpacing
Shortcut { Control {
sequence: "Escape" Layout.fillWidth: true
onActivated: root.destroy()
}
color: Kirigami.Theme.backgroundColor contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
background: AbstractButton { Kirigami.Avatar {
onClicked: root.destroy() id: avatar
}
BusyIndicator { Layout.preferredWidth: Kirigami.Units.iconSizes.medium
visible: image.status !== Image.Ready && root.blurhash === "" Layout.preferredHeight: Kirigami.Units.iconSizes.medium
anchors.centerIn: parent
running: visible
}
AnimatedImage { name: modelData.author.name ?? modelData.author.displayName
id: image source: modelData.author.avatarMediaId ? ("image://mxc/" + modelData.author.avatarMediaId) : ""
anchors.centerIn: parent color: modelData.author.color
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
width: Math.min(root.imageWidth !== -1 ? root.imageWidth : sourceSize.width, root.width) Label {
height: Math.min(root.imageHeight !== -1 ? root.imageWidth : sourceSize.height, root.height) id: nameLabel
fillMode: Image.PreserveAspectFit text: modelData.author.displayName
textFormat: Text.PlainText
font.weight: Font.Bold
color: author.color
}
Label {
id: timeLabel
Image { text: time.toLocaleString(Qt.locale(), Locale.ShortFormat)
anchors.centerIn: parent }
width: image.width }
height: image.height Label {
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : "" id: imageLabel
visible: root.blurhash !== "" && parent.status !== Image.Ready Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
text: modelData.display
font.weight: Font.Bold
elide: Text.ElideRight
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Zoom in")
Accessible.name: text
icon.name: "zoom-in"
display: AbstractButton.IconOnly
onClicked: {
image.scaleFactor = image.scaleFactor + 0.25
if (image.scaleFactor > 3) {
image.scaleFactor = 3
}
}
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Zoom out")
Accessible.name: text
icon.name: "zoom-out"
display: AbstractButton.IconOnly
onClicked: {
image.scaleFactor = image.scaleFactor - 0.25
if (image.scaleFactor < 0.25) {
image.scaleFactor = 0.25
}
}
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Rotate left")
Accessible.name: text
icon.name: "image-rotate-left-symbolic"
display: AbstractButton.IconOnly
onClicked: image.rotationAngle = image.rotationAngle - 90
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Rotate right")
Accessible.name: text
icon.name: "image-rotate-right-symbolic"
display: AbstractButton.IconOnly
onClicked: image.rotationAngle = image.rotationAngle + 90
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Save as")
Accessible.name: text
icon.name: "document-save"
display: AbstractButton.IconOnly
onClicked: {
var dialog = saveAsDialog.createObject(ApplicationWindow.overlay)
dialog.open()
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
}
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
ToolButton {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
text: i18n("Close")
Accessible.name: text
icon.name: "dialog-close"
display: AbstractButton.IconOnly
onClicked: {
root.close()
}
ToolTip.text: text
ToolTip.delay: Kirigami.Units.toolTipDelay
ToolTip.visible: hovered
}
}
background: Rectangle {
color: Kirigami.Theme.alternateBackgroundColor
}
Kirigami.Separator {
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
height: 1
}
} }
TapHandler { BusyIndicator {
acceptedButtons: Qt.RightButton Layout.fillWidth: true
onTapped: { visible: image.status !== Image.Ready && root.blurhash === ""
const contextMenu = fileDelegateContextMenu.createObject(parent, { running: visible
author: modelData.author, }
message: modelData.message, // Provides container to fill the space that isn't taken up by the top controls and clips the image when zooming makes it larger than the available area.
eventId: modelData.eventId, Item {
source: modelData.source, id: imageContainer
file: root.parent, Layout.fillWidth: true
mimeType: modelData.mimeType, Layout.fillHeight: true
progressInfo: modelData.progressInfo, Layout.leftMargin: Kirigami.Units.largeSpacing
plainMessage: modelData.message, Layout.rightMargin: Kirigami.Units.largeSpacing
}); Layout.bottomMargin: Kirigami.Units.largeSpacing
contextMenu.closeFullscreen.connect(root.destroy) clip: true
contextMenu.open();
Image {
id: image
property var scaleFactor: 1
property int rotationAngle: 0
property var rotationInsensitiveWidth: Math.min(root.imageWidth !== -1 ? root.imageWidth : sourceSize.width, imageContainer.width - Kirigami.Units.largeSpacing * 2)
property var rotationInsensitiveHeight: Math.min(root.imageHeight !== -1 ? root.imageHeight : sourceSize.height, imageContainer.height - Kirigami.Units.largeSpacing * 2)
anchors.centerIn: parent
width: rotationAngle % 180 === 0 ? rotationInsensitiveWidth : rotationInsensitiveHeight
height: rotationAngle % 180 === 0 ? rotationInsensitiveHeight : rotationInsensitiveWidth
fillMode: Image.PreserveAspectFit
clip: true
Behavior on width {
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
Behavior on height {
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
Image {
anchors.centerIn: parent
width: image.width
height: image.height
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : ""
visible: root.blurhash !== "" && parent.status !== Image.Ready
}
transform: [
Rotation {
origin.x: image.width / 2
origin.y: image.height / 2
angle: image.rotationAngle
Behavior on angle {
RotationAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
},
Scale {
origin.x: image.width / 2
origin.y: image.height / 2
xScale: image.scaleFactor
yScale: image.scaleFactor
Behavior on xScale {
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
Behavior on yScale {
NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
}
}
]
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
const contextMenu = fileDelegateContextMenu.createObject(parent, {
author: modelData.author,
message: modelData.message,
eventId: modelData.eventId,
source: modelData.source,
file: root.parent,
mimeType: modelData.mimeType,
progressInfo: modelData.progressInfo,
plainMessage: modelData.message,
});
contextMenu.closeFullscreen.connect(root.close)
contextMenu.open();
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
root.close()
}
} }
} }
} }
Button { Component {
anchors.top: parent.top id: saveAsDialog
anchors.right: parent.right FileDialog {
fileMode: FileDialog.SaveFile
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: {
if (!currentFile) {
return;
}
currentRoom.downloadFile(eventId, currentFile)
}
}
}
text: i18n("Close") onClosed: {
icon.name: "dialog-close" image.scaleFactor = 1
display: AbstractButton.IconOnly image.rotationAngle = 0
width: Kirigami.Units.gridUnit * 2
height: Kirigami.Units.gridUnit * 2
onClicked: root.destroy()
} }
} }

View File

@@ -6,6 +6,8 @@ import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import Qt.labs.platform 1.1 import Qt.labs.platform 1.1
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
import NeoChat.Component 1.0 import NeoChat.Component 1.0
import NeoChat.Dialog 1.0 import NeoChat.Dialog 1.0
@@ -50,6 +52,7 @@ TimelineContainer {
ToolTip.text: model.display ToolTip.text: model.display
ToolTip.visible: hoverHandler.hovered ToolTip.visible: hoverHandler.hovered
ToolTip.delay: Kirigami.Units.toolTipDelay
HoverHandler { HoverHandler {
id: hoverHandler id: hoverHandler
@@ -89,17 +92,21 @@ TimelineContainer {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent) onLongPressed: openFileContext(model, parent)
onTapped: { onTapped: {
fullScreenImage.createObject(parent, { img.ToolTip.hide()
filename: eventId, fullScreenImage.open()
source: mediaUrl,
blurhash: model.content.info["xyz.amorgan.blurhash"],
imageWidth: content.info.w,
imageHeight: content.info.h,
modelData: model
}).showFullScreen();
} }
} }
FullScreenImage {
id: fullScreenImage
filename: eventId
source: mediaUrl
blurhash: model.content.info["xyz.amorgan.blurhash"]
imageWidth: content.info.w
imageHeight: content.info.h
modelData: model
}
function downloadAndOpen() { function downloadAndOpen() {
if (downloaded) { if (downloaded) {
openSavedFile() openSavedFile()

View File

@@ -141,6 +141,10 @@ if(ANDROID)
"preferences-system-users" "preferences-system-users"
"preferences-desktop-theme-global" "preferences-desktop-theme-global"
"notifications" "notifications"
"zoom-in"
"zoom-out"
"image-rotate-left-symbolic"
"image-rotate-right-symbolic"
) )
else() else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF5::KIOWidgets) target_link_libraries(neochat PUBLIC Qt::Widgets KF5::KIOWidgets)