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:
Haris Gušić
2021-09-13 16:09:25 +02:00
committed by GitHub
parent 327e42d842
commit aca0db963b
10 changed files with 547 additions and 204 deletions

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;

View 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;

View 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();
};

View File

@@ -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)

View 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();
}

View 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

View File

@@ -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();
}

View File

@@ -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;
};