1 /*
2 This file is part of the game 'KJumpingCube'
3
4 SPDX-FileCopyrightText: 1998-2000 Matthias Kiefer <matthias.kiefer@gmx.de>
5 SPDX-FileCopyrightText: 2012-2013 Ian Wadhan <iandw.au@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include "kcubeboxwidget.h"
11
12 #include <KgTheme>
13
14 #include <QTimer>
15 #include <QLabel>
16 #include <QPainter>
17
18 #include <assert.h>
19 #include <QStandardPaths>
20
21 #include "prefs.h"
22
23 #include "kjumpingcube_debug.h"
24
KCubeBoxWidget(const int d,QWidget * parent)25 KCubeBoxWidget::KCubeBoxWidget (const int d, QWidget *parent)
26 : QWidget (parent),
27 m_side (d),
28 m_popup (new QLabel (this))
29 {
30 qCDebug(KJUMPINGCUBE_LOG) << "CONSTRUCT KCubeBoxWidget: side" << m_side;
31 cubes.clear();
32 init();
33 }
34
~KCubeBoxWidget()35 KCubeBoxWidget::~KCubeBoxWidget()
36 {
37 }
38
loadSettings()39 bool KCubeBoxWidget::loadSettings()
40 {
41 qCDebug(KJUMPINGCUBE_LOG) << "LOAD VIEW SETTINGS";
42 bool reColorCubes = ((color1 != Prefs::color1()) ||
43 (color2 != Prefs::color2()) ||
44 (color0 != Prefs::color0()));
45
46 color1 = Prefs::color1();
47 color2 = Prefs::color2();
48 color0 = Prefs::color0();
49
50 if (Prefs::animationNone()) {
51 cascadeAnimation = None;
52 }
53 else if (Prefs::animationDelay() || (Prefs::animationSpeed() <= 1)) {
54 cascadeAnimation = Darken;
55 }
56 else if (Prefs::animationBlink()) {
57 cascadeAnimation = RapidBlink;
58 }
59 else if (Prefs::animationSpread()) {
60 cascadeAnimation = Scatter;
61 }
62
63 animationTime = Prefs::animationSpeed() * 150;
64
65 // NOTE: When the box-size (Prefs::cubeDim()) changes, Game::newGame() calls
66 // KCubeBoxWidget::loadSettings() first, then KCubeBoxWidget::setDim().
67
68 if (reColorCubes) {
69 makeStatusPixmaps (sWidth); // Make new status pixmaps.
70 makeSVGCubes (cubeSize);
71 setColors ();
72 }
73 return reColorCubes;
74 }
75
reset()76 void KCubeBoxWidget::reset() // Called if a player wins or requests New game.
77 {
78 for (KCubeWidget * cube : std::as_const(cubes)) {
79 cube->reset();
80 }
81
82 KCubeWidget::enableClicks(true);
83 currentAnimation = None;
84 }
85
displayCube(int index,Player owner,int value)86 void KCubeBoxWidget::displayCube (int index, Player owner, int value)
87 {
88 cubes.at(index)->setOwner (owner);
89 cubes.at(index)->setValue (value);
90 }
91
highlightCube(int index,bool highlight)92 void KCubeBoxWidget::highlightCube (int index, bool highlight)
93 {
94 if (highlight) {
95 cubes.at(index)->setDark();
96 }
97 else {
98 cubes.at(index)->setNeutral();
99 }
100 }
101
timedCubeHighlight(int index)102 void KCubeBoxWidget::timedCubeHighlight (int index)
103 {
104 if (m_highlighted > 0) {
105 highlightDone();
106 }
107 cubes.at(index)->setDark();
108 m_highlighted = index;
109 m_highlightTimer->start();
110 }
111
highlightDone()112 void KCubeBoxWidget::highlightDone()
113 {
114 cubes.at(m_highlighted)->setNeutral();
115 m_highlightTimer->stop();
116 m_highlighted = -1;
117 }
118
setColors()119 void KCubeBoxWidget::setColors ()
120 {
121 for (KCubeWidget * cube : std::as_const(cubes)) {
122 cube->updateColors();
123 }
124 }
125
setDim(int d)126 void KCubeBoxWidget::setDim(int d)
127 {
128 if (d != m_side) {
129 m_side = d;
130 initCubes();
131 reCalculateGraphics (width(), height());
132 reset();
133 }
134 }
135
136 /* ***************************************************************** **
137 ** slots **
138 ** ***************************************************************** */
139
setWaitCursor()140 void KCubeBoxWidget::setWaitCursor()
141 {
142 setCursor (Qt::BusyCursor);
143 }
144
setNormalCursor()145 void KCubeBoxWidget::setNormalCursor()
146 {
147 setCursor (Qt::PointingHandCursor);
148 }
149
checkClick(int x,int y)150 bool KCubeBoxWidget::checkClick (int x, int y)
151 {
152 /* IDW TODO - Remove this from the view OR rewrite it as a MouseEvent().
153 *
154 // IDW TODO - Write a new mouse-click event for KCubeBoxWidget? Remove the
155 // one that KCubeWidget has?
156 */
157 qCDebug(KJUMPINGCUBE_LOG) << "Emit mouseClick (" << x << y << ")";
158 Q_EMIT mouseClick (x, y);
159 return false;
160 }
161
162 /* ***************************************************************** **
163 ** initializing functions **
164 ** ***************************************************************** */
init()165 void KCubeBoxWidget::init()
166 {
167 currentAnimation = None;
168 animationSteps = 12;
169 animationCount = 0;
170
171 setMinimumSize (200, 200);
172 color1 = Prefs::color1(); // Set preferred colors.
173 color2 = Prefs::color2();
174 color0 = Prefs::color0();
175
176 KgTheme theme((QByteArray()));
177 theme.readFromDesktopFile(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("pics/default.desktop")));
178 svg.load (theme.graphicsPath());
179
180 initCubes();
181
182 animationTime = Prefs::animationSpeed() * 150;
183 animationTimer = new QTimer(this);
184
185 m_highlightTimer = new QTimer(this);
186 m_highlightTimer->setInterval (1500);
187 m_highlighted = -1;
188
189 connect(animationTimer, &QTimer::timeout, this, &KCubeBoxWidget::nextAnimationStep);
190 connect(m_highlightTimer, &QTimer::timeout, this, &KCubeBoxWidget::highlightDone);
191 setNormalCursor();
192 setPopup();
193 }
194
initCubes()195 void KCubeBoxWidget::initCubes()
196 {
197 qDeleteAll (cubes);
198 cubes.clear();
199
200 int nCubes = m_side * m_side;
201 for (int n = 0; n < nCubes; n++) {
202 KCubeWidget * cube = new KCubeWidget (this);
203 cubes.append (cube);
204 cube->setCoordinates (n / m_side, n % m_side, m_side - 1);
205 cube->setPixmaps (&elements);
206 connect(cube, &KCubeWidget::clicked, this, &KCubeBoxWidget::checkClick);
207 cube->show();
208 }
209 }
210
makeStatusPixmaps(const int width)211 void KCubeBoxWidget::makeStatusPixmaps (const int width)
212 {
213 qreal d, p;
214 QImage status (width, width, QImage::Format_ARGB32_Premultiplied);
215 QPainter s (&status);
216 sWidth = width;
217
218 d = width/4.0;
219 p = width/2.0;
220 status.fill (0);
221 svg.render (&s, QStringLiteral("player_1"));
222 colorImage (status, color1, width);
223 svg.render (&s, QStringLiteral("lighting"));
224 svg.render (&s, QStringLiteral("pip"), QRectF (p - d/2.0, p - d/2.0, d, d));
225 status1 = QPixmap::fromImage (status);
226
227 d = width/5.0;
228 p = width/3.0;
229 status.fill (0);
230 svg.render (&s, QStringLiteral("player_2"));
231 colorImage (status, color2, width);
232 svg.render (&s, QStringLiteral("lighting"));
233 svg.render (&s, QStringLiteral("pip"), QRectF (p - d/2.0, p - d/2.0, d, d));
234 svg.render (&s, QStringLiteral("pip"), QRectF (p + p - d/2.0, p + p - d/2.0, d, d));
235 s.end();
236 status2 = QPixmap::fromImage (status);
237 }
238
makeSVGBackground(const int w,const int h)239 void KCubeBoxWidget::makeSVGBackground (const int w, const int h)
240 {
241 QImage img (w, h, QImage::Format_ARGB32_Premultiplied);
242 QPainter p (&img);
243 img.fill (0);
244 svg.render (&p, QStringLiteral("background"));
245 p.end();
246 background = QPixmap::fromImage (img);
247 }
248
makeSVGCubes(const int width)249 void KCubeBoxWidget::makeSVGCubes (const int width)
250 {
251 QImage img (width, width, QImage::Format_ARGB32_Premultiplied);
252 QPainter q; // Paints whole faces of the dice.
253
254 QImage pip (width/7, width/7, QImage::Format_ARGB32_Premultiplied);
255 QPainter r; // Paints the pips on the faces of the dice.
256
257 QRectF rect (0, 0, width, width);
258 qreal pc = 20.0; // % radius on corners.
259 elements.clear();
260 for (int i = FirstElement; i <= LastElement; i++) {
261 q.begin(&img);
262 q.setPen (Qt::NoPen);
263 if (i == Pip) {
264 pip.fill (0);
265 }
266 else {
267 img.fill (0);
268 }
269
270 // NOTE: "neutral", "player_1" and "player_2" from file "default.svg" cause
271 // odd effects at the corners. You get a cleaner look if they are omitted.
272
273 switch (i) {
274 case Neutral:
275 // svg.render (&q, "neutral");
276 q.setBrush (color0);
277 q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize);
278 svg.render (&q, QStringLiteral("lighting"));
279 break;
280 case Player1:
281 // svg.render (&q, "player_1");
282 q.setBrush (color1);
283 q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize);
284 svg.render (&q, QStringLiteral("lighting"));
285 break;
286 case Player2:
287 // svg.render (&q, "player_2");
288 q.setBrush (color2);
289 q.drawRoundedRect (rect, pc, pc, Qt::RelativeSize);
290 svg.render (&q, QStringLiteral("lighting"));
291 break;
292 case Pip:
293 r.begin(&pip);
294 svg.render (&r, QStringLiteral("pip"));
295 r.end();
296 break;
297 case BlinkLight:
298 svg.render (&q, QStringLiteral("blink_light"));
299 break;
300 case BlinkDark:
301 svg.render (&q, QStringLiteral("blink_dark"));
302 break;
303 default:
304 break;
305 }
306 q.end();
307 elements.append
308 ((i == Pip) ? QPixmap::fromImage (pip) : QPixmap::fromImage (img));
309 }
310 }
311
colorImage(QImage & img,const QColor & c,const int w)312 void KCubeBoxWidget::colorImage (QImage & img, const QColor & c, const int w)
313 {
314 QRgb rgba = c.rgba();
315 for (int i = 0; i < w; i++) {
316 for (int j = 0; j < w; j++) {
317 if (img.pixel (i, j) != 0) {
318 img.setPixel (i, j, rgba);
319 }
320 }
321 }
322 }
323
paintEvent(QPaintEvent *)324 void KCubeBoxWidget::paintEvent (QPaintEvent * /* event unused */)
325 {
326 QPainter p (this);
327 p.drawPixmap (0, 0, background);
328 }
329
resizeEvent(QResizeEvent * event)330 void KCubeBoxWidget::resizeEvent (QResizeEvent * event)
331 {
332 reCalculateGraphics (event->size().width(), event->size().height());
333 }
334
reCalculateGraphics(const int w,const int h)335 void KCubeBoxWidget::reCalculateGraphics (const int w, const int h)
336 {
337 int boxSize = qMin(w, h);
338 int frameWidth = boxSize / 30;
339 // qCDebug(KJUMPINGCUBE_LOG) << "boxSize" << boxSize << "frameWidth" << frameWidth;
340 boxSize = boxSize - (2 * frameWidth);
341 cubeSize = (boxSize / m_side);
342 boxSize = (cubeSize * m_side);
343 topLeft.setX ((w - boxSize)/2);
344 topLeft.setY ((h - boxSize)/2);
345
346 // qCDebug(KJUMPINGCUBE_LOG) << "Dimension:" << m_side << "cubeSize:" << cubeSize << "topLeft:" << topLeft;
347 makeSVGBackground (w, h);
348 makeSVGCubes (cubeSize);
349 for (int x = 0; x < m_side; x++) {
350 for (int y = 0; y < m_side; y++) {
351 int index = x * m_side + y;
352 cubes.at (index)->move (
353 topLeft.x() + (x * cubeSize),
354 topLeft.y() + (y * cubeSize));
355 cubes.at (index)->resize (cubeSize, cubeSize);
356 }
357 }
358 setPopup();
359 }
360
sizeHint() const361 QSize KCubeBoxWidget::sizeHint() const
362 {
363 return QSize(400,400);
364 }
365
366 /* ***************************************************************** **
367 ** other private functions **
368 ** ***************************************************************** */
369
startAnimation(bool cascading,int index)370 void KCubeBoxWidget::startAnimation (bool cascading, int index)
371 {
372 int interval = 0;
373 m_index = index;
374 currentAnimation = cascading ? cascadeAnimation : ComputerMove;
375 switch (currentAnimation) {
376 case None:
377 animationCount = 0;
378 return; // Should never happen.
379 break;
380 case ComputerMove:
381 interval = 150 + (Prefs::animationSpeed() - 1) * 50; // 150-600 msec.
382 animationCount = 4;
383 cubes.at (index)->setLight();
384 break;
385 case Darken:
386 interval = animationTime;
387 animationCount = 1;
388 cubes.at (index)->setDark();
389 break;
390 case RapidBlink:
391 interval = 60 + Prefs::animationSpeed() * 30; // 120-360 msec.
392 animationCount = 4;
393 cubes.at (index)->setLight();
394 break;
395 case Scatter:
396 interval = (animationTime + animationSteps/2) / animationSteps;
397 animationCount = animationSteps;
398 break;
399 }
400 animationTimer->setInterval (interval);
401 animationTimer->start();
402 }
403
nextAnimationStep()404 void KCubeBoxWidget::nextAnimationStep()
405 {
406 animationCount--;
407 if (animationCount < 1) {
408 animationTimer->stop(); // Finish normally.
409 cubes.at (m_index)->setNeutral();
410 currentAnimation = None;
411 Q_EMIT animationDone (m_index);
412 return;
413 }
414 switch (currentAnimation) {
415 case None:
416 return; // Should not happen.
417 break;
418 case ComputerMove:
419 case RapidBlink:
420 if (animationCount%2 == 1) { // Set light or dark phase.
421 cubes.at (m_index)->setDark();
422 }
423 else {
424 cubes.at (m_index)->setLight();
425 }
426 break;
427 case Darken:
428 break; // Should never happen (1 tick).
429 case Scatter:
430 int step = animationSteps - animationCount;
431 if (step <= 2) { // Set the animation phase.
432 cubes.at (m_index)->shrink(1.0 - step * 0.3);
433 }
434 else if (step < 7) {
435 cubes.at (m_index)->expand((step - 2) * 0.2);
436 }
437 else if (step == 7) {
438 cubes.at (m_index)->expand(1.2);
439 scatterDots (0);
440 }
441 else {
442 scatterDots (step - 7);
443 }
444 break;
445 }
446 }
447
scatterDots(int step)448 void KCubeBoxWidget::scatterDots (int step)
449 {
450 Player player = cubes.at(m_index)->owner();
451 int d = m_side - 1;
452 int x = m_index / m_side;
453 int y = m_index % m_side;
454 if (x > 0) cubes.at (m_index - m_side)->migrateDot (+1, 0, step, player);
455 if (x < d) cubes.at (m_index + m_side)->migrateDot (-1, 0, step, player);
456 if (y > 0) cubes.at (m_index - 1) ->migrateDot ( 0, +1, step, player);
457 if (y < d) cubes.at (m_index + 1) ->migrateDot ( 0, -1, step, player);
458 }
459
killAnimation()460 int KCubeBoxWidget::killAnimation()
461 {
462 if (animationTimer->isActive()) {
463 animationTimer->stop(); // Stop current animation immediately.
464 }
465 return m_index;
466 }
467
playerPixmap(const int p)468 const QPixmap & KCubeBoxWidget::playerPixmap (const int p)
469 {
470 return ((p == 1) ? status1 : status2);
471 }
472
setPopup()473 void KCubeBoxWidget::setPopup()
474 {
475 QFont f;
476 f.setPixelSize ((int) (height() * 0.04 + 0.5));
477 f.setWeight (QFont::Bold);
478 f.setStretch (QFont::Expanded);
479 m_popup->setStyleSheet(QStringLiteral("QLabel { color : rgba(255, 255, 255, 75%); }"));
480 m_popup->setFont (f);
481 m_popup->resize (width(), (int) (height() * 0.08 + 0.5));
482 m_popup->setAlignment (Qt::AlignCenter);
483 }
484
showPopup(const QString & message)485 void KCubeBoxWidget::showPopup (const QString & message)
486 {
487 m_popup->setText (message);
488 m_popup->move ((this->width() - m_popup->width()) / 2,
489 (this->height() - m_popup->height()) / 2 +
490 (cubes.at (0)->height() / 5));
491 m_popup->raise();
492 m_popup->show();
493 update();
494 }
495
hidePopup()496 void KCubeBoxWidget::hidePopup()
497 {
498 m_popup->hide();
499 update();
500 }
501
502
503