KDEGames

kgamepopupitem.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Dmitry Suzdalev <dimsuz@gmail.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "kgamepopupitem.h"
8
9// KF
10#include <KColorScheme>
11#include <KStatefulBrush>
12// Qt
13#include <QGraphicsScene>
14#include <QGraphicsTextItem>
15#include <QGraphicsView>
16#include <QIcon>
17#include <QPainter>
18#include <QTimeLine>
19#include <QTimer>
20
21// margin on the sides of message box
22static const int MARGIN = 15;
23// offset of message from start of the scene
24static const int SHOW_OFFSET = 15;
25// space between pixmap and text
26static const int SOME_SPACE = 10;
27// width of the border in pixels
28static const qreal BORDER_PEN_WIDTH = 1.0;
29
30class TextItemWithOpacity : public QGraphicsTextItem
31{
33
34public:
35 TextItemWithOpacity(QGraphicsItem *parent = nullptr)
37 , m_opacity(1.0)
38 {
39 }
40 void setOpacity(qreal opa)
41 {
42 m_opacity = opa;
43 }
44 void setTextColor(const KStatefulBrush &brush)
45 {
46 m_brush = brush;
47 }
48 void paint(QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
49
51 void mouseClicked();
52
53private:
54 void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override;
55
56private:
57 qreal m_opacity;
58 KStatefulBrush m_brush;
59};
60
61void TextItemWithOpacity::paint(QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget)
62{
63 // hope that it is ok to call this function here - i.e. I hope it won't be too expensive :)
64 // we call it here (and not in setTextColor), because KstatefulBrush
65 // absolutely needs QWidget parameter :)
66 // NOTE from majewsky: For some weird reason, setDefaultTextColor does on some systems not check
67 // whether the given color is equal to the one already set. Just calling setDefaultTextColor without
68 // this check may result in an infinite loop of paintEvent -> setDefaultTextColor -> update -> paintEvent...
69 const QColor textColor = widget ? m_brush.brush(widget->palette()).color() : QColor(Qt::black);
70 if (textColor != defaultTextColor()) {
71 setDefaultTextColor(textColor);
72 }
73 // render contents
74 p->save();
75 p->setOpacity(m_opacity);
76 QGraphicsTextItem::paint(p, option, widget);
77 p->restore();
78}
79
80void TextItemWithOpacity::mouseReleaseEvent(QGraphicsSceneMouseEvent *ev)
81{
82 // NOTE: this item is QGraphicsTextItem which "eats" mouse events
83 // because of interaction with links. Because of that let's make a
84 // special signal to indicate mouse click
85 Q_EMIT mouseClicked();
87}
88
89class KGamePopupItemPrivate
90{
91private:
92 KGamePopupItemPrivate(const KGamePopupItemPrivate &);
93 const KGamePopupItemPrivate &operator=(const KGamePopupItemPrivate &);
94
95public:
96 KGamePopupItemPrivate() = default;
97
98public:
99 /**
100 * Timeline for animations
101 */
102 QTimeLine m_timeLine;
103 /**
104 * Timer used to start hiding
105 */
106 QTimer m_timer;
107 /**
108 * Holds bounding rect of an item
109 */
110 QRect m_boundRect;
111 /**
112 * Holds bounding rect of an item (mapped to scene coordinates)
113 */
114 QRectF m_mappedBoundRect;
115 /**
116 * Offset of message from start of the scene (mapped to scene coordinates)
117 */
118 qreal m_mapped_SHOW_OFFSET;
119 /**
120 * Position where item will appear
121 */
122 KGamePopupItem::Position m_position = KGamePopupItem::BottomLeft;
123 /**
124 * Timeout to stay visible on screen
125 */
126 int m_timeout = 2000;
127 /**
128 * Item opacity
129 */
130 qreal m_opacity = 1.0;
131 /**
132 * Opacity used while animating appearing in center
133 */
134 qreal m_animOpacity = -1;
135 /**
136 * Pixmap to display at the left of the text
137 */
138 QPixmap m_iconPix;
139 /**
140 * Set to true when mouse hovers the message
141 */
142 bool m_hoveredByMouse = false;
143 /**
144 * Set to true if this popup item hides on mouse click.
145 */
146 bool m_hideOnClick = true;
147 /**
148 * Child of KGamePopupItem used to display text
149 */
150 TextItemWithOpacity *m_textChildItem = nullptr;
151 /**
152 * Part of the scene that is actually visible in QGraphicsView
153 * This is needed for item to work correctly when scene is larger than
154 * the View
155 */
156 QRectF m_visibleSceneRect;
157 /**
158 * Background brush color
159 */
160 KStatefulBrush m_brush;
161 /**
162 * popup angles sharpness
163 */
164 KGamePopupItem::Sharpness m_sharpness = KGamePopupItem::Square;
165 /**
166 * painter path to draw a frame
167 */
168 QPainterPath m_path;
169 /**
170 * Indicates if some link is hovered in text item
171 */
172 bool m_linkHovered = false;
173};
174
176 : QGraphicsItem(parent)
177 , d_ptr(new KGamePopupItemPrivate)
178{
180
181 hide();
182 d->m_textChildItem = new TextItemWithOpacity(this);
183 d->m_textChildItem->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
184 // above call said to enable ItemIsFocusable which we don't need.
185 // So disabling it
186 d->m_textChildItem->setFlag(QGraphicsItem::ItemIsFocusable, false);
187
189 connect(d->m_textChildItem, &TextItemWithOpacity::linkHovered, this, &KGamePopupItem::onLinkHovered);
190 connect(d->m_textChildItem, &TextItemWithOpacity::mouseClicked, this, &KGamePopupItem::onTextItemClicked);
191
192 setZValue(100); // is 100 high enough???
193 d->m_textChildItem->setZValue(100);
194
195 QIcon infoIcon = QIcon::fromTheme(QStringLiteral("dialog-information"));
196 // default size is 32
197 setMessageIcon(infoIcon.pixmap(32, 32));
198
199 d->m_timer.setSingleShot(true);
200
202 // ignore scene transformations
204
205 // setup default colors
207 d->m_textChildItem->setTextColor(KStatefulBrush(KColorScheme::Tooltip, KColorScheme::NormalText));
208
209 connect(&d->m_timeLine, &QTimeLine::frameChanged, this, &KGamePopupItem::animationFrame);
210 connect(&d->m_timeLine, &QTimeLine::finished, this, &KGamePopupItem::hideMe);
211 connect(&d->m_timer, &QTimer::timeout, this, &KGamePopupItem::playHideAnimation);
212}
213
215
217{
219
220 Q_UNUSED(option);
221
222 p->save();
223
224 QPen pen = p->pen();
225 pen.setWidthF(BORDER_PEN_WIDTH);
226 p->setPen(pen);
227
228 if (d->m_animOpacity != -1) // playing Center animation
229 {
230 p->setOpacity(d->m_animOpacity);
231 } else {
232 p->setOpacity(d->m_opacity);
233 }
234 p->setBrush(widget ? d->m_brush.brush(widget->palette()) : QBrush());
235 p->drawPath(d->m_path);
236 p->drawPixmap(MARGIN, static_cast<int>(d->m_boundRect.height() / 2) - d->m_iconPix.height() / 2.0 / d->m_iconPix.devicePixelRatio(), d->m_iconPix);
237 p->restore();
238}
239
241{
243
244 if (d->m_timeLine.state() == QTimeLine::Running || d->m_timer.isActive()) {
245 if (mode == ReplacePrevious) {
246 forceHide(InstantHide);
247 } else {
248 return; // we're already showing a message
249 }
250 }
251
252 // NOTE: we blindly take first visible view we found. I.e. we don't support
253 // multiple views. If no visible scene is found, we simply pick the first one.
254 QGraphicsView *sceneView = nullptr;
255 const auto views = scene()->views();
256 for (QGraphicsView *view : views) {
257 if (view->isVisible()) {
258 sceneView = view;
259 break;
260 }
261 }
262 if (!sceneView) {
263 sceneView = views.at(0);
264 }
265
266 QPolygonF poly = sceneView->mapToScene(sceneView->viewport()->contentsRect());
267 d->m_visibleSceneRect = poly.boundingRect();
268
269 d->m_textChildItem->setHtml(text);
270
271 d->m_position = pos;
272
273 // do as QGS docs say: notify the scene about rect change
275
276 // recalculate bounding rect
277 const qreal iconDpr = d->m_iconPix.devicePixelRatio();
278 qreal w = d->m_textChildItem->boundingRect().width() + MARGIN * 2 + d->m_iconPix.width() / iconDpr + SOME_SPACE;
279 qreal h = d->m_textChildItem->boundingRect().height() + MARGIN * 2;
280 if (d->m_iconPix.height() / iconDpr > h) {
281 h = d->m_iconPix.height() / iconDpr + MARGIN * 2;
282 }
283 d->m_boundRect = QRect(0, 0, w, h);
284
285 // adjust to take into account the width of the pen
286 // used to draw the border
287 const qreal borderRadius = BORDER_PEN_WIDTH / 2.0;
288 d->m_boundRect.adjust(-borderRadius, -borderRadius, borderRadius, borderRadius);
289
290 d->m_mappedBoundRect = sceneView->mapToScene(d->m_boundRect).boundingRect();
291 d->m_mapped_SHOW_OFFSET = qAbs(sceneView->mapToScene(0, SHOW_OFFSET).y());
292
294 roundRectPath.moveTo(w, d->m_sharpness);
295 roundRectPath.arcTo(w - (2 * d->m_sharpness), 0.0, (2 * d->m_sharpness), (d->m_sharpness), 0.0, 90.0);
296 roundRectPath.lineTo(d->m_sharpness, 0.0);
297 roundRectPath.arcTo(0.0, 0.0, (2 * d->m_sharpness), (2 * d->m_sharpness), 90.0, 90.0);
298 roundRectPath.lineTo(0.0, h - (d->m_sharpness));
299 roundRectPath.arcTo(0.0, h - (2 * d->m_sharpness), 2 * d->m_sharpness, 2 * d->m_sharpness, 180.0, 90.0);
300 roundRectPath.lineTo(w - (d->m_sharpness), h);
301 roundRectPath.arcTo(w - (2 * d->m_sharpness), h - (2 * d->m_sharpness), (2 * d->m_sharpness), (2 * d->m_sharpness), 270.0, 90.0);
302 roundRectPath.closeSubpath();
303
304 d->m_path = roundRectPath;
305
306 // adjust y-pos of text item so it appears centered
307 d->m_textChildItem->setPos(d->m_textChildItem->x(), d->m_boundRect.height() / 2 - d->m_textChildItem->boundingRect().height() / 2);
308
309 // setup animation
310 setupTimeline();
311
312 // move to the start position
313 animationFrame(d->m_timeLine.startFrame());
314 show();
315 d->m_timeLine.start();
316
317 if (d->m_timeout != 0) {
318 // 300 msec to animate showing message + d->m_timeout to stay visible => then hide
319 d->m_timer.start(300 + d->m_timeout);
320 }
321}
322
323void KGamePopupItem::setupTimeline()
324{
326
327 d->m_timeLine.setDirection(QTimeLine::Forward);
328 d->m_timeLine.setDuration(300);
329 if (d->m_position == TopLeft || d->m_position == TopRight) {
330 int start = static_cast<int>(d->m_visibleSceneRect.top() - d->m_mappedBoundRect.height() - d->m_mapped_SHOW_OFFSET);
331 int end = static_cast<int>(d->m_visibleSceneRect.top() + d->m_mapped_SHOW_OFFSET);
332 d->m_timeLine.setFrameRange(start, end);
333 } else if (d->m_position == BottomLeft || d->m_position == BottomRight) {
334 int start = static_cast<int>(d->m_visibleSceneRect.bottom() + d->m_mapped_SHOW_OFFSET);
335 int end = static_cast<int>(d->m_visibleSceneRect.bottom() - d->m_mappedBoundRect.height() - d->m_mapped_SHOW_OFFSET);
336 d->m_timeLine.setFrameRange(start, end);
337 } else if (d->m_position == Center) {
338 d->m_timeLine.setFrameRange(0, d->m_timeLine.duration());
339 setPos(d->m_visibleSceneRect.left() + d->m_visibleSceneRect.width() / 2 - d->m_mappedBoundRect.width() / 2,
340 d->m_visibleSceneRect.top() + d->m_visibleSceneRect.height() / 2 - d->m_mappedBoundRect.height() / 2);
341 }
342}
343
344void KGamePopupItem::animationFrame(int frame)
345{
347
348 if (d->m_position == TopLeft || d->m_position == BottomLeft) {
349 setPos(d->m_visibleSceneRect.left() + d->m_mapped_SHOW_OFFSET, frame);
350 } else if (d->m_position == TopRight || d->m_position == BottomRight) {
351 setPos(d->m_visibleSceneRect.right() - d->m_mappedBoundRect.width() - d->m_mapped_SHOW_OFFSET, frame);
352 } else if (d->m_position == Center) {
353 d->m_animOpacity = frame * d->m_opacity / d->m_timeLine.duration();
354 d->m_textChildItem->setOpacity(d->m_animOpacity);
355 update();
356 }
357}
358
359void KGamePopupItem::playHideAnimation()
360{
362
363 if (d->m_hoveredByMouse) {
364 return;
365 }
366 // let's hide
367 d->m_timeLine.setDirection(QTimeLine::Backward);
368 d->m_timeLine.start();
369}
370
372{
374
375 d->m_timeout = msec;
376}
377
379{
381
382 d->m_hideOnClick = hide;
383}
384
386{
387 Q_D(const KGamePopupItem);
388
389 return d->m_hideOnClick;
390}
391
393{
395
396 d->m_opacity = opacity;
397 d->m_textChildItem->setOpacity(opacity);
398}
399
401{
402 Q_D(const KGamePopupItem);
403
404 return d->m_boundRect;
405}
406
407void KGamePopupItem::hideMe()
408{
410
411 d->m_animOpacity = -1;
412 // and restore child's opacity too
413 d->m_textChildItem->setOpacity(d->m_opacity);
414
415 // if we just got moved out of visibility, let's do more - let's hide :)
416 if (d->m_timeLine.direction() == QTimeLine::Backward) {
417 hide();
418 Q_EMIT hidden();
419 }
420}
421
422void KGamePopupItem::hoverEnterEvent(QGraphicsSceneHoverEvent *)
423{
425
426 d->m_hoveredByMouse = true;
427}
428
429void KGamePopupItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *)
430{
432
433 d->m_hoveredByMouse = false;
434
435 if (d->m_timeout != 0 && !d->m_timer.isActive() && d->m_timeLine.state() != QTimeLine::Running) {
436 playHideAnimation(); // let's hide
437 }
438}
439
441{
443
444 d->m_iconPix = pix;
445 d->m_textChildItem->setPos(MARGIN + pix.width() / pix.devicePixelRatio() + SOME_SPACE, MARGIN);
446 // bounding rect is updated in showMessage()
447}
448
450{
451 Q_D(const KGamePopupItem);
452
453 return d->m_timeout;
454}
455
457{
459
460 if (!isVisible()) {
461 return;
462 }
463
464 if (howToHide == InstantHide) {
465 d->m_timeLine.stop();
466 d->m_timer.stop();
467 hide();
468 Q_EMIT hidden();
469 } else if (howToHide == AnimatedHide) {
470 // forcefully unset it even if it is set
471 // so we'll hide in any event
472 d->m_hoveredByMouse = false;
473 d->m_timer.stop();
474 playHideAnimation();
475 }
476}
477
479{
480 Q_D(const KGamePopupItem);
481
482 return d->m_opacity;
483}
484
486{
488
489 d->m_brush = KStatefulBrush(brush);
490}
491
493{
495
496 KStatefulBrush brush(color, d->m_brush.brush(QPalette::Active));
497 d->m_textChildItem->setTextColor(brush);
498}
499
500void KGamePopupItem::onLinkHovered(const QString &link)
501{
503
504 if (link.isEmpty()) {
505 d->m_textChildItem->setCursor(Qt::ArrowCursor);
506 } else {
507 d->m_textChildItem->setCursor(Qt::PointingHandCursor);
508 }
509
510 d->m_linkHovered = !link.isEmpty();
511 Q_EMIT linkHovered(link);
512}
513
515{
517
518 d->m_sharpness = sharpness;
519}
520
522{
523 Q_D(const KGamePopupItem);
524
525 return d->m_sharpness;
526}
527
528void KGamePopupItem::mousePressEvent(QGraphicsSceneMouseEvent *)
529{
530 // it is needed to reimplement this function to receive future
531 // mouse release events
532}
533
534void KGamePopupItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *)
535{
537
538 // NOTE: text child item is QGraphicsTextItem which "eats" mouse events
539 // because of interaction with links. Because of that TextItemWithOpacity has
540 // special signal to indicate mouse click which we catch in a onTextItemClicked()
541 // slot
542 if (d->m_hideOnClick) {
543 forceHide();
544 }
545}
546
547void KGamePopupItem::onTextItemClicked()
548{
550
551 // if link is hovered we don't hide as click should go to the link
552 if (d->m_hideOnClick && !d->m_linkHovered) {
553 forceHide();
554 }
555}
556
557#include "kgamepopupitem.moc" // For automocing TextItemWithOpacity
558#include "moc_kgamepopupitem.cpp" // For automocing KGamePopupItem
QGraphicsItem capable of showing short popup messages which do not interrupt the gameplay.
qreal messageOpacity() const
KGamePopupItem(QGraphicsItem *parent=nullptr)
Constructs a message item.
Sharpness
Possible values for the popup angles sharpness.
void setMessageOpacity(qreal opacity)
Sets the message opacity from 0 (fully transparent) to 1 (fully opaque) For example 0....
void hidden()
Emitted when the popup finishes hiding.
void setSharpness(Sharpness sharpness)
Sets the popup angles sharpness.
void showMessage(const QString &text, Position pos, ReplaceMode mode=LeavePrevious)
Shows the message: item will appear at specified place of the scene using simple animation Item will ...
void setMessageIcon(const QPixmap &pix)
Sets custom pixmap to show instead of default icon on the left.
HideType
Used to specify how to hide in forceHide() - instantly or animatedly.
QRectF boundingRect() const override
void paint(QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget) override
Paints item.
void linkHovered(const QString &link)
Emitted when user hovers a link in item.
ReplaceMode
Possible values for message showing mode in respect to a previous message.
void forceHide(HideType type=AnimatedHide)
Requests the item to be hidden immediately.
void setHideOnMouseClick(bool hide)
Sets whether to hide this popup item on mouse click.
void setBackgroundBrush(const QBrush &brush)
Sets brush used to paint item background By default system-default brush is used.
void setTextColor(const QColor &color)
Sets default color for unformatted text By default system-default color is used.
Sharpness sharpness() const
bool hidesOnMouseClick() const
Position
The possible places in the scene where a message can be shown.
int messageTimeout() const
void setMessageTimeout(int msec)
Sets the amount of time the item will stay visible on screen before it goes away.
void linkActivated(const QString &link)
Emitted when user clicks on a link in item.
~KGamePopupItem() override
Destructs a message item.
QBrush brush(const QPalette &) const
Q_SCRIPTABLE Q_NOREPLY void start()
KIOCORE_EXPORT CopyJob * link(const QList< QUrl > &src, const QUrl &destDir, JobFlags flags=DefaultFlags)
const QList< QKeySequence > & end()
const QColor & color() const const
bool isVisible() const const
qreal opacity() const const
QPointF pos() const const
void prepareGeometryChange()
QGraphicsScene * scene() const const
void setAcceptHoverEvents(bool enabled)
void setFlag(GraphicsItemFlag flag, bool enabled)
void setPos(const QPointF &pos)
void setZValue(qreal z)
void update(const QRectF &rect)
QList< QGraphicsView * > views() const const
QColor defaultTextColor() const const
void linkActivated(const QString &link)
void linkHovered(const QString &link)
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
void setDefaultTextColor(const QColor &col)
QIcon fromTheme(const QString &name)
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
Q_SIGNALSQ_SIGNALS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
void drawPath(const QPainterPath &path)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
const QPen & pen() const const
void restore()
void save()
void setBrush(Qt::BrushStyle style)
void setOpacity(qreal opacity)
void setPen(Qt::PenStyle style)
void setWidthF(qreal width)
ArrowCursor
LinksAccessibleByMouse
void finished()
void frameChanged(int frame)
void timeout()
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:50 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.