Port image overlay to qml
Allows you to zoom and pan now. relates to #647
This commit is contained in:
parent
c3e2e73175
commit
66520eae19
@ -310,7 +310,6 @@ set(SRC_FILES
|
||||
# Dialogs
|
||||
src/dialogs/CreateRoom.cpp
|
||||
src/dialogs/FallbackAuth.cpp
|
||||
src/dialogs/ImageOverlay.cpp
|
||||
src/dialogs/PreviewUploadOverlay.cpp
|
||||
src/dialogs/ReCaptcha.cpp
|
||||
|
||||
@ -519,7 +518,6 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||
# Dialogs
|
||||
src/dialogs/CreateRoom.h
|
||||
src/dialogs/FallbackAuth.h
|
||||
src/dialogs/ImageOverlay.h
|
||||
src/dialogs/PreviewUploadOverlay.h
|
||||
src/dialogs/ReCaptcha.h
|
||||
|
||||
|
@ -136,6 +136,14 @@ Page {
|
||||
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageOverlay
|
||||
|
||||
ImageOverlay {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Ctrl+K"
|
||||
onActivated: {
|
||||
@ -234,6 +242,15 @@ Page {
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
function onShowImageOverlay(room, eventId, url, proportionalHeight, originalWidth) {
|
||||
var dialog = imageOverlay.createObject(timelineRoot, {
|
||||
"room": room,
|
||||
"eventId": eventId,
|
||||
"url": url
|
||||
});
|
||||
dialog.showFullScreen();
|
||||
}
|
||||
|
||||
target: TimelineManager
|
||||
}
|
||||
|
||||
|
@ -63,10 +63,9 @@ Item {
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
// TODO(Nico): Replace this with a qml thingy, that also can show animated images
|
||||
enabled: type == MtxEvent.ImageMessage && (img.status == Image.Ready || mxcimage.loaded)
|
||||
//enabled: type == MtxEvent.ImageMessage && (img.status == Image.Ready || mxcimage.loaded)
|
||||
onSingleTapped: {
|
||||
TimelineManager.openImageOverlay(url, room.data.eventId);
|
||||
TimelineManager.openImageOverlay(room, url, eventId);
|
||||
eventPoint.accepted = true;
|
||||
}
|
||||
gesturePolicy: TapHandler.ReleaseWithinBounds
|
||||
|
110
resources/qml/dialogs/ImageOverlay.qml
Normal file
110
resources/qml/dialogs/ImageOverlay.qml
Normal file
@ -0,0 +1,110 @@
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
|
||||
import ".."
|
||||
|
||||
import im.nheko 1.0
|
||||
|
||||
Window {
|
||||
id: imageOverlay
|
||||
|
||||
required property string url
|
||||
required property string eventId
|
||||
required property Room room
|
||||
|
||||
flags: Qt.FramelessWindowHint
|
||||
|
||||
visibility: Window.FullScreen
|
||||
color: Qt.rgba(0.2,0.2,0.2,0.66)
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: imageOverlay.close()
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
height: Math.min(parent.height, img.implicitHeight)
|
||||
width: Math.min(parent.width, img.implicitWidth)
|
||||
x: (parent.width - img.width)/2
|
||||
y: (parent.height - img.height)/2
|
||||
|
||||
Image {
|
||||
id: img
|
||||
|
||||
visible: !mxcimage.loaded
|
||||
anchors.fill: parent
|
||||
source: url.replace("mxc://", "image://MxcImage/")
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
mipmap: true
|
||||
}
|
||||
|
||||
MxcAnimatedImage {
|
||||
id: mxcimage
|
||||
|
||||
visible: loaded
|
||||
anchors.fill: parent
|
||||
roomm: imageOverlay.room
|
||||
play: !Settings.animateImagesOnHover || mouseArea.hovered
|
||||
eventId: imageOverlay.eventId
|
||||
}
|
||||
|
||||
PinchHandler {
|
||||
}
|
||||
|
||||
WheelHandler {
|
||||
property: "scale"
|
||||
}
|
||||
|
||||
DragHandler {
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: mouseArea
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Nheko.paddingLarge
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
ImageButton {
|
||||
height: 48
|
||||
width: 48
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/download.svg"
|
||||
//ToolTip.visible: hovered
|
||||
//ToolTip.delay: Nheko.tooltipDelay
|
||||
//ToolTip.text: qsTr("Download")
|
||||
onClicked: {
|
||||
if (room) {
|
||||
room.saveMedia(eventId);
|
||||
} else {
|
||||
TimelineManager.saveMedia(url);
|
||||
}
|
||||
imageOverlay.close();
|
||||
}
|
||||
}
|
||||
ImageButton {
|
||||
height: 48
|
||||
width: 48
|
||||
hoverEnabled: true
|
||||
image: ":/icons/icons/ui/dismiss.svg"
|
||||
//ToolTip.visible: hovered
|
||||
//ToolTip.delay: Nheko.tooltipDelay
|
||||
//ToolTip.text: qsTr("Close")
|
||||
onClicked: imageOverlay.close()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -70,7 +70,7 @@ ApplicationWindow {
|
||||
displayName: profile.displayName
|
||||
userid: profile.userid
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: TimelineManager.openImageOverlay(profile.avatarUrl, "")
|
||||
onClicked: TimelineManager.openImageOverlay(null, profile.avatarUrl, "")
|
||||
|
||||
ImageButton {
|
||||
hoverEnabled: true
|
||||
|
@ -37,7 +37,7 @@ Rectangle {
|
||||
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: CallManager.callParty
|
||||
displayName: CallManager.callPartyDisplayName
|
||||
onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
|
||||
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
|
||||
}
|
||||
|
||||
Label {
|
||||
|
@ -44,7 +44,7 @@ Rectangle {
|
||||
url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
userid: CallManager.callParty
|
||||
displayName: CallManager.callPartyDisplayName
|
||||
onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
|
||||
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
|
||||
}
|
||||
|
||||
Label {
|
||||
|
@ -81,7 +81,7 @@ Popup {
|
||||
url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
|
||||
displayName: room.roomName
|
||||
roomid: room.roomid
|
||||
onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
|
||||
onClicked: TimelineManager.openImageOverlay(room, room.avatarUrl(userid), room.data.eventId)
|
||||
}
|
||||
|
||||
Button {
|
||||
|
@ -133,6 +133,7 @@
|
||||
<file>qml/device-verification/NewVerificationRequest.qml</file>
|
||||
<file>qml/device-verification/Success.qml</file>
|
||||
<file>qml/device-verification/Waiting.qml</file>
|
||||
<file>qml/dialogs/ImageOverlay.qml</file>
|
||||
<file>qml/dialogs/ImagePackEditorDialog.qml</file>
|
||||
<file>qml/dialogs/ImagePackSettingsDialog.qml</file>
|
||||
<file>qml/dialogs/PhoneNumberInputDialog.qml</file>
|
||||
|
@ -1,102 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGuiApplication>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
|
||||
#include "dialogs/ImageOverlay.h"
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace dialogs;
|
||||
|
||||
ImageOverlay::ImageOverlay(const QPixmap &image, QWidget *parent)
|
||||
: QWidget{parent}
|
||||
, originalImage_{image}
|
||||
{
|
||||
setMouseTracking(true);
|
||||
setParent(nullptr);
|
||||
|
||||
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
|
||||
setWindowRole(QStringLiteral("imageoverlay"));
|
||||
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setWindowState(Qt::WindowFullScreen);
|
||||
close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
|
||||
|
||||
connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing);
|
||||
connect(this, &ImageOverlay::closing, this, &ImageOverlay::close);
|
||||
|
||||
raise();
|
||||
}
|
||||
|
||||
void
|
||||
ImageOverlay::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
// Full screen overlay.
|
||||
painter.fillRect(QRect(0, 0, width(), height()), QColor(55, 55, 55, 170));
|
||||
|
||||
// Left and Right margins
|
||||
int outer_margin = width() * 0.12;
|
||||
int buttonSize = 36;
|
||||
int margin = outer_margin * 0.1;
|
||||
|
||||
int max_width = width() - 2 * outer_margin;
|
||||
int max_height = height();
|
||||
|
||||
image_ = utils::scaleDown(max_width, max_height, originalImage_);
|
||||
|
||||
int diff_x = max_width - image_.width();
|
||||
int diff_y = max_height - image_.height();
|
||||
|
||||
content_ = QRect(outer_margin + diff_x / 2, diff_y / 2, image_.width(), image_.height());
|
||||
close_button_ = QRect(width() - margin - buttonSize, margin, buttonSize, buttonSize);
|
||||
save_button_ = QRect(width() - (2 * margin) - (2 * buttonSize), margin, buttonSize, buttonSize);
|
||||
|
||||
// Draw main content_.
|
||||
painter.drawPixmap(content_, image_);
|
||||
|
||||
// Draw top right corner X.
|
||||
QPen pen;
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
pen.setWidthF(5);
|
||||
pen.setColor("gray");
|
||||
|
||||
auto center = close_button_.center();
|
||||
|
||||
painter.setPen(pen);
|
||||
painter.drawLine(center - QPointF(15, 15), center + QPointF(15, 15));
|
||||
painter.drawLine(center + QPointF(15, -15), center - QPointF(15, -15));
|
||||
|
||||
// Draw download button
|
||||
center = save_button_.center();
|
||||
painter.drawLine(center - QPointF(0, 15), center + QPointF(0, 15));
|
||||
painter.drawLine(center - QPointF(15, 0), center + QPointF(0, 15));
|
||||
painter.drawLine(center + QPointF(0, 15), center + QPointF(15, 0));
|
||||
}
|
||||
|
||||
void
|
||||
ImageOverlay::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
if (close_button_.contains(event->pos()))
|
||||
emit closing();
|
||||
else if (save_button_.contains(event->pos()))
|
||||
emit saving();
|
||||
else if (!content_.contains(event->pos()))
|
||||
emit closing();
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
// SPDX-FileCopyrightText: 2022 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QMouseEvent>
|
||||
#include <QPixmap>
|
||||
#include <QShortcut>
|
||||
|
||||
namespace dialogs {
|
||||
|
||||
class ImageOverlay : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ImageOverlay(const QPixmap &image, QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void closing();
|
||||
void saving();
|
||||
|
||||
private:
|
||||
QPixmap originalImage_;
|
||||
QPixmap image_;
|
||||
|
||||
QRect content_;
|
||||
QRect close_button_;
|
||||
QRect save_button_;
|
||||
QShortcut *close_shortcut_;
|
||||
};
|
||||
} // dialogs
|
@ -6,10 +6,12 @@
|
||||
#include "TimelineViewManager.h"
|
||||
|
||||
#include <QDropEvent>
|
||||
#include <QFileDialog>
|
||||
#include <QMetaType>
|
||||
#include <QPalette>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
|
||||
#include "BlurhashProvider.h"
|
||||
@ -32,7 +34,6 @@
|
||||
#include "SingleImagePackModel.h"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "UsersModel.h"
|
||||
#include "dialogs/ImageOverlay.h"
|
||||
#include "emoji/EmojiModel.h"
|
||||
#include "emoji/Provider.h"
|
||||
#include "encryption/DeviceVerificationFlow.h"
|
||||
@ -331,11 +332,6 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
||||
isInitialSync_ = true;
|
||||
emit initialSyncChanged(true);
|
||||
});
|
||||
|
||||
connect(this,
|
||||
&TimelineViewManager::openImageOverlayInternalCb,
|
||||
this,
|
||||
&TimelineViewManager::openImageOverlayInternal);
|
||||
}
|
||||
|
||||
void
|
||||
@ -416,23 +412,13 @@ TimelineViewManager::escapeEmoji(QString str) const
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId)
|
||||
TimelineViewManager::openImageOverlay(TimelineModel *room, QString mxcUrl, QString eventId)
|
||||
{
|
||||
if (mxcUrl.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MxcImageProvider::download(mxcUrl.remove(QStringLiteral("mxc://")),
|
||||
QSize(),
|
||||
[this, eventId](QString, QSize, QImage img, QString) {
|
||||
if (img.isNull()) {
|
||||
nhlog::ui()->error(
|
||||
"Error when retrieving image for overlay.");
|
||||
return;
|
||||
}
|
||||
|
||||
emit openImageOverlayInternalCb(eventId, std::move(img));
|
||||
});
|
||||
emit showImageOverlay(room, eventId, mxcUrl);
|
||||
}
|
||||
|
||||
void
|
||||
@ -443,23 +429,44 @@ TimelineViewManager::openImagePackSettings(QString roomid)
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img)
|
||||
TimelineViewManager::saveMedia(QString mxcUrl)
|
||||
{
|
||||
auto pixmap = QPixmap::fromImage(img);
|
||||
const QString downloadsFolder =
|
||||
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||
const QString openLocation = downloadsFolder + "/" + mxcUrl.splitRef(u'/').constLast();
|
||||
|
||||
auto imgDialog = new dialogs::ImageOverlay(pixmap);
|
||||
imgDialog->showFullScreen();
|
||||
const QString filename = QFileDialog::getSaveFileName(getWidget(), {}, openLocation);
|
||||
|
||||
auto room = rooms_->currentRoom();
|
||||
connect(imgDialog, &dialogs::ImageOverlay::saving, room, [eventId, imgDialog, room]() {
|
||||
// hide the overlay while presenting the save dialog for better
|
||||
// cross platform support.
|
||||
imgDialog->hide();
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
if (!room->saveMedia(eventId)) {
|
||||
imgDialog->show();
|
||||
} else {
|
||||
imgDialog->close();
|
||||
const auto url = mxcUrl.toStdString();
|
||||
|
||||
http::client()->download(url,
|
||||
[filename, url](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve image {}: {} {}",
|
||||
url,
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
QFile file(filename);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(QByteArray(data.data(), (int)data.size()));
|
||||
file.close();
|
||||
|
||||
return;
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("Error while saving file to: {}", e.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -60,8 +60,9 @@ public:
|
||||
|
||||
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|
||||
bool isWindowFocused() const { return isWindowFocused_; }
|
||||
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId);
|
||||
Q_INVOKABLE void openImageOverlay(TimelineModel *room, QString mxcUrl, QString eventId);
|
||||
Q_INVOKABLE void openImagePackSettings(QString roomid);
|
||||
Q_INVOKABLE void saveMedia(QString mxcUrl);
|
||||
Q_INVOKABLE QColor userColor(QString id, QColor background);
|
||||
Q_INVOKABLE QString escapeEmoji(QString str) const;
|
||||
Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); }
|
||||
@ -85,13 +86,13 @@ signals:
|
||||
void narrowViewChanged();
|
||||
void focusChanged();
|
||||
void focusInput();
|
||||
void openImageOverlayInternalCb(QString eventId, QImage img);
|
||||
void openRoomMembersDialog(MemberList *members, TimelineModel *room);
|
||||
void openRoomSettingsDialog(RoomSettings *settings);
|
||||
void openInviteUsersDialog(InviteesModel *invitees);
|
||||
void openProfile(UserProfile *profile);
|
||||
void showImagePackSettings(TimelineModel *room, ImagePackListModel *packlist);
|
||||
void openLeaveRoomDialog(QString roomid);
|
||||
void showImageOverlay(TimelineModel *room, QString eventId, QString url);
|
||||
|
||||
public slots:
|
||||
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||
@ -120,9 +121,6 @@ public slots:
|
||||
|
||||
RoomlistModel *rooms() { return rooms_; }
|
||||
|
||||
private slots:
|
||||
void openImageOverlayInternal(QString eventId, QImage img);
|
||||
|
||||
private:
|
||||
#ifdef USE_QUICK_VIEW
|
||||
QQuickView *view;
|
||||
|
@ -20,10 +20,12 @@
|
||||
void
|
||||
MxcAnimatedImage::startDownload()
|
||||
{
|
||||
nhlog::ui()->debug("START DOWNLOAD!!!");
|
||||
if (!room_)
|
||||
return;
|
||||
if (eventId_.isEmpty())
|
||||
return;
|
||||
nhlog::ui()->debug("START DOWNLOAD2!!!");
|
||||
|
||||
auto event = room_->eventById(eventId_);
|
||||
if (!event) {
|
||||
@ -92,6 +94,8 @@ MxcAnimatedImage::startDownload()
|
||||
"Playing movie with size: {}, {}", buffer.bytesAvailable(), buffer.isOpen());
|
||||
movie.setFormat(mimeType);
|
||||
movie.setDevice(&buffer);
|
||||
|
||||
if (height() != 0 && width() != 0)
|
||||
movie.setScaledSize(this->size().toSize());
|
||||
if (buffer.bytesAvailable() <
|
||||
4LL * 1024 * 1024 * 1024) // cache images smaller than 4MB in RAM
|
||||
@ -145,6 +149,17 @@ MxcAnimatedImage::startDownload()
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
MxcAnimatedImage::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
|
||||
{
|
||||
QQuickItem::geometryChanged(newGeometry, oldGeometry);
|
||||
|
||||
if (newGeometry.size() != oldGeometry.size()) {
|
||||
if (height() != 0 && width() != 0)
|
||||
movie.setScaledSize(newGeometry.size().toSize());
|
||||
}
|
||||
}
|
||||
|
||||
QSGNode *
|
||||
MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
|
||||
{
|
||||
@ -171,8 +186,8 @@ MxcAnimatedImage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeD
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
n->setRect(QRect(0, 0, width(), height()));
|
||||
n->setFiltering(QSGTexture::Nearest);
|
||||
n->setRect(0, 0, width(), height());
|
||||
n->setFiltering(QSGTexture::Linear);
|
||||
n->setMipmapFiltering(QSGTexture::None);
|
||||
|
||||
return n;
|
||||
|
@ -60,6 +60,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
QSGNode *updatePaintNode(QSGNode *oldNode,
|
||||
QQuickItem::UpdatePaintNodeData *updatePaintNodeData) override;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user