Color grabber zoom preview (#1869)
* Add ColorGrabWidget The new widget aims to decouple color grabbing from the SidePanelWidget. * Refactor SidePanelWidget to use ColorGrabWidget - All color grabbing functionality is now moved to ColorGrabWidget - SidePanelWidget now uses a more organized sigslot approach - Removed QColorPickingEventFilter * Fix bug and complete implementation Timer not yet implemented. Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Add 0.5s timer Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Fix failing builds Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Add hex color editor Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Add right mouse button instant preview Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Make zoom widget toggle-able * Implement OverlayMessage class * Make Right click do the same as Space * Unzoom widget when mouse leaves it Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Fix some small issues Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Toggle panel when grabbing color Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Show with timer even if magnifier active Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Reduce timer delay Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com> * Fix OverlayMessage bug Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>
This commit is contained in:
@@ -7,6 +7,7 @@ target_sources(
|
||||
capturewidget.h
|
||||
colorpicker.h
|
||||
hovereventfilter.h
|
||||
overlaymessage.h
|
||||
selectionwidget.h
|
||||
notifierbox.h
|
||||
modificationcommand.h)
|
||||
@@ -19,6 +20,7 @@ target_sources(
|
||||
capturewidget.cpp
|
||||
colorpicker.cpp
|
||||
hovereventfilter.cpp
|
||||
overlaymessage.cpp
|
||||
notifierbox.cpp
|
||||
selectionwidget.cpp
|
||||
modificationcommand.cpp)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "src/widgets/capture/hovereventfilter.h"
|
||||
#include "src/widgets/capture/modificationcommand.h"
|
||||
#include "src/widgets/capture/notifierbox.h"
|
||||
#include "src/widgets/capture/overlaymessage.h"
|
||||
#include "src/widgets/orientablepushbutton.h"
|
||||
#include "src/widgets/panel/sidepanelwidget.h"
|
||||
#include "src/widgets/panel/utilitypanel.h"
|
||||
@@ -84,7 +85,6 @@ CaptureWidget::CaptureWidget(uint id,
|
||||
this,
|
||||
&CaptureWidget::childLeave);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
m_showInitialMsg = m_config.showHelpValue();
|
||||
m_opacity = m_config.contrastOpacityValue();
|
||||
m_uiColor = m_config.uiMainColorValue();
|
||||
m_contrastUiColor = m_config.uiContrastColorValue();
|
||||
@@ -199,6 +199,18 @@ CaptureWidget::CaptureWidget(uint id,
|
||||
});
|
||||
|
||||
initPanel();
|
||||
|
||||
OverlayMessage::init(this,
|
||||
QGuiAppCurrentScreen().currentScreen()->geometry());
|
||||
|
||||
if (m_config.showHelpValue()) {
|
||||
OverlayMessage::push(
|
||||
tr("Select an area with the mouse, or press Esc to exit."
|
||||
"\nPress Enter to capture the screen."
|
||||
"\nPress Right Click to show the color picker."
|
||||
"\nUse the Mouse Wheel to change the thickness of your tool."
|
||||
"\nPress Space to open the side panel."));
|
||||
}
|
||||
}
|
||||
|
||||
CaptureWidget::~CaptureWidget()
|
||||
@@ -364,12 +376,6 @@ void CaptureWidget::paintEvent(QPaintEvent* paintEvent)
|
||||
|
||||
// draw inactive region
|
||||
drawInactiveRegion(&painter);
|
||||
|
||||
// show initial message on screen capture call if required (before selecting
|
||||
// area)
|
||||
if (m_showInitialMsg) {
|
||||
drawInitialMessage(&painter);
|
||||
}
|
||||
}
|
||||
|
||||
void CaptureWidget::showColorPicker(const QPoint& pos)
|
||||
@@ -485,7 +491,7 @@ void CaptureWidget::mousePressEvent(QMouseEvent* e)
|
||||
showColorPicker(m_mousePressedPos);
|
||||
return;
|
||||
} else if (e->button() == Qt::LeftButton) {
|
||||
m_showInitialMsg = false;
|
||||
OverlayMessage::pop();
|
||||
m_mouseIsClicked = true;
|
||||
|
||||
// Click using a tool excluding tool MOVE
|
||||
@@ -972,7 +978,7 @@ void CaptureWidget::initPanel()
|
||||
this,
|
||||
&CaptureWidget::updateActiveLayer);
|
||||
|
||||
m_sidePanel = new SidePanelWidget(&m_context.screenshot);
|
||||
m_sidePanel = new SidePanelWidget(&m_context.screenshot, this);
|
||||
connect(m_sidePanel,
|
||||
&SidePanelWidget::colorChanged,
|
||||
this,
|
||||
@@ -1325,7 +1331,6 @@ void CaptureWidget::selectAll()
|
||||
m_selection->setGeometry(newGeometry);
|
||||
m_context.selection = extendedRect(newGeometry);
|
||||
m_selection->setVisible(true);
|
||||
m_showInitialMsg = false;
|
||||
m_buttonHandler->updatePosition(m_selection->geometry());
|
||||
updateSizeIndicator();
|
||||
m_buttonHandler->show();
|
||||
@@ -1664,56 +1669,6 @@ QRect CaptureWidget::extendedRect(const QRect& r) const
|
||||
r.height() * devicePixelRatio);
|
||||
}
|
||||
|
||||
void CaptureWidget::drawInitialMessage(QPainter* painter)
|
||||
{
|
||||
if (nullptr == painter) {
|
||||
return;
|
||||
}
|
||||
#if (defined(Q_OS_MACOS) || defined(Q_OS_LINUX))
|
||||
QRect helpRect;
|
||||
QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen();
|
||||
if (currentScreen) {
|
||||
helpRect = currentScreen->geometry();
|
||||
} else {
|
||||
helpRect = QGuiApplication::primaryScreen()->geometry();
|
||||
}
|
||||
#else
|
||||
QRect helpRect = QGuiApplication::primaryScreen()->geometry();
|
||||
#endif
|
||||
|
||||
helpRect.moveTo(mapFromGlobal(helpRect.topLeft()));
|
||||
|
||||
QString helpTxt =
|
||||
tr("Select an area with the mouse, or press Esc to exit."
|
||||
"\nPress Enter to capture the screen."
|
||||
"\nPress Right Click to show the color picker."
|
||||
"\nUse the Mouse Wheel to change the thickness of your tool."
|
||||
"\nPress Space to open the side panel.");
|
||||
|
||||
// We draw the white contrasting background for the text, using the
|
||||
// same text and options to get the boundingRect that the text will
|
||||
// have.
|
||||
QRectF bRect = painter->boundingRect(helpRect, Qt::AlignCenter, helpTxt);
|
||||
|
||||
// These four calls provide padding for the rect
|
||||
const int margin = QApplication::fontMetrics().height() / 2;
|
||||
bRect.setWidth(bRect.width() + margin);
|
||||
bRect.setHeight(bRect.height() + margin);
|
||||
bRect.setX(bRect.x() - margin);
|
||||
bRect.setY(bRect.y() - margin);
|
||||
|
||||
QColor rectColor(m_uiColor);
|
||||
rectColor.setAlpha(180);
|
||||
QColor textColor(
|
||||
(ColorUtils::colorIsDark(rectColor) ? Qt::white : Qt::black));
|
||||
|
||||
painter->setBrush(QBrush(rectColor, Qt::SolidPattern));
|
||||
painter->setPen(QPen(textColor));
|
||||
|
||||
painter->drawRect(bRect);
|
||||
painter->drawText(helpRect, Qt::AlignCenter, helpTxt);
|
||||
}
|
||||
|
||||
void CaptureWidget::drawInactiveRegion(QPainter* painter)
|
||||
{
|
||||
QColor overlayColor(0, 0, 0, m_opacity);
|
||||
|
||||
@@ -131,7 +131,6 @@ private:
|
||||
|
||||
QRect extendedSelection() const;
|
||||
QRect extendedRect(const QRect& r) const;
|
||||
void drawInitialMessage(QPainter* painter);
|
||||
void drawInactiveRegion(QPainter* painter);
|
||||
void drawToolsData(bool updateLayersPanel = true,
|
||||
bool drawSelection = false);
|
||||
@@ -159,7 +158,6 @@ private:
|
||||
bool m_newSelection;
|
||||
bool m_grabbing;
|
||||
bool m_movingSelection;
|
||||
bool m_showInitialMsg;
|
||||
bool m_captureDone;
|
||||
bool m_previewEnabled;
|
||||
bool m_adjustmentButtonPressed;
|
||||
|
||||
110
src/widgets/capture/overlaymessage.cpp
Normal file
110
src/widgets/capture/overlaymessage.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "overlaymessage.h"
|
||||
#include "colorutils.h"
|
||||
#include "confighandler.h"
|
||||
#include "qguiappcurrentscreen.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <QPen>
|
||||
#include <QScreen>
|
||||
#include <QWidget>
|
||||
|
||||
OverlayMessage::OverlayMessage(QWidget* parent, const QRect& targetArea)
|
||||
: QWidget(parent)
|
||||
, m_targetArea(targetArea)
|
||||
{
|
||||
// NOTE: do not call the static functions from the constructor
|
||||
m_instance = this;
|
||||
m_messageStack.push(QString()); // Default message is empty
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
setAttribute(Qt::WA_AlwaysStackOnTop);
|
||||
QWidget::hide();
|
||||
}
|
||||
|
||||
void OverlayMessage::init(QWidget* parent, const QRect& targetArea)
|
||||
{
|
||||
new OverlayMessage(parent, targetArea);
|
||||
}
|
||||
|
||||
void OverlayMessage::push(const QString& msg)
|
||||
{
|
||||
m_instance->m_messageStack.push(msg);
|
||||
setVisibility(true);
|
||||
}
|
||||
|
||||
void OverlayMessage::pop()
|
||||
{
|
||||
if (m_instance->m_messageStack.size() > 1) {
|
||||
m_instance->m_messageStack.pop();
|
||||
}
|
||||
|
||||
if (m_instance->m_messageStack.size() == 1) {
|
||||
// Only empty message left (don't show it)
|
||||
m_instance->QWidget::hide();
|
||||
} else {
|
||||
// Still visible, resize for new message
|
||||
m_instance->updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayMessage::setVisibility(bool visible)
|
||||
{
|
||||
m_instance->updateGeometry();
|
||||
m_instance->setVisible(visible);
|
||||
}
|
||||
|
||||
OverlayMessage* OverlayMessage::instance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
void OverlayMessage::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
QRectF bRect = boundingRect();
|
||||
bRect.moveTo(0, 0);
|
||||
|
||||
QColor rectColor(ConfigHandler().uiMainColorValue());
|
||||
rectColor.setAlpha(180);
|
||||
QColor textColor(
|
||||
(ColorUtils::colorIsDark(rectColor) ? Qt::white : Qt::black));
|
||||
|
||||
painter.setBrush(QBrush(rectColor, Qt::SolidPattern));
|
||||
painter.setPen(QPen(textColor));
|
||||
|
||||
float margin = painter.pen().widthF();
|
||||
painter.drawRect(bRect - QMarginsF(margin, margin, margin, margin));
|
||||
painter.drawText(bRect, Qt::AlignCenter, m_messageStack.top());
|
||||
}
|
||||
|
||||
void OverlayMessage::showEvent(QShowEvent*)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
QRectF OverlayMessage::boundingRect() const
|
||||
{
|
||||
// We draw the white contrasting background for the text, using the
|
||||
// same text and options to get the boundingRect that the text will
|
||||
// have.
|
||||
QRectF bRect = QApplication::fontMetrics().boundingRect(
|
||||
m_targetArea, Qt::AlignCenter, m_messageStack.top());
|
||||
|
||||
// These four calls provide padding for the rect
|
||||
const int margin = QApplication::fontMetrics().height() / 2;
|
||||
bRect.setWidth(bRect.width() + margin);
|
||||
bRect.setHeight(bRect.height() + margin);
|
||||
bRect.setX(bRect.x() - margin);
|
||||
bRect.setY(bRect.y() - margin);
|
||||
return bRect;
|
||||
}
|
||||
|
||||
void OverlayMessage::updateGeometry()
|
||||
{
|
||||
m_instance->setGeometry(m_instance->boundingRect().toRect());
|
||||
QWidget::updateGeometry();
|
||||
}
|
||||
|
||||
OverlayMessage* OverlayMessage::m_instance = nullptr;
|
||||
43
src/widgets/capture/overlaymessage.h
Normal file
43
src/widgets/capture/overlaymessage.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <QStack>
|
||||
#include <QWidget>
|
||||
|
||||
/**
|
||||
* @brief Overlay a message in capture mode.
|
||||
*
|
||||
* The message must be initialized by calling `init` before it can be used. That
|
||||
* can be done once per capture session. The class is a singleton.
|
||||
*
|
||||
* To change the active message call `push`. This will automatically show the
|
||||
* widget. Previous messages won't be forgotten and will be reactivated after
|
||||
* you call `pop`. You can show/hide the message using `setVisibility` (this
|
||||
* won't push/pop anything).
|
||||
*
|
||||
* @note You have to make sure that widgets pop the messages they pushed when
|
||||
* they are closed, to avoid potential bugs and resource leaks.
|
||||
*/
|
||||
class OverlayMessage : public QWidget
|
||||
{
|
||||
public:
|
||||
OverlayMessage() = delete;
|
||||
|
||||
static void init(QWidget* parent, const QRect& targetArea);
|
||||
static void push(const QString& msg);
|
||||
static void pop();
|
||||
static void setVisibility(bool visible);
|
||||
static OverlayMessage* instance();
|
||||
|
||||
private:
|
||||
QStack<QString> m_messageStack;
|
||||
QRect m_targetArea;
|
||||
static OverlayMessage* m_instance;
|
||||
|
||||
OverlayMessage(QWidget* parent, const QRect& center);
|
||||
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
void showEvent(QShowEvent*) override;
|
||||
|
||||
QRectF boundingRect() const;
|
||||
void updateGeometry();
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
# Required to generate MOC
|
||||
target_sources(flameshot PRIVATE sidepanelwidget.h utilitypanel.h)
|
||||
target_sources(flameshot PRIVATE sidepanelwidget.h utilitypanel.h colorgrabwidget.h)
|
||||
|
||||
target_sources(flameshot PRIVATE sidepanelwidget.cpp utilitypanel.cpp)
|
||||
target_sources(flameshot PRIVATE sidepanelwidget.cpp utilitypanel.cpp colorgrabwidget.cpp)
|
||||
|
||||
219
src/widgets/panel/colorgrabwidget.cpp
Normal file
219
src/widgets/panel/colorgrabwidget.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
#include "colorgrabwidget.h"
|
||||
#include "sidepanelwidget.h"
|
||||
|
||||
#include "colorutils.h"
|
||||
#include "confighandler.h"
|
||||
#include "overlaymessage.h"
|
||||
#include "src/core/qguiappcurrentscreen.h"
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QKeyEvent>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
#include <QShortcut>
|
||||
#include <QTimer>
|
||||
#include <stdexcept>
|
||||
|
||||
// Width (= height) and zoom level of the widget before the user clicks
|
||||
#define WIDTH1 77
|
||||
#define ZOOM1 11
|
||||
// Width (= height) and zoom level of the widget after the user clicks
|
||||
#define WIDTH2 165
|
||||
#define ZOOM2 15
|
||||
|
||||
// NOTE: WIDTH1(2) should be divisible by ZOOM1(2) for best precision.
|
||||
// WIDTH1 should be odd so the cursor can be centered on a pixel.
|
||||
|
||||
ColorGrabWidget::ColorGrabWidget(QPixmap* p, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_pixmap(p)
|
||||
, m_mousePressReceived(false)
|
||||
, m_extraZoomActive(false)
|
||||
, m_magnifierActive(false)
|
||||
{
|
||||
if (p == nullptr) {
|
||||
throw std::logic_error("Pixmap must not be null");
|
||||
}
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
// We don't need this widget to receive mouse events because we use
|
||||
// eventFilter on other objects that do
|
||||
setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
setWindowFlags(Qt::BypassWindowManagerHint | Qt::FramelessWindowHint);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void ColorGrabWidget::startGrabbing()
|
||||
{
|
||||
// NOTE: grabMouse() would prevent move events being received
|
||||
// With this method we just need to make sure that mouse press and release
|
||||
// events get consumed before they reach their target widget.
|
||||
// This is undone in the destructor.
|
||||
qApp->setOverrideCursor(Qt::CrossCursor);
|
||||
qApp->installEventFilter(this);
|
||||
OverlayMessage::push(
|
||||
"Press Enter or Left Mouse Button to accept color\n"
|
||||
"Press and hold Left Mouse Button to precisely select color\n"
|
||||
"Press Space or Right Mouse Button to toggle magnifier\n"
|
||||
"Press ESC to cancel");
|
||||
}
|
||||
|
||||
QColor ColorGrabWidget::color()
|
||||
{
|
||||
return m_color;
|
||||
}
|
||||
|
||||
bool ColorGrabWidget::eventFilter(QObject*, QEvent* event)
|
||||
{
|
||||
// Consume shortcut events and handle key presses from whole app
|
||||
if (event->type() == QEvent::KeyPress ||
|
||||
event->type() == QEvent::Shortcut) {
|
||||
QKeySequence key = event->type() == QEvent::KeyPress
|
||||
? static_cast<QKeyEvent*>(event)->key()
|
||||
: static_cast<QShortcutEvent*>(event)->key();
|
||||
if (key == Qt::Key_Escape) {
|
||||
emit grabAborted();
|
||||
finalize();
|
||||
} else if (key == Qt::Key_Return || key == Qt::Key_Enter) {
|
||||
emit colorGrabbed(m_color);
|
||||
finalize();
|
||||
} else if (key == Qt::Key_Space && !m_extraZoomActive) {
|
||||
setMagnifierActive(!m_magnifierActive);
|
||||
}
|
||||
return true;
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
// NOTE: This relies on the fact that CaptureWidget tracks mouse moves
|
||||
|
||||
if (m_extraZoomActive && !geometry().contains(cursorPos())) {
|
||||
setExtraZoomActive(false);
|
||||
return true;
|
||||
}
|
||||
if (!m_extraZoomActive && !m_magnifierActive) {
|
||||
// This fixes an issue when the mouse leaves the zoom area before
|
||||
// the widget even appears.
|
||||
hide();
|
||||
}
|
||||
if (!m_extraZoomActive) {
|
||||
// Update only before the user clicks the mouse, after the mouse
|
||||
// press the widget remains static.
|
||||
updateWidget();
|
||||
}
|
||||
|
||||
// Hide overlay message when cursor is over it
|
||||
OverlayMessage* overlayMsg = OverlayMessage::instance();
|
||||
overlayMsg->setVisibility(
|
||||
!overlayMsg->geometry().contains(cursorPos()));
|
||||
|
||||
m_color = getColorAtPoint(cursorPos());
|
||||
emit colorUpdated(m_color);
|
||||
return true;
|
||||
} else if (event->type() == QEvent::MouseButtonPress) {
|
||||
m_mousePressReceived = true;
|
||||
auto* e = static_cast<QMouseEvent*>(event);
|
||||
if (e->buttons() == Qt::RightButton) {
|
||||
setMagnifierActive(!m_magnifierActive);
|
||||
} else if (e->buttons() == Qt::LeftButton) {
|
||||
setExtraZoomActive(true);
|
||||
}
|
||||
return true;
|
||||
} else if (event->type() == QEvent::MouseButtonRelease) {
|
||||
if (!m_mousePressReceived) {
|
||||
// Do not consume event if it corresponds to the mouse press that
|
||||
// triggered the color grabbing in the first place. This prevents
|
||||
// focus issues in the capture widget when the color grabber is
|
||||
// closed.
|
||||
return false;
|
||||
}
|
||||
auto* e = static_cast<QMouseEvent*>(event);
|
||||
if (e->button() == Qt::LeftButton && m_extraZoomActive) {
|
||||
emit colorGrabbed(getColorAtPoint(cursorPos()));
|
||||
finalize();
|
||||
}
|
||||
return true;
|
||||
} else if (event->type() == QEvent::MouseButtonDblClick) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ColorGrabWidget::paintEvent(QPaintEvent*)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.drawImage(QRectF(0, 0, width(), height()), m_previewImage);
|
||||
}
|
||||
|
||||
void ColorGrabWidget::showEvent(QShowEvent*)
|
||||
{
|
||||
updateWidget();
|
||||
}
|
||||
|
||||
QPoint ColorGrabWidget::cursorPos() const
|
||||
{
|
||||
return QCursor::pos(QGuiAppCurrentScreen().currentScreen());
|
||||
}
|
||||
|
||||
/// @note The point is in screen coordinates.
|
||||
QColor ColorGrabWidget::getColorAtPoint(const QPoint& p) const
|
||||
{
|
||||
if (m_extraZoomActive && geometry().contains(p)) {
|
||||
QPoint point = mapFromGlobal(p);
|
||||
// we divide coordinate-wise to avoid rounding to nearest
|
||||
return m_previewImage.pixel(
|
||||
QPoint(point.x() / ZOOM2, point.y() / ZOOM2));
|
||||
}
|
||||
QPoint point = p;
|
||||
#if defined(Q_OS_MACOS)
|
||||
QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen();
|
||||
if (currentScreen) {
|
||||
point = QPoint((p.x() - currentScreen->geometry().x()) *
|
||||
currentScreen->devicePixelRatio(),
|
||||
(p.y() - currentScreen->geometry().y()) *
|
||||
currentScreen->devicePixelRatio());
|
||||
}
|
||||
#endif
|
||||
QPixmap pixel = m_pixmap->copy(QRect(point, point));
|
||||
return pixel.toImage().pixel(0, 0);
|
||||
}
|
||||
|
||||
void ColorGrabWidget::setExtraZoomActive(bool active)
|
||||
{
|
||||
m_extraZoomActive = active;
|
||||
if (!active && !m_magnifierActive) {
|
||||
hide();
|
||||
} else {
|
||||
if (!isVisible()) {
|
||||
QTimer::singleShot(250, this, [this]() { show(); });
|
||||
} else {
|
||||
QTimer::singleShot(250, this, [this]() { updateWidget(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ColorGrabWidget::setMagnifierActive(bool active)
|
||||
{
|
||||
m_magnifierActive = active;
|
||||
setVisible(active);
|
||||
}
|
||||
|
||||
void ColorGrabWidget::updateWidget()
|
||||
{
|
||||
int width = m_extraZoomActive ? WIDTH2 : WIDTH1;
|
||||
float zoom = m_extraZoomActive ? ZOOM2 : ZOOM1;
|
||||
// Set window size and move its center to the mouse cursor
|
||||
QRect rect(0, 0, width, width);
|
||||
rect.moveCenter(cursorPos());
|
||||
setGeometry(rect);
|
||||
// Store a pixmap containing the zoomed-in section around the cursor
|
||||
QRect sourceRect(0, 0, width / zoom, width / zoom);
|
||||
sourceRect.moveCenter(rect.center());
|
||||
m_previewImage = m_pixmap->copy(sourceRect).toImage();
|
||||
// Repaint
|
||||
update();
|
||||
}
|
||||
|
||||
void ColorGrabWidget::finalize()
|
||||
{
|
||||
qApp->removeEventFilter(this);
|
||||
qApp->restoreOverrideCursor();
|
||||
OverlayMessage::pop();
|
||||
close();
|
||||
}
|
||||
45
src/widgets/panel/colorgrabwidget.h
Normal file
45
src/widgets/panel/colorgrabwidget.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef COLORGRABWIDGET_H
|
||||
#define COLORGRABWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class SidePanelWidget;
|
||||
class OverlayMessage;
|
||||
|
||||
class ColorGrabWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ColorGrabWidget(QPixmap* p, QWidget* parent = nullptr);
|
||||
|
||||
void startGrabbing();
|
||||
|
||||
QColor color();
|
||||
|
||||
signals:
|
||||
void colorUpdated(const QColor& color);
|
||||
void colorGrabbed(const QColor& color);
|
||||
void grabAborted();
|
||||
|
||||
private:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
void paintEvent(QPaintEvent* e);
|
||||
void showEvent(QShowEvent* event) override;
|
||||
|
||||
QPoint cursorPos() const;
|
||||
QColor getColorAtPoint(const QPoint& point) const;
|
||||
void setExtraZoomActive(bool active);
|
||||
void setMagnifierActive(bool active);
|
||||
void updateWidget();
|
||||
void finalize();
|
||||
|
||||
QPixmap* m_pixmap;
|
||||
QImage m_previewImage;
|
||||
QColor m_color;
|
||||
|
||||
bool m_mousePressReceived;
|
||||
bool m_extraZoomActive;
|
||||
bool m_magnifierActive;
|
||||
};
|
||||
|
||||
#endif // COLORGRABWIDGET_H
|
||||
@@ -2,57 +2,33 @@
|
||||
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
|
||||
|
||||
#include "sidepanelwidget.h"
|
||||
#include "colorgrabwidget.h"
|
||||
#include "src/core/qguiappcurrentscreen.h"
|
||||
#include "src/utils/colorutils.h"
|
||||
#include "src/utils/pathinfo.h"
|
||||
#include "utilitypanel.h"
|
||||
#include <QApplication>
|
||||
#include <QDebug> // TODO remove
|
||||
#include <QFormLayout>
|
||||
#include <QKeyEvent>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QShortcut>
|
||||
#include <QSlider>
|
||||
#include <QVBoxLayout>
|
||||
#if defined(Q_OS_MACOS)
|
||||
#include <QScreen>
|
||||
#endif
|
||||
|
||||
class QColorPickingEventFilter : public QObject
|
||||
{
|
||||
public:
|
||||
explicit QColorPickingEventFilter(SidePanelWidget* pw,
|
||||
QObject* parent = nullptr)
|
||||
: QObject(parent)
|
||||
, m_pw(pw)
|
||||
{}
|
||||
|
||||
bool eventFilter(QObject*, QEvent* event) override
|
||||
{
|
||||
event->accept();
|
||||
switch (event->type()) {
|
||||
case QEvent::MouseMove:
|
||||
return m_pw->handleMouseMove(static_cast<QMouseEvent*>(event));
|
||||
case QEvent::MouseButtonPress:
|
||||
return m_pw->handleMouseButtonPressed(
|
||||
static_cast<QMouseEvent*>(event));
|
||||
case QEvent::KeyPress:
|
||||
return m_pw->handleKeyPress(static_cast<QKeyEvent*>(event));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
SidePanelWidget* m_pw;
|
||||
};
|
||||
|
||||
////////////////////////
|
||||
|
||||
SidePanelWidget::SidePanelWidget(QPixmap* p, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_pixmap(p)
|
||||
, m_eventFilter(nullptr)
|
||||
{
|
||||
m_layout = new QVBoxLayout(this);
|
||||
if (parent) {
|
||||
parent->installEventFilter(this);
|
||||
}
|
||||
|
||||
QFormLayout* colorForm = new QFormLayout();
|
||||
m_thicknessSlider = new QSlider(Qt::Horizontal);
|
||||
@@ -64,6 +40,23 @@ SidePanelWidget::SidePanelWidget(QPixmap* p, QWidget* parent)
|
||||
colorForm->addRow(tr("Active color:"), m_colorLabel);
|
||||
m_layout->addLayout(colorForm);
|
||||
|
||||
m_colorWheel = new color_widgets::ColorWheel(this);
|
||||
m_colorWheel->setColor(m_color);
|
||||
m_colorHex = new QLineEdit(this);
|
||||
m_colorHex->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QColor background = this->palette().window().color();
|
||||
bool isDark = ColorUtils::colorIsDark(background);
|
||||
QString modifier =
|
||||
isDark ? PathInfo::whiteIconPath() : PathInfo::blackIconPath();
|
||||
QIcon grabIcon(modifier + "colorize.svg");
|
||||
m_colorGrabButton = new QPushButton(grabIcon, tr("Grab Color"));
|
||||
|
||||
m_layout->addWidget(m_colorGrabButton);
|
||||
m_layout->addWidget(m_colorWheel);
|
||||
m_layout->addWidget(m_colorHex);
|
||||
|
||||
// thickness sigslots
|
||||
connect(m_thicknessSlider,
|
||||
&QSlider::sliderMoved,
|
||||
this,
|
||||
@@ -72,22 +65,20 @@ SidePanelWidget::SidePanelWidget(QPixmap* p, QWidget* parent)
|
||||
&SidePanelWidget::thicknessChanged,
|
||||
this,
|
||||
&SidePanelWidget::updateThickness);
|
||||
|
||||
QColor background = this->palette().window().color();
|
||||
bool isDark = ColorUtils::colorIsDark(background);
|
||||
QString modifier =
|
||||
isDark ? PathInfo::whiteIconPath() : PathInfo::blackIconPath();
|
||||
QIcon grabIcon(modifier + "colorize.svg");
|
||||
m_colorGrabButton = new QPushButton(grabIcon, QLatin1String(""));
|
||||
updateGrabButton(false);
|
||||
// color hex editor sigslots
|
||||
connect(m_colorHex, &QLineEdit::editingFinished, this, [=]() {
|
||||
if (!QColor::isValidColor(m_colorHex->text())) {
|
||||
m_colorHex->setText(m_color.name(QColor::HexRgb));
|
||||
} else {
|
||||
updateColor(m_colorHex->text());
|
||||
}
|
||||
});
|
||||
// color grab button sigslots
|
||||
connect(m_colorGrabButton,
|
||||
&QPushButton::pressed,
|
||||
this,
|
||||
&SidePanelWidget::colorGrabberActivated);
|
||||
m_layout->addWidget(m_colorGrabButton);
|
||||
|
||||
m_colorWheel = new color_widgets::ColorWheel(this);
|
||||
m_colorWheel->setColor(m_color);
|
||||
&SidePanelWidget::startColorGrab);
|
||||
// color wheel sigslots
|
||||
connect(m_colorWheel,
|
||||
&color_widgets::ColorWheel::mouseReleaseOnColor,
|
||||
this,
|
||||
@@ -96,15 +87,20 @@ SidePanelWidget::SidePanelWidget(QPixmap* p, QWidget* parent)
|
||||
&color_widgets::ColorWheel::colorChanged,
|
||||
this,
|
||||
&SidePanelWidget::updateColorNoWheel);
|
||||
m_layout->addWidget(m_colorWheel);
|
||||
}
|
||||
|
||||
void SidePanelWidget::updateColor(const QColor& c)
|
||||
{
|
||||
m_color = c;
|
||||
updateColorNoWheel(c);
|
||||
m_colorWheel->setColor(c);
|
||||
}
|
||||
|
||||
void SidePanelWidget::updateColorNoWheel(const QColor& c)
|
||||
{
|
||||
m_colorLabel->setStyleSheet(
|
||||
QStringLiteral("QLabel { background-color : %1; }").arg(c.name()));
|
||||
m_colorWheel->setColor(m_color);
|
||||
m_colorHex->setText(c.name(QColor::HexRgb));
|
||||
}
|
||||
|
||||
void SidePanelWidget::updateThickness(const int& t)
|
||||
@@ -113,95 +109,70 @@ void SidePanelWidget::updateThickness(const int& t)
|
||||
m_thicknessSlider->setValue(m_thickness);
|
||||
}
|
||||
|
||||
void SidePanelWidget::updateColorNoWheel(const QColor& c)
|
||||
void SidePanelWidget::startColorGrab()
|
||||
{
|
||||
m_color = c;
|
||||
m_colorLabel->setStyleSheet(
|
||||
QStringLiteral("QLabel { background-color : %1; }").arg(c.name()));
|
||||
m_revertColor = m_color;
|
||||
m_colorGrabber = new ColorGrabWidget(m_pixmap);
|
||||
connect(m_colorGrabber,
|
||||
&ColorGrabWidget::colorUpdated,
|
||||
this,
|
||||
&SidePanelWidget::onColorUpdated);
|
||||
connect(m_colorGrabber,
|
||||
&ColorGrabWidget::colorGrabbed,
|
||||
this,
|
||||
&SidePanelWidget::onColorGrabFinished);
|
||||
connect(m_colorGrabber,
|
||||
&ColorGrabWidget::grabAborted,
|
||||
this,
|
||||
&SidePanelWidget::onColorGrabAborted);
|
||||
|
||||
emit togglePanel();
|
||||
m_colorGrabber->startGrabbing();
|
||||
}
|
||||
|
||||
void SidePanelWidget::colorGrabberActivated()
|
||||
void SidePanelWidget::onColorGrabFinished()
|
||||
{
|
||||
grabKeyboard();
|
||||
grabMouse(Qt::CrossCursor);
|
||||
setMouseTracking(true);
|
||||
m_colorBackup = m_color;
|
||||
if (!m_eventFilter) {
|
||||
m_eventFilter = new QColorPickingEventFilter(this, this);
|
||||
}
|
||||
installEventFilter(m_eventFilter);
|
||||
updateGrabButton(true);
|
||||
}
|
||||
|
||||
void SidePanelWidget::releaseColorGrab()
|
||||
{
|
||||
setMouseTracking(false);
|
||||
removeEventFilter(m_eventFilter);
|
||||
releaseMouse();
|
||||
releaseKeyboard();
|
||||
setFocus();
|
||||
updateGrabButton(false);
|
||||
}
|
||||
|
||||
QColor SidePanelWidget::grabPixmapColor(const QPoint& p)
|
||||
{
|
||||
QColor c;
|
||||
if (m_pixmap) {
|
||||
#if defined(Q_OS_MACOS)
|
||||
QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen();
|
||||
QPoint point = p;
|
||||
if (currentScreen) {
|
||||
point = QPoint((p.x() - currentScreen->geometry().x()) *
|
||||
currentScreen->devicePixelRatio(),
|
||||
(p.y() - currentScreen->geometry().y()) *
|
||||
currentScreen->devicePixelRatio());
|
||||
}
|
||||
QPixmap pixel = m_pixmap->copy(QRect(point, point));
|
||||
#else
|
||||
QPixmap pixel = m_pixmap->copy(QRect(p, p));
|
||||
#endif
|
||||
c = pixel.toImage().pixel(0, 0);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
bool SidePanelWidget::handleKeyPress(QKeyEvent* e)
|
||||
{
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
releaseColorGrab();
|
||||
updateColor(m_colorBackup);
|
||||
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
||||
updateColor(grabPixmapColor(QCursor::pos()));
|
||||
releaseColorGrab();
|
||||
emit colorChanged(m_color);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SidePanelWidget::handleMouseButtonPressed(QMouseEvent* e)
|
||||
{
|
||||
if (m_colorGrabButton->geometry().contains(e->pos()) ||
|
||||
e->button() == Qt::RightButton) {
|
||||
updateColorNoWheel(m_colorBackup);
|
||||
} else if (e->button() == Qt::LeftButton) {
|
||||
updateColor(grabPixmapColor(QCursor::pos()));
|
||||
}
|
||||
releaseColorGrab();
|
||||
finalizeGrab();
|
||||
m_color = m_colorGrabber->color();
|
||||
emit colorChanged(m_color);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SidePanelWidget::handleMouseMove(QMouseEvent* e)
|
||||
void SidePanelWidget::onColorGrabAborted()
|
||||
{
|
||||
updateColorNoWheel(grabPixmapColor(e->globalPos()));
|
||||
return true;
|
||||
finalizeGrab();
|
||||
// Restore color that was selected before we started grabbing
|
||||
updateColor(m_revertColor);
|
||||
}
|
||||
|
||||
void SidePanelWidget::updateGrabButton(const bool activated)
|
||||
void SidePanelWidget::onColorUpdated(const QColor& color)
|
||||
{
|
||||
if (activated) {
|
||||
m_colorGrabButton->setText(tr("Press ESC to cancel"));
|
||||
} else {
|
||||
m_colorGrabButton->setText(tr("Grab Color"));
|
||||
updateColorNoWheel(color);
|
||||
}
|
||||
|
||||
void SidePanelWidget::finalizeGrab()
|
||||
{
|
||||
emit togglePanel();
|
||||
}
|
||||
|
||||
bool SidePanelWidget::eventFilter(QObject* obj, QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::ShortcutOverride) {
|
||||
// Override Escape shortcut from CaptureWidget
|
||||
auto* e = static_cast<QKeyEvent*>(event);
|
||||
if (e->key() == Qt::Key_Escape && m_colorHex->hasFocus()) {
|
||||
m_colorHex->clearFocus();
|
||||
e->accept();
|
||||
return true;
|
||||
}
|
||||
} else if (event->type() == QEvent::MouseButtonPress) {
|
||||
// Clicks outside of the Color Hex editor
|
||||
m_colorHex->clearFocus();
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void SidePanelWidget::hideEvent(QHideEvent* event)
|
||||
{
|
||||
QWidget::hideEvent(event);
|
||||
m_colorHex->clearFocus();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
class QVBoxLayout;
|
||||
class QPushButton;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class ColorGrabWidget;
|
||||
class QColorPickingEventFilter;
|
||||
class QSlider;
|
||||
|
||||
@@ -28,32 +30,30 @@ signals:
|
||||
|
||||
public slots:
|
||||
void updateColor(const QColor& c);
|
||||
void updateColorNoWheel(const QColor& c);
|
||||
void updateThickness(const int& t);
|
||||
|
||||
private slots:
|
||||
void updateColorNoWheel(const QColor& c);
|
||||
|
||||
private slots:
|
||||
void colorGrabberActivated();
|
||||
void releaseColorGrab();
|
||||
void startColorGrab();
|
||||
void onColorGrabFinished();
|
||||
void onColorGrabAborted();
|
||||
void onColorUpdated(const QColor& color);
|
||||
|
||||
private:
|
||||
QColor grabPixmapColor(const QPoint& p);
|
||||
void finalizeGrab();
|
||||
|
||||
bool handleKeyPress(QKeyEvent* e);
|
||||
bool handleMouseButtonPressed(QMouseEvent* e);
|
||||
bool handleMouseMove(QMouseEvent* e);
|
||||
|
||||
void updateGrabButton(const bool activated);
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
|
||||
QVBoxLayout* m_layout;
|
||||
QPushButton* m_colorGrabButton;
|
||||
ColorGrabWidget* m_colorGrabber;
|
||||
color_widgets::ColorWheel* m_colorWheel;
|
||||
QLabel* m_colorLabel;
|
||||
QLineEdit* m_colorHex;
|
||||
QPixmap* m_pixmap;
|
||||
QColor m_colorBackup;
|
||||
QColor m_color;
|
||||
QColor m_revertColor;
|
||||
QSlider* m_thicknessSlider;
|
||||
int m_thickness;
|
||||
QColorPickingEventFilter* m_eventFilter;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user