KWin
Loading...
Searching...
No Matches
mouseclick.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2012 Filip Wieladek <wattos@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "mouseclick.h"
11// KConfigSkeleton
12#include "mouseclickconfig.h"
13
14#include "core/rendertarget.h"
15#include "core/renderviewport.h"
17
18#include <QAction>
19
20#include <KConfigGroup>
21#include <KGlobalAccel>
22
23#include <QPainter>
24#include <QTabletEvent>
25
26#include <cmath>
27
28namespace KWin
29{
30
32{
33 MouseClickConfig::instance(effects->config());
34 m_enabled = false;
35
36 QAction *a = new QAction(this);
37 a->setObjectName(QStringLiteral("ToggleMouseClick"));
38 a->setText(i18n("Toggle Mouse Click Effect"));
39 KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_Asterisk));
40 KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << (Qt::META | Qt::Key_Asterisk));
41 connect(a, &QAction::triggered, this, &MouseClickEffect::toggleEnabled);
42
44
45 m_buttons[0] = std::make_unique<MouseButton>(i18nc("Left mouse button", "Left"), Qt::LeftButton);
46 m_buttons[1] = std::make_unique<MouseButton>(i18nc("Middle mouse button", "Middle"), Qt::MiddleButton);
47 m_buttons[2] = std::make_unique<MouseButton>(i18nc("Right mouse button", "Right"), Qt::RightButton);
48}
49
51{
52 if (m_enabled) {
54 }
55}
56
57void MouseClickEffect::reconfigure(ReconfigureFlags)
58{
59 MouseClickConfig::self()->read();
60 m_colors[0] = MouseClickConfig::color1();
61 m_colors[1] = MouseClickConfig::color2();
62 m_colors[2] = MouseClickConfig::color3();
63 m_lineWidth = MouseClickConfig::lineWidth();
64 m_ringLife = MouseClickConfig::ringLife();
65 m_ringMaxSize = MouseClickConfig::ringSize();
66 m_ringCount = MouseClickConfig::ringCount();
67 m_showText = MouseClickConfig::showText();
68 m_font = MouseClickConfig::font();
69}
70
71void MouseClickEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
72{
73 const int time = m_lastPresentTime.count() ? (presentTime - m_lastPresentTime).count() : 0;
74
75 for (auto &click : m_clicks) {
76 click->m_time += time;
77 }
78
79 for (int i = 0; i < BUTTON_COUNT; ++i) {
80 if (m_buttons[i]->m_isPressed) {
81 m_buttons[i]->m_time += time;
82 }
83 }
84
85 while (m_clicks.size() > 0) {
86 if (m_clicks.front()->m_time <= m_ringLife) {
87 break;
88 }
89 m_clicks.pop_front();
90 }
91
92 if (isActive()) {
93 m_lastPresentTime = presentTime;
94 } else {
95 m_lastPresentTime = std::chrono::milliseconds::zero();
96 }
97
98 effects->prePaintScreen(data, presentTime);
99}
100
101void MouseClickEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
102{
103 effects->paintScreen(renderTarget, viewport, mask, region, screen);
104
106 paintScreenSetupGl(renderTarget, viewport.projectionMatrix());
107 }
108 for (const auto &click : m_clicks) {
109 for (int i = 0; i < m_ringCount; ++i) {
110 float alpha = computeAlpha(click.get(), i);
111 float size = computeRadius(click.get(), i);
112 if (size > 0 && alpha > 0) {
113 QColor color = m_colors[click->m_button];
114 color.setAlphaF(alpha);
115 drawCircle(viewport, color, click->m_pos.x(), click->m_pos.y(), size);
116 }
117 }
118
119 if (m_showText && click->m_frame) {
120 float frameAlpha = (click->m_time * 2.0f - m_ringLife) / m_ringLife;
121 frameAlpha = frameAlpha < 0 ? 1 : -(frameAlpha * frameAlpha) + 1;
122 click->m_frame->render(renderTarget, viewport, infiniteRegion(), frameAlpha, frameAlpha);
123 }
124 }
125 for (const auto &tool : std::as_const(m_tabletTools)) {
126 const int step = m_ringMaxSize * (1. - tool.m_pressure);
127 for (qreal size = m_ringMaxSize; size > 0; size -= step) {
128 drawCircle(viewport, tool.m_color, tool.m_globalPosition.x(), tool.m_globalPosition.y(), size);
129 }
130 }
132 paintScreenFinishGl();
133 }
134}
135
137{
139 repaint();
140}
141
142float MouseClickEffect::computeRadius(const MouseEvent *click, int ring)
143{
144 float ringDistance = m_ringLife / (m_ringCount * 3);
145 if (click->m_press) {
146 return ((click->m_time - ringDistance * ring) / m_ringLife) * m_ringMaxSize;
147 }
148 return ((m_ringLife - click->m_time - ringDistance * ring) / m_ringLife) * m_ringMaxSize;
149}
150
151float MouseClickEffect::computeAlpha(const MouseEvent *click, int ring)
152{
153 float ringDistance = m_ringLife / (m_ringCount * 3);
154 return (m_ringLife - (float)click->m_time - ringDistance * (ring)) / m_ringLife;
155}
156
157void MouseClickEffect::slotMouseChanged(const QPointF &pos, const QPointF &,
158 Qt::MouseButtons buttons, Qt::MouseButtons oldButtons,
159 Qt::KeyboardModifiers, Qt::KeyboardModifiers)
160{
161 if (buttons == oldButtons) {
162 return;
163 }
164
165 std::unique_ptr<MouseEvent> m;
166 int i = BUTTON_COUNT;
167 while (--i >= 0) {
168 MouseButton *b = m_buttons[i].get();
169 if (isPressed(b->m_button, buttons, oldButtons)) {
170 m = std::make_unique<MouseEvent>(i, pos.toPoint(), 0, createEffectFrame(pos.toPoint(), b->m_labelDown), true);
171 break;
172 } else if (isReleased(b->m_button, buttons, oldButtons) && (!b->m_isPressed || b->m_time > m_ringLife)) {
173 // we might miss a press, thus also check !b->m_isPressed, bug #314762
174 m = std::make_unique<MouseEvent>(i, pos.toPoint(), 0, createEffectFrame(pos.toPoint(), b->m_labelUp), false);
175 break;
176 }
177 b->setPressed(b->m_button & buttons);
178 }
179
180 if (m) {
181 m_clicks.push_back(std::move(m));
182 }
183 repaint();
184}
185
186std::unique_ptr<EffectFrame> MouseClickEffect::createEffectFrame(const QPoint &pos, const QString &text)
187{
188 if (!m_showText) {
189 return nullptr;
190 }
191 QPoint point(pos.x() + m_ringMaxSize, pos.y());
192 std::unique_ptr<EffectFrame> frame = std::make_unique<EffectFrame>(EffectFrameStyled, false, point, Qt::AlignLeft);
193 frame->setFont(m_font);
194 frame->setText(text);
195 return frame;
196}
197
198void MouseClickEffect::repaint()
199{
200 if (m_clicks.size() > 0) {
201 QRegion dirtyRegion;
202 const int radius = m_ringMaxSize + m_lineWidth;
203 for (auto &click : m_clicks) {
204 dirtyRegion += QRect(click->m_pos.x() - radius, click->m_pos.y() - radius, 2 * radius, 2 * radius);
205 if (click->m_frame) {
206 dirtyRegion += click->m_frame->geometry();
207 }
208 }
209 effects->addRepaint(dirtyRegion);
210 }
211 if (!m_tabletTools.isEmpty()) {
212 QRegion dirtyRegion;
213 const int radius = m_ringMaxSize + m_lineWidth;
214 for (const auto &event : std::as_const(m_tabletTools)) {
215 dirtyRegion += QRect(event.m_globalPosition.x() - radius, event.m_globalPosition.y() - radius, 2 * radius, 2 * radius);
216 }
217 effects->addRepaint(dirtyRegion);
218 }
219}
220
221bool MouseClickEffect::isReleased(Qt::MouseButtons button, Qt::MouseButtons buttons, Qt::MouseButtons oldButtons)
222{
223 return !(button & buttons) && (button & oldButtons);
224}
225
226bool MouseClickEffect::isPressed(Qt::MouseButtons button, Qt::MouseButtons buttons, Qt::MouseButtons oldButtons)
227{
228 return (button & buttons) && !(button & oldButtons);
229}
230
231void MouseClickEffect::toggleEnabled()
232{
233 m_enabled = !m_enabled;
234
235 if (m_enabled) {
236 connect(effects, &EffectsHandler::mouseChanged, this, &MouseClickEffect::slotMouseChanged);
238 } else {
239 disconnect(effects, &EffectsHandler::mouseChanged, this, &MouseClickEffect::slotMouseChanged);
241 }
242
243 m_clicks.clear();
244 m_tabletTools.clear();
245
246 for (int i = 0; i < BUTTON_COUNT; ++i) {
247 m_buttons[i]->m_time = 0;
248 m_buttons[i]->m_isPressed = false;
249 }
250}
251
253{
254 return m_enabled && (m_clicks.size() != 0 || !m_tabletTools.isEmpty());
255}
256
257void MouseClickEffect::drawCircle(const RenderViewport &viewport, const QColor &color, float cx, float cy, float r)
258{
260 drawCircleGl(viewport, color, cx, cy, r);
262 drawCircleQPainter(color, cx, cy, r);
263 }
264}
265
266void MouseClickEffect::drawCircleGl(const RenderViewport &viewport, const QColor &color, float cx, float cy, float r)
267{
268 static const int num_segments = 80;
269 static const float theta = 2 * 3.1415926 / float(num_segments);
270 static const float c = cosf(theta); // precalculate the sine and cosine
271 static const float s = sinf(theta);
272 const float scale = viewport.scale();
273 float t;
274
275 float x = r; // we start at angle = 0
276 float y = 0;
277
278 GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer();
279 vbo->reset();
280 QList<QVector2D> verts;
281 verts.reserve(num_segments * 2);
282
283 for (int ii = 0; ii < num_segments; ++ii) {
284 verts.push_back(QVector2D((x + cx) * scale, (y + cy) * scale)); // output vertex
285 // apply the rotation matrix
286 t = x;
287 x = c * x - s * y;
288 y = s * t + c * y;
289 }
290 vbo->setVertices(verts);
292 vbo->render(GL_LINE_LOOP);
293}
294
295void MouseClickEffect::drawCircleQPainter(const QColor &color, float cx, float cy, float r)
296{
297 QPainter *painter = effects->scenePainter();
298 painter->save();
299 painter->setPen(color);
300 painter->drawArc(cx - r, cy - r, r * 2, r * 2, 0, 5760);
301 painter->restore();
302}
303
304void MouseClickEffect::paintScreenSetupGl(const RenderTarget &renderTarget, const QMatrix4x4 &projectionMatrix)
305{
308 shader->setColorspaceUniformsFromSRGB(renderTarget.colorDescription());
309
310 glLineWidth(m_lineWidth);
311 glEnable(GL_BLEND);
312 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
313}
314
315void MouseClickEffect::paintScreenFinishGl()
316{
317 glDisable(GL_BLEND);
318
320}
321
322bool MouseClickEffect::tabletToolEvent(QTabletEvent *event)
323{
324 auto &tabletEvent = m_tabletTools[event->uniqueId()];
325 if (!tabletEvent.m_color.isValid()) {
326 switch (event->pointerType()) {
327 case QPointingDevice::PointerType::Unknown:
328 case QPointingDevice::PointerType::Generic:
329 case QPointingDevice::PointerType::Finger:
330 case QPointingDevice::PointerType::Pen:
331 tabletEvent.m_color = MouseClickConfig::color1();
332 break;
333 case QPointingDevice::PointerType::Eraser:
334 tabletEvent.m_color = MouseClickConfig::color2();
335 break;
336 case QPointingDevice::PointerType::Cursor:
337 tabletEvent.m_color = MouseClickConfig::color3();
338 break;
339 case QPointingDevice::PointerType::AllPointerTypes:
340 Q_UNREACHABLE();
341 break;
342 }
343 }
344 switch (event->type()) {
345 case QEvent::TabletPress:
346 tabletEvent.m_pressed = true;
347 break;
348 case QEvent::TabletRelease:
349 tabletEvent.m_pressed = false;
350 break;
351 case QEvent::TabletLeaveProximity:
352 m_tabletTools.remove(event->uniqueId());
353 return false;
354 default:
355 break;
356 }
357 tabletEvent.m_globalPosition = event->globalPos();
358 tabletEvent.m_pressure = event->pressure();
359
360 return false;
361}
362
364{
365 return m_colors[0];
366}
367
369{
370 return m_colors[1];
371}
372
374{
375 return m_colors[2];
376}
377
379{
380 return m_lineWidth;
381}
382
384{
385 return m_ringLife;
386}
387
389{
390 return m_ringMaxSize;
391}
392
394{
395 return m_ringCount;
396}
397
399{
400 return m_showText;
401}
402
404{
405 return m_font;
406}
407
409{
410 return m_enabled;
411}
412
413} // namespace
414
415#include "moc_mouseclick.cpp"
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen)
Q_SCRIPTABLE void addRepaint(const QRectF &r)
CompositingType compositingType
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime)
bool isOpenGLCompositing() const
Whether the Compositor is OpenGL based (either GL 1 or 2).
void mouseChanged(const QPointF &pos, const QPointF &oldpos, Qt::MouseButtons buttons, Qt::MouseButtons oldbuttons, Qt::KeyboardModifiers modifiers, Qt::KeyboardModifiers oldmodifiers)
KSharedConfigPtr config() const
QPainter * scenePainter()
Provides access to the QPainter which is rendering to the back buffer.
bool setUniform(const char *name, float value)
Definition glshader.cpp:301
static GLVertexBuffer * streamingBuffer()
~MouseClickEffect() override
void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override
void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion &region, Output *screen) override
void postPaintScreen() override
void reconfigure(ReconfigureFlags) override
bool tabletToolEvent(QTabletEvent *event) override
bool isActive() const override
QMatrix4x4 projectionMatrix() const
GLShader * getBoundShader() const
static ShaderManager * instance()
GLShader * pushShader(ShaderTraits traits)
@ ReconfigureAll
Definition effect.h:601
#define BUTTON_COUNT
Definition mouseclick.h:23
KWIN_EXPORT QRect infiniteRegion()
Definition globals.h:234
@ EffectFrameStyled
Displays a Plasma-styled frame around the contents.
Definition effectframe.h:29
@ QPainterCompositing
Definition globals.h:39
EffectsHandler * effects