refactored the scaling to a separate function to allow different size images
[divverent/nexuiz.git] / data / qcsrc / menu / menu.qc
1 ///////////////////////////////////////////////
2 // Menu Source File
3 ///////////////////////
4 // This file belongs to dpmod/darkplaces
5 // AK contains all menu functions (especially the required ones)
6 ///////////////////////////////////////////////
7
8 float mouseButtonsPressed;
9 vector menuMousePos;
10 float menuShiftState;
11 float menuPrevTime;
12 float menuAlpha;
13 float menuLogoAlpha;
14 float prevMenuAlpha;
15 float menuInitialized;
16 float menuNotTheFirstFrame;
17 float menuMouseMode;
18
19 void SUB_Null() { };
20
21 void() m_init =
22 {
23         cvar_set("_menu_alpha", "0");
24
25         dprint_load();
26         check_unacceptable_compiler_bugs();
27
28         // list all game dirs (TEST)
29         if(cvar("developer"))
30         {
31                 float i;
32                 string s;
33                 for(i = 0; ; ++i)
34                 {
35                         s = getgamedirinfo(i, GETGAMEDIRINFO_NAME);
36                         if not(s)
37                                 break;
38                         print(s, ": ", getgamedirinfo(i, GETGAMEDIRINFO_DESCRIPTION));
39                 }
40         }
41 }
42
43 float MENU_ASPECT = 1.2; // 1280x1024
44 float conwidth_s, conheight_s, realconwidth, realconheight, screenconwidth, screenconheight;
45 void draw_reset_cropped()
46 {
47         draw_reset(screenconwidth, screenconheight, 0.5 * (realconwidth - screenconwidth), 0.5 * (realconheight - screenconheight));
48 }
49 void draw_reset_full()
50 {
51         draw_reset(realconwidth, realconheight, 0, 0);
52 }
53 void UpdateConWidthHeight()
54 {
55         conwidth_s = conwidth;
56         conheight_s = conheight;
57         realconwidth = cvar("vid_conwidth");
58         realconheight = cvar("vid_conheight");
59         if(realconwidth / realconheight > MENU_ASPECT)
60         {
61                 // widescreen
62                 conwidth = realconheight * MENU_ASPECT;
63                 conheight = realconheight;
64         }
65         else
66         {
67                 // squarescreen
68                 conwidth = realconwidth;
69                 conheight = realconwidth / MENU_ASPECT;
70         }
71         screenconwidth = conwidth;
72         screenconheight = conheight;
73         if(conwidth < 800)
74         {
75                 conheight *= 800 / conwidth;
76                 conwidth = 800;
77         }
78         if(conheight < 600)
79         {
80                 conwidth *= 600 / conheight;
81                 conheight = 600;
82         }
83         if(main)
84         {
85                 if(conwidth_s != conwidth || conheight_s != conheight)
86                 {
87                         draw_reset_cropped();
88                         main.resizeNotify(main, '0 0 0', eX * conwidth + eY * conheight, '0 0 0', eX * conwidth + eY * conheight);
89                 }
90         }
91 }
92
93 void() m_init_delayed =
94 {
95         float fh, glob, n, i;
96         string s;
97
98         dprint_load();
99
100         menuInitialized = 0;
101         if(!preMenuInit())
102                 return;
103         menuInitialized = 1;
104         GameCommand_Init();
105
106         RegisterWeapons();
107
108         fh = -1;
109         if(cvar_string("menu_skin") != "")
110         {
111                 draw_currentSkin = strcat("gfx/menu/", cvar_string("menu_skin"));
112                 fh = fopen(strcat(draw_currentSkin, "/skinvalues.txt"), FILE_READ);
113         }
114         if(fh < 0)
115         if(cvar_defstring("menu_skin") != "")
116         {
117                 draw_currentSkin = strcat("gfx/menu/", cvar_defstring("menu_skin"));
118                 fh = fopen(strcat(draw_currentSkin, "/skinvalues.txt"), FILE_READ);
119         }
120         if(fh < 0)
121         {
122                 draw_currentSkin = "gfx/menu/default";
123                 fh = fopen(strcat(draw_currentSkin, "/skinvalues.txt"), FILE_READ);
124         }
125         draw_currentSkin = strzone(draw_currentSkin);
126         while((s = fgets(fh)))
127         {
128                 // these two are handled by skinlist.qc
129                 if(substring(s, 0, 6) == "title ")
130                         continue;
131                 if(substring(s, 0, 7) == "author ")
132                         continue;
133                 n = tokenize_console(s);
134                 if(n >= 2)
135                         Skin_ApplySetting(argv(0), substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
136         }
137         fclose(fh);
138
139         glob = search_begin(strcat(draw_currentSkin, "/*.tga"), TRUE, TRUE);
140         if(glob >= 0)
141         {
142                 n = search_getsize(glob);
143                 for(i = 0; i < n; ++i)
144                         precache_pic(search_getfilename(glob, i));
145                 search_end(glob);
146         }
147
148         draw_setMousePointer(SKINGFX_CURSOR, SKINSIZE_CURSOR, SKINOFFSET_CURSOR);
149
150         conwidth = conheight = -1;
151         UpdateConWidthHeight();
152         draw_reset_cropped();
153
154         loadTooltips();
155         main = spawnMainWindow(); main.configureMainWindow(main);
156         unloadTooltips();
157
158         main.resizeNotify(main, '0 0 0', eX * conwidth + eY * conheight, '0 0 0', eX * conwidth + eY * conheight);
159         main.focused = 1;
160         menuShiftState = 0;
161         menuMousePos = '0.5 0.5 0';
162
163         if(Menu_Active)
164                 m_display(); // delayed menu display
165 };
166
167 void(float key, float ascii) m_keyup =
168 {
169         if(!menuInitialized)
170                 return;
171         if(!Menu_Active)
172                 return;
173         draw_reset_cropped();
174         main.keyUp(main, key, ascii, menuShiftState);
175         if(key >= K_MOUSE1 && key <= K_MOUSE3)
176         {
177                 --mouseButtonsPressed;
178                 if(!mouseButtonsPressed)
179                         main.mouseRelease(main, menuMousePos);
180                 if(mouseButtonsPressed < 0)
181                 {
182                         mouseButtonsPressed = 0;
183                         print("Warning: released an already released button\n");
184                 }
185         }
186         if(key == K_ALT) menuShiftState -= (menuShiftState & S_ALT);
187         if(key == K_CTRL) menuShiftState -= (menuShiftState & S_CTRL);
188         if(key == K_SHIFT) menuShiftState -= (menuShiftState & S_SHIFT);
189 };
190
191 void(float key, float ascii) m_keydown =
192 {
193         if(!menuInitialized)
194                 return;
195         if(!Menu_Active)
196                 return;
197         if(keyGrabber)
198         {
199                 entity e;
200                 e = keyGrabber;
201                 keyGrabber = NULL;
202                 e.keyGrabbed(e, key, ascii);
203         }
204         else
205         {
206                 draw_reset_cropped();
207                 if(key >= K_MOUSE1 && key <= K_MOUSE3)
208                         if(!mouseButtonsPressed)
209                                 main.mousePress(main, menuMousePos);
210                 if(!main.keyDown(main, key, ascii, menuShiftState))
211                         if(key == K_ESCAPE)
212                                 if(gamestatus & (GAME_ISSERVER | GAME_CONNECTED)) // don't back out to console only
213                                         m_hide(); // disable menu on unhandled ESC
214         }
215         if(key >= K_MOUSE1 && key <= K_MOUSE3)
216         {
217                 ++mouseButtonsPressed;
218                 if(mouseButtonsPressed > 10)
219                 {
220                         mouseButtonsPressed = 10;
221                         print("Warning: pressed an already pressed button\n");
222                 }
223         }
224         if(key == K_ALT) menuShiftState |= S_ALT;
225         if(key == K_CTRL) menuShiftState |= S_CTRL;
226         if(key == K_SHIFT) menuShiftState |= S_SHIFT;
227 };
228
229 float SCALEMODE_CROP = 0;
230 float SCALEMODE_LETTERBOX = 1;
231 float SCALEMODE_WIDTH = 2;
232 float SCALEMODE_HEIGHT = 3;
233 float SCALEMODE_STRETCH = 4;
234 void draw_Picture_Aligned(vector algn, float scalemode, string img, float a)
235 {
236         vector sz, org, isz, isz_w, isz_h;
237         float width_is_larger;
238
239         sz = draw_PictureSize(img);
240         width_is_larger = (sz_x * draw_scale_y >= sz_y * draw_scale_x);
241         isz_w = '1 0 0' + '0 1 0' * ((sz_y / sz_x) * (draw_scale_x / draw_scale_y)); 
242         isz_h = '0 1 0' + '1 0 0' * ((sz_x / sz_y) * (draw_scale_y / draw_scale_x)); 
243
244         switch(scalemode)
245         {
246                 default:
247                 case SCALEMODE_CROP:
248                         isz = (width_is_larger ? isz_h : isz_w);
249                         break;
250                 case SCALEMODE_LETTERBOX:
251                         isz = (width_is_larger ? isz_w : isz_h);
252                         break;
253                 case SCALEMODE_WIDTH:
254                         isz = isz_w;
255                         break;
256                 case SCALEMODE_HEIGHT:
257                         isz = isz_h;
258                         break;
259                 case SCALEMODE_STRETCH:
260                         isz = '1 1 0';
261                         break;
262         }
263
264         org = eX * (algn_x * (1 - isz_x)) + eY * (algn_y * (1 - isz_y));
265         draw_Picture(org, img, isz, '1 1 1', a);
266 }
267
268 void(string img, float a, string algn, float force1) drawBackground =
269 {
270         vector v;
271         float i, l;
272         string c;
273         float scalemode;
274
275         v_z = 0;
276
277         scalemode = SCALEMODE_CROP;
278
279         for(i = 0; i < strlen(algn); ++i)
280         {
281                 c = substring(algn, i, 1);
282                 switch(c)
283                 {
284                         case "c": scalemode = SCALEMODE_CROP; goto nopic;
285                         case "l": scalemode = SCALEMODE_LETTERBOX; goto nopic;
286                         case "h": scalemode = SCALEMODE_HEIGHT; goto nopic;
287                         case "w": scalemode = SCALEMODE_WIDTH; goto nopic;
288                         case "s": scalemode = SCALEMODE_STRETCH; goto nopic;
289                         case "1": case "4": case "7": v_x = 0.0; break;
290                         case "2": case "5": case "8": v_x = 0.5; break;
291                         case "3": case "6": case "9": v_x = 1.0; break;
292                         default: v_x = random(); break;
293                 }
294                 switch(c)
295                 {
296                         case "7": case "8": case "9": v_y = 0.0; break;
297                         case "4": case "5": case "6": v_y = 0.5; break;
298                         case "1": case "2": case "3": v_y = 1.0; break;
299                         default: v_y = random(); break;
300                 }
301                 if(l == 0)
302                         draw_Picture_Aligned(v, scalemode, img, a);
303                 else if(force1)
304                         // force all secondary layers to use alpha 1. Prevents ugly issues
305                         // with overlap. It's a flag because it cannot be used for the
306                         // ingame background
307                         draw_Picture_Aligned(v, scalemode, strcat(img, "_l", ftos(l+1)), 1);
308                 else
309                         draw_Picture_Aligned(v, scalemode, strcat(img, "_l", ftos(l+1)), a);
310                 ++l;
311 :nopic
312         }
313 }
314
315 vector menuTooltipAveragedMousePos;
316 entity menuTooltipItem;
317 vector menuTooltipOrigin;
318 vector menuTooltipSize;
319 float menuTooltipAlpha;
320 float menuTooltipState; // 0: no tooltip, 1: fading in, 2: displaying, 3: fading out
321 float m_testmousetooltipbox(vector pos)
322 {
323         if(pos_x >= menuTooltipOrigin_x && pos_x < menuTooltipOrigin_x + menuTooltipSize_x)
324         if(pos_y >= menuTooltipOrigin_y && pos_y < menuTooltipOrigin_y + menuTooltipSize_y)
325                 return FALSE;
326         return TRUE;
327 }
328 float m_testtooltipbox(vector tooltippos)
329 {
330         if(tooltippos_x < 0)
331                 return FALSE;
332         if(tooltippos_y < 0)
333                 return FALSE;
334         if(tooltippos_x + menuTooltipSize_x > 1)
335                 return FALSE;
336         if(tooltippos_y + menuTooltipSize_y > 1)
337                 return FALSE;
338         /*
339         menuTooltipOrigin_x = rint(tooltippos_x * cvar("vid_width")) / cvar("vid_width");
340         menuTooltipOrigin_y = rint(tooltippos_y * cvar("vid_height")) / cvar("vid_height");
341         menuTooltipOrigin_z = 0;
342         */
343         menuTooltipOrigin = tooltippos;
344         return TRUE;
345 }
346 float m_allocatetooltipbox(vector pos)
347 {
348         vector avoidplus, avoidminus;
349         vector v;
350
351         avoidplus_x = (SKINAVOID_TOOLTIP_x + SKINSIZE_CURSOR_x - SKINOFFSET_CURSOR_x) / conwidth;
352         avoidplus_y = (SKINAVOID_TOOLTIP_y + SKINSIZE_CURSOR_y - SKINOFFSET_CURSOR_y) / conheight;
353         avoidplus_z = 0;
354
355         avoidminus_x = (SKINAVOID_TOOLTIP_x + SKINOFFSET_CURSOR_x) / conwidth + menuTooltipSize_x;
356         avoidminus_y = (SKINAVOID_TOOLTIP_y + SKINOFFSET_CURSOR_y) / conheight + menuTooltipSize_y;
357         avoidminus_z = 0;
358
359         // bottom right
360         v = pos + avoidplus;
361         if(m_testtooltipbox(v))
362                 return TRUE;
363         
364         // bottom center
365         v_x = pos_x - menuTooltipSize_x * 0.5;
366         if(m_testtooltipbox(v))
367                 return TRUE;
368
369         // bottom left
370         v_x = pos_x - avoidminus_x;
371         if(m_testtooltipbox(v))
372                 return TRUE;
373
374         // top left
375         v_y = pos_y - avoidminus_y;
376         if(m_testtooltipbox(v))
377                 return TRUE;
378
379         // top center
380         v_x = pos_x - menuTooltipSize_x * 0.5;
381         if(m_testtooltipbox(v))
382                 return TRUE;
383         
384         // top right
385         v_x = pos_x + avoidplus_x;
386         if(m_testtooltipbox(v))
387                 return TRUE;
388         
389         return FALSE;
390 }
391 entity m_findtooltipitem(entity root, vector pos)
392 {
393         entity it;
394         entity best;
395
396         best = world;
397         it = root;
398
399         while(it.instanceOfContainer)
400         {
401                 while(it.instanceOfNexposee && it.focusedChild)
402                 {
403                         it = it.focusedChild;
404                         pos = globalToBox(pos, it.Container_origin, it.Container_size);
405                 }
406                 if(it.instanceOfNexposee)
407                 {
408                         it = it.itemFromPoint(it, pos);
409                         if(it.tooltip)
410                                 best = it;
411                         it = world;
412                 }
413                 else if(it.instanceOfModalController)
414                         it = it.focusedChild;
415                 else
416                         it = it.itemFromPoint(it, pos);
417                 if(!it)
418                         break;
419                 if(it.tooltip)
420                         best = it;
421                 pos = globalToBox(pos, it.Container_origin, it.Container_size);
422         }
423
424         return best;
425 }
426 void m_tooltip(vector pos)
427 {
428         float f, i, w;
429         entity it;
430         vector fontsize, p;
431         string s;
432
433         fontsize = '1 0 0' * (SKINFONTSIZE_TOOLTIP / conwidth) + '0 1 0' * (SKINFONTSIZE_TOOLTIP / conheight);
434
435         f = bound(0, frametime * 2, 1);
436         menuTooltipAveragedMousePos = menuTooltipAveragedMousePos * (1 - f) + pos * f;
437         f = vlen(pos - menuTooltipAveragedMousePos);
438
439         if(f < 0.01)
440                 it = m_findtooltipitem(main, pos);
441         else    
442                 it = world;
443
444         // float menuTooltipState; // 0: static, 1: fading in, 2: fading out
445         if(it != menuTooltipItem)
446         {
447                 switch(menuTooltipState)
448                 {
449                         case 0:
450                                 if(menuTooltipItem)
451                                 {
452                                         // another item: fade out first
453                                         menuTooltipState = 2;
454                                 }
455                                 else
456                                 {
457                                         // new item: fade in
458                                         menuTooltipState = 1;
459                                         menuTooltipItem = it;
460
461                                         menuTooltipOrigin_x = -1; // unallocated
462                                         i = 0;
463                                         w =  0;
464                                         getWrappedLine_remaining = it.tooltip;
465                                         while(getWrappedLine_remaining)
466                                         {
467                                                 s = getWrappedLine(SKINWIDTH_TOOLTIP / fontsize_x, draw_TextWidth_WithoutColors);
468                                                 ++i;
469                                                 f = draw_TextWidth(s, FALSE);
470                                                 if(f > w)
471                                                         w = f;
472                                         }
473                                         menuTooltipSize_x = w * fontsize_x + 2 * (SKINMARGIN_TOOLTIP_x / conwidth);
474                                         menuTooltipSize_y = i * fontsize_y + 2 * (SKINMARGIN_TOOLTIP_y / conheight);
475                                         menuTooltipSize_z = 0;
476                                 }
477                                 break;
478                         case 1:
479                                 // changing item while fading in: fade out first
480                                 menuTooltipState = 2;
481                                 break;
482                         case 2:
483                                 // changing item while fading out: can't
484                                 break;
485                 }
486         }
487         else if(menuTooltipState == 2) // re-fade in?
488                 menuTooltipState = 1;
489
490         if(menuTooltipItem)
491                 if(!m_testmousetooltipbox(pos))
492                         menuTooltipState = 2; // fade out if mouse touches it
493
494         switch(menuTooltipState)
495         {
496                 case 1:
497                         menuTooltipAlpha = bound(0, menuTooltipAlpha + 5 * frametime, 1);
498                         if(menuTooltipAlpha == 1)
499                                 menuTooltipState = 0;
500                         break;
501                 case 2:
502                         menuTooltipAlpha = bound(0, menuTooltipAlpha - 2 * frametime, 1);
503                         if(menuTooltipAlpha == 0)
504                         {
505                                 menuTooltipState = 0;
506                                 menuTooltipItem = world;
507                         }
508                         break;
509         }
510
511         if(menuTooltipItem)
512         {
513                 if(menuTooltipOrigin_x < 0) // unallocated?
514                         m_allocatetooltipbox(pos);
515
516                 if(menuTooltipOrigin_x >= 0)
517                 {
518                         // draw the tooltip!
519                         p = SKINBORDER_TOOLTIP;
520                         p_x *= 1 / conwidth;
521                         p_y *= 1 / conheight;
522                         draw_BorderPicture(menuTooltipOrigin, SKINGFX_TOOLTIP, menuTooltipSize, '1 1 1', menuTooltipAlpha, p);
523                         p = menuTooltipOrigin;
524                         p_x += SKINMARGIN_TOOLTIP_x / conwidth;
525                         p_y += SKINMARGIN_TOOLTIP_y / conheight;
526                         getWrappedLine_remaining = menuTooltipItem.tooltip;
527                         while(getWrappedLine_remaining)
528                         {
529                                 s = getWrappedLine(SKINWIDTH_TOOLTIP / fontsize_x, draw_TextWidth_WithoutColors);
530                                 draw_Text(p, s, fontsize, '1 1 1', SKINALPHA_TOOLTIP * menuTooltipAlpha, FALSE);
531                                 p_y += fontsize_y;
532                         }
533                 }
534         }
535 }
536
537 void() m_draw =
538 {
539         float t;
540         float realFrametime;
541
542         menuMouseMode = cvar("menu_mouse_absolute");
543
544         if(main)
545                 UpdateConWidthHeight();
546
547         if(!menuInitialized)
548         {
549                 // TODO draw an info image about this situation
550                 m_init_delayed();
551                 return;
552         }
553         if(!menuNotTheFirstFrame)
554         {
555                 menuNotTheFirstFrame = 1;
556                 if(Menu_Active)
557                 if(!cvar("menu_video_played"))
558                 {
559                         localcmd("set menu_video_played 1; cd loop $menu_cdtrack; play sound/announcer/male/welcome.ogg\n");
560                         menuLogoAlpha = -0.8; // no idea why, but when I start this at zero, it jumps instead of fading
561                 }
562         }
563
564         t = gettime();
565         realFrametime = frametime = min(0.2, t - menuPrevTime);
566         menuPrevTime = t;
567         time += frametime;
568
569         t = cvar("menu_slowmo");
570         if(t)
571         {
572                 frametime *= t;
573                 realFrametime *= t;
574         }
575         else
576                 t = 1;
577
578         if(Menu_Active)
579         {
580                 if(getmousetarget() == (menuMouseMode ? MT_CLIENT : MT_MENU) && (getkeydest() == KEY_MENU || getkeydest() == KEY_MENU_GRABBED))
581                         setkeydest(keyGrabber ? KEY_MENU_GRABBED : KEY_MENU);
582                 else
583                         m_hide();
584         }
585
586         if(cvar("cl_capturevideo"))
587                 frametime = t / cvar("cl_capturevideo_fps"); // make capturevideo work smoothly
588
589         dprint_load();
590         gamestatus = 0;
591         if(isserver())
592                 gamestatus = gamestatus | GAME_ISSERVER;
593         if(clientstate() == CS_CONNECTED)
594                 gamestatus = gamestatus | GAME_CONNECTED;
595         if(cvar("developer"))
596                 gamestatus = gamestatus | GAME_DEVELOPER;
597
598         prevMenuAlpha = menuAlpha;
599         if(Menu_Active)
600         {
601                 if(menuAlpha == 0 && menuLogoAlpha < 2)
602                 {
603                         menuLogoAlpha = menuLogoAlpha + frametime * 2;
604                 }
605                 else
606                 {
607                         menuAlpha = min(1, menuAlpha + frametime * 5);
608                         menuLogoAlpha = 2;
609                 }
610         }
611         else
612         {
613                 menuAlpha = max(0, menuAlpha - frametime * 5);
614                 menuLogoAlpha = 2;
615         }
616
617         draw_reset_cropped();
618
619         if(!(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)))
620         {
621                 if(menuLogoAlpha > 0)
622                 {
623                         draw_reset_full();
624                         draw_Fill('0 0 0', '1 1 0', SKINCOLOR_BACKGROUND, 1);
625                         drawBackground(SKINGFX_BACKGROUND, bound(0, menuLogoAlpha, 1), SKINALIGN_BACKGROUND, TRUE);
626                         draw_reset_cropped();
627                         if(menuAlpha <= 0 && SKINALPHA_CURSOR_INTRO > 0)
628                         {
629                                 draw_alpha = SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1);
630                                 draw_drawMousePointer(menuMousePos);
631                                 draw_alpha = 1;
632                         }
633                 }
634         }
635         else if(SKINALPHA_BACKGROUND_INGAME)
636         {
637                 if(menuAlpha > 0)
638                 {
639                         draw_reset_full();
640                         drawBackground(SKINGFX_BACKGROUND_INGAME, menuAlpha * SKINALPHA_BACKGROUND_INGAME, SKINALIGN_BACKGROUND_INGAME, FALSE);
641                         draw_reset_cropped();
642                 }
643         }
644
645         if(menuAlpha != prevMenuAlpha)
646                 cvar_set("_menu_alpha", ftos(menuAlpha));
647
648         draw_reset_cropped();
649         preMenuDraw();
650         draw_reset_cropped();
651
652         if(menuAlpha <= 0)
653         {
654                 if(prevMenuAlpha > 0)
655                         main.initializeDialog(main, main.firstChild);
656                 draw_reset_cropped();
657                 postMenuDraw();
658                 return;
659         }
660
661         draw_alpha *= menuAlpha;
662
663         if(menuMouseMode)
664         {
665                 vector newMouse;
666                 newMouse = globalToBox(getmousepos(), draw_shift, draw_scale);
667                 if(newMouse != '0 0 0')
668                         if(newMouse != menuMousePos)
669                         {
670                                 menuMousePos = newMouse;
671                                 if(mouseButtonsPressed)
672                                         main.mouseDrag(main, menuMousePos);
673                                 else
674                                         main.mouseMove(main, menuMousePos);
675                         }
676         }
677         else
678         {
679                 if(frametime > 0)
680                 {
681                         vector dMouse;
682                         dMouse = getmousepos() * (frametime / realFrametime); // for capturevideo
683                         if(dMouse != '0 0 0')
684                         {
685                                 dMouse = globalToBoxSize(dMouse, draw_scale);
686                                 menuMousePos += dMouse * cvar("menu_mouse_speed");
687                                 menuMousePos_x = bound(0, menuMousePos_x, 1);
688                                 menuMousePos_y = bound(0, menuMousePos_y, 1);
689                                 if(mouseButtonsPressed)
690                                         main.mouseDrag(main, menuMousePos);
691                                 else
692                                         main.mouseMove(main, menuMousePos);
693                         }
694                 }
695         }
696         main.draw(main);
697
698         m_tooltip(menuMousePos);
699
700         draw_alpha = max(draw_alpha, SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1));
701
702         draw_drawMousePointer(menuMousePos);
703
704         draw_reset_cropped();
705         postMenuDraw();
706
707         frametime = 0;
708 };
709
710 void() m_display =
711 {
712         Menu_Active = true;
713         setkeydest(KEY_MENU);
714         setmousetarget((menuMouseMode ? MT_CLIENT : MT_MENU));
715
716         if(!menuInitialized)
717                 return;
718
719         if(mouseButtonsPressed)
720                 main.mouseRelease(main, menuMousePos);
721         mouseButtonsPressed = 0;
722
723         main.focusEnter(main);
724         main.showNotify(main);
725 };
726
727 void() m_hide =
728 {
729         Menu_Active = false;
730         setkeydest(KEY_GAME);
731         setmousetarget(MT_CLIENT);
732
733         if(!menuInitialized)
734                 return;
735
736         main.focusLeave(main);
737         main.hideNotify(main);
738 };
739
740 void() m_toggle =
741 {
742         if(Menu_Active)
743                 m_hide();
744         else
745                 m_display();
746 };
747
748 void() m_shutdown =
749 {
750         entity e;
751
752         m_hide();
753         for(e = NULL; (e = nextent(e)) != NULL; )
754         {
755                 if(e.destroy)
756                         e.destroy(e);
757         }
758 };
759
760 void m_focus_item_chain(entity outermost, entity innermost)
761 {
762         if(innermost.parent != outermost)
763                 m_focus_item_chain(outermost, innermost.parent);
764         innermost.parent.setFocus(innermost.parent, innermost);
765 }
766
767 void m_activate_window(entity wnd)
768 {
769         entity par;
770         par = wnd.parent;
771         if(par)
772                 m_activate_window(par);
773
774         if(par.instanceOfModalController)
775         {
776                 if(wnd.tabSelectingButton)
777                         // tabs
778                         TabButton_Click(wnd.tabSelectingButton, wnd);
779                 else
780                         // root
781                         par.initializeDialog(par, wnd);
782         }
783         else if(par.instanceOfNexposee)
784         {
785                 // nexposee (sorry for violating abstraction here)
786                 par.selectedChild = wnd;
787                 par.animationState = 1;
788                 setFocusContainer(par, NULL);
789         }
790         else if(par.instanceOfContainer)
791         {
792                 // other containers
793                 if(par.focused)
794                         par.setFocus(par, wnd);
795         }
796 }
797
798 void m_setpointerfocus(entity wnd)
799 {
800         if(wnd.instanceOfContainer)
801         {
802                 entity focus = wnd.preferredFocusedGrandChild(wnd);
803                 if(focus)
804                 {
805                         menuMousePos = focus.origin + 0.5 * focus.size;
806                         menuMousePos_x *= 1 / conwidth;
807                         menuMousePos_y *= 1 / conheight;
808                         if(wnd.focused) // why does this never happen?
809                                 m_focus_item_chain(wnd, focus);
810                 }
811         }
812 }
813
814 void(string itemname) m_goto =
815 {
816         entity e;
817         if(!menuInitialized)
818                 return;
819         if(itemname == "") // this can be called by GameCommand
820         {
821                 if(gamestatus & (GAME_ISSERVER | GAME_CONNECTED))
822                         m_hide();
823                 else
824                 {
825                         m_activate_window(main.mainNexposee);
826                         m_display();
827                 }
828         }
829         else
830         {
831                 e = findstring(NULL, name, itemname);
832                 if(e)
833                 {
834                         m_hide();
835                         m_activate_window(e);
836                         m_setpointerfocus(e);
837                         m_display();
838                 }
839         }
840 }
841
842 void() m_goto_skin_selector =
843 {
844         if(!menuInitialized)
845                 return;
846         // TODO add code to switch back to the skin selector (no idea how to do it now)
847         m_goto("skinselector");
848 }
849
850 void() m_goto_video_settings =
851 {
852         if(!menuInitialized)
853                 return;
854         // TODO add code to switch back to the skin selector (no idea how to do it now)
855         m_goto("videosettings");
856 }