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