File indexing completed on 2024-05-19 04:29:25

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         ) * 180.0 / M_PI;
0243 
0244     const qreal snapDistance = qMax(m_d->minimumSnapDistance * m_d->minimumSnapDistance, radiusSquared * 4.0);
0245     const bool controlPressed = e->modifiers() & Qt::ControlModifier;
0246     const bool shiftPressed = e->modifiers() & Qt::ShiftModifier;
0247 
0248     if (controlPressed && shiftPressed) {
0249         angle = std::round(angle);
0250     } else if (!shiftPressed && (controlPressed || distanceSquared < snapDistance)) {
0251         angle = std::round(angle / m_d->snapAngle) * m_d->snapAngle;
0252     }
0253 
0254     setAngle(angle);
0255     
0256     e->accept();
0257 }
0258 
0259 void KisAngleGauge::mouseDoubleClickEvent(QMouseEvent *e)
0260 {
0261     if (e->button() == Qt::LeftButton) {
0262         reset();
0263         e->accept();
0264     } else {
0265         e->ignore();
0266     }
0267 }
0268 
0269 void KisAngleGauge::wheelEvent(QWheelEvent *e)
0270 {
0271     if (e->angleDelta().y() > 0) {
0272         if (e->modifiers() & Qt::ControlModifier) {
0273             setAngle(std::floor((m_d->angle + m_d->snapAngle) / m_d->snapAngle) * m_d->snapAngle);
0274         } else {
0275             setAngle(m_d->angle + 1.0);
0276         }
0277     } else if (e->angleDelta().y() < 0) {
0278         if (e->modifiers() & Qt::ControlModifier) {
0279             setAngle(std::ceil((m_d->angle - m_d->snapAngle) / m_d->snapAngle) * m_d->snapAngle);
0280         } else {
0281             setAngle(m_d->angle - 1.0);
0282         }
0283     }
0284     e->accept();
0285 }
0286 
0287 void KisAngleGauge::keyPressEvent(QKeyEvent *e)
0288 {
0289     if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Right) {
0290         if (e->modifiers() & Qt::ControlModifier) {
0291             setAngle(std::floor((m_d->angle + m_d->snapAngle) / m_d->snapAngle) * m_d->snapAngle);
0292         } else {
0293             setAngle(m_d->angle + 1.0);
0294         }
0295         e->accept();
0296     } else if (e->key() == Qt::Key_Down || e->key() == Qt::Key_Left) {
0297         if (e->modifiers() & Qt::ControlModifier) {
0298             setAngle(std::ceil((m_d->angle - m_d->snapAngle) / m_d->snapAngle) * m_d->snapAngle);
0299         } else {
0300             setAngle(m_d->angle - 1.0);
0301         }
0302         e->accept();
0303     } else {
0304         e->ignore();
0305     }
0306 }
0307 
0308 void KisAngleGauge::enterEvent(QEvent *e)
0309 {
0310     m_d->isMouseHover = true;
0311     update();
0312     QWidget::enterEvent(e);
0313 }
0314 
0315 void KisAngleGauge::leaveEvent(QEvent *e)
0316 {
0317     m_d->isMouseHover = false;
0318     update();
0319     QWidget::leaveEvent(e);
0320 }