1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 Copyright (C) 2013 COR Entertainment, LLC.
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
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 
14 See the GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 
20 */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include <string.h>
27 #include <ctype.h>
28 
29 #include "client.h"
30 #include "qmenu.h"
31 
32 static void	 ItemName_Draw (menuaction_s *a, FNT_font_t font, const float *color);
33 static void	 Action_Draw (menuaction_s *a, FNT_font_t font);
34 static void  Menu_DrawStatusBar( const char *string );
35 static void  Menu_DrawToolTip (const menuitem_s *item);
36 static void  Label_Draw (menutxt_s *s, FNT_font_t font, const float *color);
37 static void	 Slider_DoSlide( menuslider_s *s, int dir );
38 static void	 Slider_Draw (menuslider_s *s, FNT_font_t font);
39 static void	 SpinControl_Draw (menulist_s *s, FNT_font_t font);
40 static void	 SpinControl_DoSlide( menulist_s *s, int dir );
41 static void  SubMenu_Draw (menuframework_s *s, FNT_font_t font);
42 
43 static void Menu_DrawHorizBar (const char *pathbase, float x, float y, float w, float base_size, float alpha);
44 static void Menu_DrawVertBar (const char *pathbase, float x, float y, float h, float base_size, float alpha);
45 
46 void Menu_DrawBorder (menuframework_s *menu, const char *title, const char *prefix);
47 
48 static menuvec2_t Menu_GetBorderSize (menuframework_s *s);
49 
50 // dark color = same blue as icons, light color = white
51 const float dark_color[4] = {0, 1, 200.0f/255.0f, 1};
52 const float light_color[4] = {1, 1, 1, 1};
53 const float highlight_color[4] = {1, 1, 1, 0.95};
54 
55 #define VID_WIDTH viddef.width
56 #define VID_HEIGHT viddef.height
57 
Menu_PredictSize(const char * str)58 int Menu_PredictSize (const char *str)
59 {
60 	FNT_font_t		font;
61 
62 	font = FNT_AutoGet( CL_menuFont );
63 
64 	return FNT_PredictSize (font, str, true);
65 }
66 
ItemName_Draw(menuitem_s * a,FNT_font_t font,const float * color)67 void ItemName_Draw (menuitem_s *a, FNT_font_t font, const float *color)
68 {
69 	int text_x, text_y;
70 	unsigned int align, cmode;
71 
72 	if (a->generic.namedraw != NULL)
73 	{
74 		a->generic.namedraw (a, font);
75 		return;
76 	}
77 
78 	if (a->generic.name == NULL)
79 		return;
80 
81 	text_x = Item_GetX (*a);
82 	text_y = Item_GetY (*a) + MenuText_UpperMargin (a, font->size);
83 
84 	if ((a->generic.flags & QMF_BUTTON))
85 	{
86 		int border_x, width;
87 
88 		width = CHASELINK(a->generic.lsize).x+CHASELINK(a->generic.rsize).x;
89 
90 		if (!a->generic.parent->horizontal)
91 		{
92 			align = FNT_ALIGN_CENTER;
93 			text_x = a->generic.parent->x + width/2;
94 			border_x = a->generic.parent->x;
95 		}
96 		else
97 		{
98 			align = FNT_ALIGN_RIGHT;
99 			border_x = text_x - width + RCOLUMN_OFFSET/2;
100 		}
101 
102 		// HACK
103 		if (color == dark_color)
104 			Menu_DrawHorizBar ("menu/button_border", border_x, Item_GetY(*a)+2, width, font->height*2-4, a->generic.highlight_alpha*a->generic.highlight_alpha);
105 	}
106 	else if ( a->generic.flags & QMF_RIGHT_COLUMN )
107 	{
108 		align = FNT_ALIGN_LEFT;
109 	}
110 	else
111 	{
112 		if (a->generic.parent->horizontal)
113 		{
114 			align = FNT_ALIGN_RIGHT;
115 			text_x += LCOLUMN_OFFSET;
116 		}
117 		else
118 		{
119 			align = FNT_ALIGN_LEFT;
120 			text_x = a->generic.parent->x;
121 		}
122 	}
123 
124 	if ( a->generic.flags & QMF_STRIP_COLOR || color == highlight_color )
125 		cmode = FNT_CMODE_TWO;
126 	else
127 		cmode = FNT_CMODE_QUAKE_SRS;
128 
129 	Menu_DrawString (
130 		text_x, text_y,
131 		a->generic.name, cmode, align, color
132 	);
133 }
134 
Action_Draw(menuaction_s * a,FNT_font_t font)135 void Action_Draw (menuaction_s *a, FNT_font_t font)
136 {
137 	if ( a->generic.itemdraw )
138 		a->generic.itemdraw( a, font );
139 }
140 
Field_Draw(menufield_s * f,FNT_font_t font)141 void Field_Draw (menufield_s *f, FNT_font_t font)
142 {
143 	char tempbuffer[128]="";
144 	int x, y;
145 
146 	y = Item_GetY (*f) + MenuText_UpperMargin (f, font->size);
147 	x = Item_GetX (*f) + RCOLUMN_OFFSET;
148 
149 	strncpy( tempbuffer, f->buffer, f->generic.visible_length);
150 
151 	Menu_DrawHorizBar (
152 		"menu/slide_border", (float)x, (float)y-2.0,
153 		(float)(f->generic.visible_length*font->size)+4.0, (float)(font->size)+4.0,
154 		f->generic.highlight_alpha*f->generic.highlight_alpha
155 	);
156 
157 	menu_box.x = x+4;
158 	menu_box.y = y;
159 	menu_box.height = 0;
160 	menu_box.width = f->generic.visible_length*font->width;
161 	FNT_BoundedPrint (font, f->buffer, FNT_CMODE_QUAKE_SRS, FNT_ALIGN_LEFT, &menu_box, light_color);
162 
163 	if ( cursor.menuitem == f )
164 	{
165 		if ( ( ( int ) ( Sys_Milliseconds() / 300 ) ) & 1 )
166 			Draw_StretchPic (menu_box.x + menu_box.width - font->size / 8, menu_box.y-1, font->size, font->size+4, "menu/field_cursor");
167 	}
168 }
169 
Field_Key(int key)170 qboolean Field_Key (int key)
171 {
172 	menufield_s *f;
173 	extern int keydown[];
174 
175 	f = cursor.menuitem;
176 
177 	if (f == NULL || f->generic.type != MTYPE_FIELD || key > 127)
178 		return false;
179 
180 	switch ( key )
181 	{
182 	case K_KP_SLASH:
183 		key = '/';
184 		break;
185 	case K_KP_MINUS:
186 		key = '-';
187 		break;
188 	case K_KP_PLUS:
189 		key = '+';
190 		break;
191 	case K_KP_HOME:
192 		key = '7';
193 		break;
194 	case K_KP_UPARROW:
195 		key = '8';
196 		break;
197 	case K_KP_PGUP:
198 		key = '9';
199 		break;
200 	case K_KP_LEFTARROW:
201 		key = '4';
202 		break;
203 	case K_KP_5:
204 		key = '5';
205 		break;
206 	case K_KP_RIGHTARROW:
207 		key = '6';
208 		break;
209 	case K_KP_END:
210 		key = '1';
211 		break;
212 	case K_KP_DOWNARROW:
213 		key = '2';
214 		break;
215 	case K_KP_PGDN:
216 		key = '3';
217 		break;
218 	case K_KP_INS:
219 		key = '0';
220 		break;
221 	case K_KP_DEL:
222 		key = '.';
223 		break;
224 	}
225 
226 	/*
227 	** support pasting from the clipboard
228 	*/
229 	if ( ( toupper( key ) == 'V' && keydown[K_CTRL] ) ||
230 		 ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keydown[K_SHIFT] ) )
231 	{
232 		char *cbd;
233 
234 		if ( ( cbd = Sys_GetClipboardData() ) != 0 )
235 		{
236 			strtok( cbd, "\n\r\b" );
237 
238 			strncpy( f->buffer, cbd, f->length - 1 );
239 			f->cursor = strlen( f->buffer );
240 
241 			free( cbd );
242 		}
243 		return true;
244 	}
245 
246 	switch ( key )
247 	{
248 	case K_KP_LEFTARROW:
249 	case K_LEFTARROW:
250 	case K_BACKSPACE:
251 		if ( f->cursor > 0 )
252 		{
253 			memmove( &f->buffer[f->cursor-1], &f->buffer[f->cursor], strlen( &f->buffer[f->cursor] ) + 1 );
254 			f->cursor--;
255 		}
256 		break;
257 
258 	case K_KP_DEL:
259 	case K_DEL:
260 		memmove( &f->buffer[f->cursor], &f->buffer[f->cursor+1], strlen( &f->buffer[f->cursor+1] ) + 1 );
261 		break;
262 
263 	case K_KP_ENTER:
264 	case K_ENTER:
265 	case K_ESCAPE:
266 	case K_TAB:
267 		return false;
268 
269 	case K_SPACE:
270 	default:
271 		if ( !isdigit( key ) && ( f->generic.flags & QMF_NUMBERSONLY ) )
272 			return false;
273 
274 		{
275 			int maxlength = f->length;
276 			if (f->length > sizeof(f->buffer)-2 || f->length == 0)
277 			{
278 				maxlength = sizeof(f->buffer)-2;
279 			}
280 			if ( f->cursor < maxlength )
281 			{
282 				f->buffer[f->cursor++] = key;
283 				f->buffer[f->cursor] = 0;
284 			}
285 		}
286 	}
287 
288 	return true;
289 }
290 
_Menu_AddItem(menuframework_s * menu,menucommon_s * item)291 void _Menu_AddItem( menuframework_s *menu, menucommon_s *item )
292 {
293 	if ( menu->nitems < MAXMENUITEMS )
294 	{
295 		menu->items[menu->nitems] = item;
296 		( ( menucommon_s * ) menu->items[menu->nitems] )->parent = menu;
297 		menu->nitems++;
298 	}
299 }
300 
301 // Returns true or false depending on whether the given item is selectable.
Menu_ItemSelectable(menuitem_s * item)302 static inline qboolean Menu_ItemSelectable (menuitem_s *item)
303 {
304 	if (item->generic.type == MTYPE_NOT_INTERACTIVE)
305 		return false;
306 	if (item->generic.type == MTYPE_SUBMENU)
307 		return ((menuframework_s *)item)->enable_highlight;
308 	return true;
309 }
310 
Cursor_SelectItem(menuitem_s * item)311 void Cursor_SelectItem (menuitem_s *item)
312 {
313 	cursor.menuitem = item;
314 	if (item == NULL)
315 	{
316 		cursor.menulayer = -1;
317 		return;
318 	}
319 	cursor.menulayer = Cursor_GetLayer ();
320 }
321 
322 // Returns the top level node of the menu tree that contains the item
Menu_GetItemTree(menuitem_s * item)323 menuframework_s	*Menu_GetItemTree (menuitem_s *item)
324 {
325 	if (item->generic.parent == NULL)
326 		return (menuframework_s *)item;
327 	return Menu_GetItemTree ((menuitem_s *)item->generic.parent);
328 }
329 
330 // Returns true if the current cursor item is within the specified menu tree.
Menu_ContainsCursorItem(menuframework_s * menu)331 qboolean Menu_ContainsCursorItem (menuframework_s *menu)
332 {
333 	menuitem_s	*item;
334 
335 	item = cursor.menuitem;
336 
337 	if (item != NULL)
338 	{
339 		menuframework_s *menu2 = item->generic.parent;
340 		while (menu2 != NULL)
341 		{
342 			if (menu2 == menu)
343 				return true;
344 			menu2 = menu2->generic.parent;
345 		}
346 	}
347 
348 	return false;
349 }
350 
351 // Attempt to select the first reasonable menu item in the specified menu tree
352 // as the cursor item. Returns false if unsuccessful (if there are no
353 // selectable menu items within the menu or any of its submenus.) Does nothing
354 // if the cursor item is already within the menu or one of its submenus.
Cursor_SelectMenu(menuframework_s * menu)355 qboolean Cursor_SelectMenu (menuframework_s *menu)
356 {
357 	int			i;
358 	menuitem_s	*item;
359 
360 	if (Menu_ContainsCursorItem (menu))
361 		return true;
362 
363 	refreshCursorLink ();
364 
365 	if (menu->default_cursor_selection != NULL)
366 	{
367 		if (Menu_ItemSelectable (menu->default_cursor_selection))
368 		{
369 			Cursor_SelectItem (menu->default_cursor_selection);
370 			return true;
371 		}
372 		if (menu->default_cursor_selection->generic.type == MTYPE_SUBMENU)
373 		{
374 			if (Cursor_SelectMenu ((menuframework_s *)menu->default_cursor_selection))
375 				return true;
376 		}
377 	}
378 
379 	for (i = 0; i < menu->nitems; i++)
380 	{
381 		item = menu->items[i];
382 		if (Menu_ItemSelectable (item))
383 		{
384 			Cursor_SelectItem (item);
385 			return true;
386 		}
387 		if (item->generic.type == MTYPE_SUBMENU)
388 		{
389 			menuframework_s *sm = (menuframework_s *)item;
390 			if (sm->navagable && Cursor_SelectMenu (sm))
391 				return true;
392 		}
393 	}
394 
395 	return false;
396 }
397 
398 // Find the index of the item within its parent menu.
Menu_ItemIndex(menuitem_s * item)399 int Menu_ItemIndex (menuitem_s *item)
400 {
401 	int 			ret;
402 	menuframework_s	*menu;
403 
404 	menu = item->generic.parent;
405 
406 	for (ret = 0; ret < menu->nitems; ret++)
407 	{
408 		if (menu->items[ret] == item)
409 			return ret;
410 	}
411 
412 	Com_Error (ERR_FATAL, "CAN'T HAPPEN: Item is not in its own parent menu!");
413 
414 	return 0;
415 }
416 
417 // returns true if the item is a submenu that the cursor can advance "into."
CanAdvanceInto(menuitem_s * item,qboolean allow_capture)418 static qboolean CanAdvanceInto (menuitem_s *item, qboolean allow_capture)
419 {
420 	if (item->generic.type != MTYPE_SUBMENU)
421 		return false;
422 
423 	// if the submenu will "capture" the cursor, only advance into it if the
424 	// tab key has been pressed instead an arrow key.
425 	if (!allow_capture && (item->generic.flags & QMF_SUBMENU_CAPTURE))
426 		return false;
427 
428 	// selectable submenus aren't treated like submenus
429 	if (Menu_ItemSelectable (item))
430 		return false;
431 
432 	return true;
433 }
434 
435 // returns true if the cursor can advance out of the submenu
CanAdvanceOutOf(menuframework_s * submenu,qboolean allow_capture)436 static qboolean CanAdvanceOutOf (menuframework_s *submenu, qboolean allow_capture)
437 {
438 	// if the submenu has "captured" the cursor, only advance out of it if the
439 	// tab key has been pressed instead of an arrow key.
440 	if (!allow_capture && (submenu->generic.flags & QMF_SUBMENU_CAPTURE))
441 		return false;
442 
443 	return true;
444 }
445 
446 // Selects either the next or the previous menu item.
Menu_AdvanceCursor(int dir,qboolean allow_capture)447 void Menu_AdvanceCursor (int dir, qboolean allow_capture)
448 {
449 	int				item_index;
450 	menuframework_s	*menu;
451 	menuitem_s		*item, *newitem;
452 
453 	item = cursor.menuitem;
454 
455 	if (item == NULL)
456 		return;
457 
458 	menu = item->generic.parent;
459 
460 	if (item->generic.type == MTYPE_VERT_SCROLLBAR)
461 	{
462 		menu->yscroll += 2*dir;
463 		if (menu->yscroll < 0)
464 			menu->yscroll = 0;
465 		if (menu->yscroll > menu->maxscroll)
466 			menu->yscroll = menu->maxscroll;
467 		return;
468 	}
469 
470 	item_index = Menu_ItemIndex (item);
471 
472 	do
473 	{
474 		item_index += dir;
475 		while (item_index < 0 || item_index >= menu->nitems)
476 		{
477 			menuframework_s *parent = menu->generic.parent;
478 			if (parent == NULL || !CanAdvanceOutOf (menu, allow_capture))
479 			{
480 				item_index += menu->nitems;
481 				item_index %= menu->nitems;
482 			}
483 			else
484 			{
485 				item_index = Menu_ItemIndex ((menuitem_s *)menu) + dir;
486 				menu = parent;
487 			}
488 		}
489 		newitem = menu->items[item_index];
490 		while (CanAdvanceInto (newitem, allow_capture))
491 		{
492 			menu = (menuframework_s *)newitem;
493 			if (dir == 1)
494 				item_index = 0;
495 			else
496 				item_index = menu->nitems-1;
497 			newitem = menu->items[item_index];
498 		}
499 	} while (!Menu_ItemSelectable (newitem));
500 
501 	Cursor_SelectItem (newitem);
502 }
503 
Menu_Center(menuframework_s * menu)504 void Menu_Center( menuframework_s *menu )
505 {
506 	menu->y = ( (int)VID_HEIGHT - Menu_TrueHeight(*menu) ) / 2;
507 }
508 
SpinControl_MaxLines(menulist_s * s)509 int SpinControl_MaxLines (menulist_s *s)
510 {
511 	int i;
512 
513 	for (i = 0; s->itemnames[i]; i++)
514 	{
515 		if (strchr( s->itemnames[i], '\n' ))
516 			return 2;
517 	}
518 
519 	return 1;
520 }
521 
SpinControl_MaxWidth(menulist_s * s)522 int SpinControl_MaxWidth (menulist_s *s)
523 {
524 	char buffer[100];
525 	int i;
526 	int ret = 0;
527 
528 	for (i = 0; s->itemnames[i]; i++)
529 	{
530 		if ( !strchr( s->itemnames[i], '\n' ) )
531 		{
532 			int npix = Menu_PredictSize (s->itemnames[i]);
533 			if (npix > ret)
534 				ret = npix;
535 		}
536 		else
537 		{
538 			int npix;
539 			strcpy( buffer, s->itemnames[i] );
540 			*strchr( buffer, '\n' ) = 0;
541 			npix = Menu_PredictSize (buffer);
542 			if (npix > ret)
543 				ret = npix;
544 			strcpy( buffer, strchr( s->itemnames[i], '\n' ) + 1 );
545 			npix = Menu_PredictSize (buffer);
546 			if (npix > ret)
547 				ret = npix;
548 		}
549 	}
550 
551 	return ret;
552 }
553 
Menu_Item_LeftSize(menucommon_s * self,FNT_font_t font)554 static inline menuvec2_t Menu_Item_LeftSize (menucommon_s *self, FNT_font_t font)
555 {
556 	menuvec2_t ret;
557 	ret.x = ret.y = 0;
558 
559 	if (self->type == MTYPE_SUBMENU)
560 	{
561 		ret = Menu_GetBorderSize ((menuframework_s *)self);
562 		if (self->flags & QMF_SNUG_LEFT)
563 			ret.x += CHASELINK(self->parent->lwidth);
564 		return ret;
565 	}
566 
567 	if (self->name && !(self->flags & QMF_RIGHT_COLUMN))
568 	{
569 		ret.x = Menu_PredictSize (self->name);
570 		if (self->type != MTYPE_TEXT)
571 			ret.x -= LCOLUMN_OFFSET;
572 	}
573 
574 	if ((self->flags & QMF_BUTTON))
575 	{
576 		ret.y = 2*font->height;
577 		if (ret.x < CHASELINK(self->parent->lwidth) && !self->parent->horizontal)
578 			ret.x = CHASELINK(self->parent->lwidth);
579 	}
580 	else
581 	{
582 		ret.y = font->height;
583 	}
584 
585 	return ret;
586 }
587 
Menu_Item_RightSize(menucommon_s * self,FNT_font_t font)588 static inline menuvec2_t Menu_Item_RightSize (menucommon_s *self, FNT_font_t font)
589 {
590 	menuvec2_t ret;
591 	ret.x = ret.y = 0;
592 
593 	ret.y = font->height;
594 
595 	if (self->name && (self->flags & QMF_RIGHT_COLUMN))
596 	{
597 		ret.x = Menu_PredictSize (self->name);
598 		if (self->type != MTYPE_TEXT)
599 			ret.x += RCOLUMN_OFFSET;
600 	}
601 
602 	if (self->visible_length != 0)
603 		ret.x += font->width * self->visible_length + RCOLUMN_OFFSET;
604 
605 	switch ( self->type )
606 	{
607 		case MTYPE_SLIDER:
608 			ret.x = font->width * LONGINPUT_SIZE + RCOLUMN_OFFSET;
609 			break;
610 		case MTYPE_FIELD:
611 			ret.y += 4;
612 			break;
613 		case MTYPE_SPINCONTROL:
614 			ret.x += SpinControl_MaxWidth ((menulist_s *)self);
615 			ret.y = SpinControl_MaxLines ((menulist_s *)self)*font->height;
616 			if (ret.y > font->height)
617 				ret.y += font->height-font->size;
618 			break;
619 		case MTYPE_SUBMENU:
620 			ret.y = Menu_TrueHeight(*(menuframework_s *)self);
621 			ret.x = CHASELINK(((menuframework_s *)self)->lwidth) +
622 					CHASELINK(((menuframework_s *)self)->rwidth);
623 			if (self->flags & QMF_SNUG_LEFT)
624 				ret.x -= CHASELINK(self->parent->lwidth);
625 			{
626 				menuvec2_t border = Menu_GetBorderSize ((menuframework_s*)self);
627 				if (ret.x < border.x + border.x/2)
628 					ret.x = border.x + border.x/2;
629 				ret.x += border.x;
630 				ret.y += border.y;
631 			}
632 			break;
633 		case MTYPE_ACTION:
634 		case MTYPE_TEXT:
635 			break;
636 	}
637 
638 	if ((self->flags & QMF_BUTTON) && ret.x < CHASELINK(self->parent->rwidth) && !self->parent->horizontal)
639 		ret.x = CHASELINK(self->parent->rwidth);
640 
641 	return ret;
642 }
643 
644 /*
645 =============
646 Menu_AutoArrange
647 
648 This section of code is responsible for taking a menu tree and arranging all
649 the submenus and menu items on screen, in such a way that they fit the
650 constraints imposed on them. I'm afraid this is complicated enough to justify
651 a small essay.
652 
653 The reason this is so complicated is because of those constraints. Menu width,
654 menu item x, and menu item y are all "linkable" attributes, meaning they can
655 be linked together so they always have the same value. If updated in one
656 place, the updates show up everywhere else. Here's the problem: these linkable
657 attributes depend on each other, sometimes in complex ways.
658 
659 For example, take a table with two rows:
660 ITEMA	ITEMB	ITEMC
661 ITEMD	ITEME	ITEMF
662 ITEMA and ITEMD's x-coordinates are linked, ITEMB and ITEME's x-coordinates
663 are linked, etc.
664 
665 Now the easy way to do this would be to lay out the entirety of row 1, then
666 the entirety of row 2. But suppose you have a case where ITEMA and ITEME are
667 narrow, while ITEMD and ITEMB are wide? Here's what the table looks like
668 before it's been laid out at all:
669 [ITEMA][=ITEMB=][ITEMC]
670 [=ITEMD=][ITEME][ITEMF]
671 
672 Laying out the first row goes well. The required amount of space is inserted
673 between ITEMA and ITEMB, and because ITEMB and ITEME are linked, that change
674 is propagated to the second row automatically. Likewise for the space between
675 ITEMB and ITEMC, and the propagation from ITEMC to ITEMF.
676 [ITEMA]---[=ITEMB=]---[ITEMC]
677 [=ITEMD=]-[ITEME]-----[ITEMF]
678 
679 Then the second row is laid out. Since ITEMD is wider, the space between it
680 and ITEME isn't enough, so ITEME has to be moved for the space to be
681 sufficient. Because ITEMF is still far enough away from ITEME, it doesn't get
682 moved, and as a result, ITEMC doesn't get moved either! ITEMC's position is no
683 longer valid, so the first row has to be laid out again.
684 [ITEMA]-----[=ITEMB=]-[ITEMC]
685 [=ITEMD=]---[ITEME]---[ITEMF]
686 
687 To insure a proper layout, Menu_Arrange is simply called over and over again
688 until no more movement is detected. Actually, the number of calls is limited
689 at 5 to prevent things from getting too out of hand.
690 =============
691 */
692 
693 static int changes = 0;
694 #define MENU_INCREASELINK(l,v) \
695 {\
696 	if ((l).status == linkstatus_link) \
697 	{\
698 		if (*((l).ptr) < v) \
699 		{ \
700 			changes++; \
701 			*((l).ptr) = v; \
702 		} \
703 		else \
704 			v = *((l).ptr); \
705 	}\
706 	else if (reset || (l).val < v) \
707 	{\
708 		changes++;\
709 		(l).val = v; \
710 	}\
711 	else \
712 	{\
713 		v = (l).val;\
714 	}\
715 }
716 
Menu_Arrange(menuframework_s * menu,qboolean reset,FNT_font_t font)717 void Menu_Arrange (menuframework_s *menu, qboolean reset, FNT_font_t font)
718 {
719 	int i, x, y;
720 	int itemheight, horiz_height;
721 	menucommon_s *item;
722 
723 	// TODO: move this into its own function?
724 	if (!menu->initialized)
725 	{
726 		menu->initialized = true;
727 		menu->vertical_scrollbar.parent = menu;
728 		menu->vertical_scrollbar.type = MTYPE_VERT_SCROLLBAR;
729 	}
730 
731 	x = y = 0;
732 
733 	horiz_height = 0;
734 
735 	if (reset)
736 	{
737 		RESETLINK (menu->rwidth, 0);
738 		RESETLINK (menu->lwidth, 0);
739 		RESETLINK (menu->height, 0);
740 	}
741 
742 	for (i = 0; i < menu->nitems; i++ )
743 	{
744 		int lwidth;
745 		int rwidth;
746 
747 		item = ((menucommon_s * )menu->items[i]);
748 
749 		if (item->type == MTYPE_SUBMENU)
750 		{
751 			menuvec2_t border;
752 			int ntries;
753 			int old_nchanges = changes;
754 
755 			border = Menu_GetBorderSize ((menuframework_s*)item);
756 
757 			y += border.y;
758 			MENU_INCREASELINK (item->y, y);
759 			y -= border.y;
760 
761 			Menu_Arrange ((menuframework_s *)item, reset, font);
762 
763 			ntries = 0;
764 			// attempt to keep repetition localized where possible
765 			while (old_nchanges != changes && ++ntries < 3)
766 			{
767 				old_nchanges = changes;
768 				Menu_Arrange ((menuframework_s *)item, false, font);
769 			}
770 		}
771 		else
772 			MENU_INCREASELINK (item->y, y);
773 
774 		if (item->namesizecallback)
775 			CHASELINK(item->lsize) = item->namesizecallback (item, font);
776 		else
777 			CHASELINK(item->lsize) = Menu_Item_LeftSize (item, font);
778 		if (item->itemsizecallback)
779 			CHASELINK(item->rsize) = item->itemsizecallback (item, font);
780 		else
781 			CHASELINK(item->rsize) = Menu_Item_RightSize (item, font);
782 
783 		itemheight = Item_GetHeight(*((menuitem_s *)item));
784 		lwidth = CHASELINK(item->lsize).x;
785 		rwidth = CHASELINK(item->rsize).x;
786 
787 		if (menu->horizontal)
788 		{
789 			if (horiz_height < itemheight)
790 				horiz_height = itemheight;
791 		}
792 		else
793 			y += itemheight;
794 
795 		if (menu->horizontal)
796 		{
797 			if (i == 0)
798 				MENU_INCREASELINK (menu->lwidth, lwidth)
799 			else
800 				x += lwidth + RCOLUMN_OFFSET - LCOLUMN_OFFSET;
801 			MENU_INCREASELINK (item->x, x)
802 			x += rwidth;
803 		}
804 		else
805 		{
806 			MENU_INCREASELINK (menu->lwidth, lwidth);
807 			MENU_INCREASELINK (menu->rwidth, rwidth);
808 		}
809 	}
810 
811 	if (menu->horizontal)
812 	{
813 		MENU_INCREASELINK (menu->rwidth, x);
814 		MENU_INCREASELINK (menu->height, horiz_height);
815 	}
816 	else
817 		MENU_INCREASELINK (menu->height, y);
818 
819 	if (!menu->horizontal && menu->maxlines != 0 && menu->maxlines < menu->nitems)
820 		menu->maxheight = CHASELINK (((menucommon_s*)menu->items[menu->maxlines])->y);
821 
822 	menu->maxscroll = CHASELINK(menu->height) - menu->maxheight;
823 	menu->scroll_range = (float)menu->maxheight - font->size/2.0;
824 	menu->scrollbar_size = menu->scroll_range - menu->maxscroll;
825 	if (menu->scrollbar_size < font->size)
826 		menu->scrollbar_size = font->size;
827 	menu->scroll_top = (float)menu->y+font->size/4.0;
828 }
829 
Menu_AutoArrange(menuframework_s * menu)830 void Menu_AutoArrange (menuframework_s *menu)
831 {
832 	int ntries;
833 	FNT_font_t		font;
834 
835 	font = FNT_AutoGet( CL_menuFont );
836 
837 	Menu_Arrange (menu, true, font);
838 	ntries = 0;
839 	while (changes && ++ntries < 5)
840 	{
841 		changes = 0;
842 		Menu_Arrange (menu, false, font);
843 	}
844 }
845 
Item_ScrollVisible(menuitem_s * item)846 static inline qboolean Item_ScrollVisible (menuitem_s *item)
847 {
848 	menuframework_s *menu = item->generic.parent;
849 	// scrolling disabled on this item
850 	if (menu->maxheight == 0)
851 		return true;
852 	if (CHASELINK(item->generic.y)-menu->yscroll < 0)
853 		return false;
854 	if (CHASELINK(item->generic.y)-menu->yscroll + Item_GetHeight(*item) > menu->maxheight)
855 		return false;
856 	return true;
857 }
858 
Item_UpdateHighlightAlpha(menuitem_s * item)859 void Item_UpdateHighlightAlpha (menuitem_s *item)
860 {
861 	if (cursor.menuitem == item)
862 	{
863 		item->generic.highlight_alpha += cls.frametime*0.5f;
864 		if (item->generic.highlight_alpha > 1.0)
865 			item->generic.highlight_alpha = 1.0;
866 	}
867 	else
868 	{
869 		item->generic.highlight_alpha -= cls.frametime*0.5f;
870 		if (item->generic.highlight_alpha < 0.9)
871 			item->generic.highlight_alpha = 0.9;
872 	}
873 }
874 
Menu_Draw(menuframework_s * menu,FNT_font_t font)875 void Menu_Draw (menuframework_s *menu, FNT_font_t font)
876 {
877 	int i;
878 	menuitem_s *item;
879 
880 	if (menu->bordertexture != NULL)
881 		Menu_DrawBorder (menu, menu->bordertitle, menu->bordertexture);
882 
883 	// may get covered up later by a higher-priority status bar
884 	if (menu->num_apply_pending != 0)
885 		Menu_DrawStatusBar ("Some changes must be applied!\n");
886 	else
887 		Menu_DrawStatusBar (menu->statusbar);
888 
889 	Item_UpdateHighlightAlpha ((menuitem_s *)&menu->vertical_scrollbar);
890 
891 	/*
892 	** draw contents
893 	*/
894 	for ( i = 0; i < menu->nitems; i++ )
895 	{
896 		item = ((menuitem_s * )menu->items[i]);
897 
898 		if (!Item_ScrollVisible (item))
899 			continue;
900 
901 		Item_UpdateHighlightAlpha (item);
902 
903 		// TODO: cleaner method
904 		if (item->generic.type == MTYPE_NOT_INTERACTIVE)
905 		{
906 			if (item->generic.itemdraw != NULL)
907 				item->generic.itemdraw (item, font);
908 
909 			if (item->generic.namedraw != NULL)
910 				item->generic.namedraw (item, font);
911 			else
912 				Label_Draw ((menutxt_s *) menu->items[i], font, dark_color);
913 		}
914 		else if (item->generic.type != MTYPE_SUBMENU)
915 		{
916 			ItemName_Draw ((menuitem_s *) menu->items[i], font, dark_color);
917 		}
918 
919 		switch ( item->generic.type )
920 		{
921 		case MTYPE_FIELD:
922 			Field_Draw ((menufield_s *) menu->items[i], font);
923 			break;
924 		case MTYPE_SLIDER:
925 			Slider_Draw ((menuslider_s *) menu->items[i], font);
926 			break;
927 		case MTYPE_SPINCONTROL:
928 			SpinControl_Draw ((menulist_s *) menu->items[i], font);
929 			break;
930 		case MTYPE_ACTION:
931 			Action_Draw ((menuaction_s *) menu->items[i], font);
932 			break;
933 		case MTYPE_TEXT:
934 			break;
935 		case MTYPE_SUBMENU:
936 			SubMenu_Draw ((menuframework_s *) menu->items[i], font);
937 			break;
938 		}
939 	}
940 }
941 
Cursor_MouseSelectItem(menuitem_s * item)942 static void Cursor_MouseSelectItem (menuitem_s *item)
943 {
944 	menuitem_s *lastitem = (menuitem_s *)cursor.menuitem;
945 
946 	// Selected a new item-- reset double-click count
947 	if (lastitem != item)
948 	{
949 		memset (cursor.buttonclicks, 0, sizeof(cursor.buttonclicks));
950 		memset (cursor.buttontime, 0, sizeof(cursor.buttontime));
951 	}
952 
953 	Cursor_SelectItem (item);
954 	cursor.mouseaction = false;
955 }
956 
Menu_AssignCursor(menuframework_s * menu)957 void Menu_AssignCursor (menuframework_s *menu)
958 {
959 	int i;
960 	float right;
961 	menuitem_s *item;
962 
963 	if (!menu->navagable || !cursor.mouseaction)
964 		return;
965 
966 	right = menu->x + CHASELINK(menu->rwidth) + CHASELINK(menu->lwidth);
967 
968 	if (menu->maxheight != 0 && CHASELINK(menu->height) > menu->maxheight && cursor.x > right)
969 	{
970 		// select the scrollbar
971 		item = &menu->vertical_scrollbar;
972 		Cursor_MouseSelectItem (item);
973 	}
974 	else for ( i = 0; i < menu->nitems; i++ )
975 	{
976 		int mincoord, maxcoord;
977 
978 		item = ((menuitem_s * )menu->items[i]);
979 
980 		if (!item)
981 			continue;
982 
983 		if (menu->horizontal)
984 		{
985 			maxcoord = mincoord = Item_GetX (*item);
986 			maxcoord += CHASELINK(item->generic.rsize).x;
987 			mincoord -= CHASELINK(item->generic.lsize).x;
988 			if (cursor.x < mincoord || cursor.x > maxcoord)
989 				continue;
990 		}
991 		else
992 		{
993 			maxcoord = mincoord = Item_GetY (*item);
994 			maxcoord += Item_GetHeight (*item);
995 			if (cursor.y < mincoord || cursor.y > maxcoord)
996 				continue;
997 		}
998 
999 		if (item->generic.type == MTYPE_SUBMENU && ((menuframework_s *)item)->navagable)
1000 		{
1001 			// navagable menus should have at least one selectable item in
1002 			// them.
1003 			Menu_AssignCursor ((menuframework_s *)item);
1004 			return;
1005 		}
1006 
1007 		if (!Menu_ItemSelectable (item))
1008 			continue;
1009 
1010 		// We've found a valid candiate for selection
1011 		Cursor_MouseSelectItem (item);
1012 		return;
1013 	}
1014 }
1015 
1016 void Menu_DrawHighlightItem (menuitem_s *item);
1017 
1018 // Draws only the item labels, and draws everything highlighted. For cases
1019 // where the menu itself is being used as a sort of complex widget.
Menu_DrawHighlightMenu(menuframework_s * menu,FNT_font_t font)1020 void Menu_DrawHighlightMenu (menuframework_s *menu, FNT_font_t font)
1021 {
1022 	int i;
1023 	menuitem_s *item;
1024 
1025 	for ( i = 0; i < menu->nitems; i++ )
1026 	{
1027 		item = ((menuitem_s * )menu->items[i]);
1028 
1029 		Menu_DrawHighlightItem (item);
1030 	}
1031 }
1032 
Menu_DrawHighlightItem(menuitem_s * item)1033 void Menu_DrawHighlightItem (menuitem_s *item)
1034 {
1035 	FNT_font_t font = FNT_AutoGet (CL_menuFont);
1036 
1037 	if (!Item_ScrollVisible (item))
1038 		return;
1039 
1040 	if (item->generic.cursorcallback)
1041 		item->generic.cursorcallback (item, font);
1042 
1043 	// highlighting
1044 	if (item->generic.cursordraw != NULL)
1045 	{
1046 		item->generic.cursordraw( item, font );
1047 		return;
1048 	}
1049 
1050 	if (item->generic.namedraw != NULL)
1051 		return;
1052 
1053 	if (item->generic.type == MTYPE_SUBMENU && ((menuframework_s *)item)->enable_highlight)
1054 		Menu_DrawHighlightMenu ((menuframework_s *)item, font);
1055 	else if (item->generic.type == MTYPE_TEXT)
1056 		Label_Draw (item, font, highlight_color);
1057 	else
1058 		ItemName_Draw (item, font, highlight_color);
1059 }
1060 
Menu_DrawHighlight(void)1061 void Menu_DrawHighlight (void)
1062 {
1063 	menuframework_s *menu;
1064 	menuitem_s *item = cursor.menuitem;
1065 
1066 	if (item == NULL || item->generic.type == MTYPE_VERT_SCROLLBAR)
1067 		return;
1068 
1069 	menu = item->generic.parent;
1070 
1071 	// Scrolling - make sure the selected item is entirely on screen if
1072 	// possible. TODO: add smoothing?
1073 	if (menu->maxheight != 0)
1074 	{
1075 		int newscroll;
1076 		int y = CHASELINK(item->generic.y);
1077 		newscroll = clamp (menu->yscroll, y + Item_GetHeight(*item) - menu->maxheight, y);
1078 		if (newscroll != menu->yscroll)
1079 		{
1080 			menu->yscroll = newscroll;
1081 			return; // we'll draw it next frame - not in the right position now
1082 		}
1083 	}
1084 
1085 	if (item->generic.cursordraw == NULL && menu->cursordraw != NULL)
1086 	{
1087 		menu->cursordraw( menu );
1088 		return;
1089 	}
1090 
1091 	if (item->generic.type == MTYPE_SUBMENU)
1092 		Menu_DrawBorder ((menuframework_s *)item, NULL, "menu/sm_");
1093 
1094 	// no actions you can do with these types, so use them only for scrolling
1095 	// and then return
1096 	if (item->generic.type == MTYPE_NOT_INTERACTIVE)
1097 		return;
1098 
1099 	Menu_DrawHighlightItem (item);
1100 
1101 	if ( item->generic.statusbar )
1102 		Menu_DrawStatusBar( item->generic.statusbar );
1103 
1104 	if ( item->generic.tooltip )
1105 		Menu_DrawToolTip (item);
1106 }
1107 
1108 // needed because global_menu_xoffset must be added to only the top level of
1109 // any menu tree.
Screen_Draw(menuframework_s * screen,menuvec2_t offset)1110 void Screen_Draw (menuframework_s *screen, menuvec2_t offset)
1111 {
1112 	FNT_font_t font = FNT_AutoGet (CL_menuFont);
1113 	screen->x = offset.x;
1114 	//TODO: figure out what to do about centered windows
1115 /*	screen->y = offset.y;*/
1116 	Menu_AutoArrange (screen);
1117 	Menu_Draw (screen, font);
1118 }
1119 
Menu_GetBorderSize(menuframework_s * s)1120 static menuvec2_t Menu_GetBorderSize (menuframework_s *s)
1121 {
1122 	char topcorner_name[MAX_QPATH];
1123 	menuvec2_t ret;
1124 	FNT_font_t		font;
1125 
1126 	font = FNT_AutoGet( CL_menuFont );
1127 
1128 	ret.x = ret.y = 0;
1129 
1130 	if (s->bordertexture != NULL)
1131 	{
1132 		Com_sprintf (topcorner_name, MAX_QPATH, "%stopcorner.tga", s->bordertexture);
1133 		Draw_GetPicSize (&ret.x, &ret.y, topcorner_name );
1134 		ret.x = (int)((float)ret.x/64.0*(float)font->size*4.0);
1135 		ret.y = (int)((float)ret.y/64.0*(float)font->size*4.0);
1136 		ret.x -= font->size;
1137 		ret.y -= font->size;
1138 	}
1139 
1140 	return ret;
1141 }
1142 
Menu_DrawBox(int x,int y,int w,int h,float alpha,const char * title,const char * prefix)1143 void Menu_DrawBox (int x, int y, int w, int h, float alpha, const char *title, const char *prefix)
1144 {
1145 	char topcorner_name[MAX_QPATH];
1146 	char bottomcorner_name[MAX_QPATH];
1147 	char top_name[MAX_QPATH];
1148 	char bottom_name[MAX_QPATH];
1149 	char side_name[MAX_QPATH];
1150 	char background_name[MAX_QPATH];
1151 	int _tile_w, _tile_h;
1152 	float tile_w, tile_h;
1153 	FNT_font_t		font;
1154 
1155 	font = FNT_AutoGet( CL_menuFont );
1156 
1157 	Com_sprintf (topcorner_name, MAX_QPATH, "%stopcorner.tga", prefix);
1158 	Com_sprintf (bottomcorner_name, MAX_QPATH, "%sbottomcorner.tga", prefix);
1159 	Com_sprintf (top_name, MAX_QPATH, "%stop.tga", prefix);
1160 	Com_sprintf (bottom_name, MAX_QPATH, "%sbottom.tga", prefix);
1161 	Com_sprintf (side_name, MAX_QPATH, "%sside.tga", prefix);
1162 	Com_sprintf (background_name, MAX_QPATH, "%sbackground.tga", prefix);
1163 
1164 	// assume all tiles are the same size
1165 	Draw_GetPicSize (&_tile_w, &_tile_h, topcorner_name );
1166 
1167 	tile_w = (float)_tile_w/64.0*(float)font->size*4.0;
1168 	tile_h = (float)_tile_h/64.0*(float)font->size*4.0;
1169 
1170 	// make room for the scrollbar
1171 	if (!strcmp (prefix, "menu/sm_"))
1172 	{
1173 		w += font->size;
1174 		x -= font->size/2;
1175 	}
1176 
1177 	if (w < tile_w)
1178 	{
1179 		w = tile_w;
1180 	}
1181 
1182 	if (h < tile_h)
1183 	{
1184 		h = tile_h;
1185 	}
1186 
1187 	// hacky stuff to make things look right
1188 	if (strcmp (prefix, "menu/sm_"))
1189 	{
1190 		x -= tile_w/2;
1191 		w += tile_w;
1192 	}
1193 	if (!strcmp (prefix, "menu/m_"))
1194 	{
1195 		y -= tile_h/8;
1196 		if (h > tile_h)
1197 			h += tile_h/4;
1198 	}
1199 
1200 
1201 	Draw_AlphaStretchTilingPic( x-tile_w/2, y-tile_h/2, tile_w, tile_h, topcorner_name, alpha );
1202 	Draw_AlphaStretchTilingPic( x+w+tile_w/2, y-tile_h/2, -tile_w, tile_h, topcorner_name, alpha );
1203 	Draw_AlphaStretchTilingPic( x-tile_w/2, y+h-tile_h/2, tile_w, tile_h, bottomcorner_name, alpha );
1204 	Draw_AlphaStretchTilingPic( x+w+tile_w/2, y+h-tile_h/2, -tile_w, tile_h, bottomcorner_name, alpha );
1205 	if (w > tile_w)
1206 	{
1207 		Draw_AlphaStretchTilingPic( x+tile_w/2, y-tile_h/2, w-tile_w, tile_h, top_name, alpha );
1208 		Draw_AlphaStretchTilingPic( x+tile_w/2, y+h-tile_h/2, w-tile_w, tile_h, bottom_name, alpha );
1209 	}
1210 
1211 	if (h > tile_h)
1212 	{
1213 		Draw_AlphaStretchTilingPic( x-tile_w/2, y+tile_h/2, tile_w, h-tile_h, side_name, alpha );
1214 		Draw_AlphaStretchTilingPic( x+w+tile_w/2, y+tile_h/2, -tile_w, h-tile_h, side_name, alpha );
1215 		if (w > tile_w)
1216 			Draw_AlphaStretchTilingPic( x+tile_w/2, y+tile_h/2, w-tile_w, h-tile_h, background_name, alpha );
1217 	}
1218 
1219 	if (title != NULL)
1220 	{
1221 		int i;
1222 		int textwidth = Menu_PredictSize (title);
1223 		Menu_DrawHorizBar ("menu/slide_border", x+w/2-textwidth/2-2, y-font->size-2, textwidth+4, font->size+4, 1);
1224 		// Redraw multiple times to get a bold effect
1225 		for (i = 0; i < 4; i++)
1226 			Menu_DrawString (x+w/2, y-font->size, title, FNT_CMODE_QUAKE, FNT_ALIGN_CENTER, highlight_color);
1227 	}
1228 }
1229 
1230 
Menu_DrawVertBar(const char * pathbase,float x,float y,float h,float base_size,float alpha)1231 void Menu_DrawVertBar (const char *pathbase, float x, float y, float h, float base_size, float alpha)
1232 {
1233 	char scratch[MAX_QPATH];
1234 
1235 	Com_sprintf( scratch, sizeof( scratch ), "%s%s", pathbase, "_end");
1236 
1237 	Draw_AlphaStretchTilingPic (x, y-base_size/2.0, base_size, base_size, scratch, alpha);
1238 	Draw_AlphaStretchTilingPic (x+base_size, y+h+base_size/2.0, -base_size, -base_size, scratch, alpha);
1239 	if (h > base_size)
1240 		Draw_AlphaStretchTilingPic (x, y+base_size/2.0, base_size, h-base_size, pathbase, alpha);
1241 }
1242 
Menu_DrawHorizBar(const char * pathbase,float x,float y,float w,float base_size,float alpha)1243 void Menu_DrawHorizBar (const char *pathbase, float x, float y, float w, float base_size, float alpha)
1244 {
1245 	char scratch[MAX_QPATH];
1246 
1247 	Com_sprintf( scratch, sizeof( scratch ), "%s%s", pathbase, "_end");
1248 
1249 	Draw_AlphaStretchTilingPic (x-base_size/2.0, y, base_size, base_size, scratch, alpha);
1250 	Draw_AlphaStretchTilingPic (x+w+base_size/2.0, y+base_size, -base_size, -base_size, scratch, alpha);
1251 	if (w > base_size)
1252 		Draw_AlphaStretchTilingPic (x+base_size/2.0, y, w-base_size, base_size, pathbase, alpha);
1253 }
1254 
Menu_DrawScrollbar(menuframework_s * menu)1255 void Menu_DrawScrollbar (menuframework_s *menu)
1256 {
1257 	float		scrollbar_pos;
1258 	float		charscale, right;
1259 	float		alpha;
1260 	FNT_font_t	font;
1261 
1262 	font = FNT_AutoGet( CL_menuFont );
1263 	charscale = font->size;
1264 
1265 	if (menu->maxheight == 0 || CHASELINK(menu->height) <= menu->maxheight)
1266 		return;
1267 
1268 	right = menu->x + CHASELINK(menu->rwidth) + CHASELINK(menu->lwidth) + charscale/2.0;
1269 
1270 	scrollbar_pos = menu->scroll_top + (float)menu->yscroll*(menu->scroll_range-menu->scrollbar_size)/menu->maxscroll;
1271 	alpha = menu->vertical_scrollbar.highlight_alpha;
1272 
1273 	Menu_DrawVertBar ("menu/scroll_border", right, menu->y, menu->maxheight, charscale, alpha*alpha);
1274 	Menu_DrawVertBar ("menu/scroll_cursor", right, scrollbar_pos, menu->scrollbar_size, charscale, alpha);
1275 }
1276 
Menu_DrawBorder(menuframework_s * menu,const char * title,const char * prefix)1277 void Menu_DrawBorder (menuframework_s *menu, const char *title, const char *prefix)
1278 {
1279 	int height, width;
1280 
1281 	height = CHASELINK(menu->height);
1282 
1283 	if (menu->maxheight != 0 && height > menu->maxheight)
1284 		height = menu->maxheight;
1285 
1286 	if (strcmp (prefix, "menu/m_"))
1287 	{
1288 		menu->borderalpha = 0.50;
1289 	}
1290 	else if (Menu_ContainsCursorItem (menu))
1291 	{
1292 		menu->borderalpha += cls.frametime*2;
1293 		if (menu->borderalpha > 1.0)
1294 			menu->borderalpha = 1.0;
1295 	}
1296 	else
1297 	{
1298 		menu->borderalpha -= cls.frametime*2;
1299 		if (menu->borderalpha < 0.5)
1300 			menu->borderalpha = 0.5;
1301 	}
1302 
1303 	width = CHASELINK(menu->lwidth) + CHASELINK(menu->rwidth);
1304 	Menu_DrawBox (menu->x, menu->y, width, height, menu->borderalpha, title, prefix);
1305 }
1306 
Menu_DrawToolTip(const menuitem_s * item)1307 void Menu_DrawToolTip (const menuitem_s *item)
1308 {
1309 
1310 	int				x, y;
1311 	int				width;
1312 	FNT_font_t		font;
1313 
1314 	font = FNT_AutoGet (CL_menuFont);
1315 
1316 	width = Menu_PredictSize (item->generic.tooltip);
1317 
1318 	x = clamp (cursor.x, Item_GetX (*item) - width, VID_WIDTH - width);
1319 
1320 	y = clamp (cursor.y - font->size - 4, Item_GetY(*item) - font->size, Item_GetY(*item));
1321 
1322 	Menu_DrawHorizBar ("menu/slide_border", x-2, y, width+4, font->size+4, 1);
1323 	Menu_DrawString (
1324 		x, y,
1325 		item->generic.tooltip, FNT_CMODE_QUAKE_SRS, FNT_ALIGN_LEFT,
1326 		light_color
1327 	);
1328 }
1329 
Menu_DrawString(int x,int y,const char * string,unsigned int cmode,unsigned int align,const float * color)1330 void Menu_DrawString (int x, int y, const char *string, unsigned int cmode, unsigned int align, const float *color)
1331 {
1332 	FNT_font_t		font;
1333 
1334 	font = FNT_AutoGet( CL_menuFont );
1335 
1336 	if (align == FNT_ALIGN_RIGHT)
1337 	{
1338 		menu_box.x = 0;
1339 		menu_box.width = x;
1340 	}
1341 	else if (align == FNT_ALIGN_CENTER)
1342 	{
1343 		//width only needs to be an upper bound
1344 		int width = strlen(string)*font->size;
1345 		menu_box.x = x-width/2;
1346 		menu_box.width = width;
1347 	}
1348 	else
1349 	{
1350 		menu_box.x = x;
1351 		menu_box.width = 0;
1352 	}
1353 
1354 	menu_box.y = y;
1355 	menu_box.height = 0;
1356 
1357 	FNT_BoundedPrint (font, string, cmode, align, &menu_box, color);
1358 
1359 	if (align == FNT_ALIGN_RIGHT)
1360 		menu_box.x = x-menu_box.width;
1361 	else if (align == FNT_ALIGN_CENTER)
1362 		menu_box.x = x-menu_box.width/2;
1363 }
1364 
Menu_DrawStatusBar(const char * string)1365 void Menu_DrawStatusBar( const char *string )
1366 {
1367 	FNT_font_t		font;
1368 
1369 	font = FNT_AutoGet( CL_menuFont );
1370 
1371 	if ( string )
1372 	{
1373 		Draw_Fill (0, VID_HEIGHT-font->size-10, VID_WIDTH, font->size+10, RGBA(0.25, 0.25, 0.25, 1));
1374 		Menu_DrawString (VID_WIDTH/2, VID_HEIGHT-font->size-5, string, FNT_CMODE_QUAKE, FNT_ALIGN_CENTER, light_color);
1375 	}
1376 }
1377 
Menu_ActivateItem(menuitem_s * item)1378 void Menu_ActivateItem (menuitem_s *item)
1379 {
1380 	if (item == NULL || item->generic.callback == NULL)
1381 		return;
1382 	if ((item->generic.flags & QMF_ACTION_WAIT))
1383 	{
1384 		if (!item->generic.apply_pending)
1385 		{
1386 			item->generic.apply_pending = true;
1387 			Menu_GetItemTree (item)->num_apply_pending++;
1388 		}
1389 	}
1390 	else
1391 	{
1392 		item->generic.callback (item);
1393 	}
1394 }
1395 
Menu_ApplyItem(menuitem_s * item)1396 void Menu_ApplyItem (menuitem_s *item)
1397 {
1398 	if (item != NULL && item->generic.callback != NULL && (item->generic.flags & QMF_ACTION_WAIT) && item->generic.apply_pending)
1399 	{
1400 		Menu_GetItemTree (item)->num_apply_pending--;
1401 		item->generic.callback (item);
1402 		item->generic.apply_pending = false;
1403 	}
1404 }
1405 
Menu_ApplyMenu(menuframework_s * menu)1406 void Menu_ApplyMenu (menuframework_s *menu)
1407 {
1408 	int i;
1409 	menuitem_s *item;
1410 
1411 	for ( i = 0; i < menu->nitems; i++ )
1412 	{
1413 		item = ((menuitem_s * )menu->items[i]);
1414 
1415 		if (item->generic.type == MTYPE_SUBMENU)
1416 			Menu_ApplyMenu ((menuframework_s *)item);
1417 		else
1418 			Menu_ApplyItem (item);
1419 	}
1420 }
1421 
Menu_SetStatusBar(menuframework_s * m,const char * string)1422 void Menu_SetStatusBar( menuframework_s *m, const char *string )
1423 {
1424 	m->statusbar = string;
1425 }
1426 
Menu_SlideItem(int dir)1427 void Menu_SlideItem (int dir)
1428 {
1429 	menucommon_s *item = cursor.menuitem;
1430 
1431 	if ( item )
1432 	{
1433 		switch ( item->type )
1434 		{
1435 		case MTYPE_SLIDER:
1436 			Slider_DoSlide( ( menuslider_s * ) item, dir );
1437 			break;
1438 		case MTYPE_SPINCONTROL:
1439 			SpinControl_DoSlide( ( menulist_s * ) item, dir );
1440 			break;
1441 		}
1442 	}
1443 }
1444 
Label_Draw(menutxt_s * s,FNT_font_t font,const float * color)1445 void Label_Draw (menutxt_s *s, FNT_font_t font, const float *color)
1446 {
1447 	unsigned int align;
1448 	unsigned int cmode;
1449 
1450 	if ( s->generic.name == NULL)
1451 		return;
1452 
1453 	if ( s->generic.flags & QMF_RIGHT_COLUMN )
1454 		align = FNT_ALIGN_LEFT;
1455 	else
1456 		align = FNT_ALIGN_RIGHT;
1457 
1458 	cmode = FNT_CMODE_QUAKE_SRS;
1459 	if (color == highlight_color)
1460 		cmode = FNT_CMODE_TWO;
1461 
1462 	Menu_DrawString (
1463 		Item_GetX (*s), Item_GetY (*s) + MenuText_UpperMargin (s, font->size),
1464 		s->generic.name, cmode, align, color
1465 	);
1466 }
1467 
Slider_DoSlide(menuslider_s * s,int dir)1468 void Slider_DoSlide( menuslider_s *s, int dir )
1469 {
1470 	s->curvalue += dir;
1471 
1472 	if ( s->curvalue > s->maxvalue )
1473 		s->curvalue = s->maxvalue;
1474 	else if ( s->curvalue < s->minvalue )
1475 		s->curvalue = s->minvalue;
1476 
1477 	Menu_ActivateItem (s);
1478 }
1479 
Slider_Draw(menuslider_s * s,FNT_font_t font)1480 void Slider_Draw (menuslider_s *s, FNT_font_t font)
1481 {
1482 	float		maxscroll, curscroll, scroll_range, cursor_size, x, y, width;
1483 	float 		charscale;
1484 
1485 	charscale = font->size;
1486 
1487 	x = Item_GetX (*s) + RCOLUMN_OFFSET;
1488 	y = Item_GetY (*s) + MenuText_UpperMargin (s, font->size);
1489 
1490 	curscroll = s->curvalue - s->minvalue;
1491 	maxscroll = s->maxvalue - s->minvalue;
1492 
1493 	s->range = (float) curscroll / ( float ) maxscroll;
1494 
1495 	width = charscale * (float)LONGINPUT_SIZE;
1496 	scroll_range = width-charscale/2.0;
1497 	cursor_size = charscale;
1498 
1499 	Menu_DrawHorizBar (
1500 		"menu/slide_border", x, y, width, charscale,
1501 		s->generic.highlight_alpha*s->generic.highlight_alpha
1502 	);
1503 	Menu_DrawHorizBar (
1504 		"menu/slide_cursor", x+charscale/4.0+s->range*(scroll_range-cursor_size), y,
1505 		cursor_size, charscale,
1506 		s->generic.highlight_alpha
1507 	);
1508 }
1509 
SpinControl_DoSlide(menulist_s * s,int dir)1510 void SpinControl_DoSlide( menulist_s *s, int dir )
1511 {
1512 	int i;
1513 	s->curvalue += dir;
1514 
1515 	if ( s->curvalue < 0 )
1516 	{
1517 		if (s->generic.flags & QMF_ALLOW_WRAP)
1518 		{
1519 			for (i = 0; s->itemnames[i]; i++)
1520 				continue;
1521 			s->curvalue += i;
1522 		}
1523 		else
1524 			s->curvalue = 0;
1525 	}
1526 	else if ( s->itemnames[s->curvalue] == 0 )
1527 	{
1528 		if (s->generic.flags & QMF_ALLOW_WRAP)
1529 			s->curvalue = 0;
1530 		else
1531 			s->curvalue--;
1532 	}
1533 
1534 	Menu_ActivateItem (s);
1535 }
1536 
SpinControl_Draw(menulist_s * s,FNT_font_t font)1537 void SpinControl_Draw (menulist_s *s, FNT_font_t font)
1538 {
1539 	char buffer[100];
1540 	int item_x, item_y;
1541 
1542 	item_x = Item_GetX (*s) + RCOLUMN_OFFSET;
1543 	item_y = Item_GetY (*s) + MenuText_UpperMargin (s, SpinControl_MaxLines (s)*font->size);
1544 
1545 	if (s->generic.namedraw == NULL && s->generic.name != NULL && s->generic.flags & QMF_RIGHT_COLUMN)
1546 	{
1547 		// Both the name and item go in the right column.
1548 		item_x += Menu_PredictSize (s->generic.name);
1549 	}
1550 
1551 	if (s->generic.itemdraw != NULL)
1552 	{
1553 		s->generic.itemdraw (s, font);
1554 	}
1555 	else if ( !strchr( s->itemnames[s->curvalue], '\n' ) )
1556 	{
1557 		Menu_DrawString (
1558 			item_x, item_y,
1559 			s->itemnames[s->curvalue], FNT_CMODE_QUAKE_SRS, FNT_ALIGN_LEFT,
1560 			light_color
1561 		);
1562 	}
1563 	else
1564 	{
1565 		strcpy( buffer, s->itemnames[s->curvalue] );
1566 		*strchr( buffer, '\n' ) = 0;
1567 		Menu_DrawString (
1568 			item_x, item_y,
1569 			buffer, FNT_CMODE_QUAKE_SRS, FNT_ALIGN_LEFT,
1570 			light_color
1571 		);
1572 		strcpy( buffer, strchr( s->itemnames[s->curvalue], '\n' ) + 1 );
1573 		Menu_DrawString (
1574 			item_x, menu_box.y + font->height,
1575 			buffer, FNT_CMODE_QUAKE_SRS, FNT_ALIGN_LEFT,
1576 			light_color
1577 		);
1578 	}
1579 }
1580 
SubMenu_Draw(menuframework_s * sm,FNT_font_t font)1581 void SubMenu_Draw (menuframework_s *sm, FNT_font_t font)
1582 {
1583 	sm->x = Item_GetX (*sm);
1584 	if (sm->generic.flags & QMF_SNUG_LEFT)
1585 		sm->x -= CHASELINK(sm->generic.parent->lwidth);
1586 	sm->y = Item_GetY (*sm);
1587 	if (sm->navagable)
1588 		Menu_DrawScrollbar (sm);
1589 	Menu_Draw (sm, font);
1590 }
1591 
1592 // utility functions
1593 
Menu_MakeTable(menuframework_s * menu,int nrows,int ncolumns,size_t * celltype_size,menuframework_s * header,menuframework_s * rows,void * columns,const char ** contents)1594 void Menu_MakeTable (menuframework_s *menu, int nrows, int ncolumns, size_t *celltype_size, menuframework_s *header, menuframework_s *rows, void *columns, const char **contents)
1595 {
1596 	int i, j;
1597 	menuframework_s *cur_row = rows;
1598 	// char because the measurements in celltype_size are in bytes
1599 	char *cur_cell_p = columns;
1600 
1601 	menu->nitems = 0;
1602 	for (i = 0; i < nrows; i++)
1603 	{
1604 		cur_row->nitems = 0;
1605 		cur_row->generic.type = MTYPE_SUBMENU;
1606 		cur_row->horizontal = true;
1607 		cur_row->navagable = false;
1608 		cur_row->enable_highlight = true;
1609 		if (cur_row != header)
1610 		{
1611 			LINK(header->lwidth, cur_row->lwidth);
1612 			LINK(header->rwidth, cur_row->rwidth);
1613 		}
1614 		for (j = 0; j < ncolumns; j++)
1615 		{
1616 			menuitem_s *cur_cell = (menuitem_s *)cur_cell_p;
1617 			if (cur_row != header)
1618 			{
1619 				menucommon_s *header_cell = (menucommon_s *)header->items[j];
1620 				memcpy (cur_cell, header_cell, sizeof (menucommon_s));
1621 				//reset after memcpy
1622 				cur_cell->generic.x.status = linkstatus_literal;
1623 				LINK(header_cell->x, cur_cell->generic.x);
1624 			}
1625 			cur_cell->generic.name = contents[i*ncolumns+j];
1626 			Menu_AddItem (cur_row, cur_cell);
1627 			cur_cell_p += celltype_size[j];
1628 		}
1629 		Menu_AddItem (menu, cur_row);
1630 		cur_row++;
1631 	}
1632 	Menu_AutoArrange (menu);
1633 }
1634