Make TextOverlay non-clickable; fix NextLevelOverlay
[numtypysics.git] / Overlay.cpp
blob8454c1a22b88ba0aba68c02fe8732d570c454f82
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;
210 class TextOverlay: public OverlayBase
212 private:
213 const std::string& m_text;
214 public:
215 TextOverlay(GameControl& game, const std::string& text,
216 int x=10, int y=10, bool dragging_allowed=false)
217 : OverlayBase(game, x, y, dragging_allowed)
218 , m_text(text)
220 m_canvas = Font::headingFont()->renderFont(m_text, 1);
223 virtual bool handleEvent( SDL_Event& ev ) {
224 /* Do not react to or consume mouse/keyboard events */
225 return false;
229 class IconOverlay: public OverlayBase
231 std::string m_filename;
232 public:
233 IconOverlay(GameControl& game, const char* file, int x=100,int y=20, bool dragging_allowed=true)
234 : OverlayBase(game,x,y,dragging_allowed),
235 m_filename( file )
237 m_canvas = new Image( m_filename.c_str() );
242 class UiOverlay : public OverlayBase
244 public:
245 UiOverlay(GameControl& game, const Rect& pos)
246 : OverlayBase( game, pos.tl.x, pos.tl.y, true )
247 , m_bgColour(0xf0f0e0)
248 , m_fgColour(0)
249 , m_motion(0)
250 , m_width(pos.width())
251 , m_height(pos.height())
253 m_canvas = new Canvas( pos.width(), pos.height() );
254 m_canvas->setBackground( m_bgColour );
255 m_canvas->drawRect( 0, 0, pos.width(), pos.height(),
256 m_canvas->makeColour(0xffffff) );
257 m_canvas->drawRect( 3, 3, pos.width()-6, pos.height()-6,
258 m_canvas->makeColour(m_bgColour) );
261 void moveTo( const Vec2& dest )
263 if ( dest != Vec2(m_x,m_y) ) {
264 m_motion = 10;
265 m_dest = dest;
269 void onTick( int tick )
271 if ( m_motion > 0 ) {
272 Vec2 pos(m_x,m_y);
273 pos *= m_motion-1;
274 pos += m_dest;
275 pos = pos /m_motion;
276 m_x = pos.x;
277 m_y = pos.y;
278 m_motion--;
279 m_game.m_refresh = 1;
283 template<class C>
284 void addButton( const char* icon, const Rect& r, void (C::*func)() )
286 if ( Config::findFile(icon) != icon ) {
287 Image *i = new Image(icon);
288 m_canvas->drawImage( i, r.tl.x, r.tl.y );
289 delete i;
290 } else {
291 Path p(icon);
292 if ( p.size() > 1 ) {
293 p.translate( r.tl - p.bbox().tl );
294 m_canvas->drawPath( p, m_fgColour, false );
295 } else {
296 Font::headingFont()->drawCenter( m_canvas, r.centroid(),
297 icon, m_fgColour );
300 addHotSpot( r, func );
302 int m_bgColour;
303 int m_fgColour;
304 private:
305 int m_motion;
306 Vec2 m_dest;
307 protected:
308 int m_width;
309 int m_height;
313 class ListOverlay : public UiOverlay
315 public:
316 ListOverlay( GameControl& game,
317 ListProvider* provider,
318 int x, int y,
319 int w, int h )
320 : UiOverlay(game,Rect(x,y,x+w,y+h)),
321 m_provider( provider ),
322 m_first( 0 ),
323 m_current( 0 ),
324 m_vgap( 2 ),
325 m_hgap( 2 ),
326 m_offset(0,0),
327 m_velocity(0,0),
328 m_listarea( m_hgap, m_vgap, w-m_hgap, h-m_vgap )
330 Canvas *first = m_provider->provideItem( m_first, NULL );
331 m_itemh = first->height();
332 m_numvis = m_listarea.height() / (m_itemh + m_vgap) + 2;
334 if ( m_numvis <= m_provider->countItems() ) {
335 m_allowscroll = false;
336 } else {
337 m_allowscroll = true;
338 m_listarea.tl.y += 20;
339 m_listarea.br.y -= 20;
340 m_numvis = m_listarea.height() / (m_itemh + m_vgap);
341 addButton( "Back", Rect(0,0,w,20), &ListOverlay::doBack );
342 addButton( "Forward", Rect(0,y-20,w,20), &ListOverlay::doForward );
345 m_items = (Canvas**)calloc( sizeof(Canvas*), m_numvis);
346 m_items[0] = first;
348 for ( int i=1; i<m_numvis; i++ ) {
349 m_items[i] = m_provider->provideItem( i, NULL );
351 renderItems();
354 ~ListOverlay()
356 for ( int i=0; i<m_numvis; i++ ) {
357 if ( m_items[0] ) {
358 m_provider->releaseItem( m_items[0] );
363 void renderItems()
365 m_canvas->clear( m_listarea );
366 for ( int i=0; i < m_numvis; i++ ) {
367 int q = m_first + i;
368 if ( q < m_provider->countItems() ) {
369 m_canvas->drawImage( m_items[q%m_numvis],
370 m_listarea.tl.x + m_offset.x,
371 m_listarea.tl.y + m_offset.y + (m_itemh+m_vgap)*i );
373 if ( q == m_current ) {
374 m_canvas->drawRect( m_listarea.tl.x-1 + m_offset.x,
375 m_listarea.tl.y + (m_itemh+m_vgap)*i - 1 + m_offset.y,
376 m_items[q%m_numvis]->width()+2,
377 m_items[q%m_numvis]->height()+2,
378 m_fgColour,
379 false );
382 dirty();
385 void onTick( int tick )
387 UiOverlay::onTick( tick );
388 if ( m_velocity.y ) {
389 m_offset += m_velocity;
390 while ( m_offset.y < -m_itemh-m_vgap ) {
391 m_offset.y += m_itemh+m_vgap;
392 doForward();
394 while ( m_offset.y >= m_itemh+m_vgap ) {
395 m_offset.y -= m_itemh+m_vgap;
396 doBack();
398 if ( m_velocity.y > 0 ) m_velocity.y--;
399 if ( m_velocity.y < 0 ) m_velocity.y++;
400 m_offset.x = 0;
401 renderItems();
405 virtual bool onClick( int x, int y )
407 int i = m_first + (y - m_vgap) / (m_itemh + m_vgap);
408 if ( i >= 0 && i < m_provider->countItems() ) {
409 m_current = i;
410 m_provider->onSelection( m_current,
411 x - m_listarea.tl.x,
412 y - m_listarea.tl.y - (m_itemh+m_vgap)*i );
413 renderItems();
417 virtual bool onKey( int k )
419 switch (k) {
420 case SDLK_UP:
421 if ( m_current > 0 ) {
422 m_current--;
423 if ( m_current < m_first ) {
424 doBack();
426 renderItems();
428 return true;
429 case SDLK_DOWN:
430 if ( m_current < m_provider->countItems()-1 ) {
431 m_current++;
432 if ( m_current >= m_first+m_numvis-1 ) {
433 doForward();
435 renderItems();
437 return true;
439 return false;
442 bool onDrag( int dx, int dy )
444 m_velocity.x = dx;
445 m_velocity.y = dy;
448 void doBack()
450 if ( m_first > 0 ) {
451 m_first--;
452 m_items[m_first%m_numvis] = m_provider->provideItem( m_first,
453 m_items[m_first%m_numvis] );
457 void doForward()
459 int last = m_first + m_numvis - 1;
460 if ( last < m_provider->countItems() ) {
461 m_first++;
462 last = m_first + m_numvis - 1;
463 m_items[last%m_numvis] = m_provider->provideItem( last, m_items[last%m_numvis] );
467 private:
468 ListProvider *m_provider;
469 int m_first;
470 int m_current;
471 int m_numvis;
472 int m_itemh;
473 int m_hgap, m_vgap;
474 bool m_allowscroll;
475 Rect m_listarea;
476 Vec2 m_offset;
477 Vec2 m_velocity;
478 Canvas** m_items;
482 class MenuOverlay : public UiOverlay
484 public:
485 MenuOverlay(GameControl& game)
486 : UiOverlay(game,Rect(100,100,400,200))
488 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 );
490 void doPause() {
491 m_game.gotoLevel( m_game.m_level + 1 );
494 void onShow() { moveTo(Vec2(200,200)); }
498 class NextLevelOverlay : public UiOverlay
500 public:
501 NextLevelOverlay( GameControl& game, int winner,
502 int width=400, int height=240 )
503 : UiOverlay(game,Rect(FULLSCREEN_RECT.centroid().x-width/2,
504 FULLSCREEN_RECT.centroid().y-height/2,
505 FULLSCREEN_RECT.centroid().x+width/2,
506 FULLSCREEN_RECT.centroid().y+height/2 ) ),
507 m_levelIcon(-2),
508 m_winner(winner),
509 m_icon(NULL)
511 char text[15];
512 if (winner == -1)
514 snprintf(text, 15, "Bravo!");
516 else
518 snprintf(text, 15, "Player %d wins!", winner+1);
521 Font::titleFont()->drawCenter( m_canvas, Vec2(200,32), text, 0 );
523 addHotSpot( Rect(0, 0,100,180), &NextLevelOverlay::doPrevLevel );
524 addHotSpot( Rect(300, 0,400,180), &NextLevelOverlay::doNextLevel );
526 addButton( "<<", Rect(13,100,50,140),
527 &NextLevelOverlay::doPrevLevel );
528 addButton( ">>", Rect(m_width-60,100,m_width,140),
529 &NextLevelOverlay::doNextLevel );
530 addButton( "Action Replay", Rect(0,180,200,240),
531 &NextLevelOverlay::doActionReplay );
532 addButton( "Continue", Rect(200,180,400,240),
533 &NextLevelOverlay::doContinue );
535 ~NextLevelOverlay()
537 delete m_icon;
539 virtual void onShow()
541 UiOverlay::onShow();
542 m_game.m_refresh = true; //for fullscreen fade
543 m_game.m_fade = true;
544 m_selectedLevel = m_game.m_level+1;
546 virtual void onHide()
548 UiOverlay::onHide();
549 m_game.m_refresh = true; //for fullscreen fade
550 m_game.m_fade = false;
552 virtual void draw( Canvas& screen, const Rect& area )
554 UiOverlay::draw( screen, area );
555 if ( genIcon() ) {
556 Font::blurbFont()->drawCenter( &screen, Vec2(m_x+200,m_y+70),
557 m_game.levels().levelName(m_selectedLevel), 0 );
558 int icon_x = m_x + (m_width-m_icon->width())/2;
559 int icon_y = m_y + (m_height-m_icon->height())/2;
560 screen.drawRect(icon_x-1, icon_y-1,
561 m_icon->width()+2, m_icon->height()+2,
562 screen.makeColour(0x909090));
563 screen.drawImage(m_icon, icon_x, icon_y);
564 } else {
565 dirty();
568 void doPrevLevel()
570 if (m_selectedLevel>0) {
571 m_selectedLevel--;
572 dirty();
575 void doNextLevel()
577 m_selectedLevel++;
578 dirty();
580 void doActionReplay()
582 m_game.gotoLevel( m_game.m_level,true );
584 void doContinue()
586 m_game.gotoLevel( m_selectedLevel );
588 private:
589 bool genIcon()
591 if ( m_icon==NULL || m_levelIcon != m_selectedLevel ) {
593 //Worker w( NULL, 1, 100, 200 );
595 delete m_icon;
596 m_icon = NULL;
597 if ( m_selectedLevel < m_game.levels().numLevels() ) {
598 Canvas temp( SCREEN_WIDTH, SCREEN_HEIGHT );
599 if ( m_game.renderScene( temp, m_selectedLevel) ) {
600 m_icon = temp.scale( ICON_SCALE_FACTOR*SCREEN_HEIGHT/480 );
602 } else {
603 m_icon = new Image("theend.png");
604 m_caption = "no more levels!";
605 m_selectedLevel = m_game.levels().numLevels();
607 m_levelIcon = m_selectedLevel;
609 return m_icon;
611 int m_selectedLevel;
612 int m_levelIcon;
613 int m_winner;
614 Canvas* m_icon;
615 std::string m_caption;
620 class EditOverlay : public IconOverlay
622 int m_saving, m_sending;
623 public:
624 EditOverlay( GameControl& game )
625 : IconOverlay(game,"edit.png"),
626 m_saving(0), m_sending(0)
629 for ( int i=0; i<18; i++ ) {
630 addHotSpot( pos(i), i );
632 for ( int i=0; i<NUM_BRUSHES; i++ ) {
633 m_canvas->drawRect( pos(i), m_canvas->makeColour(brushColours[i]), true );
636 Rect pos( int i )
638 int c = i%3, r = i/3;
639 return Rect( c*28+13, r*28+33, c*28+33, r*28+53 );
641 int index( int x, int y )
643 int r = (y-33)/28;
644 int c = (x-13)/28;
645 if ( r<0 || c<0 || c>2 ) return -1;
646 return r*3+c;
648 void outline( Canvas& screen, int i, int c )
650 Rect r = pos(i) + Vec2(m_x,m_y);
651 r.tl.x-=2; r.tl.y-=2;
652 r.br.x+=2; r.br.y+=2;
653 screen.drawRect( r, c, false );
655 virtual void draw( Canvas& screen, const Rect& area )
657 IconOverlay::draw( screen, area );
658 outline( screen, m_game.m_colour, 0 );
659 if ( m_game.m_strokeFixed ) outline( screen, 12, 0 );
660 if ( m_game.m_strokeSleep ) outline( screen, 13, 0 );
661 if ( m_game.m_strokeDecor ) outline( screen, 14, 0 );
662 if ( m_sending ) outline( screen, 16, screen.makeColour((m_sending--)<<9) );
663 if ( m_saving ) outline( screen, 17, screen.makeColour((m_saving--)<<9) );
664 if ( m_saving || m_sending ) dirty();
666 virtual bool onHotSpot( int i )
668 switch (i) {
669 case 12: m_game.m_strokeFixed = ! m_game.m_strokeFixed; break;
670 case 13: m_game.m_strokeSleep = ! m_game.m_strokeSleep; break;
671 case 14: m_game.m_strokeDecor = ! m_game.m_strokeDecor; break;
672 case 16: if ( m_game.send() ) m_sending=10; break;
673 case 17: if ( m_game.save() ) m_saving=10; break;
674 default: if (i<NUM_BRUSHES) m_game.m_colour = i; else return false;
676 dirty();
677 return true;
683 Overlay* createTextOverlay( GameControl& game, const std::string& text )
685 return new TextOverlay(game, text);
688 Overlay* createIconOverlay( GameControl& game, const char* file,
689 int x,int y,
690 bool dragging_allowed )
692 return new IconOverlay( game, file, x, y, dragging_allowed );
695 Overlay* createEditOverlay( GameControl& game )
697 return new EditOverlay( game );
700 Overlay* createNextLevelOverlay( GameControl& game, int winner )
702 return new NextLevelOverlay( game, winner );
705 Overlay* createListOverlay( GameControl& game,
706 ListProvider* provider,
707 int x, int y,
708 int w, int h )
710 return new ListOverlay( game, provider, x, y, w, h );
713 Overlay* createMenuOverlay( GameControl& game )
715 return new MenuOverlay( game );