File indexing completed on 2024-05-12 16:02:01

0001 /*
0002  * KDE. Krita Project.
0003  *
0004  * SPDX-FileCopyrightText: 2020 Deif Lou <ginoba@gmail.com>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include <QPainter>
0010 #include <QMouseEvent>
0011 #include <cmath>
0012 
0013 #include "KisAngleGauge.h"
0014 
0015 struct KisAngleGauge::Private
0016 {
0017     static constexpr qreal minimumSnapDistance{40.0};
0018     qreal angle;
0019     qreal snapAngle;
0020     qreal resetAngle;
0021     IncreasingDirection increasingDirection;
0022     bool isPressed;
0023     bool isMouseHover;
0024 };
0025 
0026 KisAngleGauge::KisAngleGauge(QWidget* parent)
0027     : QWidget(parent)
0028     , m_d(new Private)
0029 {
0030     m_d->angle = 0.0;
0031     m_d->snapAngle = 15.0;
0032     m_d->resetAngle = 0.0;
0033     m_d->increasingDirection = IncreasingDirection_CounterClockwise;
0034     m_d->isPressed = false;
0035     m_d->isMouseHover = false;
0036     
0037     setFocusPolicy(Qt::WheelFocus);
0038 }
0039 
0040 KisAngleGauge::~KisAngleGauge()
0041 {}
0042 
0043 qreal KisAngleGauge::angle() const
0044 {
0045     return m_d->angle;
0046 }
0047 
0048 qreal KisAngleGauge::snapAngle() const
0049 {
0050     return m_d->snapAngle;
0051 }
0052 
0053 qreal KisAngleGauge::resetAngle() const
0054 {
0055     return m_d->resetAngle;
0056 }
0057 
0058 KisAngleGauge::IncreasingDirection KisAngleGauge::increasingDirection() const
0059 {
0060     return m_d->increasingDirection;
0061 }
0062 
0063 void KisAngleGauge::setAngle(qreal newAngle)
0064 {
0065     if (qFuzzyCompare(newAngle, m_d->angle)) {
0066         return;
0067     }
0068 
0069     m_d->angle = newAngle;
0070     update();
0071     emit angleChanged(newAngle);
0072 }
0073 
0074 void KisAngleGauge::setSnapAngle(qreal newSnapAngle)
0075 {
0076     m_d->snapAngle = newSnapAngle;
0077 }
0078 
0079 void KisAngleGauge::setResetAngle(qreal newResetAngle)
0080 {
0081     m_d->resetAngle = newResetAngle;
0082 }
0083 
0084 void KisAngleGauge::setIncreasingDirection(IncreasingDirection newIncreasingDirection)
0085 {
0086     m_d->increasingDirection = newIncreasingDirection;
0087     update();
0088 }
0089 
0090 void KisAngleGauge::reset()
0091 {
0092     setAngle(resetAngle());
0093 }
0094 
0095 void KisAngleGauge::paintEvent(QPaintEvent *e)
0096 {
0097     QPainter painter(this);
0098     const QPointF center(width() / 2.0, height() / 2.0);
0099     const qreal minSide = std::min(center.x(), center.y());
0100     const qreal radius = minSide * 0.9;
0101     const qreal lineMarkerRadius = minSide * 0.1;
0102     const qreal angleInRadians = m_d->angle * M_PI / 180.0;
0103     const QPointF d(
0104         center.x() + std::cos(angleInRadians) * radius,
0105         m_d->increasingDirection == IncreasingDirection_CounterClockwise
0106         ? center.y() - std::sin(angleInRadians) * radius
0107         : center.y() + std::sin(angleInRadians) * radius
0108     );
0109 
0110     painter.setRenderHint(QPainter::Antialiasing, true);
0111 
0112     QColor backgroundColor, circleColor, axesColor, angleLineColor, angleLineMarkerColor;
0113     if (palette().color(QPalette::Window).lightness() < 128) {
0114         circleColor = palette().color(QPalette::Light);
0115         axesColor = palette().color(QPalette::Light);
0116         axesColor.setAlpha(200);
0117         if (isEnabled()) {
0118             backgroundColor = palette().color(QPalette::Dark);
0119             angleLineColor = QColor(255, 255, 255, 128);
0120             angleLineMarkerColor = QColor(255, 255, 255, 200);
0121         } else {
0122             backgroundColor = palette().color(QPalette::Window);
0123             angleLineColor = palette().color(QPalette::Light);
0124             angleLineMarkerColor = palette().color(QPalette::Light);
0125         }
0126     } else {
0127         circleColor = palette().color(QPalette::Dark);
0128         axesColor = palette().color(QPalette::Dark);
0129         axesColor.setAlpha(200);
0130         if (isEnabled()) {
0131             backgroundColor = palette().color(QPalette::Light);
0132             angleLineColor = QColor(0, 0, 0, 128);
0133             angleLineMarkerColor = QColor(0, 0, 0, 200);
0134         } else {
0135             backgroundColor = palette().color(QPalette::Window);
0136             angleLineColor = palette().color(QPalette::Dark);
0137             angleLineMarkerColor = palette().color(QPalette::Dark);
0138         }
0139     }
0140 
0141     // Background
0142     painter.setPen(Qt::transparent);
0143     painter.setBrush(backgroundColor);
0144     painter.drawEllipse(center, radius, radius);
0145 
0146     // Axes lines
0147     painter.setPen(QPen(axesColor, 1.0, Qt::DotLine));
0148     painter.drawLine(center.x(), center.y() - radius + 1.0, center.x(), center.y() + radius - 1.0);
0149     painter.drawLine(center.x() - radius + 1.0, center.y(), center.x() + radius - 1.0, center.y());
0150 
0151     // Outer circle
0152     if (this->hasFocus()) {
0153         painter.setPen(QPen(palette().color(QPalette::Highlight), 2.0));
0154     } else {
0155         if (m_d->isMouseHover && isEnabled()) {
0156             painter.setPen(QPen(palette().color(QPalette::Highlight), 1.0));
0157         } else {
0158             painter.setPen(QPen(circleColor, 1.0));
0159         }
0160     }
0161     painter.setBrush(Qt::transparent);
0162     painter.drawEllipse(center, radius, radius);
0163 
0164     // Angle line
0165     painter.setPen(QPen(angleLineColor, 1.0));
0166     painter.drawLine(center, d);
0167 
0168     // Inner line marker
0169     painter.setPen(Qt::transparent);
0170     painter.setBrush(angleLineMarkerColor);
0171     painter.drawEllipse(center, lineMarkerRadius, lineMarkerRadius);
0172 
0173     // Outer line marker
0174     painter.setBrush(angleLineMarkerColor);
0175     painter.drawEllipse(d, lineMarkerRadius, lineMarkerRadius);
0176 
0177     e->accept();
0178 }
0179 
0180 void KisAngleGauge::mousePressEvent(QMouseEvent *e)
0181 {
0182     if (e->button() != Qt::LeftButton) {
0183         e->ignore();
0184         return;
0185     }
0186 
0187     const QPointF center(width() / 2.0, height() / 2.0);
0188     const qreal radius = std::min(center.x(), center.y());
0189     const qreal radiusSquared = radius * radius;
0190     const QPointF delta(e->x() - center.x(), e->y() - center.y());
0191     const qreal distanceSquared = delta.x() * delta.x() + delta.y() * delta.y();
0192 
0193     if (distanceSquared > radiusSquared) {
0194         e->ignore();
0195         return;
0196     }
0197 
0198     qreal angle =
0199         std::atan2(
0200             m_d->increasingDirection == IncreasingDirection_CounterClockwise ? -delta.y() : delta.y(),
0201             delta.x()
0202         );
0203     
0204     if (e->modifiers() & Qt::ControlModifier)  {
0205         const qreal sa = m_d->snapAngle * M_PI / 180.0;
0206         angle = std::round(angle / sa) * sa;
0207     }
0208 
0209     setAngle(angle * 180.0 / M_PI);
0210 
0211     m_d->isPressed = true;
0212     
0213     e->accept();
0214 }
0215 
0216 void KisAngleGauge::mouseReleaseEvent(QMouseEvent *e)
0217 {
0218     if (e->button() == Qt::LeftButton && m_d->isPressed) {
0219         m_d->isPressed = false;
0220         e->accept();
0221         return;
0222     }
0223     e->ignore();
0224 }
0225 
0226 void KisAngleGauge::mouseMoveEvent(QMouseEvent *e)
0227 {
0228     if (!(e->buttons() & Qt::LeftButton) || !m_d->isPressed) {
0229         e->ignore();
0230         return;
0231     }
0232 
0233     const QPointF center(width() / 2.0, height() / 2.0);
0234     const qreal radius = std::min(center.x(), center.y());
0235     const qreal radiusSquared = radius * radius;
0236     const QPointF delta(e->x() - center.x(), e->y() - center.y());
0237     const qreal distanceSquared = delta.x() * delta.x() + delta.y() * delta.y();
0238     qreal angle =
0239         std::atan2(
0240             m_d->increasingDirection == IncreasingDirection_CounterClockwise ? -delta.y() : delta.y(),
0241             delta.x()
0242         );
0243 
0244     const qreal snapDistance = qMax(m_d->minimumSnapDistance * m_d->minimumSnapDistance, radiusSquared * 4.0);
0245     if ((e->modifiers() & Qt::ControlModifier) || distanceSquared < snapDistance) {
0246         const qreal sa = m_d->snapAngle * M_PI / 180.0;
0247         angle = std::round(angle / sa) * sa;
0248     }
0249 
0250     setAngle(angle * 180.0 / M_PI);
0251     
0252     e->accept();
0253 }
0254 
0255 void KisAngleGauge::mouseDoubleClickEvent(QMouseEvent *e)
0256 {
0257     if (e->button() == Qt::LeftButton) {
0258         reset();
0259         e->accept();
0260     } else {
0261         e->ignore();
0262     }
0263 }
0264 
0265 void KisAngleGauge::wheelEvent(QWheelEvent *e)
0266 {
0267     if (e->angleDelta().y() > 0) {
0268         if (e->modifiers() & Qt::ControlModifier) {
0269             setAngle(std::floor((m_d->angle + m_d->snapAngle) / m_d->snapAngle) * m_d->snapAngle);
0270         } else {
0271             setAngle(m_d->angle + 1.0);
0272         }
0273     } else if (e->angleDelta().y() < 0) {
0274         if (e->modifiers() & Qt::ControlModifier) {
0275             setAngle(std::ceil((m_d->angle - m_d->snapAngle) / m_d->snapAngle) * m_d->snapAngle);
0276         } else {
0277             setAngle(m_d->angle - 1.0);
0278         }
0279     }
0280     e->accept();
0281 }
0282 
0283 void KisAngleGauge::keyPressEvent(QKeyEvent *e)
0284 {
0285     if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Right) {
0286         if (e->modifiers() & Qt::ControlModifier) {
0287             setAngle(std::floor((m_d->angle + m_d->snapAngle) / m_d->snapAngle) * m_d->snapAngle);
0288         } else {
0289             setAngle(m_d->angle + 1.0);
0290         }
0291         e->accept();
0292     } else if (e->key() == Qt::Key_Down || e->key() == Qt::Key_Left) {
0293         if (e->modifiers() & Qt::ControlModifier) {
0294             setAngle(std::ceil((m_d->angle - m_d->snapAngle) / m_d->snapAngle) * m_d->snapAngle);
0295         } else {
0296             setAngle(m_d->angle - 1.0);
0297         }
0298         e->accept();
0299     } else {
0300         e->ignore();
0301     }
0302 }
0303 
0304 void KisAngleGauge::enterEvent(QEvent *e)
0305 {
0306     m_d->isMouseHover = true;
0307     update();
0308     QWidget::enterEvent(e);
0309 }
0310 
0311 void KisAngleGauge::leaveEvent(QEvent *e)
0312 {
0313     m_d->isMouseHover = false;
0314     update();
0315     QWidget::leaveEvent(e);
0316 }