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 &region, 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"