Minor MingW32 build fixes.
[xiph/unicode.git] / planarity / gameboard_logic_button.c
blobf31886d03dd800557f3c6c1275bed889663927b1
1 /*
3 * gPlanarity:
4 * The geeky little puzzle game with a big noodly crunch!
5 *
6 * gPlanarity copyright (C) 2005 Monty <monty@xiph.org>
7 * Original Flash game by John Tantalo <john.tantalo@case.edu>
8 * Original game concept by Mary Radcliffe
10 * gPlanarity is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
15 * gPlanarity is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with Postfish; see the file COPYING. If not, write to the
22 * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include <math.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdk.h>
33 #include "graph.h"
34 #include "gameboard.h"
35 #include "gameboard_draw_button.h"
36 #include "main.h"
39 /************************ manage the buttons for buttonbar and dialogs *********************/
41 /* determine the x/y/w/h box around the rollover text */
42 static GdkRectangle rollover_box(Gameboard *g, buttonstate *b){
43 GdkRectangle r;
44 int width = g->g.width;
45 int height = g->g.height;
47 int x = b->x - b->ex.width/2 + b->ex.x_bearing -2;
48 int y = b->y - BUTTON_RADIUS - BUTTON_TEXT_BORDER + b->ex.y_bearing -2;
50 if(x<BUTTON_TEXT_BORDER)x=BUTTON_TEXT_BORDER;
51 if(y<BUTTON_TEXT_BORDER)y=BUTTON_TEXT_BORDER;
52 if(x+b->ex.width >= width - BUTTON_TEXT_BORDER)
53 x = width - BUTTON_TEXT_BORDER - b->ex.width;
54 if(y+b->ex.height >= height - BUTTON_TEXT_BORDER)
55 y = height - BUTTON_TEXT_BORDER - b->ex.height;
57 r.x=x;
58 r.y=y;
59 r.width=b->ex.width+5;
60 r.height=b->ex.height+5;
62 return r;
65 /* draw the actual rollover */
66 static void stroke_rollover(Gameboard *g, buttonstate *b, cairo_t *c){
67 set_font(c, BUTTON_TEXT_SIZE, 0, 1);
70 GdkRectangle r=rollover_box(g,b);
72 cairo_move_to (c, r.x - b->ex.x_bearing +2, r.y -b->ex.y_bearing+2 );
74 cairo_set_line_width(c,3);
75 cairo_set_source_rgba(c,1,1,1,.9);
76 cairo_text_path (c, b->rollovertext);
77 cairo_stroke(c);
79 cairo_set_source_rgba(c,BUTTON_TEXT_COLOR);
80 cairo_move_to (c, r.x - b->ex.x_bearing +2, r.y -b->ex.y_bearing+2 );
83 cairo_show_text (c, b->rollovertext);
88 /* request an expose for a button region*/
89 static void invalidate_button(Gameboard *g,buttonstate *b){
90 GdkRectangle r;
92 r.x=b->x-BUTTON_RADIUS-1;
93 r.y=b->y-BUTTON_RADIUS-1;
94 r.width=BUTTON_RADIUS*2+2;
95 r.height=BUTTON_RADIUS*2+2;
97 gdk_window_invalidate_rect(g->w.window,&r,FALSE);
100 /* request an expose for a rollover region*/
101 static void invalidate_rollover(Gameboard *g,buttonstate *b){
102 GdkRectangle r = rollover_box(g,b);
103 invalidate_button(g,b);
104 gdk_window_invalidate_rect(g->w.window,&r,FALSE);
107 /* like invalidation, but just expands a rectangular region */
108 static void expand_rectangle_button(buttonstate *b,GdkRectangle *r){
109 int x=b->x-BUTTON_RADIUS-1;
110 int y=b->y-BUTTON_RADIUS-1;
111 int x2=x+BUTTON_RADIUS*2+2;
112 int y2=y+BUTTON_RADIUS*2+2;
114 int rx2=r->x+r->width;
115 int ry2=r->y+r->height;
117 if(r->width==0 || r->height==0){
118 r->x=x;
119 r->y=y;
120 r->width=x2-x;
121 r->height=y2-y;
124 if(x<r->x)
125 r->x=x;
126 if(y<r->y)
127 r->y=y;
128 if(rx2<x2)
129 rx2=x2;
130 r->width=rx2-r->x;
131 if(ry2<y2)
132 ry2=y2;
133 r->height=ry2-r->y;
136 /* like invalidation, but just expands a rectangular region */
137 static void expand_rectangle_rollover(Gameboard *g,buttonstate *b,GdkRectangle *r){
138 GdkRectangle rr = rollover_box(g,b);
139 int x=rr.x;
140 int y=rr.y;
141 int x2=x+rr.width;
142 int y2=y+rr.height;
144 int rx2=r->x+r->width;
145 int ry2=r->y+r->height;
147 if(r->width==0 || r->height==0){
148 r->x=x;
149 r->y=y;
150 r->width=x2-x;
151 r->height=y2-y;
154 if(x<r->x)
155 r->x=x;
156 if(y<r->y)
157 r->y=y;
158 if(rx2<x2)
159 rx2=x2;
160 r->width=rx2-r->x;
161 if(ry2<y2)
162 ry2=y2;
163 r->height=ry2-r->y;
166 /* cache buttons as small surfaces */
167 static cairo_surface_t *cache_button(Gameboard *g,
168 void (*draw)(cairo_t *c,
169 double x,
170 double y),
171 double pR,double pG,double pB,double pA,
172 double fR,double fG,double fB,double fA){
173 cairo_surface_t *ret =
174 cairo_surface_create_similar (cairo_get_target (g->wc),
175 CAIRO_CONTENT_COLOR_ALPHA,
176 BUTTON_RADIUS*2+1,
177 BUTTON_RADIUS*2+1);
179 cairo_t *c = cairo_create(ret);
181 cairo_save(c);
182 cairo_set_operator(c,CAIRO_OPERATOR_CLEAR);
183 cairo_set_source_rgba (c, 1,1,1,1);
184 cairo_paint(c);
185 cairo_restore(c);
187 cairo_set_source_rgba(c,fR,fG,fB,fA);
188 cairo_set_line_width(c,BUTTON_LINE_WIDTH);
189 draw(c,BUTTON_RADIUS+.5,BUTTON_RADIUS+.5);
191 cairo_set_source_rgba(c,pR,pG,pB,pA);
192 cairo_stroke(c);
194 cairo_destroy(c);
195 return ret;
198 /* determine the x/y/w/h box around a button */
199 static GdkRectangle button_box(buttonstate *b){
200 GdkRectangle r;
202 int x = b->x - BUTTON_RADIUS-1;
203 int y = b->y - BUTTON_RADIUS-1;
204 r.x=x;
205 r.y=y;
206 r.width = BUTTON_RADIUS*2+2;
207 r.height= BUTTON_RADIUS*2+2;
209 return r;
212 static int animate_x_axis(Gameboard *g, buttonstate *b, GdkRectangle *r){
213 int ret=0;
215 if(b->x_target != b->x){
216 ret=1;
217 expand_rectangle_button(b,r);
218 if(b->rollover)
219 expand_rectangle_rollover(g,b,r);
221 if(b->x_target > b->x){
222 b->x+=DEPLOY_DELTA;
223 if(b->x>b->x_target)b->x=b->x_target;
224 }else{
225 b->x-=DEPLOY_DELTA;
226 if(b->x<b->x_target)b->x=b->x_target;
228 expand_rectangle_button(b,r);
229 if(b->rollover)
230 expand_rectangle_rollover(g,b,r);
233 return ret;
235 static int animate_y_axis(Gameboard *g, buttonstate *b, GdkRectangle *r){
236 int ret=0;
238 if(b->y_target != b->y){
239 ret=1;
240 expand_rectangle_button(b,r);
241 if(b->rollover)
242 expand_rectangle_rollover(g,b,r);
244 if(b->y_target > b->y){
245 b->y+=DEPLOY_DELTA;
246 if(b->y>b->y_target)b->y=b->y_target;
247 }else{
248 b->y-=DEPLOY_DELTA;
249 if(b->y<b->y_target)b->y=b->y_target;
251 expand_rectangle_button(b,r);
252 if(b->rollover)
253 expand_rectangle_rollover(g,b,r);
256 if(b->alphad != b->y_active - b->y){
257 double alpha=0;
258 if(b->y_inactive>b->y_active){
259 if(b->y<=b->y_active)
260 alpha=1.;
261 else if (b->y>=b->y_inactive)
262 alpha=0.;
263 else
264 alpha = (double)(b->y_inactive-b->y)/(b->y_inactive-b->y_active);
265 }else if (b->y_inactive<b->y_active){
266 if(b->y>=b->y_active)
267 alpha=1.;
268 else if (b->y<=b->y_inactive)
269 alpha=0.;
270 else
271 alpha = (double)(b->y_inactive-b->y)/(b->y_inactive-b->y_active);
273 if(alpha != b->alpha){
274 ret=1;
275 expand_rectangle_button(b,r);
276 b->alpha=alpha;
278 b->alphad = b->y_active - b->y;
281 return ret;
284 /* do the animation math for one button for one frame */
285 static int animate_one(Gameboard *g,buttonstate *b, GdkRectangle *r){
286 int ret=0;
288 /* does this button need to be deployed? */
289 if(g->b.sweeperd>0){
290 if(b->y_target != b->y_active){
291 if(g->b.sweeper >= b->sweepdeploy){
292 b->y_target=b->y_active;
293 ret=1;
297 /* does this button need to be undeployed? */
298 if(g->b.sweeperd<0){
299 if(b->y_target != b->y_inactive){
300 if(-g->b.sweeper >= b->sweepdeploy){
301 b->y_target=b->y_inactive;
302 ret=1;
307 ret |= animate_x_axis(g,b,r);
308 ret |= animate_y_axis(g,b,r);
310 return ret;
313 /******************** toplevel abstraction calls *********************/
315 /* initialize the persistent caches; called once when gameboard is
316 first created */
317 void init_buttons(Gameboard *g){
318 buttonstate *states=g->b.states;
319 memset(g->b.states,0,sizeof(g->b.states));
321 states[0].idle = cache_button(g, path_button_exit,
322 BUTTON_QUIT_IDLE_PATH,
323 BUTTON_QUIT_IDLE_FILL);
324 states[0].lit = cache_button(g, path_button_exit,
325 BUTTON_QUIT_LIT_PATH,
326 BUTTON_QUIT_LIT_FILL);
328 states[1].idle = cache_button(g, path_button_back,
329 BUTTON_IDLE_PATH,
330 BUTTON_IDLE_FILL);
331 states[1].lit = cache_button(g, path_button_back,
332 BUTTON_LIT_PATH,
333 BUTTON_LIT_FILL);
334 states[2].idle = cache_button(g, path_button_reset,
335 BUTTON_IDLE_PATH,
336 BUTTON_IDLE_FILL);
337 states[2].lit = cache_button(g, path_button_reset,
338 BUTTON_LIT_PATH,
339 BUTTON_LIT_FILL);
340 states[3].idle = cache_button(g, path_button_pause,
341 BUTTON_IDLE_PATH,
342 BUTTON_IDLE_FILL);
343 states[3].lit = cache_button(g, path_button_pause,
344 BUTTON_LIT_PATH,
345 BUTTON_LIT_FILL);
346 states[4].idle = cache_button(g, path_button_help,
347 BUTTON_IDLE_PATH,
348 BUTTON_IDLE_FILL);
349 states[4].lit = cache_button(g, path_button_help,
350 BUTTON_LIT_PATH,
351 BUTTON_LIT_FILL);
352 states[5].idle = cache_button(g, path_button_expand,
353 BUTTON_IDLE_PATH,
354 BUTTON_IDLE_FILL);
355 states[5].lit = cache_button(g, path_button_expand,
356 BUTTON_LIT_PATH,
357 BUTTON_LIT_FILL);
358 states[6].idle = cache_button(g, path_button_shrink,
359 BUTTON_IDLE_PATH,
360 BUTTON_IDLE_FILL);
361 states[6].lit = cache_button(g, path_button_shrink,
362 BUTTON_LIT_PATH,
363 BUTTON_LIT_FILL);
364 states[7].idle = cache_button(g, path_button_lines,
365 BUTTON_IDLE_PATH,
366 BUTTON_IDLE_FILL);
367 states[7].lit = cache_button(g, path_button_lines,
368 BUTTON_LIT_PATH,
369 BUTTON_LIT_FILL);
370 states[8].idle = cache_button(g, path_button_int,
371 BUTTON_IDLE_PATH,
372 BUTTON_IDLE_FILL);
373 states[8].lit = cache_button(g, path_button_int,
374 BUTTON_LIT_PATH,
375 BUTTON_LIT_FILL);
376 states[9].idle = cache_button(g, path_button_check,
377 BUTTON_CHECK_IDLE_PATH,
378 BUTTON_CHECK_IDLE_FILL);
379 states[9].lit = cache_button(g, path_button_check,
380 BUTTON_CHECK_LIT_PATH,
381 BUTTON_CHECK_LIT_FILL);
382 states[10].idle= cache_button(g, path_button_play,
383 BUTTON_CHECK_IDLE_PATH,
384 BUTTON_CHECK_IDLE_FILL);
385 states[10].lit = cache_button(g, path_button_play,
386 BUTTON_CHECK_LIT_PATH,
387 BUTTON_CHECK_LIT_FILL);
390 /* match x/y to a button if any */
391 buttonstate *find_button(Gameboard *g, int x,int y){
392 int i;
393 buttonstate *states=g->b.states;
395 for(i=0;i<NUMBUTTONS;i++){
396 buttonstate *b=states+i;
397 if(b->position>0)
398 if( (b->x-x)*(b->x-x) + (b->y-y)*(b->y-y) <= BUTTON_RADIUS*BUTTON_RADIUS+4)
399 if(b->y != b->y_inactive)
400 return b;
402 return 0;
405 /* set a button to pressed or lit */
406 void button_set_state(Gameboard *g, buttonstate *b, int rollover, int press){
407 int i;
408 buttonstate *states=g->b.states;
410 if(b->rollover == rollover && b->press == press)return;
412 for(i=0;i<NUMBUTTONS;i++){
413 buttonstate *bb=states+i;
414 if(bb->position>0){
415 if(bb!=b){
416 if(bb->rollover)
417 invalidate_rollover(g,bb);
418 if(bb->press)
419 invalidate_button(g,bb);
421 bb->rollover=0;
422 bb->press=0;
423 }else{
424 if(bb->rollover != rollover)
425 invalidate_rollover(g,bb);
426 if(bb->press != press)
427 invalidate_button(g,bb);
429 bb->rollover=rollover;
430 bb->press=press;
434 g->b.allclear=0;
437 /* cache the text extents of a rollover */
438 void rollover_extents(Gameboard *g, buttonstate *b){
440 cairo_t *c = cairo_create(g->foreground);
442 set_font(c, BUTTON_TEXT_SIZE, 0, 1);
443 cairo_text_extents (c, b->rollovertext, &b->ex);
445 cairo_destroy(c);
448 /* perform a single frame of animation for all buttons/rollovers */
449 gboolean animate_button_frame(gpointer ptr){
450 Gameboard *g=(Gameboard *)ptr;
451 GdkRectangle expose;
452 buttonstate *states=g->b.states;
453 int ret=0,i,pos;
455 if(!g->first_expose)return 1;
457 g->b.sweeper += g->b.sweeperd;
459 /* avoid the normal expose event mechanism
460 during the button animations. This direct method is much faster as
461 it won't accidentally combine exposes over long stretches of
462 alpha-blended surfaces. */
464 for(pos=1;pos<=3;pos++){
465 memset(&expose,0,sizeof(expose));
466 for(i=0;i<NUMBUTTONS;i++)
467 if(states[i].position == pos)
468 ret|=animate_one(g,states+i,&expose);
469 if(expose.width && expose.height)
470 gameboard_draw(g,
471 expose.x,
472 expose.y,
473 expose.width,
474 expose.height);
477 if(!ret)g->b.sweeperd = 0;
479 if(!ret && g->button_timer!=0){
480 g_source_remove(g->button_timer);
481 g->button_timer=0;
484 if(!ret && g->button_callback)
485 // undeploy finished... call the undeploy callback
486 g->button_callback(g);
488 return ret;
491 /* the normal expose method for all buttons; called from the master
492 widget's expose */
493 void expose_buttons(Gameboard *g,cairo_t *c, int x,int y,int w,int h){
494 int i;
495 buttonstate *states=g->b.states;
497 for(i=0;i<NUMBUTTONS;i++){
499 buttonstate *b=states+i;
501 if(b->position>0){
502 GdkRectangle r = rollover_box(g,b);
503 GdkRectangle br = button_box(b);
505 if(x < br.x+br.width &&
506 y < br.y+br.height &&
507 x+w > br.x &&
508 y+h > br.y) {
510 cairo_set_source_surface(c,
511 (b->rollover || b->press ? b->lit : b->idle),
512 br.x,
513 br.y);
515 if(b->press)
516 cairo_paint_with_alpha(c,b->alpha);
517 cairo_paint_with_alpha(c,b->alpha);
521 if((b->rollover || b->press) && b->y_target!=b->y_inactive)
522 if(x < r.x+r.width &&
523 y < r.y+r.height &&
524 x+w > r.x &&
525 y+h > r.y)
527 stroke_rollover(g,b,c);
532 /* resize the button bar; called from master resize in gameboard */
533 void resize_buttons(Gameboard *g,int oldw,int oldh,int w,int h){
534 int i;
535 int dx=w/2-oldw/2;
536 int dy=h/2-oldh/2;
537 buttonstate *states=g->b.states;
539 for(i=0;i<NUMBUTTONS;i++){
540 if(abs(states[i].position) == 2){
542 states[i].x+=dx;
543 states[i].x_target+=dx;
545 states[i].y+=dy;
546 states[i].y_target+=dy;
547 states[i].y_active+=dy;
548 states[i].y_inactive+=dy;
552 dx=w-oldw;
553 dy=h-oldh;
555 for(i=0;i<NUMBUTTONS;i++){
556 if(abs(states[i].position)==1 ||
557 abs(states[i].position)==3){
558 states[i].y+=dy;
559 states[i].y_target+=dy;
560 states[i].y_active+=dy;
561 states[i].y_inactive+=dy;
565 for(i=0;i<NUMBUTTONS;i++){
566 if(abs(states[i].position) == 3){
567 states[i].x+=dx;
568 states[i].x_target+=dx;
573 /* clear all buttons to unpressed/unlit */
574 void button_clear_state(Gameboard *g){
575 int i;
576 buttonstate *states=g->b.states;
578 if(!g->b.allclear){
579 for(i=0;i<NUMBUTTONS;i++){
580 buttonstate *bb=states+i;
581 if(bb->position){
582 if(bb->rollover)
583 invalidate_rollover(g,bb);
584 if(bb->press)
585 invalidate_button(g,bb);
587 bb->rollover=0;
588 bb->press=0;
592 g->b.allclear=1;
593 g->b.grabbed=0;
596 void deploy_buttons(Gameboard *g, void (*callback)(Gameboard *g)){
598 if(!g->b.buttons_ready){
600 // sweep across the buttons inward from the horizontal edges; when
601 // the sweep passes a button it is set to deploy by making the
602 // target y equal to the active y target.
604 g->b.sweeper = 0;
605 g->b.sweeperd = 1;
607 g->button_callback = callback;
608 if(g->button_timer!=0)
609 g_source_remove(g->button_timer);
610 g->button_timer = g_timeout_add(BUTTON_ANIM_INTERVAL, animate_button_frame, (gpointer)g);
611 g->b.buttons_ready=1;
616 void undeploy_buttons(Gameboard *g, void (*callback)(Gameboard *ptr)){
618 if(g->b.buttons_ready){
620 button_clear_state(g);
621 g->b.buttons_ready=0;
623 // sweep across the buttons outward from the center; when
624 // the sweep passes a button it is set to undeploy by making the
625 // target y equal to the inactive y target.
627 g->b.sweeper = 0;
628 g->b.sweeperd = -1;
630 g->button_callback = callback;
631 if(g->button_timer!=0)
632 g_source_remove(g->button_timer);
633 g->button_timer = g_timeout_add(BUTTON_ANIM_INTERVAL, animate_button_frame, (gpointer)g);
634 }else
635 callback(g);