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