Added magnifier for more precise selections (#2219)
* added a magnifierwidget * added option to show magnifier and added option to switch to square shaped magnifier * integrated magnifierwidget into capture this could probably be done in a nicer way. right now the magnifier wont show if you select via the move tool. Co-authored-by: Silas Dohm <silas@sdohm.xyz>
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -81,6 +81,8 @@ static QMap<class QString, QSharedPointer<ValueHandler>>
|
||||
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
|
||||
|
||||
@@ -115,6 +115,8 @@ public:
|
||||
QString)
|
||||
CONFIG_GETTER_SETTER(undoLimit, setUndoLimit, int)
|
||||
CONFIG_GETTER_SETTER(buttons, setButtons, QList<CaptureTool::Type>)
|
||||
CONFIG_GETTER_SETTER(showMagnifier, setShowMagnifier, bool)
|
||||
CONFIG_GETTER_SETTER(squareMagnifier, setSquareMagnifier, bool)
|
||||
|
||||
// SPECIAL CASES
|
||||
bool startupLaunch();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 <QPointer>
|
||||
#include <QUndoStack>
|
||||
@@ -180,6 +181,7 @@ private:
|
||||
NotifierBox* m_notifierBox;
|
||||
HoverEventFilter* m_eventFilter;
|
||||
SelectionWidget* m_selection;
|
||||
MagnifierWidget* m_magnifier;
|
||||
QString m_helpMessage;
|
||||
|
||||
SelectionWidget::SideType m_mouseOverHandle;
|
||||
|
||||
172
src/widgets/capture/magnifierwidget.cpp
Normal file
172
src/widgets/capture/magnifierwidget.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
|
||||
|
||||
#include "magnifierwidget.h"
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPen>
|
||||
#include <QPixmap>
|
||||
|
||||
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<int>(x * m_devicePixelRatio - m_magPixels);
|
||||
int magY = static_cast<int>(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<int>(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<int>(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);
|
||||
}
|
||||
}
|
||||
32
src/widgets/capture/magnifierwidget.h
Normal file
32
src/widgets/capture/magnifierwidget.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
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);
|
||||
};
|
||||
Reference in New Issue
Block a user