1 | /* |
2 | * Copyright 2007-2009 Parker Coates <coates@kde.org> |
3 | * |
4 | * This file is part of Killbots. |
5 | * |
6 | * Killbots is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation, either version 2 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * Killbots is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with Killbots. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "scene.h" |
21 | |
22 | #include "numericdisplayitem.h" |
23 | #include "renderer.h" |
24 | #include "settings.h" |
25 | |
26 | #include <KGamePopupItem> |
27 | |
28 | #include <KDE/KDebug> |
29 | |
30 | #include <QtGui/QPainter> |
31 | #include <QtGui/QGraphicsSceneMouseEvent> |
32 | #include <QtGui/QGraphicsView> |
33 | |
34 | #include <cmath> |
35 | |
36 | |
37 | Killbots::Scene::Scene( QObject * parent ) |
38 | : QGraphicsScene( parent ), |
39 | m_hero( 0 ), |
40 | m_rows( 0 ), |
41 | m_columns( 0 ) |
42 | { |
43 | setItemIndexMethod( QGraphicsScene::NoIndex ); |
44 | } |
45 | |
46 | |
47 | Killbots::Scene::~Scene() |
48 | { |
49 | } |
50 | |
51 | |
52 | void Killbots::Scene::addNumericDisplay( NumericDisplayItem * displayItem ) |
53 | { |
54 | addItem( displayItem ); |
55 | displayItem->setPos( -1000000, 0 ); |
56 | m_numericDisplays << displayItem; |
57 | } |
58 | |
59 | |
60 | void Killbots::Scene::setGridSize(int rows, int columns) |
61 | { |
62 | if ( m_rows != rows || m_columns != columns ) |
63 | { |
64 | m_rows = rows; |
65 | m_columns = columns; |
66 | } |
67 | } |
68 | |
69 | |
70 | void Killbots::Scene::forgetHero() |
71 | { |
72 | m_hero = 0; |
73 | } |
74 | |
75 | |
76 | Killbots::Sprite * Killbots::Scene::createSprite( SpriteType type, QPoint position ) |
77 | { |
78 | Sprite * sprite = new Sprite(); |
79 | sprite->setSpriteType( type ); |
80 | sprite->setRenderSize( m_cellSize ); |
81 | sprite->enqueueGridPos( position ); |
82 | updateSpritePos( sprite, position ); |
83 | sprite->scale( 0, 0 ); |
84 | // A bit of a hack, but we use the sprite type for stacking order. |
85 | sprite->setZValue( type ); |
86 | |
87 | |
88 | addItem( sprite ); |
89 | |
90 | if ( type == Hero ) |
91 | m_hero = sprite; |
92 | |
93 | return sprite; |
94 | } |
95 | |
96 | |
97 | void Killbots::Scene::animateSprites( const QList<Sprite *> & newSprites, |
98 | const QList<Sprite *> & slidingSprites, |
99 | const QList<Sprite *> & teleportingSprites, |
100 | const QList<Sprite *> & destroyedSprites, |
101 | qreal value |
102 | ) const |
103 | { |
104 | static bool halfDone = false; |
105 | |
106 | if ( value == 0.0 ) |
107 | { |
108 | halfDone = false; |
109 | } |
110 | else if ( value < 1.0 ) |
111 | { |
112 | foreach ( Sprite * sprite, newSprites ) |
113 | { |
114 | sprite->resetTransform(); |
115 | sprite->scale( value, value ); |
116 | } |
117 | |
118 | foreach ( Sprite * sprite, slidingSprites ) |
119 | { |
120 | QPointF posInGridCoordinates = value * QPointF( sprite->nextGridPos() - sprite->currentGridPos() ) + sprite->currentGridPos(); |
121 | sprite->setPos( QPointF( posInGridCoordinates.x() * m_cellSize.width(), posInGridCoordinates.y() * m_cellSize.height() ) ); |
122 | } |
123 | |
124 | qreal scaleFactor = value < 0.5 |
125 | ? 1.0 - 2 * value |
126 | : 2 * value - 1.0; |
127 | |
128 | if ( !halfDone && value >= 0.5 ) |
129 | { |
130 | halfDone = true; |
131 | foreach ( Sprite * sprite, teleportingSprites ) |
132 | updateSpritePos( sprite, sprite->nextGridPos() ); |
133 | } |
134 | |
135 | foreach ( Sprite * sprite, teleportingSprites ) |
136 | { |
137 | sprite->resetTransform(); |
138 | sprite->scale( scaleFactor, scaleFactor ); |
139 | } |
140 | |
141 | foreach ( Sprite * sprite, destroyedSprites ) |
142 | { |
143 | sprite->resetTransform(); |
144 | sprite->scale( 1 - value, 1 - value ); |
145 | sprite->rotate( value * 180 ); |
146 | } |
147 | } |
148 | else |
149 | { |
150 | foreach ( Sprite * sprite, newSprites + slidingSprites + teleportingSprites ) |
151 | { |
152 | sprite->resetTransform(); |
153 | sprite->advanceGridPosQueue(); |
154 | updateSpritePos( sprite, sprite->currentGridPos() ); |
155 | } |
156 | |
157 | qDeleteAll( destroyedSprites ); |
158 | } |
159 | } |
160 | |
161 | |
162 | void Killbots::Scene::doLayout() |
163 | { |
164 | QSize size = views().first()->size(); |
165 | |
166 | // If no game has been started |
167 | if ( m_rows == 0 || m_columns == 0 ) |
168 | { |
169 | setSceneRect( QRectF( QPointF( 0, 0 ), size ) ); |
170 | return; |
171 | } |
172 | |
173 | kDebug() << "Laying out scene at" << size; |
174 | |
175 | // Make certain layout properties proportional to the scene height, |
176 | // but clamp them between reasonable values. There's probably room for more |
177 | // tweaking here. |
178 | const int baseDimension = qMin( size.width(), size.height() ) / 35; |
179 | const int spacing = qBound( 5, baseDimension, 15 ); |
180 | const int newFontPixelSize = qBound( QFontInfo( QFont() ).pixelSize(), baseDimension, 25 ); |
181 | const qreal aspectRatio = Renderer::self()->aspectRatio(); |
182 | |
183 | QSize displaySize; |
184 | // If the font size has changed, resize all the displays (visible or not). |
185 | if ( m_numericDisplays.first()->font().pixelSize() != newFontPixelSize ) |
186 | { |
187 | QFont font; |
188 | font.setPixelSize( newFontPixelSize ); |
189 | |
190 | foreach ( NumericDisplayItem * display, m_numericDisplays ) |
191 | { |
192 | display->setFont( font ); |
193 | displaySize = displaySize.expandedTo( display->preferredSize() ); |
194 | } |
195 | foreach ( NumericDisplayItem * display, m_numericDisplays ) |
196 | display->setRenderSize( displaySize ); |
197 | } |
198 | else |
199 | { |
200 | displaySize = m_numericDisplays.first()->boundingRect().size().toSize(); |
201 | } |
202 | |
203 | // The rest of the function deals only with a list of visible displays. |
204 | QList<NumericDisplayItem *> visibleDisplays; |
205 | foreach ( NumericDisplayItem * display, m_numericDisplays ) |
206 | { |
207 | if ( display->isVisible() ) |
208 | visibleDisplays << display; |
209 | } |
210 | |
211 | // Determine the total width required to arrange the displays horizontally. |
212 | const int widthOfDisplaysOnTop = visibleDisplays.size() * displaySize.width() |
213 | + ( visibleDisplays.size() - 1 ) * spacing; |
214 | |
215 | // The displays can either be placed centred, across the top of the |
216 | // scene or top-aligned, down the side of the scene. We first calculate |
217 | // what the cell size would be for both options. |
218 | int availableWidth = size.width() - 3 * spacing - displaySize.width(); |
219 | int availableHeight = size.height() - 2 * spacing; |
220 | const qreal cellWidthSide = ( availableWidth / m_columns < availableHeight / m_rows * aspectRatio ) |
221 | ? availableWidth / m_columns |
222 | : availableHeight / m_rows * aspectRatio; |
223 | |
224 | availableWidth = size.width() - 2 * spacing; |
225 | availableHeight = size.height() - 3 * spacing - displaySize.height(); |
226 | const qreal cellWidthTop = ( availableWidth / m_columns < availableHeight / m_rows * aspectRatio ) |
227 | ? availableWidth / m_columns |
228 | : availableHeight / m_rows * aspectRatio; |
229 | |
230 | // If placing the displays on top would result in larger cells, we take |
231 | // that option, but only if the displays would actually fit. |
232 | const bool displaysOnTop = ( cellWidthTop > cellWidthSide && size.width() > widthOfDisplaysOnTop ); |
233 | const qreal newCellWidth = displaysOnTop ? cellWidthTop : cellWidthSide; |
234 | m_cellSize = QSize( qRound( newCellWidth ), qRound( newCellWidth / aspectRatio ) ); |
235 | |
236 | foreach ( QGraphicsItem * item, items() ) |
237 | { |
238 | Sprite * sprite = qgraphicsitem_cast<Sprite *>( item ); |
239 | if ( sprite ) |
240 | { |
241 | sprite->setRenderSize( m_cellSize ); |
242 | updateSpritePos( sprite, sprite->currentGridPos() ); |
243 | } |
244 | } |
245 | |
246 | if ( displaysOnTop ) |
247 | { |
248 | // Set the sceneRect to centre the grid if possible, but ensure the display items are visible |
249 | const qreal sceneRectXPos = -( size.width() - m_cellSize.width() * ( m_columns - 1 ) ) / 2.0; |
250 | const qreal centeredYPos = - ( size.height() - m_cellSize.height() * ( m_rows - 1 ) ) / 2.0; |
251 | const qreal indentedYPos = - ( m_cellSize.height() / 2.0 + 2 * spacing + displaySize.height() ); |
252 | const qreal sceneRectYPos = qMin( centeredYPos, indentedYPos ); |
253 | |
254 | // Position the display items centered at the top of the scene |
255 | const qreal displayYPos = ( sceneRectYPos - ( displaySize.height() + m_cellSize.height() / 2.0 ) ) / 2; |
256 | |
257 | int xPos = sceneRectXPos + ( size.width() - widthOfDisplaysOnTop ) / 2.0; |
258 | foreach ( NumericDisplayItem * display, visibleDisplays ) |
259 | { |
260 | display->setPos( xPos, displayYPos ); |
261 | xPos += displaySize.width() + spacing; |
262 | } |
263 | |
264 | setSceneRect( QRectF( sceneRectXPos, sceneRectYPos, size.width(), size.height() ) ); |
265 | } |
266 | else |
267 | { |
268 | qreal sceneRectXPos; |
269 | const qreal centeredXPos = - ( size.width() - m_cellSize.width() * ( m_columns - 1 ) ) / 2.0; |
270 | const qreal sceneRectYPos = -( size.height() - m_cellSize.height() * ( m_rows - 1 ) ) / 2.0; |
271 | qreal displayXPos; |
272 | |
273 | // If the application layout is LTR, place the displays on left, |
274 | // otherwise, place them on the right. |
275 | if ( views().first()->layoutDirection() == Qt::LeftToRight ) |
276 | { |
277 | // Set the sceneRect to centre the grid if possible, but ensure the display items are visible |
278 | const qreal indentedXPos = - ( m_cellSize.width() / 2.0 + 2 * spacing + displaySize.width() ); |
279 | sceneRectXPos = qMin( centeredXPos, indentedXPos ); |
280 | |
281 | // Position the display items to the left of the grid |
282 | displayXPos = - ( spacing + displaySize.width() + m_cellSize.width() / 2 ); |
283 | } |
284 | else |
285 | { |
286 | // Set the sceneRect to centre the grid if possible, but ensure the display items are visible |
287 | const qreal indentedXPos = ( m_cellSize.width() * m_columns + 1 * spacing + displaySize.width() ) - size.width(); |
288 | sceneRectXPos = qMax( centeredXPos, indentedXPos ); |
289 | |
290 | // Position the display items to the right of the grid |
291 | displayXPos = m_cellSize.width() * ( m_columns - 0.5 ) + spacing; |
292 | } |
293 | |
294 | int yPos = -m_cellSize.height() / 2; |
295 | foreach ( NumericDisplayItem * display, visibleDisplays ) |
296 | { |
297 | display->setPos( displayXPos, yPos ); |
298 | yPos += displaySize.height() + spacing; |
299 | } |
300 | |
301 | setSceneRect( QRectF( sceneRectXPos, sceneRectYPos, size.width(), size.height() ) ); |
302 | } |
303 | |
304 | // Update the scene background |
305 | QPainter p; |
306 | QRect gridRect( -sceneRect().x() - m_cellSize.width() / 2, |
307 | -sceneRect().y() - m_cellSize.height() / 2, |
308 | m_columns * m_cellSize.width(), |
309 | m_rows * m_cellSize.height() |
310 | ); |
311 | |
312 | QPixmap unrotated = Renderer::self()->spritePixmap( "background" , size ); |
313 | p.begin( &unrotated ); |
314 | p.drawTiledPixmap( gridRect, Renderer::self()->spritePixmap( "cell" , m_cellSize ) ); |
315 | p.end(); |
316 | |
317 | // The background brush begins painting at 0,0 but our sceneRect doesn't |
318 | // start at 0,0 so we have to "rotate" the pixmap so that it looks right |
319 | // when tiled. |
320 | QPixmap background( size ); |
321 | background.fill( Qt::transparent ); |
322 | p.begin( &background ); |
323 | p.drawTiledPixmap( background.rect(), unrotated, -sceneRect().topLeft().toPoint() ); |
324 | p.end(); |
325 | |
326 | setBackgroundBrush( background ); |
327 | |
328 | update(); |
329 | } |
330 | |
331 | |
332 | void Killbots::Scene::mouseMoveEvent( QGraphicsSceneMouseEvent * event ) |
333 | { |
334 | getMouseDirection( event->scenePos() ); |
335 | QGraphicsScene::mouseMoveEvent( event ); |
336 | } |
337 | |
338 | |
339 | void Killbots::Scene::mouseReleaseEvent( QGraphicsSceneMouseEvent * event ) |
340 | { |
341 | HeroAction actionFromPosition = getMouseDirection( event->scenePos() ); |
342 | |
343 | if ( actionFromPosition != NoAction ) |
344 | { |
345 | Settings::ClickAction userAction = Settings::Nothing; |
346 | |
347 | if ( event->button() == Qt::LeftButton ) |
348 | { |
349 | if ( event->modifiers() & Qt::ControlModifier ) |
350 | userAction = Settings::middleClickAction(); |
351 | else |
352 | userAction = Settings::Step; |
353 | } |
354 | else if ( event->button() == Qt::RightButton ) |
355 | userAction = Settings::rightClickAction(); |
356 | else if ( event->button() == Qt::MidButton ) |
357 | userAction = Settings::middleClickAction(); |
358 | |
359 | if ( userAction == Settings::Step ) |
360 | emit clicked( actionFromPosition ); |
361 | else if ( userAction == Settings::RepeatedStep ) |
362 | emit clicked( -actionFromPosition - 1 ); |
363 | else if ( userAction == Settings::Teleport ) |
364 | emit clicked( Teleport ); |
365 | else if ( userAction == Settings::TeleportSafely ) |
366 | emit clicked( TeleportSafely ); |
367 | else if ( userAction == Settings::TeleportSafelyIfPossible ) |
368 | emit clicked( TeleportSafelyIfPossible ); |
369 | else if ( userAction == Settings::WaitOutRound ) |
370 | emit clicked( WaitOutRound ); |
371 | } |
372 | |
373 | QGraphicsScene::mouseReleaseEvent( event ); |
374 | } |
375 | |
376 | |
377 | Killbots::HeroAction Killbots::Scene::getMouseDirection( QPointF cursorPosition ) const |
378 | { |
379 | HeroAction result; |
380 | const bool heroOnScreen = m_hero && sceneRect().contains( m_hero->sceneBoundingRect() ); |
381 | |
382 | if ( heroOnScreen && !popupAtPosition( cursorPosition ) ) |
383 | { |
384 | if ( m_hero->sceneBoundingRect().contains( cursorPosition ) ) |
385 | result = Hold; |
386 | else |
387 | { |
388 | const qreal piOver4 = 0.78539816339744830961566L; |
389 | |
390 | QPointF delta = cursorPosition - m_hero->sceneBoundingRect().center(); |
391 | int direction = qRound( atan2( -delta.y(), delta.x() ) / piOver4 ); |
392 | if ( direction < 0 ) |
393 | direction += 8; |
394 | |
395 | result = static_cast<HeroAction>( direction ); |
396 | } |
397 | |
398 | views().first()->setCursor( Renderer::self()->cursorFromAction( result ) ); |
399 | } |
400 | else |
401 | { |
402 | views().first()->unsetCursor(); |
403 | result = NoAction; |
404 | } |
405 | |
406 | return result; |
407 | } |
408 | |
409 | |
410 | bool Killbots::Scene::( QPointF position ) const |
411 | { |
412 | foreach ( QGraphicsItem * item, items( position ) ) |
413 | { |
414 | if ( dynamic_cast<KGamePopupItem *>( item ) != 0 ) |
415 | return true; |
416 | } |
417 | return false; |
418 | } |
419 | |
420 | |
421 | void Killbots::Scene::updateSpritePos( Sprite * sprite, QPoint gridPosition ) const |
422 | { |
423 | sprite->setPos( gridPosition.x() * m_cellSize.width(), gridPosition.y() * m_cellSize.height() ); |
424 | } |
425 | |
426 | #include "moc_scene.cpp" |
427 | |