1 /*
2  *                               Alizarin Tetris
3  * A generic way to display menus.
4  *
5  * Copyright 2000, Kiri Wagstaff & Westley Weimer
6  */
7 
8 #include <config.h>	/* go autoconf! */
9 #include <assert.h>
10 
11 #include "atris.h"
12 #include "display.h"
13 #include "menu.h"
14 
15 /***************************************************************************
16  *      menu()
17  * Create a new menu and return it.
18  *********************************************************************PROTO*/
menu(int nButtons,char ** strings,int defaultchoice,Uint32 face_color0,Uint32 text_color0,Uint32 face_color1,Uint32 text_color1,int x,int y)19 ATMenu* menu(int nButtons, char** strings, int defaultchoice,
20 	     Uint32 face_color0, Uint32 text_color0,
21 	     Uint32 face_color1, Uint32 text_color1, int x, int y)
22 {
23   int i, yp;
24   ATMenu* am = (ATMenu*)malloc(sizeof(ATMenu)); assert(am);
25 
26   /* Create the buttons */
27   am->buttons = (ATButton**)malloc(nButtons*sizeof(ATButton*));
28   assert(am->buttons);
29   am->clicked = (char*)malloc(nButtons*sizeof(char)); assert(am->clicked);
30   am->nButtons = nButtons;
31   am->x = x; am->y = y;
32   am->defaultchoice = defaultchoice;
33 
34   yp = y;
35 
36   Assert(nButtons >= 0);
37 
38   for (i=0; i<nButtons; i++)
39     {
40       /* Line them up x-wise */
41       am->buttons[i] = button(strings[i], face_color0, text_color0,
42 			      face_color1, text_color1, x, yp);
43       yp += am->buttons[i]->area.h; /* add a separating space? */
44       am->clicked[i] = FALSE;
45     }
46 
47   am->w = am->buttons[0]->area.w;
48   am->h = (am->buttons[nButtons-1]->area.y - am->buttons[0]->area.y)
49     + am->buttons[nButtons-1]->area.h;
50   /* Set the default one */
51   if (defaultchoice >= 0) am->clicked[defaultchoice] = TRUE;
52 
53   return am;
54 }
55 
56 /***************************************************************************
57  *      show_menu()
58  * Displays the menu.
59  *********************************************************************PROTO*/
show_menu(ATMenu * am)60 void show_menu(ATMenu* am)
61 {
62   int i;
63   for (i=0; i<am->nButtons; i++)
64     show_button(am->buttons[i], am->clicked[i]);
65 }
66 
67 /***************************************************************************
68  *      check_menu()
69  * Returns the index of the button that was clicked (-1 if none).
70  *********************************************************************PROTO*/
check_menu(ATMenu * am,int x,int y)71 int check_menu(ATMenu* am, int x, int y)
72 {
73   int i, j;
74   for (i=0; i<am->nButtons; i++)
75     if (check_button(am->buttons[i], x, y))
76       {
77 	/* invert its clicked state */
78 	if (am->clicked[i])
79 	  {
80 	    am->clicked[i] = FALSE;
81 	    /* reset the default */
82 	    am->clicked[am->defaultchoice] = TRUE;
83 	    show_button(am->buttons[am->defaultchoice],
84 			am->clicked[am->defaultchoice]);
85 	  }
86 	else
87 	  {
88 	    /* blank all others */
89 	    for (j=0; j<am->nButtons; j++)
90 	      if (i!=j && am->clicked[j])
91 		{
92 		  am->clicked[j] = FALSE;
93 		  show_button(am->buttons[j], am->clicked[j]);
94 		}
95 	    am->clicked[i] = TRUE;
96 	  }
97 	/* redraw it */
98 	show_button(am->buttons[i], am->clicked[i]);
99 	Debug("Button %d was clicked.\n", i);
100 	return i;
101       }
102   return -1;
103 }
104 
105 /***************************************************************************
106  *      delete_menu()
107  * Deletes the menu and all of its buttons.
108  *********************************************************************PROTO*/
delete_menu(ATMenu * am)109 void delete_menu(ATMenu* am)
110 {
111   int i;
112   for (i=0; i<am->nButtons; i++)
113     Free(am->buttons[i]);
114   Free(am);
115 }
116 
117 #define BORDER_X	8
118 #define BORDER_Y	8
119 #define BUTTON_X_SPACE	4
120 #define RADIO_HEIGHT	12
121 
122 /***************************************************************************
123  *      setup_radio()
124  * Prepared a WalkRadio button for display.
125  *********************************************************************PROTO*/
126 void
setup_radio(WalkRadio * wr)127 setup_radio(WalkRadio *wr)
128 {
129     int i;
130     int our_height;
131 
132     wr->face_color[0] = int_button_face0;
133     wr->face_color[1] = int_button_face1;
134     wr->text_color[0] = int_button_text0;
135     wr->text_color[1] = int_button_text1;
136     wr->border_color[0] = int_button_border0;
137     wr->border_color[1] = int_button_border1;
138 
139     wr->bitmap0 = malloc(sizeof(SDL_Surface *) * wr->n); Assert(wr->bitmap0);
140     wr->bitmap1 = malloc(sizeof(SDL_Surface *) * wr->n); Assert(wr->bitmap1);
141     wr->w = 0;
142     wr->h = 0;
143 
144     for (i=0; i<wr->n; i++) {
145 	SDL_Color sc;
146 
147 	SDL_GetRGB(wr->text_color[0], screen->format, &sc.r, &sc.g, &sc.b);
148 	wr->bitmap0[i] = TTF_RenderText_Blended(sfont, wr->label[i], sc);
149 	Assert(wr->bitmap0[i]);
150 
151 	SDL_GetRGB(wr->text_color[1], screen->format, &sc.r, &sc.g, &sc.b);
152 	wr->bitmap1[i] = TTF_RenderText_Blended(sfont, wr->label[i], sc);
153 	Assert(wr->bitmap1[i]);
154 
155 	if (wr->bitmap0[i]->w + BUTTON_X_SPACE > wr->w)
156 	    wr->w = wr->bitmap0[i]->w + BUTTON_X_SPACE;
157 	if (wr->bitmap0[i]->h > wr->h) wr->h = wr->bitmap0[i]->h;
158     }
159     wr->w += 5 ;
160     wr->h += 10 ;
161     wr->area.w = wr->w + BORDER_X;
162 
163     our_height = RADIO_HEIGHT;
164     if (wr->n < our_height) our_height = wr->n;
165 
166     wr->area.h = wr->h * our_height + BORDER_Y;
167     return;
168 }
169 
170 /***************************************************************************
171  *      check_radio()
172  * Determines if we have clicked in a radio button.
173  *********************************************************************PROTO*/
174 int
check_radio(WalkRadio * wr,int x,int y)175 check_radio(WalkRadio *wr, int x, int y)
176 {
177     if (wr->inactive) return 0;
178 
179     if (    x  >= wr->area.x &&
180 	    x  <= wr->area.x + wr->area.w &&
181 	    y  >= wr->area.y &&
182 	    y  <= wr->area.y + wr->area.h   ) {
183 	int start, stop;
184 	int c;
185 
186 	if (wr->n < RADIO_HEIGHT) {	/* draw them all */
187 	    start = 0;
188 	    stop = wr->n;
189 	} else {		/* at least four */
190 	    start = wr->defaultchoice - (RADIO_HEIGHT/2);
191 	    if (start < 0) start = 0;
192 	    stop = start + RADIO_HEIGHT;
193 	    if (stop > wr->n) {	/* we're at the bottom */
194 		start -= (stop - wr->n);
195 		stop = wr->n;
196 	    }
197 	}
198 	c = (y - wr->area.y) / wr->h;
199 	c += start;
200 	if (c < 0) c = 0;
201 	if (c >= wr->n) c = wr->n - 1;
202 	wr->defaultchoice = c;
203 	return 1;
204     }
205     return 0;
206 }
207 
208 /***************************************************************************
209  *      clear_radio()
210  * Clears away a WalkRadio button. Works even if I am inactive.
211  *********************************************************************PROTO*/
212 void
clear_radio(WalkRadio * wr)213 clear_radio(WalkRadio *wr)
214 {
215     SDL_FillRect(widget_layer, &wr->area, int_black);
216     SDL_FillRect(screen, &wr->area, int_black);
217     SDL_BlitSurface(flame_layer, &wr->area, screen, &wr->area);
218     SDL_BlitSurface(widget_layer, &wr->area, screen, &wr->area);
219     SDL_UpdateSafe(screen, 1, &wr->area);
220     return;
221 }
222 
223 /***************************************************************************
224  *      draw_radio()
225  * Draws a WalkRadio button.
226  *
227  * Only draws me if I am not inactive.
228  *********************************************************************PROTO*/
229 void
draw_radio(WalkRadio * wr,int state)230 draw_radio(WalkRadio *wr, int state)
231 {
232     int start, stop, i;
233     SDL_Rect draw;
234 
235     if (wr->inactive) return;
236 
237     wr->area.x = wr->x;
238     wr->area.y = wr->y;
239 
240     SDL_FillRect(widget_layer, &wr->area, wr->border_color[state]);
241 
242     if (wr->n < RADIO_HEIGHT) {	/* draw them all */
243 	start = 0;
244 	stop = wr->n;
245     } else {		/* at least four */
246 	start = wr->defaultchoice - (RADIO_HEIGHT/2);
247 	if (start < 0) start = 0;
248 	stop = start + RADIO_HEIGHT;
249 	if (stop > wr->n) {	/* we're at the bottom */
250 	    start -= (stop - wr->n);
251 	    stop = wr->n;
252 	}
253     }
254 
255     draw.x = wr->x + BORDER_X/2;
256     draw.y = wr->y + BORDER_Y/2;
257     draw.w = wr->w;
258     draw.h = wr->h;
259 
260     for (i=start; i<stop; i++) {
261 	int on = (wr->defaultchoice == i);
262 	SDL_Rect text;
263 
264 	SDL_FillRect(widget_layer, &draw, wr->text_color[on]);
265 	draw.x += 2; draw.y += 2; draw.w -= 4; draw.h -= 4;
266 	if (state || on)
267 	    SDL_FillRect(widget_layer, &draw, wr->face_color[on]);
268 	else
269 	    SDL_FillRect(widget_layer, &draw, wr->border_color[on]);
270 	draw.x += 3; draw.y += 3; draw.w -= 6; draw.h -= 6;
271 
272 	text.x = draw.x; text.y = draw.y;
273 	text.w = wr->bitmap0[i]->w;
274 	text.h = wr->bitmap0[i]->h;
275 	text.x += (draw.w - text.w) / 2;
276 
277 	if (on) {
278 	    SDL_BlitSafe(wr->bitmap1[i], NULL, widget_layer, &text);
279 	} else {
280 	    SDL_BlitSafe(wr->bitmap0[i], NULL, widget_layer, &text);
281 	}
282 	draw.x -= 5; draw.y -= 5; draw.w += 10; draw.h += 10;
283 
284 	draw.y += draw.h; /* move down! */
285     }
286 
287     SDL_BlitSurface(flame_layer, &wr->area, screen, &wr->area);
288     SDL_BlitSurface(widget_layer, &wr->area, screen, &wr->area);
289     SDL_UpdateSafe(screen, 1, &wr->area);
290 }
291 
292 /***************************************************************************
293  *      create_single_wrg()
294  * Creates a walk-radio-group with a single walk-radio button with N
295  * choices.
296  *********************************************************************PROTO*/
297 WalkRadioGroup *
create_single_wrg(int n)298 create_single_wrg(int n)
299 {
300     WalkRadioGroup *wrg;
301 
302     Calloc(wrg, WalkRadioGroup *, 1 * sizeof(*wrg));
303     wrg->n = 1;
304     Calloc(wrg->wr, WalkRadio *, wrg->n * sizeof(*wrg->wr));
305     wrg->wr[0].n = n;
306     Calloc(wrg->wr[0].label, char **, wrg->wr[0].n * sizeof(*wrg->wr[0].label));
307     return wrg;
308 }
309 
310 /***************************************************************************
311  *      handle_radio_event()
312  * Tries to handle the given event with respect to the given set of
313  * WalkRadio's. Returns non-zero if an action said that we are done.
314  *********************************************************************PROTO*/
315 int
handle_radio_event(WalkRadioGroup * wrg,const SDL_Event * ev)316 handle_radio_event(WalkRadioGroup *wrg, const SDL_Event *ev)
317 {
318     int i, ret;
319     if (ev->type == SDL_MOUSEBUTTONDOWN) {
320 	for (i=0; i<wrg->n; i++) {
321 	    if (check_radio(&wrg->wr[i], ev->button.x, ev->button.y)) {
322 		if (i != wrg->cur) {
323 		    draw_radio(&wrg->wr[wrg->cur], 0);
324 		}
325 		draw_radio(&wrg->wr[i], 1);
326 		if (wrg->wr[i].action) {
327 		    ret = wrg->wr[i].action(&wrg->wr[i]);
328 		    return ret;
329 		} else
330 		    return wrg->wr[i].defaultchoice;
331 	    }
332 	}
333     } else if (ev->type == SDL_KEYDOWN) {
334 	switch (ev->key.keysym.sym) {
335 	    case SDLK_RETURN:
336 		if (wrg->wr[wrg->cur].action) {
337 		    ret = wrg->wr[wrg->cur].action(&wrg->wr[wrg->cur]);
338 		    return ret;
339 		} else
340 		    return wrg->wr[wrg->cur].defaultchoice;
341 		return 1;
342 
343 	    case SDLK_UP:
344 		wrg->wr[wrg->cur].defaultchoice --;
345 		if (wrg->wr[wrg->cur].defaultchoice < 0)
346 		    wrg->wr[wrg->cur].defaultchoice = wrg->wr[wrg->cur].n - 1;
347 		draw_radio(&wrg->wr[wrg->cur], 1);
348 		return -1;
349 		break;
350 
351 	    case SDLK_DOWN:
352 		wrg->wr[wrg->cur].defaultchoice ++;
353 		if (wrg->wr[wrg->cur].defaultchoice >= wrg->wr[wrg->cur].n)
354 		    wrg->wr[wrg->cur].defaultchoice = 0;
355 		draw_radio(&wrg->wr[wrg->cur], 1);
356 		return -1;
357 
358 	    case SDLK_LEFT:
359 		draw_radio(&wrg->wr[wrg->cur], 0);
360 		do {
361 		    wrg->cur--;
362 		    if (wrg->cur < 0)
363 			wrg->cur = wrg->n - 1;
364 		} while (wrg->wr[wrg->cur].inactive);
365 		draw_radio(&wrg->wr[wrg->cur], 1);
366 		return -1;
367 
368 	    case SDLK_RIGHT:
369 		draw_radio(&wrg->wr[wrg->cur], 0);
370 		do {
371 		    wrg->cur++;
372 		    if (wrg->cur >= wrg->n)
373 			wrg->cur = 0;
374 		} while (wrg->wr[wrg->cur].inactive);
375 		draw_radio(&wrg->wr[wrg->cur], 1);
376 		return -1;
377 
378 	    default:
379 		return -1;
380 	}
381     }
382     return -1;
383 }
384 
385 /*
386  * $Log: menu.c,v $
387  * Revision 1.17  2000/11/06 01:22:40  wkiri
388  * Updated menu system.
389  *
390  * Revision 1.16  2000/11/06 00:24:01  weimer
391  * add WalkRadioGroup modifications (inactive field for Kiri) and some support
392  * for special pieces
393  *
394  * Revision 1.15  2000/11/02 03:06:20  weimer
395  * better interface for walk-radio menus: we are now ready for Kiri to change
396  * them to add run-time options ...
397  *
398  * Revision 1.14  2000/10/29 21:23:28  weimer
399  * One last round of header-file changes to reflect my newest and greatest
400  * knowledge of autoconf/automake. Now if you fail to have some bizarro
401  * function, we try to go on anyway unless it is vastly needed.
402  *
403  * Revision 1.13  2000/10/29 17:23:13  weimer
404  * incorporate "xflame" flaming background for added spiffiness ...
405  *
406  * Revision 1.12  2000/10/21 01:22:44  weimer
407  * prevent segfault from out-of-range values
408  *
409  * Revision 1.11  2000/10/21 01:14:43  weimer
410  * massic autoconf/automake restructure ...
411  *
412  * Revision 1.10  2000/10/19 22:06:51  weimer
413  * minor changes ...
414  *
415  * Revision 1.9  2000/10/18 23:57:49  weimer
416  * general fixup, color changes, display changes.
417  * Notable: "Safe" Blits and Updates now perform "clipping". No more X errors,
418  * we hope!
419  *
420  * Revision 1.8  2000/10/13 02:26:54  weimer
421  * rudimentary identity functions, including adding new players
422  *
423  * Revision 1.7  2000/10/12 22:21:25  weimer
424  * display changes, more multi-local-play threading (e.g., myScore ->
425  * Score[0]), that sort of thing ...
426  *
427  * Revision 1.6  2000/10/12 19:17:08  weimer
428  * Further support for AI players and multiple game types.
429  *
430  * Revision 1.5  2000/10/12 00:49:08  weimer
431  * added "AI" support and walking radio menus for initial game configuration
432  *
433  * Revision 1.4  2000/09/09 17:05:35  wkiri
434  * Hideous log changes (Wes: how dare you include a comment character!)
435  *
436  * Revision 1.3  2000/09/09 16:58:27  weimer
437  * Sweeping Change of Ultimate Mastery. Main loop restructuring to clean up
438  * main(), isolate the behavior of the three game types. Move graphic files
439  * into graphics/-, style files into styles/-, remove some unused files,
440  * add game flow support (breaks between games, remembering your level within
441  * this game), multiplayer support for the event loop, some global variable
442  * cleanup. All that and a bag of chips!
443  *
444  * Revision 1.2  2000/09/04 22:49:51  wkiri
445  * gamemenu now uses the new nifty menus.  Also, added delete_menu.
446  *
447  * Revision 1.1  2000/09/04 19:54:26  wkiri
448  * Added menus (menu.[ch]).
449  */
450