touchclick: add touch trail effect

Signed-off-by: Hongfei Shang <shanghongfei@kylinos.cn>
This commit is contained in:
Hongfei Shang 2022-06-05 22:41:23 +08:00
parent 11bf71e00a
commit 8e3c5e20f4
7 changed files with 515 additions and 0 deletions

View File

@ -161,6 +161,7 @@ add_subdirectory(snaphelper)
add_subdirectory(startupfeedback)
add_subdirectory(trackmouse)
add_subdirectory(touchclick)
add_subdirectory(touchtrail)
add_subdirectory(wobblywindows)
###############################################################################

View File

@ -0,0 +1,14 @@
#######################################
# Effect
set(touchtrail_SOURCES
touchtrail.cpp
main.cpp
)
kwin4_add_effect_module(kwin4_effect_touchtrail ${touchtrail_SOURCES})
# Data files
install(FILES data/trailing.png DESTINATION ${KDE_INSTALL_DATADIR}/${KWIN_NAME})

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,18 @@
/*
SPDX-FileCopyrightText: 2022 Hongfei Shang <shanghongfei@kylinos.cn>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "touchtrail.h"
namespace KWin
{
KWIN_EFFECT_FACTORY_SUPPORTED(TouchTrailEffect,
"metadata.json.stripped",
return TouchTrailEffect::supported();)
} // namespace KWin
#include "main.moc"

View File

@ -0,0 +1,61 @@
{
"KPlugin": {
"Category": "Appearance",
"Description": "Visualize touch points",
"Description[ar]": "تصور نقاط اللمس",
"Description[az]": "Ekranda toxunan nöqtələri canlandırır",
"Description[ca@valencia]": "Visualitza els punts de contacte",
"Description[ca]": "Visualitza els punts de contacte",
"Description[cs]": "Vizualizovat dotekové body",
"Description[en_GB]": "Visualise touch points",
"Description[es]": "Visualizar los puntos de contacto",
"Description[fi]": "Korosta kosketuspisteet",
"Description[fr]": "Afficher les points tactiles",
"Description[hu]": "Vizualizálja az érintési pontokat",
"Description[ia]": "Visualisa punctos de contacto",
"Description[id]": "Memvisualkan titik sentuh",
"Description[it]": "Visualizza i punti di tocco",
"Description[ko]": "터치 지점 표시",
"Description[nl]": "Aanraakpunten visualiseren",
"Description[nn]": "Visualiser kontaktpunkt",
"Description[pl]": "Uwidacznia punkty dotyku",
"Description[pt_BR]": "Visualizar pontos de toque",
"Description[ru]": "Визуализация событий касания экрана",
"Description[sk]": "Vizualizovať dotykové body",
"Description[sl]": "Vizualiziraj točke dotika",
"Description[sv]": "Åskådliggör beröringspunkter",
"Description[tr]": "Dokunma noktalarını görselleştir",
"Description[uk]": "Візуалізувати точки дотику",
"Description[x-test]": "xxVisualize touch pointsxx",
"Description[zh_CN]": "高亮显示在触控屏幕上的触摸点",
"EnabledByDefault": true,
"Id": "touchtrail",
"License": "GPL",
"Name": "Touch Points",
"Name[ar]": "نقاط اللمس",
"Name[az]": "Toxunma nöqtələri",
"Name[ca@valencia]": "Punts de contacte",
"Name[ca]": "Punts de contacte",
"Name[cs]": "Dotekové body",
"Name[en_GB]": "Touch Points",
"Name[es]": "Puntos de contacto",
"Name[fi]": "Kosketuspisteet",
"Name[fr]": "Points tactiles",
"Name[hu]": "Érintési pontok",
"Name[ia]": "Punctos de contacto",
"Name[it]": "Punti di tocco",
"Name[ko]": "터치 지점",
"Name[nl]": "Aanraakpunten",
"Name[nn]": "Kontaktpunkt",
"Name[pl]": "Punkty dotyku",
"Name[pt_BR]": "Pontos de toque",
"Name[ru]": "Точки прикосновения",
"Name[sk]": "Dotykové body",
"Name[sl]": "Točke dotika",
"Name[sv]": "Beröringspunkter",
"Name[tr]": "Dokunma Noktaları",
"Name[uk]": "Точки дотику",
"Name[x-test]": "xxTouch Pointsxx",
"Name[zh_CN]": "触摸点高亮"
}
}

View File

@ -0,0 +1,340 @@
#include "touchtrail.h"
#include <kwinglplatform.h>
#include <QTime>
#include <QByteArray>
#include <cmath>
namespace KWin{
TouchTrailEffect::TouchTrailEffect()
: Effect()
, m_texture(nullptr)
{
loadTexture();
loadShader();
}
TouchTrailEffect::~TouchTrailEffect()
{
if (m_texture)
delete m_texture;
m_texture = nullptr;
}
bool TouchTrailEffect::supported()
{
return effects->isOpenGLCompositing();
}
void TouchTrailEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
{
int time = 0;
if (m_lastPresentTime.count()) {
time = (presentTime - m_lastPresentTime).count();
}
for (int i = 0; i < m_touchPoints.size(); ++i) {
QVector<TouchPoint>::iterator it = m_touchPoints[i].begin();
while (it != m_touchPoints[i].end()) {
it->life += time;
++it;
}
while (m_touchPoints[i].size() && m_touchPoints[i].first().life > MAX_LIFE) {
if (m_touchPoints[i].size() == 1) {
int detail = m_touchPoints[i].first().detail;
if (!m_idIsUse[i]) {
m_detail2ID.erase(detail);
}
}
m_touchPoints[i].removeFirst();
}
}
calTriPoints();
if (0 == m_detail2ID.size()) {
m_lastPresentTime = std::chrono::milliseconds::zero();
} else {
m_lastPresentTime = presentTime;
}
effects->prePaintScreen(data, presentTime);
}
void TouchTrailEffect::paintScreen(int mask, const QRegion &region, ScreenPaintData &data)
{
// printf("adasdasds\n");
effects->paintScreen(mask, region, data);
if (effects->isOpenGLCompositing()) {
paintScreenGL(data);
}
}
void TouchTrailEffect::paintScreenGL(ScreenPaintData &data)
{
GLShader *shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture);
shader->setUniform(GLShader::ModelViewProjectionMatrix, data.projectionMatrix());
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
for (int i = 0; i < m_triPoints.size(); ++i)
{
QVector<float> vert, texCoord;
const int &n = m_triPoints[i].size();
for (int j = 0; j < n; ++j) {
vert << m_triPoints[i][j].x() << m_triPoints[i][j].y();
texCoord << ((j & 1) ? 1.0f : 0.0f) << (j >> 1) * 1.0 / (n >> 1);
}
vbo->reset();
m_texture->bind();
vbo->setData(vert.size() / 2, 2, vert.data(), texCoord.data());
vbo->render(GL_TRIANGLE_STRIP);
m_texture->unbind();
}
glDisable(GL_BLEND);
ShaderManager::instance()->popShader();
}
void TouchTrailEffect::postPaintScreen()
{
effects->postPaintScreen();
repaint();
}
bool TouchTrailEffect::isActive() const
{
return m_detail2ID.size();
}
bool TouchTrailEffect::touchDown(qint32 id, const QPointF &pos, quint32 time)
{
// printf("当前手指ID=%d\n", id);
insertTouchPoint(id, pos);
repaint();
return false;
}
bool TouchTrailEffect::touchMotion(qint32 id, const QPointF &pos, quint32 time)
{
updateTouchPoint(id, pos);
repaint();
return false;
}
bool TouchTrailEffect::touchUp(qint32 id, quint32 time)
{
// printf("离开的手指ID=%d\n", id);
removeTouchPoint(id);
// 各触摸点开始快速衰减
// TODO 加上滑动距离作为衰减因子
for (int i = 0; i < m_touchPoints.size(); ++i) {
int sub;
for (int j = 0; j < m_touchPoints[i].size(); j++) {
if (!j) {
sub = MAX_LIFE - m_touchPoints[i][j].life;
m_touchPoints[i][j].life = MAX_LIFE;
} else {
m_touchPoints[i][j].life += sub;
}
}
}
return false;
}
void TouchTrailEffect::insertTouchPoint(qint32 detail, QPointF pos)
{
int id = -1;
do {
++id;
if (id == m_touchPoints.size()) {
m_touchPoints.append(QVector<TouchPoint>());
m_triPoints.append(QVector<QPointF>());
m_idIsUse.append(false);
}
} while (m_idIsUse[id] || m_touchPoints[id].size());
m_detail2ID[detail] = id;
m_idIsUse[id] = true;
m_touchPoints[id].append({detail, pos, 0});
// printf("当前分配的ID为 %d\n", id);
}
void TouchTrailEffect::updateTouchPoint(qint32 detail, QPointF pos)
{
// 开关混成有可能导致事件不成对
if (m_detail2ID.find(detail) == m_detail2ID.end()) {
return;
}
int id = m_detail2ID[detail];
m_touchPoints[id].append({detail, pos, 0});
}
void TouchTrailEffect::removeTouchPoint(qint32 detail)
{
// 开关混成有可能导致事件不成对
if (m_detail2ID.find(detail) == m_detail2ID.end()) {
return;
}
int id = m_detail2ID[detail];
m_idIsUse[id] = false;
if (!m_touchPoints[id].size()) {
m_detail2ID.erase(detail);
}
}
void TouchTrailEffect::loadTexture()
{
#if defined(QT_NO_DEBUG)
QString file_path = {QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral(DEFAULT_TEXTURE_IMAGE))};
#else
QString file_path = QCoreApplication::applicationDirPath() + QStringLiteral("/../../src/effects/touchtrail/data/") + QStringLiteral(DEFAULT_TEXTURE_IMAGE);
#endif
QImage img(file_path);
if (img.isNull()) {
qWarning() << "load image [" << file_path << "] failed";
return;
}
//! TODO 使用KWin中的纹理融合算法
//! 没有找到kwin里opengl的纹理融合
//! 先手写个简单的纹理和颜色的融合,简单支持
qreal Rd = 139.0 / 255.0;
qreal Gd = 134.0 / 255.0;
qreal Bd = 130.0 / 255.0;
qreal Ad = 30.0 / 255.0;
for (int i = 0; i < img.width(); ++i) {
for (int j = 0; j < img.height(); ++j) {
QRgb p = img.pixel(i, j);
qreal Rs = qRed(p) / 255.0;
qreal Gs = qGreen(p) / 255.0;
qreal Bs = qBlue(p) / 255.0;
qreal As = qAlpha(p) / 255.0;
// printf("%d\n", As);
//! 纹理和颜色融合
// img.setPixel(i, j, qRgba((Rs * As + Rd * (1 - As)) * 255,
// (Gs * As + Gd * (1 - As)) * 255,
// (Bs * As + Bd * (1 - As)) * 255,
// (fmin(As, Ad)) * 255));
//! 纹理仅控制形状
img.setPixel(i, j, qRgba(Rd * 255,
Gd * 255,
Bd * 255,
(fmin(As, Ad)) * 255));
}
}
if (!m_texture)
m_texture = new GLTexture(img);
}
void TouchTrailEffect::loadShader()
{
// m_shader.reset(ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("coverswitch-reflection.glsl")));
}
void TouchTrailEffect::calTriPoints()
{
for (int i = 0; i < m_touchPoints.size(); ++i)
{
m_triPoints[i].clear();
if (m_touchPoints[i].size() < 3) continue;
QPointF last(m_touchPoints[i].first().pos);
QVector<TouchPoint>::iterator it = m_touchPoints[i].begin();
for (++it; it != m_touchPoints[i].end(); ++it) {
QPointF &cur = it->pos;
qreal d = dist(cur.x(), cur.y(), last.x(), last.y());
if (d < MIN_POINT_SEG) continue;
if (cur.x() == last.x() || cur.y() == last.y()) {
continue;
}
// 方向为 last->cur 的向量
QPointF vec_l2c(cur.x() - last.x(), cur.y() - last.y());
// cur last线段上的中点
QPointF mid((cur.x() + last.x()) / 2, (cur.y() + last.y()) / 2);
d /= 2;
// 向量的斜率k
qreal k = (cur.y() - last.y()) / (cur.x() - last.x());
qreal vlk = 1.0 / k;
qreal alpha = atan(fabs(vlk));
qreal dx = DEFAULT_STROKE_WIDTH * cos(alpha);
qreal dy = DEFAULT_STROKE_WIDTH * sin(alpha);
QPointF p1, p2;
if (k > 0 && vec_l2c.x() > 0) {
p1.setX(mid.x() - dx); p1.setY(mid.y() + dy);
p2.setX(mid.x() + dx); p2.setY(mid.y() - dy);
} else if (k > 0 && vec_l2c.x() < 0) {
p1.setX(mid.x() + dx); p1.setY(mid.y() - dy);
p2.setX(mid.x() - dx); p2.setY(mid.y() + dy);;
} else if (k < 0 && vec_l2c.x() > 0) {
p1.setX(mid.x() + dx); p1.setY(mid.y() + dy);
p2.setX(mid.x() - dx); p2.setY(mid.y() - dy);
} else {
p1.setX(mid.x() - dx); p1.setY(mid.y() - dy);
p2.setX(mid.x() + dx); p2.setY(mid.y() + dy);
}
m_triPoints[i].append(p1);
m_triPoints[i].append(p2);
last = cur;
if (it + 1 == m_touchPoints[i].end()) {
mid = it->pos;
if (k > 0 && vec_l2c.x() > 0) {
p1.setX(mid.x() - dx); p1.setY(mid.y() + dy);
p2.setX(mid.x() + dx); p2.setY(mid.y() - dy);
} else if (k > 0 && vec_l2c.x() < 0) {
p1.setX(mid.x() + dx); p1.setY(mid.y() - dy);
p2.setX(mid.x() - dx); p2.setY(mid.y() + dy);;
} else if (k < 0 && vec_l2c.x() > 0) {
p1.setX(mid.x() + dx); p1.setY(mid.y() + dy);
p2.setX(mid.x() - dx); p2.setY(mid.y() - dy);
} else {
p1.setX(mid.x() - dx); p1.setY(mid.y() - dy);
p2.setX(mid.x() + dx); p2.setY(mid.y() + dy);
}
m_triPoints[i].append(p1);
m_triPoints[i].append(p2);
}
}
}
}
void TouchTrailEffect::repaint()
{
// effects->addRepaintFull();
if (m_triPoints.size()) {
QRect dirtyRect;
QPoint topLeft(INT32_MAX, INT32_MAX), bottomRight(INT32_MIN, INT32_MIN);
for (int i = 0; i < m_triPoints.size(); ++i) {
for (int j = 0; j < m_triPoints[i].size(); ++j) {
QPointF &p = m_triPoints[i][j];
topLeft.setX(fmin(topLeft.x(), p.x()));
topLeft.setY(fmin(topLeft.y(), p.y()));
bottomRight.setX(fmax(bottomRight.x(), p.x()));
bottomRight.setY(fmax(bottomRight.y(), p.y()));
}
}
dirtyRect.setTopLeft(topLeft);
dirtyRect.setBottomRight(bottomRight);
m_dirtyRect = dirtyRect;
effects->addRepaint(m_dirtyRect);
} else {
effects->addRepaintFull();
}
return;
}
}

View File

@ -0,0 +1,81 @@
#ifndef KWIN_TOUCHMOTIONSTREAK_H
#define KWIN_TOUCHMOTIONSTREAK_H
#include <kwineffects.h>
#include <kwinglutils.h>
#include <QList>
#include <unordered_map>
namespace KWin {
#define MAX_FINGER_COUNT 10 // 触摸屏支持最大触摸点的个数
#define MIN_POINT_SEG 2/*px*/ // 触摸点之间允许的最小欧氏距离
#define DEFAULT_STROKE_WIDTH 3/*px*/ // 条带的宽度
#define MAX_POINT_NUMBER 8 // 保存的触摸点的最大数量
#define DEFAULT_TEXTURE_IMAGE "trailing.png" // 默认纹理
#define MAX_LIFE 200 // 每个触摸点存活的最长时间
#define dist(x1,y1,x2,y2) sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1))
/*!
* \brief UKUI Touch Motion Streak Effect
* \author Yunpeng Zhu.
*/
class TouchTrailEffect
: public Effect
{
Q_OBJECT
typedef struct _touch_point{
int detail;
QPointF pos;
int life;
}TouchPoint;
public:
TouchTrailEffect();
virtual ~TouchTrailEffect();
void prePaintScreen(ScreenPrePaintData& data, std::chrono::milliseconds presentTime) override;
void paintScreen(int mask, const QRegion &region, ScreenPaintData& data) override;
void postPaintScreen() override;
bool isActive() const override;
bool touchDown(qint32 id, const QPointF &pos, quint32 time) override;
bool touchMotion(qint32 id, const QPointF &pos, quint32 time) override;
bool touchUp(qint32 id, quint32 time) override;
static bool supported();
private:
void paintScreenGL(ScreenPaintData &data);
void insertTouchPoint(qint32 detail, QPointF pos);
void updateTouchPoint(qint32 detail, QPointF pos);
void removeTouchPoint(qint32 detail);
void loadTexture();
void loadShader();
/*!
* \brief
*/
void calTriPoints();
void repaint();
//! 保存触摸点坐标
QVector<QVector<TouchPoint>> m_touchPoints;
//! 保存所需要绘制的三角形条带的坐标
QVector<QVector<QPointF>> m_triPoints;
//! 标记id是否被使用
QVector<bool> m_idIsUse;
//! 将detail映射到0~MAX_FINGER_COUNT-1
std::unordered_map<int,int> m_detail2ID;
//! 绘制
GLTexture *m_texture;
//! 记录需要重绘的区域
QRect m_dirtyRect;
std::chrono::milliseconds m_lastPresentTime = std::chrono::milliseconds::zero();
};
}
#endif // KWIN_TOUCHMOTIONSTREAK_H