You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

250 lines
7.8 KiB

#include "imageviewer_window.h"
#include "../../lib/debug_config.h"
#include <QPainter>
#include <QImageReader>
#include <QWheelEvent>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QCloseEvent>
#include <QResizeEvent>
#include <QPushButton>
#include <QLabel>
#include <QTimer>
#include <QPropertyAnimation>
#include <QGraphicsOpacityEffect>
#include <QEasingCurve>
#include <QtMath>
static QPointF mouseLocalPos(const QMouseEvent* e) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return e->position();
#else
return e->localPos();
#endif
}
static QPointF wheelPos(const QWheelEvent* e) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return e->position();
#else
return e->posF();
#endif
}
ImageViewerWindow::ImageViewerWindow(QWidget *parent)
: QWidget(parent)
{
setWindowTitle(QStringLiteral("Фото"));
setAttribute(Qt::WA_DeleteOnClose, false);
setMinimumSize(320, 240);
resize(800, 600);
setMouseTracking(true);
m_closeBtn = new QPushButton(QStringLiteral("✕"), this);
m_closeBtn->setToolTip(QStringLiteral("Закрыть"));
m_closeBtn->setCursor(Qt::ArrowCursor);
m_closeBtn->setStyleSheet(
"QPushButton { background: rgba(0,0,0,0.5); color: white; border: none; "
"border-radius: 4px; font-size: 14px; }"
"QPushButton:hover { background: rgba(255,255,255,0.25); }");
connect(m_closeBtn, &QPushButton::clicked, this, &ImageViewerWindow::close);
m_closeEffect = new QGraphicsOpacityEffect(m_closeBtn);
m_closeBtn->setGraphicsEffect(m_closeEffect);
m_closeEffect->setOpacity(0.0);
m_closeFade = new QPropertyAnimation(m_closeEffect, "opacity", this);
m_closeFade->setEasingCurve(QEasingCurve::InOutQuad);
m_hintLabel = new QLabel(QStringLiteral("Ctrl+колесо — зум"), this);
m_hintLabel->setAlignment(Qt::AlignCenter);
m_hintLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
m_hintLabel->setStyleSheet(
"QLabel { background: rgba(0,0,0,0.6); color: white; "
"border-radius: 12px; padding: 6px 16px; font-size: 13px; }");
m_hintEffect = new QGraphicsOpacityEffect(m_hintLabel);
m_hintLabel->setGraphicsEffect(m_hintEffect);
m_hintEffect->setOpacity(0.0);
m_hintFade = new QPropertyAnimation(m_hintEffect, "opacity", this);
m_hintFade->setEasingCurve(QEasingCurve::InOutQuad);
auto finishHide = [this]() {
if (m_closeEffect->opacity() < 0.01) m_closeBtn->hide();
if (m_hintEffect->opacity() < 0.01) m_hintLabel->hide();
};
connect(m_closeFade, &QPropertyAnimation::finished, this, finishHide);
connect(m_hintFade, &QPropertyAnimation::finished, this, finishHide);
m_hideTimer = new QTimer(this);
m_hideTimer->setSingleShot(true);
m_hideTimer->setInterval(1000);
connect(m_hideTimer, &QTimer::timeout, this, &ImageViewerWindow::hideOverlay);
m_closeBtn->hide();
m_hintLabel->hide();
}
void ImageViewerWindow::loadFile(const QString& path) {
QImageReader reader(path);
reader.setAutoTransform(true);
QImage im = reader.read();
if (im.isNull()) {
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "ImageViewer: cannot load %s", qPrintable(path));
m_image = QPixmap();
update();
return;
}
m_image = QPixmap::fromImage(im);
resetView();
update();
}
void ImageViewerWindow::resetView() {
m_scale = 1.0;
m_offset = QPointF(0, 0);
m_dragging = false;
}
void ImageViewerWindow::clampOffset() {
if (m_image.isNull()) { m_offset = QPointF(0, 0); return; }
double baseW = (double)m_image.width() / width();
double baseH = (double)m_image.height() / height();
double effScale = (1.0 / qMax(baseW, baseH)) * m_scale;
double dw = m_image.width() * effScale;
double dh = m_image.height() * effScale;
double mx = qMax((dw - width()) / 2.0, 0.0);
double my = qMax((dh - height()) / 2.0, 0.0);
m_offset.setX(qBound(-mx, m_offset.x(), mx));
m_offset.setY(qBound(-my, m_offset.y(), my));
}
void ImageViewerWindow::paintEvent(QPaintEvent*) {
QPainter p(this);
p.fillRect(rect(), QColor(20, 20, 22));
if (m_image.isNull()) {
p.setPen(QColor(150, 150, 150));
p.drawText(rect(), Qt::AlignCenter, QStringLiteral("Не удалось загрузить фото"));
return;
}
/* базовое вписывание: scale так, чтобы изображение влезло целиком */
double baseW = (double)m_image.width() / width();
double baseH = (double)m_image.height() / height();
double fit = 1.0 / qMax(baseW, baseH);
double effScale = fit * m_scale;
double dw = m_image.width() * effScale;
double dh = m_image.height() * effScale;
QPointF topLeft((width() - dw) / 2.0 + m_offset.x(),
(height() - dh) / 2.0 + m_offset.y());
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.drawPixmap(QRectF(topLeft, QSizeF(dw, dh)), m_image, QRectF(m_image.rect()));
}
void ImageViewerWindow::wheelEvent(QWheelEvent* e) {
if (!(e->modifiers() & Qt::ControlModifier)) { e->ignore(); return; }
if (m_image.isNull()) return;
double factor = (e->angleDelta().y() > 0) ? 1.2 : (1.0 / 1.2);
double newScale = qBound(1.0, m_scale * factor, 8.0);
if (qFuzzyCompare(newScale, m_scale)) return;
/* зум в точку курсора */
QPointF cursor = wheelPos(e);
QPointF center((double)width() / 2.0, (double)height() / 2.0);
double ratio = newScale / m_scale;
m_offset = (m_offset - (cursor - center)) * ratio + (cursor - center);
m_scale = newScale;
clampOffset();
update();
e->accept();
}
void ImageViewerWindow::mousePressEvent(QMouseEvent* e) {
if (e->button() == Qt::LeftButton && m_scale > 1.0) {
m_dragging = true;
m_dragStart = mouseLocalPos(e);
m_offsetStart = m_offset;
setCursor(Qt::ClosedHandCursor);
e->accept();
return;
}
e->ignore();
}
void ImageViewerWindow::mouseMoveEvent(QMouseEvent* e) {
showOverlay();
if (m_dragging) {
m_offset = m_offsetStart + (mouseLocalPos(e) - m_dragStart);
clampOffset();
update();
e->accept();
}
}
void ImageViewerWindow::mouseReleaseEvent(QMouseEvent* e) {
if (m_dragging && e->button() == Qt::LeftButton) {
m_dragging = false;
unsetCursor();
e->accept();
}
}
void ImageViewerWindow::mouseDoubleClickEvent(QMouseEvent* e) {
if (e->button() == Qt::LeftButton) {
resetView();
update();
e->accept();
}
}
void ImageViewerWindow::keyPressEvent(QKeyEvent* e) {
if (e->key() == Qt::Key_Escape) {
close();
e->accept();
return;
}
QWidget::keyPressEvent(e);
}
void ImageViewerWindow::resizeEvent(QResizeEvent* e) {
clampOffset();
m_closeBtn->setGeometry(width() - 48, 8, 40, 32);
m_closeBtn->raise();
QSize hs = m_hintLabel->sizeHint();
m_hintLabel->setGeometry((width() - hs.width()) / 2, height() - hs.height() - 24,
hs.width(), hs.height());
m_hintLabel->raise();
QWidget::resizeEvent(e);
}
void ImageViewerWindow::showOverlay() {
m_closeBtn->show();
m_hintLabel->show();
fadeTo(m_closeFade, m_closeEffect, 100, 1.0);
fadeTo(m_hintFade, m_hintEffect, 100, 1.0);
m_hideTimer->start();
}
void ImageViewerWindow::hideOverlay() {
fadeTo(m_closeFade, m_closeEffect, 300, 0.0);
fadeTo(m_hintFade, m_hintEffect, 300, 0.0);
}
void ImageViewerWindow::fadeTo(QPropertyAnimation* anim, QGraphicsOpacityEffect* eff,
int durMs, double target) {
anim->stop();
anim->setDuration(durMs);
anim->setStartValue(eff->opacity());
anim->setEndValue(target);
anim->start();
}
void ImageViewerWindow::closeEvent(QCloseEvent* e) {
emit closed();
QWidget::closeEvent(e);
}