Add Russian translation provided by Валерий Крувялис <valkru@mail.ru>
[xiph-mirror.git] / planarity / gameboard_logic_button.c
bloba87db0715327f94e81e85bbd3b8c2263dc088dc9
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_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,
177 BUTTON_RADIUS*2+1,
178 BUTTON_RADIUS*2+1);
179 cairo_t *c = cairo_create(ret);
180 cairo_destroy (wc);
182 cairo_save(c);
183 cairo_set_operator(c,CAIRO_OPERATOR_CLEAR);
184 cairo_set_source_rgba (c, 1,1,1,1);
185 cairo_paint(c);
186 cairo_restore(c);
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);
193 cairo_stroke(c);
195 cairo_destroy(c);
196 return ret;
199 /* determine the x/y/w/h box around a button */
200 static GdkRectangle button_box(buttonstate *b){
201 GdkRectangle r;
203 int x = b->x - BUTTON_RADIUS-1;
204 int y = b->y - BUTTON_RADIUS-1;
205 r.x=x;
206 r.y=y;
207 r.width = BUTTON_RADIUS*2+2;
208 r.height= BUTTON_RADIUS*2+2;
210 return r;
213 static int animate_x_axis(Gameboard *g, buttonstate *b, GdkRectangle *r){
214 int ret=0;
216 if(b->x_target != b->x){
217 ret=1;
218 expand_rectangle_button(b,r);
219 if(b->rollover)
220 expand_rectangle_rollover(g,b,r);
222 if(b->x_target > b->x){
223 b->x+=DEPLOY_DELTA;
224 if(b->x>b->x_target)b->x=b->x_target;
225 }else{
226 b->x-=DEPLOY_DELTA;
227 if(b->x<b->x_target)b->x=b->x_target;
229 expand_rectangle_button(b,r);
230 if(b->rollover)
231 expand_rectangle_rollover(g,b,r);
234 return ret;
236 static int animate_y_axis(Gameboard *g, buttonstate *b, GdkRectangle *r){
237 int ret=0;
239 if(b->y_target != b->y){
240 ret=1;
241 expand_rectangle_button(b,r);
242 if(b->rollover)
243 expand_rectangle_rollover(g,b,r);
245 if(b->y_target > b->y){
246 b->y+=DEPLOY_DELTA;
247 if(b->y>b->y_target)b->y=b->y_target;
248 }else{
249 b->y-=DEPLOY_DELTA;
250 if(b->y<b->y_target)b->y=b->y_target;
252 expand_rectangle_button(b,r);
253 if(b->rollover)
254 expand_rectangle_rollover(g,b,r);
257 if(b->alphad != b->y_active - b->y){
258 double alpha=0;
259 if(b->y_inactive>b->y_active){
260 if(b->y<=b->y_active)
261 alpha=1.;
262 else if (b->y>=b->y_inactive)
263 alpha=0.;
264 else
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)
268 alpha=1.;
269 else if (b->y<=b->y_inactive)
270 alpha=0.;
271 else
272 alpha = (double)(b->y_inactive-b->y)/(b->y_inactive-b->y_active);
274 if(alpha != b->alpha){
275 ret=1;
276 expand_rectangle_button(b,r);
277 b->alpha=alpha;
279 b->alphad = b->y_active - b->y;
282 return ret;
285 /* do the animation math for one button for one frame */
286 static int animate_one(Gameboard *g,buttonstate *b, GdkRectangle *r){
287 int ret=0;
289 /* does this button need to be deployed? */
290 if(g->b.sweeperd>0){
291 if(b->y_target != b->y_active){
292 if(g->b.sweeper >= b->sweepdeploy){
293 b->y_target=b->y_active;
294 ret=1;
298 /* does this button need to be undeployed? */
299 if(g->b.sweeperd<0){
300 if(b->y_target != b->y_inactive){
301 if(-g->b.sweeper >= b->sweepdeploy){
302 b->y_target=b->y_inactive;
303 ret=1;
308 ret |= animate_x_axis(g,b,r);
309 ret |= animate_y_axis(g,b,r);
311 return ret;
314 /******************** toplevel abstraction calls *********************/
316 /* initialize the persistent caches; called once when gameboard is
317 first created */
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,
330 BUTTON_IDLE_PATH,
331 BUTTON_IDLE_FILL);
332 states[1].lit = cache_button(g, path_button_back,
333 BUTTON_LIT_PATH,
334 BUTTON_LIT_FILL);
335 states[2].idle = cache_button(g, path_button_reset,
336 BUTTON_IDLE_PATH,
337 BUTTON_IDLE_FILL);
338 states[2].lit = cache_button(g, path_button_reset,
339 BUTTON_LIT_PATH,
340 BUTTON_LIT_FILL);
341 states[3].idle = cache_button(g, path_button_pause,
342 BUTTON_IDLE_PATH,
343 BUTTON_IDLE_FILL);
344 states[3].lit = cache_button(g, path_button_pause,
345 BUTTON_LIT_PATH,
346 BUTTON_LIT_FILL);
347 states[4].idle = cache_button(g, path_button_help,
348 BUTTON_IDLE_PATH,
349 BUTTON_IDLE_FILL);
350 states[4].lit = cache_button(g, path_button_help,
351 BUTTON_LIT_PATH,
352 BUTTON_LIT_FILL);
353 states[5].idle = cache_button(g, path_button_expand,
354 BUTTON_IDLE_PATH,
355 BUTTON_IDLE_FILL);
356 states[5].lit = cache_button(g, path_button_expand,
357 BUTTON_LIT_PATH,
358 BUTTON_LIT_FILL);
359 states[6].idle = cache_button(g, path_button_shrink,
360 BUTTON_IDLE_PATH,
361 BUTTON_IDLE_FILL);
362 states[6].lit = cache_button(g, path_button_shrink,
363 BUTTON_LIT_PATH,
364 BUTTON_LIT_FILL);
365 states[7].idle = cache_button(g, path_button_lines,
366 BUTTON_IDLE_PATH,
367 BUTTON_IDLE_FILL);
368 states[7].lit = cache_button(g, path_button_lines,
369 BUTTON_LIT_PATH,
370 BUTTON_LIT_FILL);
371 states[8].idle = cache_button(g, path_button_int,
372 BUTTON_IDLE_PATH,
373 BUTTON_IDLE_FILL);
374 states[8].lit = cache_button(g, path_button_int,
375 BUTTON_LIT_PATH,
376 BUTTON_LIT_FILL);
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){
393 int i;
394 buttonstate *states=g->b.states;
396 for(i=0;i<NUMBUTTONS;i++){
397 buttonstate *b=states+i;
398 if(b->position>0)
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)
401 return b;
403 return 0;
406 /* set a button to pressed or lit */
407 void button_set_state(Gameboard *g, buttonstate *b, int rollover, int press){
408 int i;
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;
415 if(bb->position>0){
416 if(bb!=b){
417 if(bb->rollover)
418 invalidate_rollover(g,bb);
419 if(bb->press)
420 invalidate_button(g,bb);
422 bb->rollover=0;
423 bb->press=0;
424 }else{
425 if(bb->rollover != rollover)
426 invalidate_rollover(g,bb);
427 if(bb->press != press)
428 invalidate_button(g,bb);
430 bb->rollover=rollover;
431 bb->press=press;
435 g->b.allclear=0;
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);
446 cairo_destroy(c);
449 /* perform a single frame of animation for all buttons/rollovers */
450 gboolean animate_button_frame(gpointer ptr){
451 Gameboard *g=(Gameboard *)ptr;
452 GdkRectangle expose;
453 buttonstate *states=g->b.states;
454 int ret=0,i,pos;
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)
471 gameboard_draw(g,
472 expose.x,
473 expose.y,
474 expose.width,
475 expose.height);
478 if(!ret)g->b.sweeperd = 0;
480 if(!ret && g->button_timer!=0){
481 g_source_remove(g->button_timer);
482 g->button_timer=0;
485 if(!ret && g->button_callback)
486 // undeploy finished... call the undeploy callback
487 g->button_callback(g);
489 return ret;
492 /* the normal expose method for all buttons; called from the master
493 widget's expose */
494 void expose_buttons(Gameboard *g,cairo_t *c, int x,int y,int w,int h){
495 int i;
496 buttonstate *states=g->b.states;
498 for(i=0;i<NUMBUTTONS;i++){
500 buttonstate *b=states+i;
502 if(b->position>0){
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 &&
508 x+w > br.x &&
509 y+h > br.y) {
511 cairo_set_source_surface(c,
512 (b->rollover || b->press ? b->lit : b->idle),
513 br.x,
514 br.y);
516 if(b->press)
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 &&
524 y < r.y+r.height &&
525 x+w > r.x &&
526 y+h > r.y)
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){
535 int i;
536 int dx=w/2-oldw/2;
537 int dy=h/2-oldh/2;
538 buttonstate *states=g->b.states;
540 for(i=0;i<NUMBUTTONS;i++){
541 if(abs(states[i].position) == 2){
543 states[i].x+=dx;
544 states[i].x_target+=dx;
546 states[i].y+=dy;
547 states[i].y_target+=dy;
548 states[i].y_active+=dy;
549 states[i].y_inactive+=dy;
553 dx=w-oldw;
554 dy=h-oldh;
556 for(i=0;i<NUMBUTTONS;i++){
557 if(abs(states[i].position)==1 ||
558 abs(states[i].position)==3){
559 states[i].y+=dy;
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){
568 states[i].x+=dx;
569 states[i].x_target+=dx;
574 /* clear all buttons to unpressed/unlit */
575 void button_clear_state(Gameboard *g){
576 int i;
577 buttonstate *states=g->b.states;
579 if(!g->b.allclear){
580 for(i=0;i<NUMBUTTONS;i++){
581 buttonstate *bb=states+i;
582 if(bb->position){
583 if(bb->rollover)
584 invalidate_rollover(g,bb);
585 if(bb->press)
586 invalidate_button(g,bb);
588 bb->rollover=0;
589 bb->press=0;
593 g->b.allclear=1;
594 g->b.grabbed=0;
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.
605 g->b.sweeper = 0;
606 g->b.sweeperd = 1;
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.
628 g->b.sweeper = 0;
629 g->b.sweeperd = -1;
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);
635 }else
636 callback(g);