File indexing completed on 2024-11-10 04:57:03
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2012 Filip Wieladek <wattos@gmail.com> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #include "mouseclick.h" 0011 // KConfigSkeleton 0012 #include "mouseclickconfig.h" 0013 0014 #include "core/rendertarget.h" 0015 #include "core/renderviewport.h" 0016 #include "effect/effecthandler.h" 0017 0018 #include <QAction> 0019 0020 #include <KConfigGroup> 0021 #include <KGlobalAccel> 0022 0023 #include <QPainter> 0024 #include <QTabletEvent> 0025 0026 #include <cmath> 0027 0028 namespace KWin 0029 { 0030 0031 MouseClickEffect::MouseClickEffect() 0032 { 0033 MouseClickConfig::instance(effects->config()); 0034 m_enabled = false; 0035 0036 QAction *a = new QAction(this); 0037 a->setObjectName(QStringLiteral("ToggleMouseClick")); 0038 a->setText(i18n("Toggle Mouse Click Effect")); 0039 KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_Asterisk)); 0040 KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_Asterisk)); 0041 connect(a, &QAction::triggered, this, &MouseClickEffect::toggleEnabled); 0042 0043 reconfigure(ReconfigureAll); 0044 0045 m_buttons[0] = std::make_unique<MouseButton>(i18nc("Left mouse button", "Left"), Qt::LeftButton); 0046 m_buttons[1] = std::make_unique<MouseButton>(i18nc("Middle mouse button", "Middle"), Qt::MiddleButton); 0047 m_buttons[2] = std::make_unique<MouseButton>(i18nc("Right mouse button", "Right"), Qt::RightButton); 0048 } 0049 0050 MouseClickEffect::~MouseClickEffect() 0051 { 0052 if (m_enabled) { 0053 effects->stopMousePolling(); 0054 } 0055 } 0056 0057 void MouseClickEffect::reconfigure(ReconfigureFlags) 0058 { 0059 MouseClickConfig::self()->read(); 0060 m_colors[0] = MouseClickConfig::color1(); 0061 m_colors[1] = MouseClickConfig::color2(); 0062 m_colors[2] = MouseClickConfig::color3(); 0063 m_lineWidth = MouseClickConfig::lineWidth(); 0064 m_ringLife = MouseClickConfig::ringLife(); 0065 m_ringMaxSize = MouseClickConfig::ringSize(); 0066 m_ringCount = MouseClickConfig::ringCount(); 0067 m_showText = MouseClickConfig::showText(); 0068 m_font = MouseClickConfig::font(); 0069 } 0070 0071 void MouseClickEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) 0072 { 0073 const int time = m_lastPresentTime.count() ? (presentTime - m_lastPresentTime).count() : 0; 0074 0075 for (auto &click : m_clicks) { 0076 click->m_time += time; 0077 } 0078 0079 for (int i = 0; i < BUTTON_COUNT; ++i) { 0080 if (m_buttons[i]->m_isPressed) { 0081 m_buttons[i]->m_time += time; 0082 } 0083 } 0084 0085 while (m_clicks.size() > 0) { 0086 if (m_clicks.front()->m_time <= m_ringLife) { 0087 break; 0088 } 0089 m_clicks.pop_front(); 0090 } 0091 0092 if (isActive()) { 0093 m_lastPresentTime = presentTime; 0094 } else { 0095 m_lastPresentTime = std::chrono::milliseconds::zero(); 0096 } 0097 0098 effects->prePaintScreen(data, presentTime); 0099 } 0100 0101 void MouseClickEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, Output *screen) 0102 { 0103 effects->paintScreen(renderTarget, viewport, mask, region, screen); 0104 0105 if (effects->isOpenGLCompositing()) { 0106 paintScreenSetupGl(renderTarget, viewport.projectionMatrix()); 0107 } 0108 for (const auto &click : m_clicks) { 0109 for (int i = 0; i < m_ringCount; ++i) { 0110 float alpha = computeAlpha(click.get(), i); 0111 float size = computeRadius(click.get(), i); 0112 if (size > 0 && alpha > 0) { 0113 QColor color = m_colors[click->m_button]; 0114 color.setAlphaF(alpha); 0115 drawCircle(viewport, color, click->m_pos.x(), click->m_pos.y(), size); 0116 } 0117 } 0118 0119 if (m_showText && click->m_frame) { 0120 float frameAlpha = (click->m_time * 2.0f - m_ringLife) / m_ringLife; 0121 frameAlpha = frameAlpha < 0 ? 1 : -(frameAlpha * frameAlpha) + 1; 0122 click->m_frame->render(renderTarget, viewport, infiniteRegion(), frameAlpha, frameAlpha); 0123 } 0124 } 0125 for (const auto &tool : std::as_const(m_tabletTools)) { 0126 const int step = m_ringMaxSize * (1. - tool.m_pressure); 0127 for (qreal size = m_ringMaxSize; size > 0; size -= step) { 0128 drawCircle(viewport, tool.m_color, tool.m_globalPosition.x(), tool.m_globalPosition.y(), size); 0129 } 0130 } 0131 if (effects->isOpenGLCompositing()) { 0132 paintScreenFinishGl(); 0133 } 0134 } 0135 0136 void MouseClickEffect::postPaintScreen() 0137 { 0138 effects->postPaintScreen(); 0139 repaint(); 0140 } 0141 0142 float MouseClickEffect::computeRadius(const MouseEvent *click, int ring) 0143 { 0144 float ringDistance = m_ringLife / (m_ringCount * 3); 0145 if (click->m_press) { 0146 return ((click->m_time - ringDistance * ring) / m_ringLife) * m_ringMaxSize; 0147 } 0148 return ((m_ringLife - click->m_time - ringDistance * ring) / m_ringLife) * m_ringMaxSize; 0149 } 0150 0151 float MouseClickEffect::computeAlpha(const MouseEvent *click, int ring) 0152 { 0153 float ringDistance = m_ringLife / (m_ringCount * 3); 0154 return (m_ringLife - (float)click->m_time - ringDistance * (ring)) / m_ringLife; 0155 } 0156 0157 void MouseClickEffect::slotMouseChanged(const QPointF &pos, const QPointF &, 0158 Qt::MouseButtons buttons, Qt::MouseButtons oldButtons, 0159 Qt::KeyboardModifiers, Qt::KeyboardModifiers) 0160 { 0161 if (buttons == oldButtons) { 0162 return; 0163 } 0164 0165 std::unique_ptr<MouseEvent> m; 0166 int i = BUTTON_COUNT; 0167 while (--i >= 0) { 0168 MouseButton *b = m_buttons[i].get(); 0169 if (isPressed(b->m_button, buttons, oldButtons)) { 0170 m = std::make_unique<MouseEvent>(i, pos.toPoint(), 0, createEffectFrame(pos.toPoint(), b->m_labelDown), true); 0171 break; 0172 } else if (isReleased(b->m_button, buttons, oldButtons) && (!b->m_isPressed || b->m_time > m_ringLife)) { 0173 // we might miss a press, thus also check !b->m_isPressed, bug #314762 0174 m = std::make_unique<MouseEvent>(i, pos.toPoint(), 0, createEffectFrame(pos.toPoint(), b->m_labelUp), false); 0175 break; 0176 } 0177 b->setPressed(b->m_button & buttons); 0178 } 0179 0180 if (m) { 0181 m_clicks.push_back(std::move(m)); 0182 } 0183 repaint(); 0184 } 0185 0186 std::unique_ptr<EffectFrame> MouseClickEffect::createEffectFrame(const QPoint &pos, const QString &text) 0187 { 0188 if (!m_showText) { 0189 return nullptr; 0190 } 0191 QPoint point(pos.x() + m_ringMaxSize, pos.y()); 0192 std::unique_ptr<EffectFrame> frame = std::make_unique<EffectFrame>(EffectFrameStyled, false, point, Qt::AlignLeft); 0193 frame->setFont(m_font); 0194 frame->setText(text); 0195 return frame; 0196 } 0197 0198 void MouseClickEffect::repaint() 0199 { 0200 if (m_clicks.size() > 0) { 0201 QRegion dirtyRegion; 0202 const int radius = m_ringMaxSize + m_lineWidth; 0203 for (auto &click : m_clicks) { 0204 dirtyRegion += QRect(click->m_pos.x() - radius, click->m_pos.y() - radius, 2 * radius, 2 * radius); 0205 if (click->m_frame) { 0206 dirtyRegion += click->m_frame->geometry(); 0207 } 0208 } 0209 effects->addRepaint(dirtyRegion); 0210 } 0211 if (!m_tabletTools.isEmpty()) { 0212 QRegion dirtyRegion; 0213 const int radius = m_ringMaxSize + m_lineWidth; 0214 for (const auto &event : std::as_const(m_tabletTools)) { 0215 dirtyRegion += QRect(event.m_globalPosition.x() - radius, event.m_globalPosition.y() - radius, 2 * radius, 2 * radius); 0216 } 0217 effects->addRepaint(dirtyRegion); 0218 } 0219 } 0220 0221 bool MouseClickEffect::isReleased(Qt::MouseButtons button, Qt::MouseButtons buttons, Qt::MouseButtons oldButtons) 0222 { 0223 return !(button & buttons) && (button & oldButtons); 0224 } 0225 0226 bool MouseClickEffect::isPressed(Qt::MouseButtons button, Qt::MouseButtons buttons, Qt::MouseButtons oldButtons) 0227 { 0228 return (button & buttons) && !(button & oldButtons); 0229 } 0230 0231 void MouseClickEffect::toggleEnabled() 0232 { 0233 m_enabled = !m_enabled; 0234 0235 if (m_enabled) { 0236 connect(effects, &EffectsHandler::mouseChanged, this, &MouseClickEffect::slotMouseChanged); 0237 effects->startMousePolling(); 0238 } else { 0239 disconnect(effects, &EffectsHandler::mouseChanged, this, &MouseClickEffect::slotMouseChanged); 0240 effects->stopMousePolling(); 0241 } 0242 0243 m_clicks.clear(); 0244 m_tabletTools.clear(); 0245 0246 for (int i = 0; i < BUTTON_COUNT; ++i) { 0247 m_buttons[i]->m_time = 0; 0248 m_buttons[i]->m_isPressed = false; 0249 } 0250 } 0251 0252 bool MouseClickEffect::isActive() const 0253 { 0254 return m_enabled && (m_clicks.size() != 0 || !m_tabletTools.isEmpty()); 0255 } 0256 0257 void MouseClickEffect::drawCircle(const RenderViewport &viewport, const QColor &color, float cx, float cy, float r) 0258 { 0259 if (effects->isOpenGLCompositing()) { 0260 drawCircleGl(viewport, color, cx, cy, r); 0261 } else if (effects->compositingType() == QPainterCompositing) { 0262 drawCircleQPainter(color, cx, cy, r); 0263 } 0264 } 0265 0266 void MouseClickEffect::drawCircleGl(const RenderViewport &viewport, const QColor &color, float cx, float cy, float r) 0267 { 0268 static const int num_segments = 80; 0269 static const float theta = 2 * 3.1415926 / float(num_segments); 0270 static const float c = cosf(theta); // precalculate the sine and cosine 0271 static const float s = sinf(theta); 0272 const float scale = viewport.scale(); 0273 float t; 0274 0275 float x = r; // we start at angle = 0 0276 float y = 0; 0277 0278 GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); 0279 vbo->reset(); 0280 QList<QVector2D> verts; 0281 verts.reserve(num_segments * 2); 0282 0283 for (int ii = 0; ii < num_segments; ++ii) { 0284 verts.push_back(QVector2D((x + cx) * scale, (y + cy) * scale)); // output vertex 0285 // apply the rotation matrix 0286 t = x; 0287 x = c * x - s * y; 0288 y = s * t + c * y; 0289 } 0290 vbo->setVertices(verts); 0291 ShaderManager::instance()->getBoundShader()->setUniform(GLShader::ColorUniform::Color, color); 0292 vbo->render(GL_LINE_LOOP); 0293 } 0294 0295 void MouseClickEffect::drawCircleQPainter(const QColor &color, float cx, float cy, float r) 0296 { 0297 QPainter *painter = effects->scenePainter(); 0298 painter->save(); 0299 painter->setPen(color); 0300 painter->drawArc(cx - r, cy - r, r * 2, r * 2, 0, 5760); 0301 painter->restore(); 0302 } 0303 0304 void MouseClickEffect::paintScreenSetupGl(const RenderTarget &renderTarget, const QMatrix4x4 &projectionMatrix) 0305 { 0306 GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor | ShaderTrait::TransformColorspace); 0307 shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projectionMatrix); 0308 shader->setColorspaceUniformsFromSRGB(renderTarget.colorDescription()); 0309 0310 glLineWidth(m_lineWidth); 0311 glEnable(GL_BLEND); 0312 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 0313 } 0314 0315 void MouseClickEffect::paintScreenFinishGl() 0316 { 0317 glDisable(GL_BLEND); 0318 0319 ShaderManager::instance()->popShader(); 0320 } 0321 0322 bool MouseClickEffect::tabletToolEvent(QTabletEvent *event) 0323 { 0324 auto &tabletEvent = m_tabletTools[event->uniqueId()]; 0325 if (!tabletEvent.m_color.isValid()) { 0326 switch (event->pointerType()) { 0327 case QPointingDevice::PointerType::Unknown: 0328 case QPointingDevice::PointerType::Generic: 0329 case QPointingDevice::PointerType::Finger: 0330 case QPointingDevice::PointerType::Pen: 0331 tabletEvent.m_color = MouseClickConfig::color1(); 0332 break; 0333 case QPointingDevice::PointerType::Eraser: 0334 tabletEvent.m_color = MouseClickConfig::color2(); 0335 break; 0336 case QPointingDevice::PointerType::Cursor: 0337 tabletEvent.m_color = MouseClickConfig::color3(); 0338 break; 0339 case QPointingDevice::PointerType::AllPointerTypes: 0340 Q_UNREACHABLE(); 0341 break; 0342 } 0343 } 0344 switch (event->type()) { 0345 case QEvent::TabletPress: 0346 tabletEvent.m_pressed = true; 0347 break; 0348 case QEvent::TabletRelease: 0349 tabletEvent.m_pressed = false; 0350 break; 0351 case QEvent::TabletLeaveProximity: 0352 m_tabletTools.remove(event->uniqueId()); 0353 return false; 0354 default: 0355 break; 0356 } 0357 tabletEvent.m_globalPosition = event->globalPos(); 0358 tabletEvent.m_pressure = event->pressure(); 0359 0360 return false; 0361 } 0362 0363 QColor MouseClickEffect::color1() const 0364 { 0365 return m_colors[0]; 0366 } 0367 0368 QColor MouseClickEffect::color2() const 0369 { 0370 return m_colors[1]; 0371 } 0372 0373 QColor MouseClickEffect::color3() const 0374 { 0375 return m_colors[2]; 0376 } 0377 0378 qreal MouseClickEffect::lineWidth() const 0379 { 0380 return m_lineWidth; 0381 } 0382 0383 int MouseClickEffect::ringLife() const 0384 { 0385 return m_ringLife; 0386 } 0387 0388 int MouseClickEffect::ringSize() const 0389 { 0390 return m_ringMaxSize; 0391 } 0392 0393 int MouseClickEffect::ringCount() const 0394 { 0395 return m_ringCount; 0396 } 0397 0398 bool MouseClickEffect::isShowText() const 0399 { 0400 return m_showText; 0401 } 0402 0403 QFont MouseClickEffect::font() const 0404 { 0405 return m_font; 0406 } 0407 0408 bool MouseClickEffect::isEnabled() const 0409 { 0410 return m_enabled; 0411 } 0412 0413 } // namespace 0414 0415 #include "moc_mouseclick.cpp"