]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/menu/menu.qc
menu: add saturation slider, fix warning about fteqcc miscompiling menu tolltip code
[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                 while(it.instanceOfNexposee && it.focusedChild)
301                 {
302                         it = it.focusedChild;
303                         pos = globalToBox(pos, it.Container_origin, it.Container_size);
304                 }
305                 if(it.instanceOfNexposee)
306                 {
307                         it = it.itemFromPoint(it, pos);
308                         if(it.tooltip)
309                                 best = it;
310                         it = world;
311                 }
312                 else if(it.instanceOfModalController)
313                         it = it.focusedChild;
314                 else
315                         it = it.itemFromPoint(it, pos);
316                 if(!it)
317                         break;
318                 if(it.tooltip)
319                         best = it;
320                 pos = globalToBox(pos, it.Container_origin, it.Container_size);
321         }
322
323         return best;
324 }
325 void m_tooltip(vector pos)
326 {
327         float f, i, w;
328         entity it;
329         vector fontsize, p;
330         string s;
331
332         fontsize = '1 0 0' * (SKINFONTSIZE_TOOLTIP / conwidth) + '0 1 0' * (SKINFONTSIZE_TOOLTIP / conheight);
333
334         f = bound(0, frametime * 2, 1);
335         menuTooltipAveragedMousePos = menuTooltipAveragedMousePos * (1 - f) + pos * f;
336         f = vlen(pos - menuTooltipAveragedMousePos);
337
338         if(f < 0.01)
339                 it = m_findtooltipitem(main, pos);
340         else    
341                 it = world;
342
343         // float menuTooltipState; // 0: static, 1: fading in, 2: fading out
344         if(it != menuTooltipItem)
345         {
346                 switch(menuTooltipState)
347                 {
348                         case 0:
349                                 if(menuTooltipItem)
350                                 {
351                                         // another item: fade out first
352                                         menuTooltipState = 2;
353                                 }
354                                 else
355                                 {
356                                         // new item: fade in
357                                         menuTooltipState = 1;
358                                         menuTooltipItem = it;
359
360                                         menuTooltipOrigin_x = -1; // unallocated
361                                         i = 0;
362                                         w =  0;
363                                         getWrappedLine_remaining = it.tooltip;
364                                         while(getWrappedLine_remaining)
365                                         {
366                                                 s = getWrappedLine(SKINWIDTH_TOOLTIP / fontsize_x, draw_TextWidth_WithoutColors);
367                                                 ++i;
368                                                 f = draw_TextWidth(s, FALSE);
369                                                 if(f > w)
370                                                         w = f;
371                                         }
372                                         menuTooltipSize_x = w * fontsize_x + 2 * (SKINMARGIN_TOOLTIP_x / conwidth);
373                                         menuTooltipSize_y = i * fontsize_y + 2 * (SKINMARGIN_TOOLTIP_y / conheight);
374                                         menuTooltipSize_z = 0;
375                                 }
376                                 break;
377                         case 1:
378                                 // changing item while fading in: fade out first
379                                 menuTooltipState = 2;
380                                 break;
381                         case 2:
382                                 // changing item while fading out: can't
383                                 break;
384                 }
385         }
386         else if(menuTooltipState == 2) // re-fade in?
387                 menuTooltipState = 1;
388
389         if(menuTooltipItem)
390                 if(!m_testmousetooltipbox(pos))
391                         menuTooltipState = 2; // fade out if mouse touches it
392
393         switch(menuTooltipState)
394         {
395                 case 1:
396                         menuTooltipAlpha = bound(0, menuTooltipAlpha + 5 * frametime, 1);
397                         if(menuTooltipAlpha == 1)
398                                 menuTooltipState = 0;
399                         break;
400                 case 2:
401                         menuTooltipAlpha = bound(0, menuTooltipAlpha - 2 * frametime, 1);
402                         if(menuTooltipAlpha == 0)
403                         {
404                                 menuTooltipState = 0;
405                                 menuTooltipItem = world;
406                         }
407                         break;
408         }
409
410         if(menuTooltipItem)
411         {
412                 if(menuTooltipOrigin_x < 0) // unallocated?
413                         m_allocatetooltipbox(pos);
414
415                 if(menuTooltipOrigin_x >= 0)
416                 {
417                         // draw the tooltip!
418                         p = SKINBORDER_TOOLTIP;
419                         p_x *= 1 / conwidth;
420                         p_y *= 1 / conheight;
421                         draw_BorderPicture(menuTooltipOrigin, SKINGFX_TOOLTIP, menuTooltipSize, '1 1 1', menuTooltipAlpha, p);
422                         p = menuTooltipOrigin;
423                         p_x += SKINMARGIN_TOOLTIP_x / conwidth;
424                         p_y += SKINMARGIN_TOOLTIP_y / conheight;
425                         getWrappedLine_remaining = menuTooltipItem.tooltip;
426                         while(getWrappedLine_remaining)
427                         {
428                                 s = getWrappedLine(SKINWIDTH_TOOLTIP / fontsize_x, draw_TextWidth_WithoutColors);
429                                 draw_Text(p, s, fontsize, '1 1 1', SKINALPHA_TOOLTIP * menuTooltipAlpha, FALSE);
430                                 p_y += fontsize_y;
431                         }
432                 }
433         }
434 }
435
436 void() m_draw =
437 {
438         float t;
439         float realFrametime;
440
441         menuMouseMode = cvar("menu_mouse_absolute");
442
443         if(main)
444                 UpdateConWidthHeight();
445
446         if(!menuInitialized)
447         {
448                 // TODO draw an info image about this situation
449                 m_init_delayed();
450                 return;
451         }
452         if(!menuNotTheFirstFrame)
453         {
454                 menuNotTheFirstFrame = 1;
455                 if(Menu_Active)
456                 if(!cvar("menu_video_played"))
457                 {
458                         localcmd("set menu_video_played 1; cd loop $menu_cdtrack; play sound/announcer/male/welcome.ogg\n");
459                         menuLogoAlpha = -0.8; // no idea why, but when I start this at zero, it jumps instead of fading
460                 }
461         }
462
463         t = gettime();
464         realFrametime = frametime = min(0.2, t - menuPrevTime);
465         menuPrevTime = t;
466         time += frametime;
467
468         t = cvar("menu_slowmo");
469         if(t)
470         {
471                 frametime *= t;
472                 realFrametime *= t;
473         }
474         else
475                 t = 1;
476
477         if(Menu_Active)
478         {
479                 if(getmousetarget() == (menuMouseMode ? MT_CLIENT : MT_MENU) && (getkeydest() == KEY_MENU || getkeydest() == KEY_MENU_GRABBED))
480                         setkeydest(keyGrabber ? KEY_MENU_GRABBED : KEY_MENU);
481                 else
482                         m_hide();
483         }
484
485         if(cvar("cl_capturevideo"))
486                 frametime = t / cvar("cl_capturevideo_fps"); // make capturevideo work smoothly
487
488         dprint_load();
489         gamestatus = 0;
490         if(isserver())
491                 gamestatus = gamestatus | GAME_ISSERVER;
492         if(clientstate() == CS_CONNECTED)
493                 gamestatus = gamestatus | GAME_CONNECTED;
494         if(cvar("developer"))
495                 gamestatus = gamestatus | GAME_DEVELOPER;
496
497         prevMenuAlpha = menuAlpha;
498         if(Menu_Active)
499         {
500                 if(menuAlpha == 0 && menuLogoAlpha < 2)
501                 {
502                         menuLogoAlpha = menuLogoAlpha + frametime * 2;
503                 }
504                 else
505                 {
506                         menuAlpha = min(1, menuAlpha + frametime * 5);
507                         menuLogoAlpha = 2;
508                 }
509         }
510         else
511         {
512                 menuAlpha = max(0, menuAlpha - frametime * 5);
513                 menuLogoAlpha = 2;
514         }
515
516         draw_reset();
517
518         if(!(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)))
519         {
520                 if(menuLogoAlpha > 0)
521                 {
522                         drawBackground(SKINGFX_BACKGROUND, bound(0, menuLogoAlpha, 1));
523                         if(menuAlpha <= 0 && SKINALPHA_CURSOR_INTRO > 0)
524                         {
525                                 draw_alpha = SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1);
526                                 draw_drawMousePointer(menuMousePos);
527                                 draw_alpha = 1;
528                         }
529                 }
530         }
531         else if(SKINALPHA_BACKGROUND_INGAME)
532         {
533                 if(menuAlpha > 0)
534                         drawBackground(SKINGFX_BACKGROUND_INGAME, menuAlpha * SKINALPHA_BACKGROUND_INGAME);
535         }
536
537         draw_reset();
538         preMenuDraw();
539         draw_reset();
540
541         if(menuAlpha <= 0)
542         {
543                 if(prevMenuAlpha > 0)
544                         main.initializeDialog(main, main.firstChild);
545                 draw_reset();
546                 postMenuDraw();
547                 return;
548         }
549
550         draw_alpha *= menuAlpha;
551
552         if(menuMouseMode)
553         {
554                 vector newMouse;
555                 newMouse = globalToBoxSize(getmousepos(), draw_scale);
556                 if(newMouse != '0 0 0')
557                         if(newMouse != menuMousePos)
558                         {
559                                 menuMousePos = newMouse;
560                                 if(mouseButtonsPressed)
561                                         main.mouseDrag(main, menuMousePos);
562                                 else
563                                         main.mouseMove(main, menuMousePos);
564                         }
565         }
566         else
567         {
568                 if(frametime > 0)
569                 {
570                         vector dMouse;
571                         dMouse = getmousepos() * (frametime / realFrametime); // for capturevideo
572                         if(dMouse != '0 0 0')
573                         {
574                                 dMouse = globalToBoxSize(dMouse, draw_scale);
575                                 menuMousePos += dMouse * cvar("menu_mouse_speed");
576                                 menuMousePos_x = bound(0, menuMousePos_x, 1);
577                                 menuMousePos_y = bound(0, menuMousePos_y, 1);
578                                 if(mouseButtonsPressed)
579                                         main.mouseDrag(main, menuMousePos);
580                                 else
581                                         main.mouseMove(main, menuMousePos);
582                         }
583                 }
584         }
585         main.draw(main);
586
587         m_tooltip(menuMousePos);
588
589         draw_alpha = max(draw_alpha, SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1));
590
591         draw_drawMousePointer(menuMousePos);
592
593         draw_reset();
594         postMenuDraw();
595
596         frametime = 0;
597 };
598
599 void() m_display =
600 {
601         Menu_Active = true;
602         setkeydest(KEY_MENU);
603         setmousetarget((menuMouseMode ? MT_CLIENT : MT_MENU));
604
605         if(!menuInitialized)
606                 return;
607
608         if(mouseButtonsPressed)
609                 main.mouseRelease(main, menuMousePos);
610         mouseButtonsPressed = 0;
611
612         main.focusEnter(main);
613         main.showNotify(main);
614 };
615
616 void() m_hide =
617 {
618         Menu_Active = false;
619         setkeydest(KEY_GAME);
620         setmousetarget(MT_CLIENT);
621
622         if(!menuInitialized)
623                 return;
624
625         main.focusLeave(main);
626         main.hideNotify(main);
627 };
628
629 void() m_toggle =
630 {
631         if(Menu_Active)
632                 m_hide();
633         else
634                 m_display();
635 };
636
637 void() m_shutdown =
638 {
639         entity e;
640
641         m_hide();
642         for(e = NULL; (e = nextent(e)) != NULL; )
643         {
644                 if(e.destroy)
645                         e.destroy(e);
646         }
647 };
648
649 void m_focus_item_chain(entity outermost, entity innermost)
650 {
651         if(innermost.parent != outermost)
652                 m_focus_item_chain(outermost, innermost.parent);
653         innermost.parent.setFocus(innermost.parent, innermost);
654 }
655
656 void m_activate_window(entity wnd)
657 {
658         entity par;
659         par = wnd.parent;
660         if(par)
661                 m_activate_window(par);
662
663         if(par.instanceOfModalController)
664         {
665                 if(wnd.tabSelectingButton)
666                         // tabs
667                         TabButton_Click(wnd.tabSelectingButton, wnd);
668                 else
669                         // root
670                         par.initializeDialog(par, wnd);
671         }
672         else if(par.instanceOfNexposee)
673         {
674                 // nexposee (sorry for violating abstraction here)
675                 par.selectedChild = wnd;
676                 par.animationState = 1;
677                 setFocusContainer(par, NULL);
678         }
679         else if(par.instanceOfContainer)
680         {
681                 // other containers
682                 if(par.focused)
683                         par.setFocus(par, wnd);
684         }
685 }
686
687 void m_setpointerfocus(entity wnd)
688 {
689         if(wnd.instanceOfContainer)
690         {
691                 entity focus = wnd.preferredFocusedGrandChild(wnd);
692                 if(focus)
693                 {
694                         menuMousePos = focus.origin + 0.5 * focus.size;
695                         menuMousePos_x *= 1 / conwidth;
696                         menuMousePos_y *= 1 / conheight;
697                         if(wnd.focused) // why does this never happen?
698                                 m_focus_item_chain(wnd, focus);
699                 }
700         }
701 }
702
703 void(string itemname) m_goto =
704 {
705         entity e;
706         if(!menuInitialized)
707                 return;
708         if(itemname == "") // this can be called by GameCommand
709         {
710                 if(gamestatus & (GAME_ISSERVER | GAME_CONNECTED))
711                         m_hide();
712                 else
713                 {
714                         m_activate_window(main.mainNexposee);
715                         m_display();
716                 }
717         }
718         else
719         {
720                 e = findstring(NULL, name, itemname);
721                 if(e)
722                 {
723                         m_hide();
724                         m_activate_window(e);
725                         m_setpointerfocus(e);
726                         m_display();
727                 }
728         }
729 }
730
731 void() m_goto_skin_selector =
732 {
733         if(!menuInitialized)
734                 return;
735         // TODO add code to switch back to the skin selector (no idea how to do it now)
736         m_goto("skinselector");
737 }