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 }