diff --git a/autotests/test_builtin_effectloader.cpp b/autotests/test_builtin_effectloader.cpp --- a/autotests/test_builtin_effectloader.cpp +++ b/autotests/test_builtin_effectloader.cpp @@ -113,6 +113,7 @@ QTest::newRow("SnapHelper") << QStringLiteral("snaphelper") << true; QTest::newRow("StartupFeedback") << QStringLiteral("startupfeedback") << true; QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << true; + QTest::newRow("Touchpoints") << QStringLiteral("touchpoints") << true; QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << true; QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << true; QTest::newRow("WobblyWindows") << QStringLiteral("wobblywindows") << true; @@ -168,6 +169,7 @@ << QStringLiteral("snaphelper") << QStringLiteral("startupfeedback") << QStringLiteral("thumbnailaside") + << QStringLiteral("touchpoints") << QStringLiteral("trackmouse") << QStringLiteral("windowgeometry") << QStringLiteral("wobblywindows") @@ -247,6 +249,7 @@ QTest::newRow("StartupFeedback") << QStringLiteral("startupfeedback") << false << xc << true; QTest::newRow("StartupFeedback-GL") << QStringLiteral("startupfeedback") << true << oc << true; QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << true << xc << true; + QTest::newRow("TouchPoints") << QStringLiteral("touchpoints") << true << xc << true; QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << true << xc << true; QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << true << xc << true; QTest::newRow("WobblyWindows") << QStringLiteral("wobblywindows") << false << xc << true; @@ -334,6 +337,7 @@ // Tries to load shader and makes our test abort // QTest::newRow("StartupFeedback-GL") << QStringLiteral("startupfeedback") << true << oc; QTest::newRow("ThumbnailAside") << QStringLiteral("thumbnailaside") << true << xc; + QTest::newRow("Touchpoints") << QStringLiteral("touchpoints") << true << xc; QTest::newRow("TrackMouse") << QStringLiteral("trackmouse") << true << xc; // TODO: Accesses EffectFrame and crashes // QTest::newRow("WindowGeometry") << QStringLiteral("windowgeometry") << true << xc; diff --git a/effects/CMakeLists.txt b/effects/CMakeLists.txt --- a/effects/CMakeLists.txt +++ b/effects/CMakeLists.txt @@ -90,6 +90,7 @@ resize/resize.cpp showfps/showfps.cpp thumbnailaside/thumbnailaside.cpp + touchpoints/touchpoints.cpp trackmouse/trackmouse.cpp windowgeometry/windowgeometry.cpp wobblywindows/wobblywindows.cpp diff --git a/effects/effect_builtins.cpp b/effects/effect_builtins.cpp --- a/effects/effect_builtins.cpp +++ b/effects/effect_builtins.cpp @@ -41,6 +41,7 @@ #include "slide/slide.h" #include "slideback/slideback.h" #include "thumbnailaside/thumbnailaside.h" +#include "touchpoints/touchpoints.h" #include "windowgeometry/windowgeometry.h" #include "zoom/zoom.h" #include "logout/logout.h" @@ -607,6 +608,21 @@ #endif EFFECT_FALLBACK }, { + QStringLiteral("touchpoints"), + i18ndc("kwin_effects", "Name of a KWin Effect", "Touch Points"), + i18ndc("kwin_effects", "Comment describing the KWin Effect", "Visualize touch points"), + QStringLiteral("Appearance"), + QString(), + QUrl(), + false, + false, +#ifdef EFFECT_BUILTINS + &createHelper, + nullptr, + nullptr +#endif +EFFECT_FALLBACK + }, { QStringLiteral("trackmouse"), i18ndc("kwin_effects", "Name of a KWin Effect", "Track Mouse"), i18ndc("kwin_effects", "Comment describing the KWin Effect", "Display a mouse cursor locating effect when activated"), diff --git a/effects/touchpoints/touchpoints.h b/effects/touchpoints/touchpoints.h new file mode 100644 --- /dev/null +++ b/effects/touchpoints/touchpoints.h @@ -0,0 +1,99 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2012 Filip Wieladek +Copyright (C) 2016 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#ifndef KWIN_TOUCHPOINTS_H +#define KWIN_TOUCHPOINTS_H + +#include + +namespace KWin +{ + +class TouchPointsEffect + : public Effect +{ + Q_OBJECT + Q_PROPERTY(qreal lineWidth READ lineWidth) + Q_PROPERTY(int ringLife READ ringLife) + Q_PROPERTY(int ringSize READ ringSize) + Q_PROPERTY(int ringCount READ ringCount) +public: + TouchPointsEffect(); + ~TouchPointsEffect(); + void prePaintScreen(ScreenPrePaintData& data, int time) override; + void paintScreen(int mask, QRegion region, ScreenPaintData& data) override; + void postPaintScreen() override; + bool isActive() const override; + bool touchDown(quint32 id, const QPointF &pos, quint32 time) override; + bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override; + bool touchUp(quint32 id, quint32 time) override; + + // for properties + qreal lineWidth() const { + return m_lineWidth; + } + int ringLife() const { + return m_ringLife; + } + int ringSize() const { + return m_ringMaxSize; + } + int ringCount() const { + return m_ringCount; + } + +private: + inline void drawCircle(const QColor& color, float cx, float cy, float r); + inline void paintScreenSetup(int mask, QRegion region, ScreenPaintData& data); + inline void paintScreenFinish(int mask, QRegion region, ScreenPaintData& data); + + void repaint(); + + float computeAlpha(int time, int ring); + float computeRadius(int time, bool press, int ring); + void drawCircleGl(const QColor& color, float cx, float cy, float r); + void drawCircleXr(const QColor& color, float cx, float cy, float r); + void drawCircleQPainter(const QColor& color, float cx, float cy, float r); + void paintScreenSetupGl(int mask, QRegion region, ScreenPaintData& data); + void paintScreenFinishGl(int mask, QRegion region, ScreenPaintData& data); + + Qt::GlobalColor colorForId(quint32 id); + + int m_ringCount = 2; + float m_lineWidth = 1.0; + int m_ringLife = 300; + float m_ringMaxSize = 20.0; + + struct TouchPoint { + QPointF pos; + int time = 0; + bool press; + QColor color; + }; + QVector m_points; + QHash m_latestPositions; + QHash m_colors; + +}; + +} // namespace + +#endif diff --git a/effects/touchpoints/touchpoints.cpp b/effects/touchpoints/touchpoints.cpp new file mode 100644 --- /dev/null +++ b/effects/touchpoints/touchpoints.cpp @@ -0,0 +1,322 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2012 Filip Wieladek +Copyright (C) 2016 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#include "touchpoints.h" + +#include +#include + +#ifdef KWIN_HAVE_XRENDER_COMPOSITING +#include +#include +#include +#endif + +#include +#include + +#include + +#include + +namespace KWin +{ + +TouchPointsEffect::TouchPointsEffect() + : Effect() +{ +} + +TouchPointsEffect::~TouchPointsEffect() = default; + +static const Qt::GlobalColor s_colors[] = { + Qt::blue, + Qt::red, + Qt::green, + Qt::cyan, + Qt::magenta, + Qt::yellow, + Qt::gray, + Qt::darkBlue, + Qt::darkRed, + Qt::darkGreen +}; + +Qt::GlobalColor TouchPointsEffect::colorForId(quint32 id) +{ + auto it = m_colors.constFind(id); + if (it != m_colors.constEnd()) { + return it.value(); + } + static int s_colorIndex = -1; + s_colorIndex = (s_colorIndex + 1) % 10; + m_colors.insert(id, s_colors[s_colorIndex]); + return s_colors[s_colorIndex]; +} + +bool TouchPointsEffect::touchDown(quint32 id, const QPointF &pos, quint32 time) +{ + TouchPoint point; + point.pos = pos; + point.press = true; + point.color = colorForId(id); + m_points << point; + m_latestPositions.insert(id, pos); + repaint(); + return false; +} + +bool TouchPointsEffect::touchMotion(quint32 id, const QPointF &pos, quint32 time) +{ + TouchPoint point; + point.pos = pos; + point.press = true; + point.color = colorForId(id); + m_points << point; + m_latestPositions.insert(id, pos); + repaint(); + return false; +} + +bool TouchPointsEffect::touchUp(quint32 id, quint32 time) +{ + auto it = m_latestPositions.constFind(id); + if (it != m_latestPositions.constEnd()) { + TouchPoint point; + point.pos = it.value(); + point.press = false; + point.color = colorForId(id); + m_points << point; + } + return false; +} + +void TouchPointsEffect::prePaintScreen(ScreenPrePaintData& data, int time) +{ + auto it = m_points.begin(); + while (it != m_points.end()) { + it->time += time; + if (it->time > m_ringLife) { + it = m_points.erase(it); + } else { + it++; + } + } + + effects->prePaintScreen(data, time); +} + +void TouchPointsEffect::paintScreen(int mask, QRegion region, ScreenPaintData& data) +{ + effects->paintScreen(mask, region, data); + + paintScreenSetup(mask, region, data); + for (auto it = m_points.constBegin(), end = m_points.constEnd(); it != end; ++it) { + for (int i = 0; i < m_ringCount; ++i) { + float alpha = computeAlpha(it->time, i); + float size = computeRadius(it->time, it->press, i); + if (size > 0 && alpha > 0) { + QColor color = it->color; + color.setAlphaF(alpha); + drawCircle(color, it->pos.x(), it->pos.y(), size); + } + } + } + paintScreenFinish(mask, region, data); +} + +void TouchPointsEffect::postPaintScreen() +{ + effects->postPaintScreen(); + repaint(); +} + +float TouchPointsEffect::computeRadius(int time, bool press, int ring) +{ + float ringDistance = m_ringLife / (m_ringCount * 3); + if (press) { + return ((time - ringDistance * ring) / m_ringLife) * m_ringMaxSize; + } + return ((m_ringLife - time - ringDistance * ring) / m_ringLife) * m_ringMaxSize; +} + +float TouchPointsEffect::computeAlpha(int time, int ring) +{ + float ringDistance = m_ringLife / (m_ringCount * 3); + return (m_ringLife - (float)time - ringDistance * (ring)) / m_ringLife; +} + +void TouchPointsEffect::repaint() +{ + if (!m_points.isEmpty()) { + QRegion dirtyRegion; + const int radius = m_ringMaxSize + m_lineWidth; + for (auto it = m_points.constBegin(), end = m_points.constEnd(); it != end; ++it) { + dirtyRegion |= QRect(it->pos.x() - radius, it->pos.y() - radius, 2*radius, 2*radius); + } + effects->addRepaint(dirtyRegion); + } +} + +bool TouchPointsEffect::isActive() const +{ + return !m_points.isEmpty(); +} + +void TouchPointsEffect::drawCircle(const QColor& color, float cx, float cy, float r) +{ + if (effects->isOpenGLCompositing()) + drawCircleGl(color, cx, cy, r); + if (effects->compositingType() == XRenderCompositing) + drawCircleXr(color, cx, cy, r); + if (effects->compositingType() == QPainterCompositing) + drawCircleQPainter(color, cx, cy, r); +} + +void TouchPointsEffect::paintScreenSetup(int mask, QRegion region, ScreenPaintData& data) +{ + if (effects->isOpenGLCompositing()) + paintScreenSetupGl(mask, region, data); +} + +void TouchPointsEffect::paintScreenFinish(int mask, QRegion region, ScreenPaintData& data) +{ + if (effects->isOpenGLCompositing()) + paintScreenFinishGl(mask, region, data); +} + +void TouchPointsEffect::drawCircleGl(const QColor& color, float cx, float cy, float r) +{ + static const int num_segments = 80; + static const float theta = 2 * 3.1415926 / float(num_segments); + static const float c = cosf(theta); //precalculate the sine and cosine + static const float s = sinf(theta); + float t; + + float x = r;//we start at angle = 0 + float y = 0; + + GLVertexBuffer* vbo = GLVertexBuffer::streamingBuffer(); + vbo->reset(); + vbo->setUseColor(true); + vbo->setColor(color); + QVector verts; + verts.reserve(num_segments * 2); + + for (int ii = 0; ii < num_segments; ++ii) { + verts << x + cx << y + cy;//output vertex + //apply the rotation matrix + t = x; + x = c * x - s * y; + y = s * t + c * y; + } + vbo->setData(verts.size() / 2, 2, verts.data(), NULL); + vbo->render(GL_LINE_LOOP); +} + +void TouchPointsEffect::drawCircleXr(const QColor& color, float cx, float cy, float r) +{ +#ifdef KWIN_HAVE_XRENDER_COMPOSITING + if (r <= m_lineWidth) + return; + + int num_segments = r+8; + float theta = 2.0 * 3.1415926 / num_segments; + float cos = cosf(theta); //precalculate the sine and cosine + float sin = sinf(theta); + float x[2] = {r, r-m_lineWidth}; + float y[2] = {0, 0}; + +#define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) + QVector strip; + strip.reserve(2*num_segments+2); + + xcb_render_pointfix_t point; + point.x = DOUBLE_TO_FIXED(x[1]+cx); + point.y = DOUBLE_TO_FIXED(y[1]+cy); + strip << point; + + for (int i = 0; i < num_segments; ++i) { + //apply the rotation matrix + const float h[2] = {x[0], x[1]}; + x[0] = cos * x[0] - sin * y[0]; + x[1] = cos * x[1] - sin * y[1]; + y[0] = sin * h[0] + cos * y[0]; + y[1] = sin * h[1] + cos * y[1]; + + point.x = DOUBLE_TO_FIXED(x[0]+cx); + point.y = DOUBLE_TO_FIXED(y[0]+cy); + strip << point; + + point.x = DOUBLE_TO_FIXED(x[1]+cx); + point.y = DOUBLE_TO_FIXED(y[1]+cy); + strip << point; + } + + const float h = x[0]; + x[0] = cos * x[0] - sin * y[0]; + y[0] = sin * h + cos * y[0]; + + point.x = DOUBLE_TO_FIXED(x[0]+cx); + point.y = DOUBLE_TO_FIXED(y[0]+cy); + strip << point; + + XRenderPicture fill = xRenderFill(color); + xcb_render_tri_strip(xcbConnection(), XCB_RENDER_PICT_OP_OVER, + fill, effects->xrenderBufferPicture(), 0, + 0, 0, strip.count(), strip.constData()); +#undef DOUBLE_TO_FIXED +#else + Q_UNUSED(color) + Q_UNUSED(cx) + Q_UNUSED(cy) + Q_UNUSED(r) +#endif +} + +void TouchPointsEffect::drawCircleQPainter(const QColor &color, float cx, float cy, float r) +{ + QPainter *painter = effects->scenePainter(); + painter->save(); + painter->setPen(color); + painter->drawArc(cx - r, cy - r, r * 2, r * 2, 0, 5760); + painter->restore(); +} + +void TouchPointsEffect::paintScreenSetupGl(int, QRegion, ScreenPaintData &data) +{ + GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::UniformColor); + shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix()); + + glLineWidth(m_lineWidth); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +void TouchPointsEffect::paintScreenFinishGl(int, QRegion, ScreenPaintData&) +{ + glDisable(GL_BLEND); + + ShaderManager::instance()->popShader(); +} + +} // namespace +