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  After  Latest  Closes network/neochat#161
This commit is contained in:
@@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user