File indexing completed on 2025-04-27 11:33:15

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     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "touchpoints.h"
0012 
0013 #include <QAction>
0014 #include <kwinglutils.h>
0015 
0016 #include <KConfigGroup>
0017 #include <KGlobalAccel>
0018 
0019 #include <QPainter>
0020 
0021 #include <cmath>
0022 
0023 namespace KWin
0024 {
0025 
0026 TouchPointsEffect::TouchPointsEffect()
0027     : Effect()
0028 {
0029 }
0030 
0031 TouchPointsEffect::~TouchPointsEffect() = default;
0032 
0033 static const Qt::GlobalColor s_colors[] = {
0034     Qt::blue,
0035     Qt::red,
0036     Qt::green,
0037     Qt::cyan,
0038     Qt::magenta,
0039     Qt::yellow,
0040     Qt::gray,
0041     Qt::darkBlue,
0042     Qt::darkRed,
0043     Qt::darkGreen};
0044 
0045 Qt::GlobalColor TouchPointsEffect::colorForId(quint32 id)
0046 {
0047     auto it = m_colors.constFind(id);
0048     if (it != m_colors.constEnd()) {
0049         return it.value();
0050     }
0051     static int s_colorIndex = -1;
0052     s_colorIndex = (s_colorIndex + 1) % 10;
0053     m_colors.insert(id, s_colors[s_colorIndex]);
0054     return s_colors[s_colorIndex];
0055 }
0056 
0057 bool TouchPointsEffect::touchDown(qint32 id, const QPointF &pos, std::chrono::microseconds time)
0058 {
0059     TouchPoint point;
0060     point.pos = pos;
0061     point.press = true;
0062     point.color = colorForId(id);
0063     m_points << point;
0064     m_latestPositions.insert(id, pos);
0065     repaint();
0066     return false;
0067 }
0068 
0069 bool TouchPointsEffect::touchMotion(qint32 id, const QPointF &pos, std::chrono::microseconds time)
0070 {
0071     TouchPoint point;
0072     point.pos = pos;
0073     point.press = true;
0074     point.color = colorForId(id);
0075     m_points << point;
0076     m_latestPositions.insert(id, pos);
0077     repaint();
0078     return false;
0079 }
0080 
0081 bool TouchPointsEffect::touchUp(qint32 id, std::chrono::microseconds time)
0082 {
0083     auto it = m_latestPositions.constFind(id);
0084     if (it != m_latestPositions.constEnd()) {
0085         TouchPoint point;
0086         point.pos = it.value();
0087         point.press = false;
0088         point.color = colorForId(id);
0089         m_points << point;
0090     }
0091     return false;
0092 }
0093 
0094 void TouchPointsEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
0095 {
0096     int time = 0;
0097     if (m_lastPresentTime.count()) {
0098         time = (presentTime - m_lastPresentTime).count();
0099     }
0100 
0101     auto it = m_points.begin();
0102     while (it != m_points.end()) {
0103         it->time += time;
0104         if (it->time > m_ringLife) {
0105             it = m_points.erase(it);
0106         } else {
0107             it++;
0108         }
0109     }
0110 
0111     if (m_points.isEmpty()) {
0112         m_lastPresentTime = std::chrono::milliseconds::zero();
0113     } else {
0114         m_lastPresentTime = presentTime;
0115     }
0116 
0117     effects->prePaintScreen(data, presentTime);
0118 }
0119 
0120 void TouchPointsEffect::paintScreen(int mask, const QRegion &region, ScreenPaintData &data)
0121 {
0122     effects->paintScreen(mask, region, data);
0123 
0124     paintScreenSetup(mask, region, data);
0125     for (auto it = m_points.constBegin(), end = m_points.constEnd(); it != end; ++it) {
0126         for (int i = 0; i < m_ringCount; ++i) {
0127             float alpha = computeAlpha(it->time, i);
0128             float size = computeRadius(it->time, it->press, i);
0129             if (size > 0 && alpha > 0) {
0130                 QColor color = it->color;
0131                 color.setAlphaF(alpha);
0132                 drawCircle(color, it->pos.x(), it->pos.y(), size);
0133             }
0134         }
0135     }
0136     paintScreenFinish(mask, region, data);
0137 }
0138 
0139 void TouchPointsEffect::postPaintScreen()
0140 {
0141     effects->postPaintScreen();
0142     repaint();
0143 }
0144 
0145 float TouchPointsEffect::computeRadius(int time, bool press, int ring)
0146 {
0147     float ringDistance = m_ringLife / (m_ringCount * 3);
0148     if (press) {
0149         return ((time - ringDistance * ring) / m_ringLife) * m_ringMaxSize;
0150     }
0151     return ((m_ringLife - time - ringDistance * ring) / m_ringLife) * m_ringMaxSize;
0152 }
0153 
0154 float TouchPointsEffect::computeAlpha(int time, int ring)
0155 {
0156     float ringDistance = m_ringLife / (m_ringCount * 3);
0157     return (m_ringLife - (float)time - ringDistance * (ring)) / m_ringLife;
0158 }
0159 
0160 void TouchPointsEffect::repaint()
0161 {
0162     if (!m_points.isEmpty()) {
0163         QRegion dirtyRegion;
0164         const int radius = m_ringMaxSize + m_lineWidth;
0165         for (auto it = m_points.constBegin(), end = m_points.constEnd(); it != end; ++it) {
0166             dirtyRegion |= QRect(it->pos.x() - radius, it->pos.y() - radius, 2 * radius, 2 * radius);
0167         }
0168         effects->addRepaint(dirtyRegion);
0169     }
0170 }
0171 
0172 bool TouchPointsEffect::isActive() const
0173 {
0174     return !m_points.isEmpty();
0175 }
0176 
0177 void TouchPointsEffect::drawCircle(const QColor &color, float cx, float cy, float r)
0178 {
0179     if (effects->isOpenGLCompositing()) {
0180         drawCircleGl(color, cx, cy, r);
0181     } else if (effects->compositingType() == QPainterCompositing) {
0182         drawCircleQPainter(color, cx, cy, r);
0183     }
0184 }
0185 
0186 void TouchPointsEffect::paintScreenSetup(int mask, QRegion region, ScreenPaintData &data)
0187 {
0188     if (effects->isOpenGLCompositing()) {
0189         paintScreenSetupGl(mask, region, data);
0190     }
0191 }
0192 
0193 void TouchPointsEffect::paintScreenFinish(int mask, QRegion region, ScreenPaintData &data)
0194 {
0195     if (effects->isOpenGLCompositing()) {
0196         paintScreenFinishGl(mask, region, data);
0197     }
0198 }
0199 
0200 void TouchPointsEffect::drawCircleGl(const QColor &color, float cx, float cy, float r)
0201 {
0202     static const int num_segments = 80;
0203     static const float theta = 2 * 3.1415926 / float(num_segments);
0204     static const float c = cosf(theta); // precalculate the sine and cosine
0205     static const float s = sinf(theta);
0206     const auto scale = effects->renderTargetScale();
0207     float t;
0208 
0209     float x = r; // we start at angle = 0
0210     float y = 0;
0211 
0212     GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
0213     vbo->reset();
0214     vbo->setUseColor(true);
0215     vbo->setColor(color);
0216     QVector<float> verts;
0217     verts.reserve(num_segments * 2);
0218 
0219     for (int ii = 0; ii < num_segments; ++ii) {
0220         verts << (x + cx) * scale << (y + cy) * scale; // output vertex
0221         // apply the rotation matrix
0222         t = x;
0223         x = c * x - s * y;
0224         y = s * t + c * y;
0225     }
0226     vbo->setData(verts.size() / 2, 2, verts.data(), nullptr);
0227     vbo->render(GL_LINE_LOOP);
0228 }
0229 
0230 void TouchPointsEffect::drawCircleQPainter(const QColor &color, float cx, float cy, float r)
0231 {
0232     QPainter *painter = effects->scenePainter();
0233     painter->save();
0234     painter->setPen(color);
0235     painter->drawArc(cx - r, cy - r, r * 2, r * 2, 0, 5760);
0236     painter->restore();
0237 }
0238 
0239 void TouchPointsEffect::paintScreenSetupGl(int, QRegion, ScreenPaintData &data)
0240 {
0241     GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor);
0242     shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix());
0243 
0244     glLineWidth(m_lineWidth);
0245     glEnable(GL_BLEND);
0246     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
0247 }
0248 
0249 void TouchPointsEffect::paintScreenFinishGl(int, QRegion, ScreenPaintData &)
0250 {
0251     glDisable(GL_BLEND);
0252 
0253     ShaderManager::instance()->popShader();
0254 }
0255 
0256 } // namespace