Fix (lots of) memory leaks in Game.cpp
[numtypysics.git] / Overlay.cpp
blob58fa9fbaf642889a0d37244e892c9df1e6d5152f
1 /*
2 * This file is part of NumptyPhysics
3 * Copyright (C) 2008 Tim Edmonds
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 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
17 #include "Overlay.h"
18 #include "Canvas.h"
19 #include "Game.h"
20 #include "Config.h"
21 #include "Path.h"
22 #include "Font.h"
23 //#include "Worker.h"
25 #include <SDL/SDL.h>
26 #include <string>
28 extern Rect FULLSCREEN_RECT;
30 class OverlayBase : public Overlay
32 public:
33 OverlayBase( GameControl& game, int x=10, int y=10, bool dragging_allowed=true )
34 : m_game(game),
35 m_x(x), m_y(y),
36 m_canvas(NULL),
37 m_isDirty(true),
38 m_dragging(false),
39 m_dragging_allowed(dragging_allowed),
40 m_buttonDown(false)
42 virtual ~OverlayBase()
44 delete m_canvas;
47 bool isDirty()
49 return m_isDirty;
52 Rect dirtyArea()
54 if ( m_isDirty ) {
55 return Rect(m_x,m_y,m_x+m_canvas->width()-1,m_y+m_canvas->height()-1);
56 } else {
57 return Rect(0,0,0,0);
61 virtual void onShow() { dirty(); }
62 virtual void onHide() {}
63 virtual void onTick( int tick ) {}
65 virtual void draw( Canvas& screen, const Rect& area )
67 if ( m_canvas ) {
68 screen.drawImage( m_canvas, m_x, m_y );
70 m_isDirty = false;
73 virtual bool handleEvent( SDL_Event& ev )
75 switch( ev.type ) {
76 case SDL_MOUSEBUTTONDOWN:
77 //printf("overlay click\n");
78 if ( ev.button.button == SDL_BUTTON_LEFT
79 && ev.button.x >= m_x && ev.button.x <= m_x + m_canvas->width()
80 && ev.button.y >= m_y && ev.button.y <= m_y + m_canvas->height() ) {
81 m_orgx = ev.button.x;
82 m_orgy = ev.button.y;
83 m_prevx = ev.button.x;
84 m_prevy = ev.button.y;
85 m_buttonDown = true;
86 return true;
88 break;
89 case SDL_MOUSEBUTTONUP:
90 if ( ev.button.button == SDL_BUTTON_LEFT ) {
91 if ( m_dragging ) {
92 m_dragging = false;
93 onDragStop( ev.button.x-m_x, ev.button.y-m_y );
94 } else if ( Abs(ev.button.x-m_orgx) < CLICK_TOLERANCE
95 && Abs(ev.button.y-m_orgy) < CLICK_TOLERANCE ){
96 onClick( m_orgx-m_x, m_orgy-m_y );
98 m_buttonDown = false;
100 break;
101 case SDL_MOUSEMOTION:
102 if ( !m_dragging
103 && m_buttonDown
104 && m_dragging_allowed
105 && ( Abs(ev.button.x-m_orgx) >= CLICK_TOLERANCE
106 || Abs(ev.button.y-m_orgy) >= CLICK_TOLERANCE ) ) {
107 m_dragging = true;
108 onDragStart( ev.button.x-m_x, ev.button.y-m_y );
109 } else if ( m_dragging ) {
110 onDrag( ev.button.x - m_prevx, ev.button.y - m_prevy );
111 m_prevx = ev.button.x;
112 m_prevy = ev.button.y;
114 break;
115 case SDL_KEYDOWN:
116 return onKey( ev.key.keysym.sym );
118 default:
119 break;
121 return false;
124 protected:
125 void dirty()
127 m_isDirty = true;
130 void dirty( const Rect& r )
132 m_isDirty = true;
135 virtual bool onKey( int k )
137 return false;
139 virtual bool onDragStart( int x, int y )
141 return false;
143 virtual bool onDragStop( int x, int y )
145 return false;
147 virtual bool onDrag( int dx, int dy )
149 m_x += dx;
150 m_y += dy;
151 m_game.m_refresh = true;
152 return false;
155 virtual bool onClick( int x, int y ) {
156 for ( int i=m_hotSpots.size()-1; i>=0; i-- ) {
157 if ( m_hotSpots[i].rect.contains( Vec2(x,y) ) ) {
158 if ( m_hotSpots[i].c ) {
159 m_hotSpots[i].c(m_hotSpots[i].o,m_hotSpots[i].f);
160 return true;
161 } else if ( onHotSpot( m_hotSpots[i].id ) ) {
162 return true;
166 return false;
169 virtual bool onHotSpot( int id ) { return false; }
171 void addHotSpot( const Rect& r, int id )
173 HotSpot hs = { r, id, NULL, NULL, NULL };
174 m_hotSpots.append( hs );
177 template<class C>
178 void addHotSpot( const Rect& r, void (C::*func)() )
180 struct Caller {
181 static void call( void* o, void (Overlay::*f)() ) {
182 return (((C*)o)->*(void (C::*)())f)();
185 HotSpot hs = { r, -1, this, (void (Overlay::*)())func, Caller::call };
186 m_hotSpots.append( hs );
189 struct HotSpot
191 Rect rect;
192 int id;
193 void *o;
194 void (Overlay::*f)();
195 void (*c)(void*,void (Overlay::*)());
197 GameControl& m_game;
198 int m_x, m_y;
199 Canvas *m_canvas;
200 private:
201 int m_orgx, m_orgy;
202 int m_prevx, m_prevy;
203 bool m_isDirty;
204 bool m_dragging;
205 bool m_dragging_allowed;
206 bool m_buttonDown;
207 Array<HotSpot> m_hotSpots;
211 class IconOverlay: public OverlayBase
213 std::string m_filename;
214 public:
215 IconOverlay(GameControl& game, const char* file, int x=100,int y=20, bool dragging_allowed=true)
216 : OverlayBase(game,x,y,dragging_allowed),
217 m_filename( file )
219 m_canvas = new Image( m_filename.c_str() );
224 class UiOverlay : public OverlayBase
226 public:
227 UiOverlay(GameControl& game, const Rect& pos)
228 : OverlayBase( game, pos.tl.x, pos.tl.y, true )
229 , m_bgColour(0xf0f0a0)
230 , m_fgColour(0)
231 , m_motion(0)
233 m_canvas = new Canvas( pos.width(), pos.height() );
234 m_canvas->setBackground( m_bgColour );
235 m_canvas->drawRect( 0, 0, pos.width(), pos.height(),
236 m_canvas->makeColour(0xffffff) );
237 m_canvas->drawRect( 3, 3, pos.width()-6, pos.height()-6,
238 m_canvas->makeColour(m_bgColour) );
241 void moveTo( const Vec2& dest )
243 if ( dest != Vec2(m_x,m_y) ) {
244 m_motion = 10;
245 m_dest = dest;
249 void onTick( int tick )
251 if ( m_motion > 0 ) {
252 Vec2 pos(m_x,m_y);
253 pos *= m_motion-1;
254 pos += m_dest;
255 pos = pos /m_motion;
256 m_x = pos.x;
257 m_y = pos.y;
258 m_motion--;
259 m_game.m_refresh = 1;
263 template<class C>
264 void addButton( const char* icon, const Rect& r, void (C::*func)() )
266 if ( Config::findFile(icon) != icon ) {
267 Image *i = new Image(icon);
268 m_canvas->drawImage( i, r.tl.x, r.tl.y );
269 delete i;
270 } else {
271 Path p(icon);
272 if ( p.size() > 1 ) {
273 p.translate( r.tl - p.bbox().tl );
274 m_canvas->drawPath( p, m_fgColour, false );
275 } else {
276 Font::headingFont()->drawCenter( m_canvas, r.centroid(),
277 icon, m_fgColour );
280 addHotSpot( r, func );
282 int m_bgColour;
283 int m_fgColour;
284 private:
285 int m_motion;
286 Vec2 m_dest;
290 class ListOverlay : public UiOverlay
292 public:
293 ListOverlay( GameControl& game,
294 ListProvider* provider,
295 int x, int y,
296 int w, int h )
297 : UiOverlay(game,Rect(x,y,x+w,y+h)),
298 m_provider( provider ),
299 m_first( 0 ),
300 m_current( 0 ),
301 m_vgap( 2 ),
302 m_hgap( 2 ),
303 m_offset(0,0),
304 m_velocity(0,0),
305 m_listarea( m_hgap, m_vgap, w-m_hgap, h-m_vgap )
307 Canvas *first = m_provider->provideItem( m_first, NULL );
308 m_itemh = first->height();
309 m_numvis = m_listarea.height() / (m_itemh + m_vgap) + 2;
311 if ( m_numvis <= m_provider->countItems() ) {
312 m_allowscroll = false;
313 } else {
314 m_allowscroll = true;
315 m_listarea.tl.y += 20;
316 m_listarea.br.y -= 20;
317 m_numvis = m_listarea.height() / (m_itemh + m_vgap);
318 addButton( "Back", Rect(0,0,w,20), &ListOverlay::doBack );
319 addButton( "Forward", Rect(0,y-20,w,20), &ListOverlay::doForward );
322 m_items = (Canvas**)calloc( sizeof(Canvas*), m_numvis);
323 m_items[0] = first;
325 for ( int i=1; i<m_numvis; i++ ) {
326 m_items[i] = m_provider->provideItem( i, NULL );
328 renderItems();
331 ~ListOverlay()
333 for ( int i=0; i<m_numvis; i++ ) {
334 if ( m_items[0] ) {
335 m_provider->releaseItem( m_items[0] );
340 void renderItems()
342 m_canvas->clear( m_listarea );
343 for ( int i=0; i < m_numvis; i++ ) {
344 int q = m_first + i;
345 if ( q < m_provider->countItems() ) {
346 m_canvas->drawImage( m_items[q%m_numvis],
347 m_listarea.tl.x + m_offset.x,
348 m_listarea.tl.y + m_offset.y + (m_itemh+m_vgap)*i );
350 if ( q == m_current ) {
351 m_canvas->drawRect( m_listarea.tl.x-1 + m_offset.x,
352 m_listarea.tl.y + (m_itemh+m_vgap)*i - 1 + m_offset.y,
353 m_items[q%m_numvis]->width()+2,
354 m_items[q%m_numvis]->height()+2,
355 m_fgColour,
356 false );
359 dirty();
362 void onTick( int tick )
364 UiOverlay::onTick( tick );
365 if ( m_velocity.y ) {
366 m_offset += m_velocity;
367 while ( m_offset.y < -m_itemh-m_vgap ) {
368 m_offset.y += m_itemh+m_vgap;
369 doForward();
371 while ( m_offset.y >= m_itemh+m_vgap ) {
372 m_offset.y -= m_itemh+m_vgap;
373 doBack();
375 if ( m_velocity.y > 0 ) m_velocity.y--;
376 if ( m_velocity.y < 0 ) m_velocity.y++;
377 m_offset.x = 0;
378 renderItems();
382 virtual bool onClick( int x, int y )
384 int i = m_first + (y - m_vgap) / (m_itemh + m_vgap);
385 if ( i >= 0 && i < m_provider->countItems() ) {
386 m_current = i;
387 m_provider->onSelection( m_current,
388 x - m_listarea.tl.x,
389 y - m_listarea.tl.y - (m_itemh+m_vgap)*i );
390 renderItems();
394 virtual bool onKey( int k )
396 switch (k) {
397 case SDLK_UP:
398 if ( m_current > 0 ) {
399 m_current--;
400 if ( m_current < m_first ) {
401 doBack();
403 renderItems();
405 return true;
406 case SDLK_DOWN:
407 if ( m_current < m_provider->countItems()-1 ) {
408 m_current++;
409 if ( m_current >= m_first+m_numvis-1 ) {
410 doForward();
412 renderItems();
414 return true;
416 return false;
419 bool onDrag( int dx, int dy )
421 m_velocity.x = dx;
422 m_velocity.y = dy;
425 void doBack()
427 if ( m_first > 0 ) {
428 m_first--;
429 m_items[m_first%m_numvis] = m_provider->provideItem( m_first,
430 m_items[m_first%m_numvis] );
434 void doForward()
436 int last = m_first + m_numvis - 1;
437 if ( last < m_provider->countItems() ) {
438 m_first++;
439 last = m_first + m_numvis - 1;
440 m_items[last%m_numvis] = m_provider->provideItem( last, m_items[last%m_numvis] );
444 private:
445 ListProvider *m_provider;
446 int m_first;
447 int m_current;
448 int m_numvis;
449 int m_itemh;
450 int m_hgap, m_vgap;
451 bool m_allowscroll;
452 Rect m_listarea;
453 Vec2 m_offset;
454 Vec2 m_velocity;
455 Canvas** m_items;
459 class MenuOverlay : public UiOverlay
461 public:
462 MenuOverlay(GameControl& game)
463 : UiOverlay(game,Rect(100,100,400,200))
465 addButton( "48,18 47,25 48,25 47,27 48,27 47,29 48,29 47,31 48,33 47,34 48,35 47,40 48,45 49,43 48,43 49,41 48,39 49,38 48,37 47,36 50,34 49,31 44,31 42,30 40,30 36,32 37,33 36,38 35,45 34,47 35,53 34,54 36,56 37,58 38,59 41,59 42,61 43,62 45,62 47,63 49,63 51,64 59,64 61,62 63,62 64,60 66,60 68,58 69,54 70,52 69,52 68,46 67,46 66,42 63,36 62,36 61,34 60,33 59,33 58,32 54,32 53,31 50,31 54,31 53,33", Rect(10,10,90,90), &MenuOverlay::doPause );
467 void doPause() {
468 m_game.gotoLevel( m_game.m_level + 1 );
471 void onShow() { moveTo(Vec2(200,200)); }
475 class NextLevelOverlay : public UiOverlay
477 public:
478 NextLevelOverlay( GameControl& game, int winner )
479 : UiOverlay(game,Rect(FULLSCREEN_RECT.centroid().x-200,
480 FULLSCREEN_RECT.centroid().y-120,
481 FULLSCREEN_RECT.centroid().x+200,
482 FULLSCREEN_RECT.centroid().y+120 ) ),
483 m_levelIcon(-2),
484 m_winner(winner),
485 m_icon(NULL)
487 char text[15];
488 if (winner == -1)
490 snprintf(text, 15, "BRAVO!");
492 else
494 snprintf(text, 15, "Player %d wins!", winner+1);
497 Font::titleFont()->drawCenter( m_canvas, Vec2(200,32), text, 0 );
499 addHotSpot( Rect(0, 0,100,180), &NextLevelOverlay::doPrevLevel );
500 addHotSpot( Rect(300, 0,400,180), &NextLevelOverlay::doNextLevel );
502 addButton( "<<", Rect(3,120,40,160),
503 &NextLevelOverlay::doPrevLevel );
504 addButton( "Action Replay", Rect(0,180,200,240),
505 &NextLevelOverlay::doActionReplay );
506 addButton( "Continue", Rect(200,180,400,240),
507 &NextLevelOverlay::doContinue );
509 ~NextLevelOverlay()
511 delete m_icon;
513 virtual void onShow()
515 UiOverlay::onShow();
516 m_game.m_refresh = true; //for fullscreen fade
517 m_game.m_fade = true;
518 m_selectedLevel = m_game.m_level+1;
520 virtual void onHide()
522 UiOverlay::onHide();
523 m_game.m_refresh = true; //for fullscreen fade
524 m_game.m_fade = false;
526 virtual void draw( Canvas& screen, const Rect& area )
528 UiOverlay::draw( screen, area );
529 if ( genIcon() ) {
530 Font::blurbFont()->drawCenter( &screen, Vec2(m_x+200,m_y+70),
531 m_game.levels().levelName(m_selectedLevel), 0 );
532 screen.drawImage( m_icon, m_x+100, m_y+75 );
533 } else {
534 dirty();
537 void doPrevLevel()
539 if (m_selectedLevel>0) {
540 m_selectedLevel--;
541 dirty();
544 void doNextLevel()
546 m_selectedLevel++;
547 dirty();
549 void doActionReplay()
551 m_game.gotoLevel( m_game.m_level,true );
553 void doContinue()
555 m_game.gotoLevel( m_selectedLevel );
557 private:
558 bool genIcon()
560 if ( m_icon==NULL || m_levelIcon != m_selectedLevel ) {
562 //Worker w( NULL, 1, 100, 200 );
564 delete m_icon;
565 m_icon = NULL;
566 if ( m_selectedLevel < m_game.levels().numLevels() ) {
567 Canvas temp( SCREEN_WIDTH, SCREEN_HEIGHT );
568 if ( m_game.renderScene( temp, m_selectedLevel) ) {
569 m_icon = temp.scale( ICON_SCALE_FACTOR );
571 } else {
572 m_icon = new Image("theend.png");
573 m_caption = "no more levels!";
574 m_selectedLevel = m_game.levels().numLevels();
576 m_levelIcon = m_selectedLevel;
578 return m_icon;
580 int m_selectedLevel;
581 int m_levelIcon;
582 int m_winner;
583 Canvas* m_icon;
584 std::string m_caption;
589 class EditOverlay : public IconOverlay
591 int m_saving, m_sending;
592 public:
593 EditOverlay( GameControl& game )
594 : IconOverlay(game,"edit.png"),
595 m_saving(0), m_sending(0)
598 for ( int i=0; i<18; i++ ) {
599 addHotSpot( pos(i), i );
601 for ( int i=0; i<NUM_BRUSHES; i++ ) {
602 m_canvas->drawRect( pos(i), m_canvas->makeColour(brushColours[i]), true );
605 Rect pos( int i )
607 int c = i%3, r = i/3;
608 return Rect( c*28+13, r*28+33, c*28+33, r*28+53 );
610 int index( int x, int y )
612 int r = (y-33)/28;
613 int c = (x-13)/28;
614 if ( r<0 || c<0 || c>2 ) return -1;
615 return r*3+c;
617 void outline( Canvas& screen, int i, int c )
619 Rect r = pos(i) + Vec2(m_x,m_y);
620 r.tl.x-=2; r.tl.y-=2;
621 r.br.x+=2; r.br.y+=2;
622 screen.drawRect( r, c, false );
624 virtual void draw( Canvas& screen, const Rect& area )
626 IconOverlay::draw( screen, area );
627 outline( screen, m_game.m_colour, 0 );
628 if ( m_game.m_strokeFixed ) outline( screen, 12, 0 );
629 if ( m_game.m_strokeSleep ) outline( screen, 13, 0 );
630 if ( m_game.m_strokeDecor ) outline( screen, 14, 0 );
631 if ( m_sending ) outline( screen, 16, screen.makeColour((m_sending--)<<9) );
632 if ( m_saving ) outline( screen, 17, screen.makeColour((m_saving--)<<9) );
633 if ( m_saving || m_sending ) dirty();
635 virtual bool onHotSpot( int i )
637 switch (i) {
638 case 12: m_game.m_strokeFixed = ! m_game.m_strokeFixed; break;
639 case 13: m_game.m_strokeSleep = ! m_game.m_strokeSleep; break;
640 case 14: m_game.m_strokeDecor = ! m_game.m_strokeDecor; break;
641 case 16: if ( m_game.send() ) m_sending=10; break;
642 case 17: if ( m_game.save() ) m_saving=10; break;
643 default: if (i<NUM_BRUSHES) m_game.m_colour = i; else return false;
645 dirty();
646 return true;
653 Overlay* createIconOverlay( GameControl& game, const char* file,
654 int x,int y,
655 bool dragging_allowed )
657 return new IconOverlay( game, file, x, y, dragging_allowed );
660 Overlay* createEditOverlay( GameControl& game )
662 return new EditOverlay( game );
665 Overlay* createNextLevelOverlay( GameControl& game, int winner )
667 return new NextLevelOverlay( game, winner );
670 Overlay* createListOverlay( GameControl& game,
671 ListProvider* provider,
672 int x, int y,
673 int w, int h )
675 return new ListOverlay( game, provider, x, y, w, h );
678 Overlay* createMenuOverlay( GameControl& game )
680 return new MenuOverlay( game );