diff --git a/src/config/generalconf.cpp b/src/config/generalconf.cpp index df26bb55..4c6579d9 100644 --- a/src/config/generalconf.cpp +++ b/src/config/generalconf.cpp @@ -57,6 +57,8 @@ GeneralConf::GeneralConf(QWidget* parent) m_layout->addStretch(); + initShowMagnifier(); + initSquareMagnifier(); // this has to be at the end initConfigButtons(); updateComponents(); @@ -79,6 +81,8 @@ void GeneralConf::_updateComponents(bool allowEmptySavePath) config.historyConfirmationToDelete()); m_checkForUpdates->setChecked(config.checkForUpdates()); m_allowMultipleGuiInstances->setChecked(config.allowMultipleGuiInstances()); + m_showMagnifier->setChecked(config.showMagnifier()); + m_squareMagnifier->setChecked(config.squareMagnifier()); #if !defined(Q_OS_WIN) m_autoCloseIdleDaemon->setChecked(config.autoCloseIdleDaemon()); @@ -622,6 +626,24 @@ const QString GeneralConf::chooseFolder(const QString pathDefault) return path; } +void GeneralConf::initShowMagnifier() +{ + m_showMagnifier = new QCheckBox(tr("Show magnifier"), this); + m_scrollAreaLayout->addWidget(m_showMagnifier); + connect(m_showMagnifier, &QCheckBox::clicked, [](bool checked) { + ConfigHandler().setShowMagnifier(checked); + }); +} + +void GeneralConf::initSquareMagnifier() +{ + m_squareMagnifier = new QCheckBox(tr("Square shaped magnifier"), this); + m_scrollAreaLayout->addWidget(m_squareMagnifier); + connect(m_squareMagnifier, &QCheckBox::clicked, [](bool checked) { + ConfigHandler().setSquareMagnifier(checked); + }); +} + void GeneralConf::togglePathFixed() { ConfigHandler().setSavePathFixed(m_screenshotPathFixedCheck->isChecked()); diff --git a/src/config/generalconf.h b/src/config/generalconf.h index ac8b24c8..1c8c2cf1 100644 --- a/src/config/generalconf.h +++ b/src/config/generalconf.h @@ -67,6 +67,8 @@ private: void initUseJpgForClipboard(); void initUploadWithoutConfirmation(); void initPredefinedColorPaletteLarge(); + void initShowMagnifier(); + void initSquareMagnifier(); void _updateComponents(bool allowEmptySavePath); @@ -100,4 +102,6 @@ private: QSpinBox* m_undoLimit; QComboBox* m_setSaveAsFileExtension; QCheckBox* m_predefinedColorPaletteLarge; + QCheckBox* m_showMagnifier; + QCheckBox* m_squareMagnifier; }; diff --git a/src/utils/confighandler.cpp b/src/utils/confighandler.cpp index cbd97570..b170cc8c 100644 --- a/src/utils/confighandler.cpp +++ b/src/utils/confighandler.cpp @@ -81,6 +81,8 @@ static QMap> OPTION("historyConfirmationToDelete" ,Bool ( true )), OPTION("checkForUpdates" ,Bool ( true )), OPTION("allowMultipleGuiInstances" ,Bool ( false )), + OPTION("showMagnifier" ,Bool ( false )), + OPTION("squareMagnifier" ,Bool ( false )), #if !defined(Q_OS_WIN) OPTION("autoCloseIdleDaemon" ,Bool ( false )), #endif diff --git a/src/utils/confighandler.h b/src/utils/confighandler.h index 5a3f4237..00248b19 100644 --- a/src/utils/confighandler.h +++ b/src/utils/confighandler.h @@ -115,6 +115,8 @@ public: QString) CONFIG_GETTER_SETTER(undoLimit, setUndoLimit, int) CONFIG_GETTER_SETTER(buttons, setButtons, QList) + CONFIG_GETTER_SETTER(showMagnifier, setShowMagnifier, bool) + CONFIG_GETTER_SETTER(squareMagnifier, setSquareMagnifier, bool) // SPECIAL CASES bool startupLaunch(); diff --git a/src/widgets/capture/CMakeLists.txt b/src/widgets/capture/CMakeLists.txt index 3bbafb65..50c82a36 100644 --- a/src/widgets/capture/CMakeLists.txt +++ b/src/widgets/capture/CMakeLists.txt @@ -9,6 +9,7 @@ target_sources( hovereventfilter.h overlaymessage.h selectionwidget.h + magnifierwidget.h notifierbox.h modificationcommand.h) @@ -23,4 +24,5 @@ target_sources( overlaymessage.cpp notifierbox.cpp selectionwidget.cpp + magnifierwidget.cpp modificationcommand.cpp) diff --git a/src/widgets/capture/capturewidget.cpp b/src/widgets/capture/capturewidget.cpp index 197b7fd2..8f79ee31 100644 --- a/src/widgets/capture/capturewidget.cpp +++ b/src/widgets/capture/capturewidget.cpp @@ -67,6 +67,7 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req, , m_panel(nullptr) , m_sidePanel(nullptr) , m_selection(nullptr) + , m_magnifier(nullptr) , m_existingObjectIsChanged(false) , m_startMove(false) , m_toolSizeByKeyboard(0) @@ -181,6 +182,10 @@ CaptureWidget::CaptureWidget(const CaptureRequest& req, initButtons(); initSelection(); // button handler must be initialized before initShortcuts(); // must be called after initSelection + // init magnify + if (m_config.showMagnifier()) + m_magnifier = new MagnifierWidget( + m_context.screenshot, m_uiColor, m_config.squareMagnifier(), this); // Init color picker m_colorPicker = new ColorPicker(this); @@ -679,6 +684,15 @@ void CaptureWidget::mouseDoubleClickEvent(QMouseEvent* event) void CaptureWidget::mouseMoveEvent(QMouseEvent* e) { + if (m_magnifier) { + if (!m_activeButton) { + m_magnifier->show(); + m_magnifier->update(); + } else { + m_magnifier->hide(); + } + } + m_context.mousePos = e->pos(); if (e->buttons() != Qt::LeftButton) { updateTool(activeButtonTool()); diff --git a/src/widgets/capture/capturewidget.h b/src/widgets/capture/capturewidget.h index 0c0ade5d..edff083a 100644 --- a/src/widgets/capture/capturewidget.h +++ b/src/widgets/capture/capturewidget.h @@ -17,6 +17,7 @@ #include "src/tools/capturecontext.h" #include "src/tools/capturetool.h" #include "src/utils/confighandler.h" +#include "src/widgets/capture/magnifierwidget.h" #include "src/widgets/capture/selectionwidget.h" #include #include @@ -180,6 +181,7 @@ private: NotifierBox* m_notifierBox; HoverEventFilter* m_eventFilter; SelectionWidget* m_selection; + MagnifierWidget* m_magnifier; QString m_helpMessage; SelectionWidget::SideType m_mouseOverHandle; diff --git a/src/widgets/capture/magnifierwidget.cpp b/src/widgets/capture/magnifierwidget.cpp new file mode 100644 index 00000000..acf38387 --- /dev/null +++ b/src/widgets/capture/magnifierwidget.cpp @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors + +#include "magnifierwidget.h" +#include +#include +#include +#include +#include +#include +#include + +MagnifierWidget::MagnifierWidget(const QPixmap& p, + const QColor& c, + bool isSquare, + QWidget* parent) + : QWidget(parent) + , m_color(c) + , m_borderColor(c) + , m_screenshot(p) + , m_square(isSquare) +{ + setFixedSize(parent->width(), parent->height()); + setAttribute(Qt::WA_TransparentForMouseEvents); + m_color.setAlpha(130); + // add padding for circular magnifier + QImage padded(p.width() + 2 * m_magPixels, + p.height() + 2 * m_magPixels, + QImage::Format_ARGB32); + padded.fill(Qt::black); + QPainter painter(&padded); + painter.drawPixmap(m_magPixels, m_magPixels, p); + m_paddedScreenshot.convertFromImage(padded); +} +void MagnifierWidget::paintEvent(QPaintEvent*) +{ + QPainter p(this); + if (m_square) + drawMagnifier(p); + else + drawMagnifierCircle(p); +} + +void MagnifierWidget::drawMagnifierCircle(QPainter& painter) +{ + int x = QCursor::pos().x() + m_magPixels; + int y = QCursor::pos().y() + m_magPixels; + int magX = static_cast(x * m_devicePixelRatio - m_magPixels); + int magY = static_cast(y * m_devicePixelRatio - m_magPixels); + QRectF magniRect(magX, magY, m_pixels, m_pixels); + + qreal drawPosX = x + m_magOffset + m_pixels * magZoom / 2; + if (drawPosX > width() - m_pixels * magZoom / 2) { + drawPosX = x - m_magOffset - m_pixels * magZoom / 2; + } + qreal drawPosY = y + m_magOffset + m_pixels * magZoom / 2; + if (drawPosY > height() - m_pixels * magZoom / 2) { + drawPosY = y - m_magOffset - m_pixels * magZoom / 2; + } + QPointF drawPos(drawPosX, drawPosY); + QRectF crossHairTop(drawPos.x() + magZoom * (-0.5), + drawPos.y() - magZoom * (m_magPixels + 0.5), + magZoom, + magZoom * (m_magPixels)); + QRectF crossHairRight(drawPos.x() + magZoom * (0.5), + drawPos.y() + magZoom * (-0.5), + magZoom * (m_magPixels), + magZoom); + QRectF crossHairBottom(drawPos.x() + magZoom * (-0.5), + drawPos.y() + magZoom * (0.5), + magZoom, + magZoom * (m_magPixels)); + QRectF crossHairLeft(drawPos.x() - magZoom * (m_magPixels + 0.5), + drawPos.y() + magZoom * (-0.5), + magZoom * (m_magPixels), + magZoom); + QRectF crossHairBorder(drawPos.x() - magZoom * (m_magPixels + 0.5) - 1, + drawPos.y() - magZoom * (m_magPixels + 0.5) - 1, + m_pixels * magZoom + 2, + m_pixels * magZoom + 2); + const auto frag = + QPainter::PixmapFragment::create(drawPos, magniRect, magZoom, magZoom); + + painter.setRenderHint(QPainter::Antialiasing, true); + QPainterPath path = QPainterPath(); + path.addEllipse(drawPos, m_pixels * magZoom / 2, m_pixels * magZoom / 2); + painter.setClipPath(path); + + painter.drawPixmapFragments( + &frag, 1, m_paddedScreenshot, QPainter::OpaqueHint); + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + for (auto& rect : + { crossHairTop, crossHairRight, crossHairBottom, crossHairLeft }) { + painter.fillRect(rect, m_color); + } + QPen pen(m_borderColor); + pen.setWidth(4); + painter.setPen(pen); + painter.drawEllipse( + drawPos, m_pixels * magZoom / 2, m_pixels * magZoom / 2); +} +// https://invent.kde.org/graphics/spectacle/-/blob/master/src/QuickEditor/QuickEditor.cpp#L841 +void MagnifierWidget::drawMagnifier(QPainter& painter) +{ + int x = QCursor::pos().x(); + int y = QCursor::pos().y(); + int magX = static_cast(x * m_devicePixelRatio - m_magPixels); + int offsetX = 0; + if (magX < 0) { + offsetX = magX; + magX = 0; + } else { + const int maxX = m_screenshot.width() - m_pixels; + if (magX > maxX) { + offsetX = magX - maxX; + magX = maxX; + } + } + int magY = static_cast(y * m_devicePixelRatio - m_magPixels); + int offsetY = 0; + if (magY < 0) { + offsetY = magY; + magY = 0; + } else { + const int maxY = m_screenshot.height() - m_pixels; + if (magY > maxY) { + offsetY = magY - maxY; + magY = maxY; + } + } + QRectF magniRect(magX, magY, m_pixels, m_pixels); + + qreal drawPosX = x + m_magOffset + m_pixels * magZoom / 2; + if (drawPosX > width() - m_pixels * magZoom / 2) { + drawPosX = x - m_magOffset - m_pixels * magZoom / 2; + } + qreal drawPosY = y + m_magOffset + m_pixels * magZoom / 2; + if (drawPosY > height() - m_pixels * magZoom / 2) { + drawPosY = y - m_magOffset - m_pixels * magZoom / 2; + } + QPointF drawPos(drawPosX, drawPosY); + QRectF crossHairTop(drawPos.x() + magZoom * (offsetX - 0.5), + drawPos.y() - magZoom * (m_magPixels + 0.5), + magZoom, + magZoom * (m_magPixels + offsetY)); + QRectF crossHairRight(drawPos.x() + magZoom * (0.5 + offsetX), + drawPos.y() + magZoom * (offsetY - 0.5), + magZoom * (m_magPixels - offsetX), + magZoom); + QRectF crossHairBottom(drawPos.x() + magZoom * (offsetX - 0.5), + drawPos.y() + magZoom * (0.5 + offsetY), + magZoom, + magZoom * (m_magPixels - offsetY)); + QRectF crossHairLeft(drawPos.x() - magZoom * (m_magPixels + 0.5), + drawPos.y() + magZoom * (offsetY - 0.5), + magZoom * (m_magPixels + offsetX), + magZoom); + QRectF crossHairBorder(drawPos.x() - magZoom * (m_magPixels + 0.5) - 1, + drawPos.y() - magZoom * (m_magPixels + 0.5) - 1, + m_pixels * magZoom + 2, + m_pixels * magZoom + 2); + const auto frag = + QPainter::PixmapFragment::create(drawPos, magniRect, magZoom, magZoom); + + painter.fillRect(crossHairBorder, m_borderColor); + painter.drawPixmapFragments(&frag, 1, m_screenshot, QPainter::OpaqueHint); + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + for (auto& rect : + { crossHairTop, crossHairRight, crossHairBottom, crossHairLeft }) { + painter.fillRect(rect, m_color); + } +} \ No newline at end of file diff --git a/src/widgets/capture/magnifierwidget.h b/src/widgets/capture/magnifierwidget.h new file mode 100644 index 00000000..70df5006 --- /dev/null +++ b/src/widgets/capture/magnifierwidget.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +class QPropertyAnimation; + +class MagnifierWidget : public QWidget +{ + Q_OBJECT +public: + explicit MagnifierWidget(const QPixmap& p, + const QColor& c, + bool isSquare, + QWidget* parent = nullptr); + +protected: + void paintEvent(QPaintEvent*) override; + +private: + const int m_magPixels = 8; + const int m_magOffset = 16; + const int magZoom = 10; + const int m_pixels = 2 * m_magPixels + 1; + const int m_devicePixelRatio = 1; + bool m_square; + QColor m_color; + QColor m_borderColor; + QPixmap m_screenshot; + QPixmap m_paddedScreenshot; + void drawMagnifier(QPainter& painter); + void drawMagnifierCircle(QPainter& painter); +};