menu now ALWAYS works in a subwindow of 5:4 aspect, scaled to just fit in. Should...
[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 void(string img, float a, float algn, float force1) drawBackground =
230 {
231         vector sz;
232         vector isz;
233         vector tl, ce, br;
234         vector v;
235         string s;
236         float i, f;
237         sz = draw_PictureSize(img);
238         // keep aspect of image
239         if(sz_x * draw_scale_y >= sz_y * draw_scale_x)
240         {
241                 // that is, sz_x/sz_y >= draw_scale_x/draw_scale_y
242                 // match up the height
243                 isz_y = 1;
244                 isz_x = isz_y * (sz_x / sz_y) * (draw_scale_y / draw_scale_x);
245         }
246         else
247         {
248                 // that is, sz_x/sz_y <= draw_scale_x/draw_scale_y
249                 // match up the width
250                 isz_x = 1;
251                 isz_y = isz_x * (sz_y / sz_x) * (draw_scale_x / draw_scale_y);
252         }
253         tl = '0 0 0';
254         ce = '0.5 0.5 0' - 0.5 * isz;
255         br = '1 1 0' - isz;
256
257         s = ftos(algn);
258         v_z = 0;
259         for(i = 0; i < strlen(s); ++i)
260         {
261                 f = stof(substring(s, i, 1));
262                 switch(f)
263                 {
264                         case 1: case 4: case 7: v_x = tl_x; break;
265                         case 2: case 5: case 8: v_x = ce_x; break;
266                         case 3: case 6: case 9: v_x = br_x; break;
267                         default: v_x = tl_x + (br_x - tl_x) * random(); break;
268                 }
269                 switch(f)
270                 {
271                         case 7: case 8: case 9: v_y = tl_y; break;
272                         case 4: case 5: case 6: v_y = ce_y; break;
273                         case 1: case 2: case 3: v_y = br_y; break;
274                         default: v_y = tl_y + (br_y - tl_y) * random(); break;
275                 }
276                 if(i == 0)
277                         draw_Picture(v, img, isz, '1 1 1', a);
278                 else if(force1)
279                         // force all secondary layers to use alpha 1. Prevents ugly issues
280                         // with overlap. It's a flag because it cannot be used for the
281                         // ingame background
282                         draw_Picture(v, strcat(img, "_l", ftos(i+1)), isz, '1 1 1', 1);
283                 else
284                         draw_Picture(v, strcat(img, "_l", ftos(i+1)), isz, '1 1 1', a);
285         }
286 }
287
288 vector menuTooltipAveragedMousePos;
289 entity menuTooltipItem;
290 vector menuTooltipOrigin;
291 vector menuTooltipSize;
292 float menuTooltipAlpha;
293 float menuTooltipState; // 0: no tooltip, 1: fading in, 2: displaying, 3: fading out
294 float m_testmousetooltipbox(vector pos)
295 {
296         if(pos_x >= menuTooltipOrigin_x && pos_x < menuTooltipOrigin_x + menuTooltipSize_x)
297         if(pos_y >= menuTooltipOrigin_y && pos_y < menuTooltipOrigin_y + menuTooltipSize_y)
298                 return FALSE;
299         return TRUE;
300 }
301 float m_testtooltipbox(vector tooltippos)
302 {
303         if(tooltippos_x < 0)
304                 return FALSE;
305         if(tooltippos_y < 0)
306                 return FALSE;
307         if(tooltippos_x + menuTooltipSize_x > 1)
308                 return FALSE;
309         if(tooltippos_y + menuTooltipSize_y > 1)
310                 return FALSE;
311         /*
312         menuTooltipOrigin_x = rint(tooltippos_x * cvar("vid_width")) / cvar("vid_width");
313         menuTooltipOrigin_y = rint(tooltippos_y * cvar("vid_height")) / cvar("vid_height");
314         menuTooltipOrigin_z = 0;
315         */
316         menuTooltipOrigin = tooltippos;
317         return TRUE;
318 }
319 float m_allocatetooltipbox(vector pos)
320 {
321         vector avoidplus, avoidminus;
322         vector v;
323
324         avoidplus_x = (SKINAVOID_TOOLTIP_x + SKINSIZE_CURSOR_x - SKINOFFSET_CURSOR_x) / conwidth;
325         avoidplus_y = (SKINAVOID_TOOLTIP_y + SKINSIZE_CURSOR_y - SKINOFFSET_CURSOR_y) / conheight;
326         avoidplus_z = 0;
327
328         avoidminus_x = (SKINAVOID_TOOLTIP_x + SKINOFFSET_CURSOR_x) / conwidth + menuTooltipSize_x;
329         avoidminus_y = (SKINAVOID_TOOLTIP_y + SKINOFFSET_CURSOR_y) / conheight + menuTooltipSize_y;
330         avoidminus_z = 0;
331
332         // bottom right
333         v = pos + avoidplus;
334         if(m_testtooltipbox(v))
335                 return TRUE;
336         
337         // bottom center
338         v_x = pos_x - menuTooltipSize_x * 0.5;
339         if(m_testtooltipbox(v))
340                 return TRUE;
341
342         // bottom left
343         v_x = pos_x - avoidminus_x;
344         if(m_testtooltipbox(v))
345                 return TRUE;
346
347         // top left
348         v_y = pos_y - avoidminus_y;
349         if(m_testtooltipbox(v))
350                 return TRUE;
351
352         // top center
353         v_x = pos_x - menuTooltipSize_x * 0.5;
354         if(m_testtooltipbox(v))
355                 return TRUE;
356         
357         // top right
358         v_x = pos_x + avoidplus_x;
359         if(m_testtooltipbox(v))
360                 return TRUE;
361         
362         return FALSE;
363 }
364 entity m_findtooltipitem(entity root, vector pos)
365 {
366         entity it;
367         entity best;
368
369         best = world;
370         it = root;
371
372         while(it.instanceOfContainer)
373         {
374                 while(it.instanceOfNexposee && it.focusedChild)
375                 {
376                         it = it.focusedChild;
377                         pos = globalToBox(pos, it.Container_origin, it.Container_size);
378                 }
379                 if(it.instanceOfNexposee)
380                 {
381                         it = it.itemFromPoint(it, pos);
382                         if(it.tooltip)
383                                 best = it;
384                         it = world;
385                 }
386                 else if(it.instanceOfModalController)
387                         it = it.focusedChild;
388                 else
389                         it = it.itemFromPoint(it, pos);
390                 if(!it)
391                         break;
392                 if(it.tooltip)
393                         best = it;
394                 pos = globalToBox(pos, it.Container_origin, it.Container_size);
395         }
396
397         return best;
398 }
399 void m_tooltip(vector pos)
400 {
401         float f, i, w;
402         entity it;
403         vector fontsize, p;
404         string s;
405
406         fontsize = '1 0 0' * (SKINFONTSIZE_TOOLTIP / conwidth) + '0 1 0' * (SKINFONTSIZE_TOOLTIP / conheight);
407
408         f = bound(0, frametime * 2, 1);
409         menuTooltipAveragedMousePos = menuTooltipAveragedMousePos * (1 - f) + pos * f;
410         f = vlen(pos - menuTooltipAveragedMousePos);
411
412         if(f < 0.01)
413                 it = m_findtooltipitem(main, pos);
414         else    
415                 it = world;
416
417         // float menuTooltipState; // 0: static, 1: fading in, 2: fading out
418         if(it != menuTooltipItem)
419         {
420                 switch(menuTooltipState)
421                 {
422                         case 0:
423                                 if(menuTooltipItem)
424                                 {
425                                         // another item: fade out first
426                                         menuTooltipState = 2;
427                                 }
428                                 else
429                                 {
430                                         // new item: fade in
431                                         menuTooltipState = 1;
432                                         menuTooltipItem = it;
433
434                                         menuTooltipOrigin_x = -1; // unallocated
435                                         i = 0;
436                                         w =  0;
437                                         getWrappedLine_remaining = it.tooltip;
438                                         while(getWrappedLine_remaining)
439                                         {
440                                                 s = getWrappedLine(SKINWIDTH_TOOLTIP / fontsize_x, draw_TextWidth_WithoutColors);
441                                                 ++i;
442                                                 f = draw_TextWidth(s, FALSE);
443                                                 if(f > w)
444                                                         w = f;
445                                         }
446                                         menuTooltipSize_x = w * fontsize_x + 2 * (SKINMARGIN_TOOLTIP_x / conwidth);
447                                         menuTooltipSize_y = i * fontsize_y + 2 * (SKINMARGIN_TOOLTIP_y / conheight);
448                                         menuTooltipSize_z = 0;
449                                 }
450                                 break;
451                         case 1:
452                                 // changing item while fading in: fade out first
453                                 menuTooltipState = 2;
454                                 break;
455                         case 2:
456                                 // changing item while fading out: can't
457                                 break;
458                 }
459         }
460         else if(menuTooltipState == 2) // re-fade in?
461                 menuTooltipState = 1;
462
463         if(menuTooltipItem)
464                 if(!m_testmousetooltipbox(pos))
465                         menuTooltipState = 2; // fade out if mouse touches it
466
467         switch(menuTooltipState)
468         {
469                 case 1:
470                         menuTooltipAlpha = bound(0, menuTooltipAlpha + 5 * frametime, 1);
471                         if(menuTooltipAlpha == 1)
472                                 menuTooltipState = 0;
473                         break;
474                 case 2:
475                         menuTooltipAlpha = bound(0, menuTooltipAlpha - 2 * frametime, 1);
476                         if(menuTooltipAlpha == 0)
477                         {
478                                 menuTooltipState = 0;
479                                 menuTooltipItem = world;
480                         }
481                         break;
482         }
483
484         if(menuTooltipItem)
485         {
486                 if(menuTooltipOrigin_x < 0) // unallocated?
487                         m_allocatetooltipbox(pos);
488
489                 if(menuTooltipOrigin_x >= 0)
490                 {
491                         // draw the tooltip!
492                         p = SKINBORDER_TOOLTIP;
493                         p_x *= 1 / conwidth;
494                         p_y *= 1 / conheight;
495                         draw_BorderPicture(menuTooltipOrigin, SKINGFX_TOOLTIP, menuTooltipSize, '1 1 1', menuTooltipAlpha, p);
496                         p = menuTooltipOrigin;
497                         p_x += SKINMARGIN_TOOLTIP_x / conwidth;
498                         p_y += SKINMARGIN_TOOLTIP_y / conheight;
499                         getWrappedLine_remaining = menuTooltipItem.tooltip;
500                         while(getWrappedLine_remaining)
501                         {
502                                 s = getWrappedLine(SKINWIDTH_TOOLTIP / fontsize_x, draw_TextWidth_WithoutColors);
503                                 draw_Text(p, s, fontsize, '1 1 1', SKINALPHA_TOOLTIP * menuTooltipAlpha, FALSE);
504                                 p_y += fontsize_y;
505                         }
506                 }
507         }
508 }
509
510 void() m_draw =
511 {
512         float t;
513         float realFrametime;
514
515         menuMouseMode = cvar("menu_mouse_absolute");
516
517         if(main)
518                 UpdateConWidthHeight();
519
520         if(!menuInitialized)
521         {
522                 // TODO draw an info image about this situation
523                 m_init_delayed();
524                 return;
525         }
526         if(!menuNotTheFirstFrame)
527         {
528                 menuNotTheFirstFrame = 1;
529                 if(Menu_Active)
530                 if(!cvar("menu_video_played"))
531                 {
532                         localcmd("set menu_video_played 1; cd loop $menu_cdtrack; play sound/announcer/male/welcome.ogg\n");
533                         menuLogoAlpha = -0.8; // no idea why, but when I start this at zero, it jumps instead of fading
534                 }
535         }
536
537         t = gettime();
538         realFrametime = frametime = min(0.2, t - menuPrevTime);
539         menuPrevTime = t;
540         time += frametime;
541
542         t = cvar("menu_slowmo");
543         if(t)
544         {
545                 frametime *= t;
546                 realFrametime *= t;
547         }
548         else
549                 t = 1;
550
551         if(Menu_Active)
552         {
553                 if(getmousetarget() == (menuMouseMode ? MT_CLIENT : MT_MENU) && (getkeydest() == KEY_MENU || getkeydest() == KEY_MENU_GRABBED))
554                         setkeydest(keyGrabber ? KEY_MENU_GRABBED : KEY_MENU);
555                 else
556                         m_hide();
557         }
558
559         if(cvar("cl_capturevideo"))
560                 frametime = t / cvar("cl_capturevideo_fps"); // make capturevideo work smoothly
561
562         dprint_load();
563         gamestatus = 0;
564         if(isserver())
565                 gamestatus = gamestatus | GAME_ISSERVER;
566         if(clientstate() == CS_CONNECTED)
567                 gamestatus = gamestatus | GAME_CONNECTED;
568         if(cvar("developer"))
569                 gamestatus = gamestatus | GAME_DEVELOPER;
570
571         prevMenuAlpha = menuAlpha;
572         if(Menu_Active)
573         {
574                 if(menuAlpha == 0 && menuLogoAlpha < 2)
575                 {
576                         menuLogoAlpha = menuLogoAlpha + frametime * 2;
577                 }
578                 else
579                 {
580                         menuAlpha = min(1, menuAlpha + frametime * 5);
581                         menuLogoAlpha = 2;
582                 }
583         }
584         else
585         {
586                 menuAlpha = max(0, menuAlpha - frametime * 5);
587                 menuLogoAlpha = 2;
588         }
589
590         draw_reset_cropped();
591
592         if(!(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)))
593         {
594                 if(menuLogoAlpha > 0)
595                 {
596                         draw_reset_full();
597                         drawBackground(SKINGFX_BACKGROUND, bound(0, menuLogoAlpha, 1), SKINALIGN_BACKGROUND, TRUE);
598                         draw_reset_cropped();
599                         if(menuAlpha <= 0 && SKINALPHA_CURSOR_INTRO > 0)
600                         {
601                                 draw_alpha = SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1);
602                                 draw_drawMousePointer(menuMousePos);
603                                 draw_alpha = 1;
604                         }
605                 }
606         }
607         else if(SKINALPHA_BACKGROUND_INGAME)
608         {
609                 if(menuAlpha > 0)
610                 {
611                         draw_reset_full();
612                         drawBackground(SKINGFX_BACKGROUND_INGAME, menuAlpha * SKINALPHA_BACKGROUND_INGAME, SKINALIGN_BACKGROUND_INGAME, FALSE);
613                         draw_reset_cropped();
614                 }
615         }
616
617         if(menuAlpha != prevMenuAlpha)
618                 cvar_set("_menu_alpha", ftos(menuAlpha));
619
620         draw_reset_cropped();
621         preMenuDraw();
622         draw_reset_cropped();
623
624         if(menuAlpha <= 0)
625         {
626                 if(prevMenuAlpha > 0)
627                         main.initializeDialog(main, main.firstChild);
628                 draw_reset_cropped();
629                 postMenuDraw();
630                 return;
631         }
632
633         draw_alpha *= menuAlpha;
634
635         if(menuMouseMode)
636         {
637                 vector newMouse;
638                 newMouse = globalToBox(getmousepos(), draw_shift, draw_scale);
639                 if(newMouse != '0 0 0')
640                         if(newMouse != menuMousePos)
641                         {
642                                 menuMousePos = newMouse;
643                                 if(mouseButtonsPressed)
644                                         main.mouseDrag(main, menuMousePos);
645                                 else
646                                         main.mouseMove(main, menuMousePos);
647                         }
648         }
649         else
650         {
651                 if(frametime > 0)
652                 {
653                         vector dMouse;
654                         dMouse = getmousepos() * (frametime / realFrametime); // for capturevideo
655                         if(dMouse != '0 0 0')
656                         {
657                                 dMouse = globalToBoxSize(dMouse, draw_scale);
658                                 menuMousePos += dMouse * cvar("menu_mouse_speed");
659                                 menuMousePos_x = bound(0, menuMousePos_x, 1);
660                                 menuMousePos_y = bound(0, menuMousePos_y, 1);
661                                 if(mouseButtonsPressed)
662                                         main.mouseDrag(main, menuMousePos);
663                                 else
664                                         main.mouseMove(main, menuMousePos);
665                         }
666                 }
667         }
668         main.draw(main);
669
670         m_tooltip(menuMousePos);
671
672         draw_alpha = max(draw_alpha, SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1));
673
674         draw_drawMousePointer(menuMousePos);
675
676         draw_reset_cropped();
677         postMenuDraw();
678
679         frametime = 0;
680 };
681
682 void() m_display =
683 {
684         Menu_Active = true;
685         setkeydest(KEY_MENU);
686         setmousetarget((menuMouseMode ? MT_CLIENT : MT_MENU));
687
688         if(!menuInitialized)
689                 return;
690
691         if(mouseButtonsPressed)
692                 main.mouseRelease(main, menuMousePos);
693         mouseButtonsPressed = 0;
694
695         main.focusEnter(main);
696         main.showNotify(main);
697 };
698
699 void() m_hide =
700 {
701         Menu_Active = false;
702         setkeydest(KEY_GAME);
703         setmousetarget(MT_CLIENT);
704
705         if(!menuInitialized)
706                 return;
707
708         main.focusLeave(main);
709         main.hideNotify(main);
710 };
711
712 void() m_toggle =
713 {
714         if(Menu_Active)
715                 m_hide();
716         else
717                 m_display();
718 };
719
720 void() m_shutdown =
721 {
722         entity e;
723
724         m_hide();
725         for(e = NULL; (e = nextent(e)) != NULL; )
726         {
727                 if(e.destroy)
728                         e.destroy(e);
729         }
730 };
731
732 void m_focus_item_chain(entity outermost, entity innermost)
733 {
734         if(innermost.parent != outermost)
735                 m_focus_item_chain(outermost, innermost.parent);
736         innermost.parent.setFocus(innermost.parent, innermost);
737 }
738
739 void m_activate_window(entity wnd)
740 {
741         entity par;
742         par = wnd.parent;
743         if(par)
744                 m_activate_window(par);
745
746         if(par.instanceOfModalController)
747         {
748                 if(wnd.tabSelectingButton)
749                         // tabs
750                         TabButton_Click(wnd.tabSelectingButton, wnd);
751                 else
752                         // root
753                         par.initializeDialog(par, wnd);
754         }
755         else if(par.instanceOfNexposee)
756         {
757                 // nexposee (sorry for violating abstraction here)
758                 par.selectedChild = wnd;
759                 par.animationState = 1;
760                 setFocusContainer(par, NULL);
761         }
762         else if(par.instanceOfContainer)
763         {
764                 // other containers
765                 if(par.focused)
766                         par.setFocus(par, wnd);
767         }
768 }
769
770 void m_setpointerfocus(entity wnd)
771 {
772         if(wnd.instanceOfContainer)
773         {
774                 entity focus = wnd.preferredFocusedGrandChild(wnd);
775                 if(focus)
776                 {
777                         menuMousePos = focus.origin + 0.5 * focus.size;
778                         menuMousePos_x *= 1 / conwidth;
779                         menuMousePos_y *= 1 / conheight;
780                         if(wnd.focused) // why does this never happen?
781                                 m_focus_item_chain(wnd, focus);
782                 }
783         }
784 }
785
786 void(string itemname) m_goto =
787 {
788         entity e;
789         if(!menuInitialized)
790                 return;
791         if(itemname == "") // this can be called by GameCommand
792         {
793                 if(gamestatus & (GAME_ISSERVER | GAME_CONNECTED))
794                         m_hide();
795                 else
796                 {
797                         m_activate_window(main.mainNexposee);
798                         m_display();
799                 }
800         }
801         else
802         {
803                 e = findstring(NULL, name, itemname);
804                 if(e)
805                 {
806                         m_hide();
807                         m_activate_window(e);
808                         m_setpointerfocus(e);
809                         m_display();
810                 }
811         }
812 }
813
814 void() m_goto_skin_selector =
815 {
816         if(!menuInitialized)
817                 return;
818         // TODO add code to switch back to the skin selector (no idea how to do it now)
819         m_goto("skinselector");
820 }
821
822 void() m_goto_video_settings =
823 {
824         if(!menuInitialized)
825                 return;
826         // TODO add code to switch back to the skin selector (no idea how to do it now)
827         m_goto("videosettings");
828 }