diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.h b/src/Gui/SettingsDialog/GeneralOptionsPage.h --- a/src/Gui/SettingsDialog/GeneralOptionsPage.h +++ b/src/Gui/SettingsDialog/GeneralOptionsPage.h @@ -46,6 +46,7 @@ QCheckBox *mRememberRect; QCheckBox *mUseLightBackground; QCheckBox *mCopyPathToClipboard; + QCheckBox *mShowMagnifier; }; #endif // GENERALOPTIONSPAGE_H diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp --- a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp +++ b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp @@ -57,10 +57,17 @@ mRememberRect = new QCheckBox(i18n("Remember selected area"), this); connect(mRememberRect, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); + // show magnifier + + mShowMagnifier = new QCheckBox(i18n("Show magnifier"), this); + mShowMagnifier->setToolTip(i18n("Hold the Shift key to temporarily toggle the magnifier when adjusting the selection rectangle.")); + connect(mShowMagnifier, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); + QVBoxLayout *rrCLayout = new QVBoxLayout; // rrCLayout->setContentsMargins(15, 10, 0, 10); rrCLayout->addWidget(mUseLightBackground); rrCLayout->addWidget(mRememberRect); + rrCLayout->addWidget(mShowMagnifier); rrLayout->addLayout(rrCLayout); mainLayout->addWidget(rrGroup, 1); @@ -86,6 +93,7 @@ cfgManager->setUseLightRegionMaskColour(mUseLightBackground->checkState() == Qt::Checked); cfgManager->setRememberLastRectangularRegion(mRememberRect->checkState() == Qt::Checked); + cfgManager->setShowMagnifierChecked(mShowMagnifier->checkState() == Qt::Checked); cfgManager->setCopySaveLocationToClipboard(mCopyPathToClipboard->checkState() == Qt::Checked); mChangesMade = false; @@ -97,6 +105,7 @@ mUseLightBackground->setChecked(cfgManager->useLightRegionMaskColour()); mRememberRect->setChecked(cfgManager->rememberLastRectangularRegion()); + mShowMagnifier->setChecked(cfgManager->showMagnifierChecked()); mCopyPathToClipboard->setChecked(cfgManager->copySaveLocationToClipboard()); mChangesMade = false; diff --git a/src/QuickEditor/EditorRoot.qml b/src/QuickEditor/EditorRoot.qml --- a/src/QuickEditor/EditorRoot.qml +++ b/src/QuickEditor/EditorRoot.qml @@ -29,6 +29,13 @@ property var selection: undefined; property color maskColour: Qt.rgba(0, 0, 0, 0.15); property color strokeColour: Qt.rgba(0.114, 0.6, 0.953, 1); + property color crossColour: Qt.rgba(0.114, 0.6, 0.953, 0.5); + property bool showMagnifier: false; + property bool toggleMagnifier: false; + property int magZoom: 5; + property int magPixels: 16; + property int magOffset: 32; + SystemPalette { id: systemPalette; } @@ -67,6 +74,20 @@ cancelImage(); } + Keys.onPressed: { + if (event.modifiers & Qt.ShiftModifier) { + toggleMagnifier = true; + cropDisplayCanvas.requestPaint(); + } + } + + Keys.onReleased: { + if (toggleMagnifier && !(event.modifiers & Qt.ShiftModifier)) { + toggleMagnifier = false; + cropDisplayCanvas.requestPaint(); + } + } + // signals signal acceptImage(int x, int y, int width, int height); @@ -188,9 +209,37 @@ ctx.strokeRect(selectionBoxX - 4, selectionBoxY - selectionTextRect.height - 2, selectionTextRect.width + 10, selectionTextRect.height + 8); ctx.fillStyle = systemPalette.windowText; ctx.fillText(selectionText, selectionBoxX, selectionBoxY); + if (selection.zoomCenterX >= 0 && selection.zoomCenterY >= 0 && (showMagnifier ^ toggleMagnifier)) { + var offsetX = magOffset; + var offsetY = magOffset; + var magX = selection.zoomCenterX; + var magY = selection.zoomCenterY; + var magWidth = crossMagnifier.width; + var magHeight = crossMagnifier.height; + + if (magX + offsetX + magWidth >= Window.width / Screen.devicePixelRatio) { + offsetX -= offsetX * 2 + magWidth; + } + + if (magY + offsetY + magHeight >= Window.height / Screen.devicePixelRatio) { + offsetY -= offsetY * 2 + magHeight; + } + + magX += offsetX; + magY += offsetY; + crossMagnifier.visible = true; + crossMagnifier.x = magX; + crossMagnifier.y = magY; + crossBackground.x = -selection.zoomCenterX * Screen.devicePixelRatio * magZoom + magPixels * magZoom; + crossBackground.y = -selection.zoomCenterY * Screen.devicePixelRatio * magZoom + magPixels * magZoom; + ctx.strokeRect(magX, magY, magWidth, magHeight); + } else { + crossMagnifier.visible = false; + } } else { midHelpText.visible = true; bottomHelpText.visible = false; + crossMagnifier.visible = false; } } @@ -241,6 +290,58 @@ anchors.centerIn: parent; } } + + // Use Rectangle so that the background is white when cursor nearby edge + Rectangle { + id: crossMagnifier; + + height: (magPixels * 2 + 1) * magZoom; + width: height; + border.width: 0; + visible: false; + clip: true + + Image { + id: crossBackground; + source: "image://snapshot/rawimage"; + smooth: false; + height: Window.height * magZoom; + width: Window.width * magZoom; + } + + Rectangle { + x: magPixels * magZoom; + y: 0; + width: magZoom; + height: magPixels * magZoom; + color: crossColour; + } + + Rectangle { + x: magPixels * magZoom; + y: (magPixels + 1) * magZoom; + width: magZoom; + height: magPixels * magZoom; + color: crossColour; + } + + Rectangle { + x: 0; + y: magPixels * magZoom; + width: magPixels * magZoom; + height: magZoom; + color: crossColour; + } + + Rectangle { + x: (magPixels + 1) * magZoom; + y: magPixels * magZoom; + width: magPixels * magZoom; + height: magZoom; + color: crossColour; + } + } + } MouseArea { @@ -273,7 +374,8 @@ selection.y = Math.min(starty, mouse.y); selection.width = Math.abs(startx - mouse.x) + 1; selection.height = Math.abs(starty - mouse.y) + 1; - + selection.zoomCenterX = mouse.x; + selection.zoomCenterY = mouse.y; cropDisplayCanvas.requestPaint(); } @@ -283,6 +385,12 @@ cropDisplayCanvas.requestPaint(); } } + + onReleased: { + selection.zoomCenterX = -1; + selection.zoomCenterY = -1; + cropDisplayCanvas.requestPaint(); + } } Component { diff --git a/src/QuickEditor/QuickEditor.cpp b/src/QuickEditor/QuickEditor.cpp --- a/src/QuickEditor/QuickEditor.cpp +++ b/src/QuickEditor/QuickEditor.cpp @@ -114,9 +114,12 @@ } } + rootItem->setProperty("showMagnifier", config->showMagnifierChecked()); + if (config->useLightRegionMaskColour()) { rootItem->setProperty("maskColour", QColor(255, 255, 255, 100)); rootItem->setProperty("strokeColour", QColor(96, 96, 96, 255)); + rootItem->setProperty("crossColour", QColor(96, 96, 96, 127)); } } diff --git a/src/QuickEditor/SelectionRectangle.qml b/src/QuickEditor/SelectionRectangle.qml --- a/src/QuickEditor/SelectionRectangle.qml +++ b/src/QuickEditor/SelectionRectangle.qml @@ -27,6 +27,14 @@ property var imageElement: null; property int minRectSize: 20; property int mouseAreaSize: 20; + property double zoomCenterX: -1; + property double zoomCenterY: -1; + + function resetZoomCenter() { + zoomCenterX = -1; + zoomCenterY = -1; + drawCanvas.requestPaint(); + } signal doubleClicked(); @@ -77,21 +85,29 @@ onPressed: { brxLimit = (parent.x + parent.width) - minRectSize; bryLimit = (parent.y + parent.height) - minRectSize; + parent.zoomCenterX = parent.x; + parent.zoomCenterY = parent.y; } onPositionChanged: { if ((parent.x + mouse.x) < brxLimit) { parent.x = parent.x + mouse.x; parent.width = parent.width - mouse.x; + parent.zoomCenterX = parent.zoomCenterX + mouse.x; } if ((parent.y + mouse.y) < bryLimit) { parent.y = parent.y + mouse.y; parent.height = parent.height - mouse.y; + parent.zoomCenterY = parent.zoomCenterY + mouse.y; } drawCanvas.requestPaint(); } + + onReleased: { + resetZoomCenter(); + } } MouseArea { @@ -110,20 +126,28 @@ onPressed: { brxLimit = parent.x + mouseAreaSize + minRectSize; bryLimit = (parent.y + parent.height) - minRectSize; + parent.zoomCenterX = parent.x + parent.width - 1; + parent.zoomCenterY = parent.y; } onPositionChanged: { if ((parent.x + parent.width + mouse.x) > brxLimit) { parent.width = parent.width + mouse.x - mouseAreaSize + 1; + parent.zoomCenterX = parent.zoomCenterX + mouse.x - mouseAreaSize + 1; } if ((parent.y + mouse.y) < bryLimit) { parent.y = parent.y + mouse.y; parent.height = parent.height - mouse.y; + parent.zoomCenterY = parent.zoomCenterY + mouse.y; } drawCanvas.requestPaint(); } + + onReleased: { + resetZoomCenter(); + } } MouseArea { @@ -142,20 +166,28 @@ onPressed: { brxLimit = (parent.x + parent.width) - minRectSize; bryLimit = parent.y + mouseAreaSize + minRectSize; + parent.zoomCenterX = parent.x; + parent.zoomCenterY = parent.y + parent.height - 1; } onPositionChanged: { if ((parent.x + mouse.x) < brxLimit) { parent.x = parent.x + mouse.x; parent.width = parent.width - mouse.x; + parent.zoomCenterX = parent.zoomCenterX + mouse.x; } if ((parent.y + parent.height + mouse.y) > bryLimit) { parent.height = parent.height + mouse.y - mouseAreaSize + 1; + parent.zoomCenterY = parent.zoomCenterY + mouse.y - mouseAreaSize + 1; } drawCanvas.requestPaint(); } + + onReleased: { + resetZoomCenter(); + } } MouseArea { @@ -174,18 +206,26 @@ onPressed: { brxLimit = parent.x + mouseAreaSize + minRectSize; bryLimit = parent.y + mouseAreaSize + minRectSize; + parent.zoomCenterX = parent.x + parent.width - 1; + parent.zoomCenterY = parent.y + parent.height - 1; } onPositionChanged: { if ((parent.x + parent.width + mouse.x) > brxLimit) { parent.width = parent.width + mouse.x - mouseAreaSize + 1; + parent.zoomCenterX = parent.zoomCenterX + mouse.x - mouseAreaSize + 1; } if ((parent.y + parent.height + mouse.y) > bryLimit) { parent.height = parent.height + mouse.y - mouseAreaSize + 1; + parent.zoomCenterY = parent.zoomCenterY + mouse.y - mouseAreaSize + 1; } drawCanvas.requestPaint(); } + + onReleased: { + resetZoomCenter(); + } } MouseArea { @@ -202,16 +242,23 @@ onPressed: { limit = (parent.y + parent.height) - minRectSize; + parent.zoomCenterX = parent.x + (parent.width >> 1) - mouseAreaSize / 2; + parent.zoomCenterY = parent.y; } onPositionChanged: { if ((parent.y + mouse.y) < limit) { parent.y = parent.y + mouse.y; parent.height = parent.height - mouse.y; + parent.zoomCenterY = parent.zoomCenterY + mouse.y; } - + parent.zoomCenterX = parent.x + (parent.width >> 1) - mouseAreaSize / 2 + mouse.x + 1; drawCanvas.requestPaint(); } + + onReleased: { + resetZoomCenter(); + } } MouseArea { @@ -228,15 +275,22 @@ onPressed: { limit = parent.y + mouseAreaSize + minRectSize; + parent.zoomCenterX = parent.x + (parent.width >> 1) - mouseAreaSize / 2; + parent.zoomCenterY = parent.y + parent.height - 1; } onPositionChanged: { if ((parent.y + parent.height + mouse.y) > limit) { parent.height = parent.height + mouse.y - mouseAreaSize + 1; + parent.zoomCenterY = parent.zoomCenterY + mouse.y - mouseAreaSize + 1; } - + parent.zoomCenterX = parent.x + (parent.width >> 1) - mouseAreaSize / 2 + mouse.x + 1; drawCanvas.requestPaint(); } + + onReleased: { + resetZoomCenter(); + } } MouseArea { @@ -253,16 +307,23 @@ onPressed: { limit = (parent.x + parent.width) - minRectSize; + parent.zoomCenterX = parent.x; + parent.zoomCenterY = parent.y + (parent.height >> 1) - mouseAreaSize / 2; } onPositionChanged: { if ((parent.x + mouse.x) < limit) { parent.x = parent.x + mouse.x; parent.width = parent.width - mouse.x; + parent.zoomCenterX = parent.zoomCenterX + mouse.x; } - + parent.zoomCenterY = parent.y + (parent.height >> 1) - mouseAreaSize / 2 + mouse.y + 1; drawCanvas.requestPaint(); } + + onReleased: { + resetZoomCenter(); + } } MouseArea { @@ -279,14 +340,21 @@ onPressed: { limit = parent.x + mouseAreaSize + minRectSize; + parent.zoomCenterX = parent.x + parent.width - 1; + parent.zoomCenterY = parent.y + (parent.height >> 1) - mouseAreaSize / 2; } onPositionChanged: { if ((parent.x + parent.width + mouse.x) > limit) { parent.width = parent.width + mouse.x - mouseAreaSize + 1; + parent.zoomCenterX = parent.zoomCenterX + mouse.x - mouseAreaSize + 1; } - + parent.zoomCenterY = parent.y + (parent.height >> 1) - mouseAreaSize / 2 + mouse.y + 1; drawCanvas.requestPaint(); } + + onReleased: { + resetZoomCenter(); + } } } diff --git a/src/SpectacleConfig.h b/src/SpectacleConfig.h --- a/src/SpectacleConfig.h +++ b/src/SpectacleConfig.h @@ -75,6 +75,9 @@ bool quitAfterSaveOrCopyChecked() const; void setQuitAfterSaveOrCopyChecked(bool enabled); + bool showMagnifierChecked() const; + void setShowMagnifierChecked(bool enabled); + qreal captureDelay() const; void setCaptureDelay(qreal delay); diff --git a/src/SpectacleConfig.cpp b/src/SpectacleConfig.cpp --- a/src/SpectacleConfig.cpp +++ b/src/SpectacleConfig.cpp @@ -128,6 +128,19 @@ mGuiConfig.sync(); } +// show magnifier + +bool SpectacleConfig::showMagnifierChecked() const +{ + return mGuiConfig.readEntry(QStringLiteral("showMagnifier"), false); +} + +void SpectacleConfig::setShowMagnifierChecked(bool enabled) +{ + mGuiConfig.writeEntry(QStringLiteral("showMagnifier"), enabled); + mGuiConfig.sync(); +} + // capture delay qreal SpectacleConfig::captureDelay() const