1/*
2 * Copyright (C) 2008-2009 Stephan Kulow <coolo@kde.org>
3 * Copyright (C) 2008-2009 Parker Coates <coates@kde.org>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "gameselectionscene.h"
20
21#include "dealerinfo.h"
22#include "renderer.h"
23
24#include <KColorUtils>
25#include <KDebug>
26#include <KLocale>
27#include <KStandardDirs>
28
29#include <QtGui/QGraphicsObject>
30#include <QtGui/QKeyEvent>
31#include <QtGui/QPainter>
32#include <QtGui/QStyleOptionGraphicsItem>
33#include <QtCore/QPropertyAnimation>
34
35#include <cmath>
36
37
38namespace
39{
40 const qreal boxPaddingRatio = 0.037;
41 const qreal spacingRatio = 0.10;
42 const qreal textToBoxHeightRatio = 1 / 6.0;
43 const qreal textToBoxWidthRatio = 0.57;
44 const int hoverTransitionDuration = 300;
45 const int minimumFontSize = 5;
46}
47
48
49class GameSelectionScene::GameSelectionBox : public QGraphicsObject
50{
51 Q_OBJECT
52 Q_PROPERTY( qreal fade READ hoverFadeAmount WRITE setHoverFadeAmount )
53
54public:
55 GameSelectionBox( const QString & name, int id )
56 : m_label( name ),
57 m_gameId( id ),
58 m_anim( new QPropertyAnimation( this, "fade", this ) ),
59 m_highlightFadeAmount( 0 ),
60 m_previewPath( KStandardDirs::locate( "data", QString( "kpat/previews/%1.png" ).arg( id ) ) )
61 {
62 setAcceptHoverEvents( true );
63 m_anim->setDuration( hoverTransitionDuration );
64 m_anim->setStartValue( qreal( 0.0 ) );
65 m_anim->setEndValue( qreal( 1.0 ) );
66 m_anim->setEasingCurve( QEasingCurve::InOutSine );
67 }
68
69 void setSize( const QSize & size )
70 {
71 if ( size != m_size )
72 {
73 m_size = size;
74 m_preview = QPixmap();
75 }
76 }
77
78 virtual QRectF boundingRect() const
79 {
80 return QRectF( QPointF(), m_size );
81 }
82
83 QString label() const
84 {
85 return m_label;
86 }
87
88 int id() const
89 {
90 return m_gameId;
91 }
92
93 void setHighlighted( bool highlighted )
94 {
95 if ( highlighted )
96 {
97 m_anim->setDirection( QAbstractAnimation::Forward );
98 if ( m_anim->state() != QAbstractAnimation::Running )
99 m_anim->start();
100 }
101 else
102 {
103 m_anim->setDirection( QAbstractAnimation::Backward );
104 if ( m_anim->state() != QAbstractAnimation::Running )
105 m_anim->start();
106 }
107 }
108
109 static bool lessThan( const GameSelectionBox * a, const GameSelectionBox * b )
110 {
111 return a->m_label < b->m_label;
112 }
113
114signals:
115 void selected( int gameId );
116 void hoverChanged( GameSelectionBox * box, bool hovered );
117
118protected:
119 virtual void mousePressEvent( QGraphicsSceneMouseEvent * event )
120 {
121 Q_UNUSED( event )
122 emit selected( m_gameId );
123 }
124
125 virtual void hoverEnterEvent( QGraphicsSceneHoverEvent * event )
126 {
127 Q_UNUSED( event )
128 emit hoverChanged( this, true );
129 }
130
131 virtual void hoverLeaveEvent( QGraphicsSceneHoverEvent * event )
132 {
133 Q_UNUSED( event )
134 emit hoverChanged( this, false );
135 }
136
137 qreal hoverFadeAmount() const
138 {
139 return m_highlightFadeAmount;
140 }
141
142 void setHoverFadeAmount( qreal amount )
143 {
144 m_highlightFadeAmount = amount;
145 update();
146 }
147
148 virtual void paint( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 )
149 {
150 Q_UNUSED( option )
151 Q_UNUSED( widget )
152
153 Renderer * r = Renderer::self();
154 int textAreaHeight = m_size.height() * textToBoxHeightRatio;
155 int padding = boxPaddingRatio * m_size.width();
156 QSize previewSize( m_size.height() - padding * 2, m_size.height() - padding * 2 - textAreaHeight );
157 QRect textRect( 0, 0, m_size.width(), textAreaHeight );
158
159 if ( m_highlightFadeAmount < 1 )
160 painter->drawPixmap( 0, 0, r->spritePixmap( "bubble", m_size ) );
161
162 if ( m_highlightFadeAmount > 0 )
163 {
164 if ( m_highlightFadeAmount < 1 )
165 {
166 // Using QPainter::setOpacity is currently very inefficient, so to
167 // paint a semitransparent pixmap, we have to do some fiddling.
168 QPixmap transPix( m_size );
169 transPix.fill( Qt::transparent );
170 QPainter p( &transPix );
171 p.drawPixmap( 0, 0, r->spritePixmap( "bubble_hover", m_size ) );
172 p.setCompositionMode( QPainter::CompositionMode_DestinationIn );
173 p.fillRect( transPix.rect(), QColor( 0, 0, 0, m_highlightFadeAmount * 255 ) );
174 painter->drawPixmap( 0, 0, transPix );
175 }
176 else
177 {
178 painter->drawPixmap( 0, 0, r->spritePixmap( "bubble_hover", m_size ) );
179 }
180 }
181
182 // Draw game preview
183 if ( m_preview.isNull() )
184 {
185 QPixmap previewPix( m_previewPath );
186 m_preview = previewPix.scaled( previewSize, Qt::KeepAspectRatio, Qt::SmoothTransformation );
187 }
188 painter->drawPixmap( ( m_size.width() - previewSize.width() ) / 2,
189 padding + textAreaHeight,
190 m_preview );
191
192 // Draw label
193 painter->setFont( scene()->font() );
194 painter->setPen( KColorUtils::mix( r->colorOfElement( "bubble_text_color" ),
195 r->colorOfElement( "bubble_hover_text_color" ),
196 m_highlightFadeAmount ) );
197 painter->drawText( textRect, Qt::AlignCenter, m_label );
198 }
199
200private:
201 QString m_label;
202 int m_gameId;
203 QSize m_size;
204 QPropertyAnimation * m_anim;
205 qreal m_highlightFadeAmount;
206 QPixmap m_preview;
207 QString m_previewPath;
208};
209
210
211GameSelectionScene::GameSelectionScene( QObject * parent )
212 : QGraphicsScene( parent ),
213 m_selectionIndex( -1 )
214{
215 foreach (const DealerInfo * i, DealerInfoList::self()->games())
216 {
217 GameSelectionBox * box = new GameSelectionBox( i->baseName(), i->baseId() );
218 m_boxes.append( box );
219 addItem( box );
220
221 connect( box, SIGNAL(selected(int)), this, SIGNAL(gameSelected(int)) );
222 connect( box, SIGNAL(hoverChanged(GameSelectionBox*,bool)), this, SLOT(boxHoverChanged(GameSelectionBox*,bool)) );
223 }
224
225 qSort( m_boxes.begin(), m_boxes.end(), GameSelectionBox::lessThan );
226}
227
228
229GameSelectionScene::~GameSelectionScene()
230{
231}
232
233
234void GameSelectionScene::resizeScene( const QSize & size )
235{
236 int numBoxes = m_boxes.size();
237 qreal boxAspect = Renderer::self()->aspectRatioOfElement( "bubble" );
238 qreal sceneAspect = qreal( size.width() ) / size.height();
239
240 // Determine the optimal number of rows/columns for the grid
241 m_columns = 1;
242 int numRows = 1;
243 int bestNumRows = 1;
244 qreal lowestError = 10e10;
245 for ( numRows = 1; numRows <= numBoxes; ++numRows )
246 {
247 m_columns = ceil( qreal( numBoxes ) / numRows );
248 int numNonEmptyRows = ceil( qreal( numBoxes ) / m_columns );
249 qreal gridAspect = boxAspect * m_columns / numNonEmptyRows;
250 qreal error = gridAspect > sceneAspect ? gridAspect / sceneAspect
251 : sceneAspect / gridAspect;
252 if ( error < lowestError )
253 {
254 lowestError = error;
255 bestNumRows = numRows;
256 }
257 }
258 numRows = bestNumRows;
259 m_columns = ceil( qreal( numBoxes ) / bestNumRows );
260
261 // Calculate the box and grid dimensions
262 qreal gridAspect = boxAspect * ( m_columns + spacingRatio * ( m_columns + 1 ) )
263 / ( numRows + spacingRatio * ( numRows + 1 ) );
264 int boxWidth, boxHeight;
265 if ( gridAspect > sceneAspect )
266 {
267 boxWidth = size.width() / ( m_columns + spacingRatio * ( m_columns + 1 ) );
268 boxHeight = boxWidth / boxAspect;
269 }
270 else
271 {
272 boxHeight = size.height() / ( numRows + spacingRatio * ( numRows + 1 ) );
273 boxWidth = boxHeight * boxAspect;
274 }
275 int gridWidth = boxWidth * ( m_columns + spacingRatio * ( m_columns + 1 ) );
276 int gridHeight = boxHeight * ( numRows + spacingRatio * ( numRows + 1 ) );
277
278 int xOffset = (gridWidth - size.width()) / 2 - boxWidth * spacingRatio;
279 int yOffset = (gridHeight - size.height()) / 2 - boxHeight * spacingRatio;
280
281 // Set up the sceneRect so that the grid is centered
282 setSceneRect( xOffset, yOffset, size.width(), size.height() );
283
284 QPixmap pix( 1, 1 );
285 QPainter p( &pix );
286 QFont f;
287
288 // Initial font size estimate
289 int pixelFontSize = boxHeight * (textToBoxHeightRatio - 1.5 * boxPaddingRatio);
290 f.setPixelSize( pixelFontSize );
291 p.setFont( f );
292
293 qreal maxLabelWidth = boxWidth * textToBoxWidthRatio;
294 int row = 0;
295 int col = 0;
296
297 foreach ( GameSelectionBox * box, m_boxes )
298 {
299 // Reduce font size until the label fits
300 while ( pixelFontSize > minimumFontSize
301 && p.boundingRect( QRectF(), box->label() ).width() > maxLabelWidth )
302 {
303 f.setPixelSize( --pixelFontSize );
304 p.setFont( f );
305 }
306
307 // Position and size the boxes
308 box->setPos( col * ( boxWidth * ( 1 + spacingRatio ) ),
309 row * ( boxHeight * ( 1 + spacingRatio ) ) );
310 box->setSize( QSize( boxWidth, boxHeight ) );
311
312 // Increment column and row
313 ++col;
314 if ( col == m_columns )
315 {
316 col = 0;
317 ++row;
318 }
319 }
320
321 setFont( f );
322}
323
324
325void GameSelectionScene::keyReleaseEvent( QKeyEvent * event )
326{
327 if ( m_selectionIndex == -1 )
328 {
329 m_selectionIndex = 0;
330 m_boxes.at( m_selectionIndex )->setHighlighted( true );
331 }
332 else if ( event->key() == Qt::Key_Up
333 && m_selectionIndex / m_columns > 0 )
334 {
335 m_boxes.at( m_selectionIndex )->setHighlighted( false );
336 m_selectionIndex -= m_columns;
337 m_boxes.at( m_selectionIndex )->setHighlighted( true );
338 }
339 else if ( event->key() == Qt::Key_Down
340 && m_selectionIndex + m_columns < m_boxes.size() )
341 {
342 m_boxes.at( m_selectionIndex )->setHighlighted( false );
343 m_selectionIndex += m_columns;
344 m_boxes.at( m_selectionIndex )->setHighlighted( true );
345 }
346 else if ( event->key() == Qt::Key_Left
347 && m_selectionIndex % m_columns > 0 )
348 {
349 m_boxes.at( m_selectionIndex )->setHighlighted( false );
350 --m_selectionIndex;
351 m_boxes.at( m_selectionIndex )->setHighlighted( true );
352 }
353 else if ( event->key() == Qt::Key_Right
354 && m_selectionIndex % m_columns < m_columns - 1
355 && m_selectionIndex < m_boxes.size() - 1 )
356 {
357 m_boxes.at( m_selectionIndex )->setHighlighted( false );
358 ++m_selectionIndex;
359 m_boxes.at( m_selectionIndex )->setHighlighted( true );
360 }
361 else if ( ( event->key() == Qt::Key_Return
362 || event->key() ==Qt::Key_Enter
363 || event->key() ==Qt::Key_Space )
364 && m_selectionIndex != -1 )
365 {
366 emit gameSelected( m_boxes.at( m_selectionIndex )->id() );
367 }
368}
369
370
371void GameSelectionScene::boxHoverChanged( GameSelectionScene::GameSelectionBox * box, bool hovered )
372{
373 if ( hovered )
374 {
375 if ( m_selectionIndex != -1 )
376 m_boxes.at( m_selectionIndex )->setHighlighted( false );
377
378 m_selectionIndex = m_boxes.indexOf( box );
379 box->setHighlighted( true );
380 }
381 else
382 {
383 if ( m_boxes.indexOf( box ) == m_selectionIndex )
384 {
385 m_selectionIndex = -1;
386 box->setHighlighted( false );
387 }
388 }
389}
390
391
392#include "gameselectionscene.moc"
393#include "moc_gameselectionscene.cpp"
394