4 * The geeky little puzzle game with a big noodly crunch!
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)
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.
34 #include "gameboard.h"
35 #include "gameboard_draw_button.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
){
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
;
59 r
.width
=b
->ex
.width
+5;
60 r
.height
=b
->ex
.height
+5;
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
);
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
){
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){
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
);
144 int rx2
=r
->x
+r
->width
;
145 int ry2
=r
->y
+r
->height
;
147 if(r
->width
==0 || r
->height
==0){
166 /* cache buttons as small surfaces */
167 static cairo_surface_t
*cache_button(Gameboard
*g
,
168 void (*draw
)(cairo_t
*c
,
171 double pR
,double pG
,double pB
,double pA
,
172 double fR
,double fG
,double fB
,double fA
){
173 cairo_t
*wc
= gdk_cairo_create(g
->w
.window
);
174 cairo_surface_t
*ret
=
175 cairo_surface_create_similar (cairo_get_target (wc
),
176 CAIRO_CONTENT_COLOR_ALPHA
,
179 cairo_t
*c
= cairo_create(ret
);
183 cairo_set_operator(c
,CAIRO_OPERATOR_CLEAR
);
184 cairo_set_source_rgba (c
, 1,1,1,1);
188 cairo_set_source_rgba(c
,fR
,fG
,fB
,fA
);
189 cairo_set_line_width(c
,BUTTON_LINE_WIDTH
);
190 draw(c
,BUTTON_RADIUS
+.5,BUTTON_RADIUS
+.5);
192 cairo_set_source_rgba(c
,pR
,pG
,pB
,pA
);
199 /* determine the x/y/w/h box around a button */
200 static GdkRectangle
button_box(buttonstate
*b
){
203 int x
= b
->x
- BUTTON_RADIUS
-1;
204 int y
= b
->y
- BUTTON_RADIUS
-1;
207 r
.width
= BUTTON_RADIUS
*2+2;
208 r
.height
= BUTTON_RADIUS
*2+2;
213 static int animate_x_axis(Gameboard
*g
, buttonstate
*b
, GdkRectangle
*r
){
216 if(b
->x_target
!= b
->x
){
218 expand_rectangle_button(b
,r
);
220 expand_rectangle_rollover(g
,b
,r
);
222 if(b
->x_target
> b
->x
){
224 if(b
->x
>b
->x_target
)b
->x
=b
->x_target
;
227 if(b
->x
<b
->x_target
)b
->x
=b
->x_target
;
229 expand_rectangle_button(b
,r
);
231 expand_rectangle_rollover(g
,b
,r
);
236 static int animate_y_axis(Gameboard
*g
, buttonstate
*b
, GdkRectangle
*r
){
239 if(b
->y_target
!= b
->y
){
241 expand_rectangle_button(b
,r
);
243 expand_rectangle_rollover(g
,b
,r
);
245 if(b
->y_target
> b
->y
){
247 if(b
->y
>b
->y_target
)b
->y
=b
->y_target
;
250 if(b
->y
<b
->y_target
)b
->y
=b
->y_target
;
252 expand_rectangle_button(b
,r
);
254 expand_rectangle_rollover(g
,b
,r
);
257 if(b
->alphad
!= b
->y_active
- b
->y
){
259 if(b
->y_inactive
>b
->y_active
){
260 if(b
->y
<=b
->y_active
)
262 else if (b
->y
>=b
->y_inactive
)
265 alpha
= (double)(b
->y_inactive
-b
->y
)/(b
->y_inactive
-b
->y_active
);
266 }else if (b
->y_inactive
<b
->y_active
){
267 if(b
->y
>=b
->y_active
)
269 else if (b
->y
<=b
->y_inactive
)
272 alpha
= (double)(b
->y_inactive
-b
->y
)/(b
->y_inactive
-b
->y_active
);
274 if(alpha
!= b
->alpha
){
276 expand_rectangle_button(b
,r
);
279 b
->alphad
= b
->y_active
- b
->y
;
285 /* do the animation math for one button for one frame */
286 static int animate_one(Gameboard
*g
,buttonstate
*b
, GdkRectangle
*r
){
289 /* does this button need to be deployed? */
291 if(b
->y_target
!= b
->y_active
){
292 if(g
->b
.sweeper
>= b
->sweepdeploy
){
293 b
->y_target
=b
->y_active
;
298 /* does this button need to be undeployed? */
300 if(b
->y_target
!= b
->y_inactive
){
301 if(-g
->b
.sweeper
>= b
->sweepdeploy
){
302 b
->y_target
=b
->y_inactive
;
308 ret
|= animate_x_axis(g
,b
,r
);
309 ret
|= animate_y_axis(g
,b
,r
);
314 /******************** toplevel abstraction calls *********************/
316 /* initialize the persistent caches; called once when gameboard is
318 void init_buttons(Gameboard
*g
){
319 buttonstate
*states
=g
->b
.states
;
320 memset(g
->b
.states
,0,sizeof(g
->b
.states
));
322 states
[0].idle
= cache_button(g
, path_button_exit
,
323 BUTTON_QUIT_IDLE_PATH
,
324 BUTTON_QUIT_IDLE_FILL
);
325 states
[0].lit
= cache_button(g
, path_button_exit
,
326 BUTTON_QUIT_LIT_PATH
,
327 BUTTON_QUIT_LIT_FILL
);
329 states
[1].idle
= cache_button(g
, path_button_back
,
332 states
[1].lit
= cache_button(g
, path_button_back
,
335 states
[2].idle
= cache_button(g
, path_button_reset
,
338 states
[2].lit
= cache_button(g
, path_button_reset
,
341 states
[3].idle
= cache_button(g
, path_button_pause
,
344 states
[3].lit
= cache_button(g
, path_button_pause
,
347 states
[4].idle
= cache_button(g
, path_button_help
,
350 states
[4].lit
= cache_button(g
, path_button_help
,
353 states
[5].idle
= cache_button(g
, path_button_expand
,
356 states
[5].lit
= cache_button(g
, path_button_expand
,
359 states
[6].idle
= cache_button(g
, path_button_shrink
,
362 states
[6].lit
= cache_button(g
, path_button_shrink
,
365 states
[7].idle
= cache_button(g
, path_button_lines
,
368 states
[7].lit
= cache_button(g
, path_button_lines
,
371 states
[8].idle
= cache_button(g
, path_button_int
,
374 states
[8].lit
= cache_button(g
, path_button_int
,
377 states
[9].idle
= cache_button(g
, path_button_check
,
378 BUTTON_CHECK_IDLE_PATH
,
379 BUTTON_CHECK_IDLE_FILL
);
380 states
[9].lit
= cache_button(g
, path_button_check
,
381 BUTTON_CHECK_LIT_PATH
,
382 BUTTON_CHECK_LIT_FILL
);
383 states
[10].idle
= cache_button(g
, path_button_play
,
384 BUTTON_CHECK_IDLE_PATH
,
385 BUTTON_CHECK_IDLE_FILL
);
386 states
[10].lit
= cache_button(g
, path_button_play
,
387 BUTTON_CHECK_LIT_PATH
,
388 BUTTON_CHECK_LIT_FILL
);
391 /* match x/y to a button if any */
392 buttonstate
*find_button(Gameboard
*g
, int x
,int y
){
394 buttonstate
*states
=g
->b
.states
;
396 for(i
=0;i
<NUMBUTTONS
;i
++){
397 buttonstate
*b
=states
+i
;
399 if( (b
->x
-x
)*(b
->x
-x
) + (b
->y
-y
)*(b
->y
-y
) <= BUTTON_RADIUS
*BUTTON_RADIUS
+4)
400 if(b
->y
!= b
->y_inactive
)
406 /* set a button to pressed or lit */
407 void button_set_state(Gameboard
*g
, buttonstate
*b
, int rollover
, int press
){
409 buttonstate
*states
=g
->b
.states
;
411 if(b
->rollover
== rollover
&& b
->press
== press
)return;
413 for(i
=0;i
<NUMBUTTONS
;i
++){
414 buttonstate
*bb
=states
+i
;
418 invalidate_rollover(g
,bb
);
420 invalidate_button(g
,bb
);
425 if(bb
->rollover
!= rollover
)
426 invalidate_rollover(g
,bb
);
427 if(bb
->press
!= press
)
428 invalidate_button(g
,bb
);
430 bb
->rollover
=rollover
;
438 /* cache the text extents of a rollover */
439 void rollover_extents(Gameboard
*g
, buttonstate
*b
){
441 cairo_t
*c
= cairo_create(g
->foreground
);
443 set_font(c
, BUTTON_TEXT_SIZE
, 0, 1);
444 cairo_text_extents (c
, b
->rollovertext
, &b
->ex
);
449 /* perform a single frame of animation for all buttons/rollovers */
450 gboolean
animate_button_frame(gpointer ptr
){
451 Gameboard
*g
=(Gameboard
*)ptr
;
453 buttonstate
*states
=g
->b
.states
;
456 if(!g
->first_expose
)return 1;
458 g
->b
.sweeper
+= g
->b
.sweeperd
;
460 /* avoid the normal expose event mechanism
461 during the button animations. This direct method is much faster as
462 it won't accidentally combine exposes over long stretches of
463 alpha-blended surfaces. */
465 for(pos
=1;pos
<=3;pos
++){
466 memset(&expose
,0,sizeof(expose
));
467 for(i
=0;i
<NUMBUTTONS
;i
++)
468 if(states
[i
].position
== pos
)
469 ret
|=animate_one(g
,states
+i
,&expose
);
470 if(expose
.width
&& expose
.height
)
478 if(!ret
)g
->b
.sweeperd
= 0;
480 if(!ret
&& g
->button_timer
!=0){
481 g_source_remove(g
->button_timer
);
485 if(!ret
&& g
->button_callback
)
486 // undeploy finished... call the undeploy callback
487 g
->button_callback(g
);
492 /* the normal expose method for all buttons; called from the master
494 void expose_buttons(Gameboard
*g
,cairo_t
*c
, int x
,int y
,int w
,int h
){
496 buttonstate
*states
=g
->b
.states
;
498 for(i
=0;i
<NUMBUTTONS
;i
++){
500 buttonstate
*b
=states
+i
;
503 GdkRectangle r
= rollover_box(g
,b
);
504 GdkRectangle br
= button_box(b
);
506 if(x
< br
.x
+br
.width
&&
507 y
< br
.y
+br
.height
&&
511 cairo_set_source_surface(c
,
512 (b
->rollover
|| b
->press
? b
->lit
: b
->idle
),
517 cairo_paint_with_alpha(c
,b
->alpha
);
518 cairo_paint_with_alpha(c
,b
->alpha
);
522 if((b
->rollover
|| b
->press
) && b
->y_target
!=b
->y_inactive
)
523 if(x
< r
.x
+r
.width
&&
528 stroke_rollover(g
,b
,c
);
533 /* resize the button bar; called from master resize in gameboard */
534 void resize_buttons(Gameboard
*g
,int oldw
,int oldh
,int w
,int h
){
538 buttonstate
*states
=g
->b
.states
;
540 for(i
=0;i
<NUMBUTTONS
;i
++){
541 if(abs(states
[i
].position
) == 2){
544 states
[i
].x_target
+=dx
;
547 states
[i
].y_target
+=dy
;
548 states
[i
].y_active
+=dy
;
549 states
[i
].y_inactive
+=dy
;
556 for(i
=0;i
<NUMBUTTONS
;i
++){
557 if(abs(states
[i
].position
)==1 ||
558 abs(states
[i
].position
)==3){
560 states
[i
].y_target
+=dy
;
561 states
[i
].y_active
+=dy
;
562 states
[i
].y_inactive
+=dy
;
566 for(i
=0;i
<NUMBUTTONS
;i
++){
567 if(abs(states
[i
].position
) == 3){
569 states
[i
].x_target
+=dx
;
574 /* clear all buttons to unpressed/unlit */
575 void button_clear_state(Gameboard
*g
){
577 buttonstate
*states
=g
->b
.states
;
580 for(i
=0;i
<NUMBUTTONS
;i
++){
581 buttonstate
*bb
=states
+i
;
584 invalidate_rollover(g
,bb
);
586 invalidate_button(g
,bb
);
597 void deploy_buttons(Gameboard
*g
, void (*callback
)(Gameboard
*g
)){
599 if(!g
->b
.buttons_ready
){
601 // sweep across the buttons inward from the horizontal edges; when
602 // the sweep passes a button it is set to deploy by making the
603 // target y equal to the active y target.
608 g
->button_callback
= callback
;
609 if(g
->button_timer
!=0)
610 g_source_remove(g
->button_timer
);
611 g
->button_timer
= g_timeout_add(BUTTON_ANIM_INTERVAL
, animate_button_frame
, (gpointer
)g
);
612 g
->b
.buttons_ready
=1;
617 void undeploy_buttons(Gameboard
*g
, void (*callback
)(Gameboard
*ptr
)){
619 if(g
->b
.buttons_ready
){
621 button_clear_state(g
);
622 g
->b
.buttons_ready
=0;
624 // sweep across the buttons outward from the center; when
625 // the sweep passes a button it is set to undeploy by making the
626 // target y equal to the inactive y target.
631 g
->button_callback
= callback
;
632 if(g
->button_timer
!=0)
633 g_source_remove(g
->button_timer
);
634 g
->button_timer
= g_timeout_add(BUTTON_ANIM_INTERVAL
, animate_button_frame
, (gpointer
)g
);