1 // creates multiple gui windows that float inside the 3d world
2 
3 // special feature is that its mostly *modeless*: you can use this menu while playing, without turning menus on or off
4 // implementationwise, it is *stateless*: it keeps no internal gui structure, hit tests are instant, usage & implementation is greatly simplified
5 
6 #include "engine.h"
7 
8 #include "textedit.h"
9 
10 static bool layoutpass, actionon = false;
11 static int mousebuttons = 0;
12 static struct gui *windowhit = NULL;
13 
14 static float firstx, firsty;
15 
16 enum {FIELDCOMMIT, FIELDABORT, FIELDEDIT, FIELDSHOW, FIELDKEY};
17 
18 static int fieldmode = FIELDSHOW;
19 static bool fieldsactive = false;
20 
21 static bool hascursor;
22 static float cursorx = 0.5f, cursory = 0.5f;
23 
24 #define SHADOW 4
25 #define ICON_SIZE (FONTH-SHADOW)
26 #define SKIN_W 256
27 #define SKIN_H 128
28 #define SKIN_SCALE 4
29 #define INSERT (3*SKIN_SCALE)
30 #define MAXCOLUMNS 16
31 
32 VARP(guiautotab, 6, 16, 40);
33 VARP(guiclicktab, 0, 0, 1);
34 VARP(guifadein, 0, 1, 1);
35 VARP(guipreviewtime, 0, 15, 1000);
36 
37 static int lastpreview = 0;
38 
throttlepreview(bool loaded)39 static inline bool throttlepreview(bool loaded)
40 {
41     if(loaded) return true;
42     if(totalmillis - lastpreview < guipreviewtime) return false;
43     lastpreview = totalmillis;
44     return true;
45 }
46 
47 struct gui : g3d_gui
48 {
49     struct list
50     {
51         int parent, w, h, springs, curspring, column;
52     };
53 
54     int firstlist, nextlist;
55     int columns[MAXCOLUMNS];
56 
57     static vector<list> lists;
58     static float hitx, hity;
59     static int curdepth, curlist, xsize, ysize, curx, cury;
60     static bool shouldmergehits, shouldautotab;
61 
resetgui62     static void reset()
63     {
64         lists.setsize(0);
65     }
66 
67     static int ty, tx, tpos, *tcurrent, tcolor; //tracking tab size and position since uses different layout method...
68 
allowautotabgui69     bool allowautotab(bool on)
70     {
71         bool oldval = shouldautotab;
72         shouldautotab = on;
73         return oldval;
74     }
75 
autotabgui76     void autotab()
77     {
78         if(tcurrent)
79         {
80             if(layoutpass && !tpos) tcurrent = NULL; //disable tabs because you didn't start with one
81             if(shouldautotab && !curdepth && (layoutpass ? 0 : cury) + ysize > guiautotab*FONTH) tab(NULL, tcolor);
82         }
83     }
84 
shouldtabgui85     bool shouldtab()
86     {
87         if(tcurrent && shouldautotab)
88         {
89             if(layoutpass)
90             {
91                 int space = guiautotab*FONTH - ysize;
92                 if(space < 0) return true;
93                 int l = lists[curlist].parent;
94                 while(l >= 0)
95                 {
96                     space -= lists[l].h;
97                     if(space < 0) return true;
98                     l = lists[l].parent;
99                 }
100             }
101             else
102             {
103                 int space = guiautotab*FONTH - cury;
104                 if(ysize > space) return true;
105                 int l = lists[curlist].parent;
106                 while(l >= 0)
107                 {
108                     if(lists[l].h > space) return true;
109                     l = lists[l].parent;
110                 }
111             }
112         }
113         return false;
114     }
115 
visiblegui116     bool visible() { return (!tcurrent || tpos==*tcurrent) && !layoutpass; }
117 
118     //tab is always at top of page
tabgui119     void tab(const char *name, int color)
120     {
121         if(curdepth != 0) return;
122         if(color) tcolor = color;
123         tpos++;
124         if(!name) name = intstr(tpos);
125         int w = max(text_width(name) - 2*INSERT, 0);
126         if(layoutpass)
127         {
128             ty = max(ty, ysize);
129             ysize = 0;
130         }
131         else
132         {
133             cury = -ysize;
134             int h = FONTH-2*INSERT,
135                 x1 = curx + tx,
136                 x2 = x1 + w + ((skinx[3]-skinx[2]) + (skinx[5]-skinx[4]))*SKIN_SCALE,
137                 y1 = cury - ((skiny[6]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE-h,
138                 y2 = cury;
139             bool hit = tcurrent && windowhit==this && hitx>=x1 && hity>=y1 && hitx<x2 && hity<y2;
140             if(hit && (!guiclicktab || mousebuttons&G3D_DOWN))
141                 *tcurrent = tpos; //roll-over to switch tab
142 
143             drawskin(x1-skinx[visible()?2:6]*SKIN_SCALE, y1-skiny[1]*SKIN_SCALE, w, h, visible()?10:19, 9, gui2d ? 1 : 2, light, alpha);
144             text_(name, x1 + (skinx[3]-skinx[2])*SKIN_SCALE - (w ? INSERT : INSERT/2), y1 + (skiny[2]-skiny[1])*SKIN_SCALE - INSERT, tcolor, visible());
145         }
146         tx += w + ((skinx[5]-skinx[4]) + (skinx[3]-skinx[2]))*SKIN_SCALE;
147     }
148 
ishorizontalgui149     bool ishorizontal() const { return curdepth&1; }
isverticalgui150     bool isvertical() const { return !ishorizontal(); }
151 
pushlistgui152     void pushlist()
153     {
154         if(layoutpass)
155         {
156             if(curlist>=0)
157             {
158                 lists[curlist].w = xsize;
159                 lists[curlist].h = ysize;
160             }
161             list &l = lists.add();
162             l.parent = curlist;
163             l.springs = 0;
164             l.column = -1;
165             curlist = lists.length()-1;
166             xsize = ysize = 0;
167         }
168         else
169         {
170             curlist = nextlist++;
171             if(curlist >= lists.length()) // should never get here unless script code doesn't use same amount of lists in layout and render passes
172             {
173                 list &l = lists.add();
174                 l.parent = curlist;
175                 l.springs = 0;
176                 l.column = -1;
177                 l.w = l.h = 0;
178             }
179             list &l = lists[curlist];
180             l.curspring = 0;
181             if(l.springs > 0)
182             {
183                 if(ishorizontal()) xsize = l.w; else ysize = l.h;
184             }
185             else
186             {
187                 xsize = l.w;
188                 ysize = l.h;
189             }
190         }
191         curdepth++;
192     }
193 
poplistgui194     void poplist()
195     {
196         if(!lists.inrange(curlist)) return;
197         list &l = lists[curlist];
198         if(layoutpass)
199         {
200             l.w = xsize;
201             l.h = ysize;
202             if(l.column >= 0) columns[l.column] = max(columns[l.column], ishorizontal() ? ysize : xsize);
203         }
204         curlist = l.parent;
205         curdepth--;
206         if(lists.inrange(curlist))
207         {
208             int w = xsize, h = ysize;
209             if(ishorizontal()) cury -= h; else curx -= w;
210             list &p = lists[curlist];
211             xsize = p.w;
212             ysize = p.h;
213             if(!layoutpass && p.springs > 0)
214             {
215                 list &s = lists[p.parent];
216                 if(ishorizontal()) xsize = s.w; else ysize = s.h;
217             }
218             layout(w, h);
219         }
220     }
221 
textgui222     int text  (const char *text, int color, const char *icon) { autotab(); return button_(text, color, icon, false, false); }
buttongui223     int button(const char *text, int color, const char *icon) { autotab(); return button_(text, color, icon, true, false); }
titlegui224     int title (const char *text, int color, const char *icon) { autotab(); return button_(text, color, icon, false, true); }
225 
separatorgui226     void separator() { autotab(); line_(FONTH/3); }
progressgui227     void progress(float percent) { autotab(); line_((FONTH*4)/5, percent); }
228 
229     //use to set min size (useful when you have progress bars)
strutgui230     void strut(float size) { layout(isvertical() ? int(size*FONTW) : 0, isvertical() ? 0 : int(size*FONTH)); }
231     //add space between list items
spacegui232     void space(float size) { layout(isvertical() ? 0 : int(size*FONTW), isvertical() ? int(size*FONTH) : 0); }
233 
springgui234     void spring(int weight)
235     {
236         if(curlist < 0) return;
237         list &l = lists[curlist];
238         if(layoutpass) { if(l.parent >= 0) l.springs += weight; return; }
239         int nextspring = min(l.curspring + weight, l.springs);
240         if(nextspring <= l.curspring) return;
241         if(ishorizontal())
242         {
243             int w = xsize - l.w;
244             layout((w*nextspring)/l.springs - (w*l.curspring)/l.springs, 0);
245         }
246         else
247         {
248             int h = ysize - l.h;
249             layout(0, (h*nextspring)/l.springs - (h*l.curspring)/l.springs);
250         }
251         l.curspring = nextspring;
252     }
253 
columngui254     void column(int col)
255     {
256         if(curlist < 0 || !layoutpass || col < 0 || col >= MAXCOLUMNS) return;
257         list &l = lists[curlist];
258         l.column = col;
259     }
260 
layoutgui261     int layout(int w, int h)
262     {
263         if(layoutpass)
264         {
265             if(ishorizontal())
266             {
267                 xsize += w;
268                 ysize = max(ysize, h);
269             }
270             else
271             {
272                 xsize = max(xsize, w);
273                 ysize += h;
274             }
275             return 0;
276         }
277         else
278         {
279             bool hit = ishit(w, h);
280             if(ishorizontal()) curx += w;
281             else cury += h;
282             return (hit && visible()) ? mousebuttons|G3D_ROLLOVER : 0;
283         }
284     }
285 
mergehitsgui286     bool mergehits(bool on)
287     {
288         bool oldval = shouldmergehits;
289         shouldmergehits = on;
290         return oldval;
291     }
292 
ishitgui293     bool ishit(int w, int h, int x = curx, int y = cury)
294     {
295         if(shouldmergehits) return windowhit==this && (ishorizontal() ? hitx>=x && hitx<x+w : hity>=y && hity<y+h);
296         if(ishorizontal()) h = ysize;
297         else w = xsize;
298         return windowhit==this && hitx>=x && hity>=y && hitx<x+w && hity<y+h;
299     }
300 
imagegui301     int image(Texture *t, float scale, const char *overlaid)
302     {
303         autotab();
304         if(scale==0) scale = 1;
305         int size = (int)(scale*2*FONTH)-SHADOW;
306         if(visible()) icon_(t, overlaid!=NULL, curx, cury, size, ishit(size+SHADOW, size+SHADOW), overlaid);
307         return layout(size+SHADOW, size+SHADOW);
308     }
309 
texturegui310     int texture(VSlot &vslot, float scale, bool overlaid)
311     {
312         autotab();
313         if(scale==0) scale = 1;
314         int size = (int)(scale*2*FONTH)-SHADOW;
315         if(visible()) previewslot(vslot, overlaid, curx, cury, size, ishit(size+SHADOW, size+SHADOW));
316         return layout(size+SHADOW, size+SHADOW);
317     }
318 
playerpreviewgui319     int playerpreview(int model, int team, int weap, float sizescale, const char *overlaid)
320     {
321         autotab();
322         if(sizescale==0) sizescale = 1;
323         int size = (int)(sizescale*2*FONTH)-SHADOW;
324         if(model>=0 && visible())
325         {
326             bool hit = ishit(size+SHADOW, size+SHADOW);
327             float xs = size, ys = size, xi = curx, yi = cury;
328             if(overlaid && hit && actionon)
329             {
330                 hudnotextureshader->set();
331                 gle::colorf(0, 0, 0, 0.75f);
332                 rect_(xi+SHADOW, yi+SHADOW, xs, ys);
333                 hudshader->set();
334             }
335             int x1 = int(floor(screenw*(xi*scale.x+origin.x))), y1 = int(floor(screenh*(1 - ((yi+ys)*scale.y+origin.y)))),
336                 x2 = int(ceil(screenw*((xi+xs)*scale.x+origin.x))), y2 = int(ceil(screenh*(1 - (yi*scale.y+origin.y))));
337             glDisable(GL_BLEND);
338             modelpreview::start(x1, y1, x2-x1, y2-y1, overlaid!=NULL);
339             game::renderplayerpreview(model, team, weap);
340             modelpreview::end();
341             hudshader->set();
342             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
343             glEnable(GL_BLEND);
344             if(overlaid)
345             {
346                 if(hit)
347                 {
348                     hudnotextureshader->set();
349                     glBlendFunc(GL_ZERO, GL_SRC_COLOR);
350                     gle::colorf(1, 0.5f, 0.5f);
351                     rect_(xi, yi, xs, ys);
352                     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
353                     hudshader->set();
354                 }
355                 if(overlaid[0]) text_(overlaid, xi + xs/12, yi + ys - ys/12 - FONTH, hit ? 0xFF0000 : 0xFFFFFF, hit, hit);
356                 if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3);
357                 gle::color(light);
358                 glBindTexture(GL_TEXTURE_2D, overlaytex->id);
359                 rect_(xi, yi, xs, ys, 0);
360             }
361         }
362         return layout(size+SHADOW, size+SHADOW);
363     }
364 
modelpreviewgui365     int modelpreview(const char *name, int anim, float sizescale, const char *overlaid, bool throttle)
366     {
367         autotab();
368         if(sizescale==0) sizescale = 1;
369         int size = (int)(sizescale*2*FONTH)-SHADOW;
370         if(name[0] && visible() && (!throttle || throttlepreview(modelloaded(name))))
371         {
372             bool hit = ishit(size+SHADOW, size+SHADOW);
373             float xs = size, ys = size, xi = curx, yi = cury;
374             if(overlaid && hit && actionon)
375             {
376                 hudnotextureshader->set();
377                 gle::colorf(0, 0, 0, 0.75f);
378                 rect_(xi+SHADOW, yi+SHADOW, xs, ys);
379                 hudshader->set();
380             }
381             int x1 = int(floor(screenw*(xi*scale.x+origin.x))), y1 = int(floor(screenh*(1 - ((yi+ys)*scale.y+origin.y)))),
382                 x2 = int(ceil(screenw*((xi+xs)*scale.x+origin.x))), y2 = int(ceil(screenh*(1 - (yi*scale.y+origin.y))));
383             glDisable(GL_BLEND);
384             modelpreview::start(x1, y1, x2-x1, y2-y1, overlaid!=NULL);
385             model *m = loadmodel(name);
386             if(m)
387             {
388                 entitylight light;
389                 light.color = vec(1, 1, 1);
390                 light.dir = vec(0, -1, 2).normalize();
391                 vec center, radius;
392                 m->boundbox(center, radius);
393                 float yaw;
394                 vec o = calcmodelpreviewpos(radius, yaw).sub(center);
395                 rendermodel(&light, name, anim, o, yaw, 0, 0, NULL, NULL, 0);
396             }
397             modelpreview::end();
398             hudshader->set();
399             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
400             glEnable(GL_BLEND);
401             if(overlaid)
402             {
403                 if(hit)
404                 {
405                     hudnotextureshader->set();
406                     glBlendFunc(GL_ZERO, GL_SRC_COLOR);
407                     gle::colorf(1, 0.5f, 0.5f);
408                     rect_(xi, yi, xs, ys);
409                     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
410                     hudshader->set();
411                 }
412                 if(overlaid[0]) text_(overlaid, xi + xs/12, yi + ys - ys/12 - FONTH, hit ? 0xFF0000 : 0xFFFFFF, hit, hit);
413                 if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3);
414                 gle::color(light);
415                 glBindTexture(GL_TEXTURE_2D, overlaytex->id);
416                 rect_(xi, yi, xs, ys, 0);
417             }
418         }
419         return layout(size+SHADOW, size+SHADOW);
420     }
421 
prefabpreviewgui422     int prefabpreview(const char *prefab, const vec &color, float sizescale, const char *overlaid, bool throttle)
423     {
424         autotab();
425         if(sizescale==0) sizescale = 1;
426         int size = (int)(sizescale*2*FONTH)-SHADOW;
427         if(prefab[0] && visible() && (!throttle || throttlepreview(prefabloaded(prefab))))
428         {
429             bool hit = ishit(size+SHADOW, size+SHADOW);
430             float xs = size, ys = size, xi = curx, yi = cury;
431             if(overlaid && hit && actionon)
432             {
433                 hudnotextureshader->set();
434                 gle::colorf(0, 0, 0, 0.75f);
435                 rect_(xi+SHADOW, yi+SHADOW, xs, ys);
436                 hudshader->set();
437             }
438             int x1 = int(floor(screenw*(xi*scale.x+origin.x))), y1 = int(floor(screenh*(1 - ((yi+ys)*scale.y+origin.y)))),
439                 x2 = int(ceil(screenw*((xi+xs)*scale.x+origin.x))), y2 = int(ceil(screenh*(1 - (yi*scale.y+origin.y))));
440             glDisable(GL_BLEND);
441             modelpreview::start(x1, y1, x2-x1, y2-y1, overlaid!=NULL);
442             previewprefab(prefab, color);
443             modelpreview::end();
444             hudshader->set();
445             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
446             glEnable(GL_BLEND);
447             if(overlaid)
448             {
449                 if(hit)
450                 {
451                     hudnotextureshader->set();
452                     glBlendFunc(GL_ZERO, GL_SRC_COLOR);
453                     gle::colorf(1, 0.5f, 0.5f);
454                     rect_(xi, yi, xs, ys);
455                     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
456                     hudshader->set();
457                 }
458                 if(overlaid[0]) text_(overlaid, xi + FONTH/2, yi + FONTH/2, hit ? 0xFF0000 : 0xFFFFFF, hit, hit);
459                 if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3);
460                 gle::color(light);
461                 glBindTexture(GL_TEXTURE_2D, overlaytex->id);
462                 rect_(xi, yi, xs, ys, 0);
463             }
464         }
465         return layout(size+SHADOW, size+SHADOW);
466     }
467 
slidergui468     void slider(int &val, int vmin, int vmax, int color, const char *label)
469     {
470         autotab();
471         int x = curx;
472         int y = cury;
473         line_((FONTH*2)/3);
474         if(visible())
475         {
476             if(!label) label = intstr(val);
477             int w = text_width(label);
478 
479             bool hit;
480             int px, py, offset = vmin < vmax ? clamp(val, vmin, vmax) : clamp(val, vmax, vmin);
481             if(ishorizontal())
482             {
483                 hit = ishit(FONTH, ysize, x, y);
484                 px = x + (FONTH-w)/2;
485                 py = y + (ysize-FONTH) - ((ysize-FONTH)*(offset-vmin))/((vmax==vmin) ? 1 : (vmax-vmin)); //vmin at bottom
486             }
487             else
488             {
489                 hit = ishit(xsize, FONTH, x, y);
490                 px = x + FONTH/2 - w/2 + ((xsize-w)*(offset-vmin))/((vmax==vmin) ? 1 : (vmax-vmin)); //vmin at left
491                 py = y;
492             }
493 
494             if(hit) color = 0xFF0000;
495             text_(label, px, py, color, hit && actionon, hit);
496             if(hit && actionon)
497             {
498                 int vnew = (vmin < vmax ? 1 : -1)+vmax-vmin;
499                 if(ishorizontal()) vnew = int((vnew*(y+ysize-FONTH/2-hity))/(ysize-FONTH));
500                 else vnew = int((vnew*(hitx-x-FONTH/2))/(xsize-w));
501                 vnew += vmin;
502                 vnew = vmin < vmax ? clamp(vnew, vmin, vmax) : clamp(vnew, vmax, vmin);
503                 if(vnew != val) val = vnew;
504             }
505         }
506     }
507 
fieldgui508     char *field(const char *name, int color, int length, int height, const char *initval, int initmode)
509     {
510         return field_(name, color, length, height, initval, initmode, FIELDEDIT);
511     }
512 
keyfieldgui513     char *keyfield(const char *name, int color, int length, int height, const char *initval, int initmode)
514     {
515         return field_(name, color, length, height, initval, initmode, FIELDKEY);
516     }
517 
field_gui518     char *field_(const char *name, int color, int length, int height, const char *initval, int initmode, int fieldtype = FIELDEDIT)
519     {
520         editor *e = useeditor(name, initmode, false, initval); // generate a new editor if necessary
521         if(layoutpass)
522         {
523             if(initval && e->mode==EDITORFOCUSED && (e!=currentfocus() || fieldmode == FIELDSHOW))
524             {
525                 if(strcmp(e->lines[0].text, initval)) e->clear(initval);
526             }
527             e->linewrap = (length<0);
528             e->maxx = (e->linewrap) ? -1 : length;
529             e->maxy = (height<=0)?1:-1;
530             e->pixelwidth = abs(length)*FONTW;
531             if(e->linewrap && e->maxy==1)
532             {
533                 int temp;
534                 text_bounds(e->lines[0].text, temp, e->pixelheight, e->pixelwidth); //only single line editors can have variable height
535             }
536             else
537                 e->pixelheight = FONTH*max(height, 1);
538         }
539         int h = e->pixelheight;
540         int w = e->pixelwidth + FONTW;
541 
542         bool wasvertical = isvertical();
543         if(wasvertical && e->maxy != 1) pushlist();
544 
545         char *result = NULL;
546         if(visible() && !layoutpass)
547         {
548             e->rendered = true;
549 
550             bool hit = ishit(w, h);
551             if(hit)
552             {
553                 if(mousebuttons&G3D_DOWN) //mouse request focus
554                 {
555                     if(fieldtype==FIELDKEY) e->clear();
556                     useeditor(name, initmode, true);
557                     e->mark(false);
558                     fieldmode = fieldtype;
559                 }
560             }
561             bool editing = (fieldmode != FIELDSHOW) && (e==currentfocus());
562             if(hit && editing && (mousebuttons&G3D_PRESSED)!=0 && fieldtype==FIELDEDIT) e->hit(int(floor(hitx-(curx+FONTW/2))), int(floor(hity-cury)), (mousebuttons&G3D_DRAGGED)!=0); //mouse request position
563             if(editing && ((fieldmode==FIELDCOMMIT) || (fieldmode==FIELDABORT) || !hit)) // commit field if user pressed enter or wandered out of focus
564             {
565                 if(fieldmode==FIELDCOMMIT || (fieldmode!=FIELDABORT && !hit)) result = e->currentline().text;
566                 e->active = (e->mode!=EDITORFOCUSED);
567                 fieldmode = FIELDSHOW;
568             }
569             else fieldsactive = true;
570 
571             e->draw(curx+FONTW/2, cury, color, hit && editing);
572 
573             hudnotextureshader->set();
574             glDisable(GL_BLEND);
575             if(editing) gle::colorf(1, 0, 0);
576             else gle::colorub(color>>16, (color>>8)&0xFF, color&0xFF);
577             rect_(curx, cury, w, h, true);
578             glEnable(GL_BLEND);
579             hudshader->set();
580         }
581         layout(w, h);
582 
583         if(e->maxy != 1)
584         {
585             int slines = e->limitscrolly();
586             if(slines > 0)
587             {
588                 int pos = e->scrolly;
589                 slider(e->scrolly, slines, 0, color, NULL);
590                 if(pos != e->scrolly) e->cy = e->scrolly;
591             }
592             if(wasvertical) poplist();
593         }
594 
595         return result;
596     }
597 
rect_gui598     void rect_(float x, float y, float w, float h, bool lines = false)
599     {
600         gle::defvertex(2);
601         gle::begin(lines ? GL_LINE_LOOP : GL_TRIANGLE_STRIP);
602         gle::attribf(x, y);
603         gle::attribf(x + w, y);
604         if(lines) gle::attribf(x + w, y + h);
605         gle::attribf(x, y + h);
606         if(!lines) gle::attribf(x + w, y + h);
607         xtraverts += gle::end();
608     }
609 
rect_gui610     void rect_(float x, float y, float w, float h, int usetc)
611     {
612         gle::defvertex(2);
613         gle::deftexcoord0();
614         gle::begin(GL_TRIANGLE_STRIP);
615         static const vec2 tc[5] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1), vec2(0, 0) };
616         gle::attribf(x, y); gle::attrib(tc[usetc]);
617         gle::attribf(x + w, y); gle::attrib(tc[usetc+1]);
618         gle::attribf(x, y + h); gle::attrib(tc[usetc+3]);
619         gle::attribf(x + w, y + h); gle::attrib(tc[usetc+2]);
620         xtraverts += gle::end();
621     }
622 
text_gui623     void text_(const char *text, int x, int y, int color, bool shadow, bool force = false)
624     {
625         if(shadow) draw_text(text, x+SHADOW, y+SHADOW, 0x00, 0x00, 0x00, -0xC0);
626         draw_text(text, x, y, color>>16, (color>>8)&0xFF, color&0xFF, force ? -0xFF : 0xFF);
627     }
628 
backgroundgui629     void background(int color, int inheritw, int inherith)
630     {
631         if(layoutpass) return;
632         hudnotextureshader->set();
633         gle::colorub(color>>16, (color>>8)&0xFF, color&0xFF, 0x80);
634         int w = xsize, h = ysize;
635         if(inheritw>0)
636         {
637             int parentw = curlist, parentdepth = 0;
638             for(;parentdepth < inheritw && lists[parentw].parent>=0; parentdepth++)
639                 parentw = lists[parentw].parent;
640             list &p = lists[parentw];
641             w = p.springs > 0 && (curdepth-parentdepth)&1 ? lists[p.parent].w : p.w;
642         }
643         if(inherith>0)
644         {
645             int parenth = curlist, parentdepth = 0;
646             for(;parentdepth < inherith && lists[parenth].parent>=0; parentdepth++)
647                 parenth = lists[parenth].parent;
648             list &p = lists[parenth];
649             h = p.springs > 0 && !((curdepth-parentdepth)&1) ? lists[p.parent].h : p.h;
650         }
651         rect_(curx, cury, w, h);
652         hudshader->set();
653     }
654 
icon_gui655     void icon_(Texture *t, bool overlaid, int x, int y, int size, bool hit, const char *title = NULL)
656     {
657         float scale = float(size)/max(t->xs, t->ys); //scale and preserve aspect ratio
658         float xs = t->xs*scale, ys = t->ys*scale;
659         x += int((size-xs)/2);
660         y += int((size-ys)/2);
661         const vec &color = hit ? vec(1, 0.5f, 0.5f) : (overlaid ? vec(1, 1, 1) : light);
662         glBindTexture(GL_TEXTURE_2D, t->id);
663         if(hit && actionon)
664         {
665             gle::colorf(0, 0, 0, 0.75f);
666             rect_(x+SHADOW, y+SHADOW, xs, ys, 0);
667         }
668         gle::color(color);
669         rect_(x, y, xs, ys, 0);
670 
671         if(overlaid)
672         {
673             if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3);
674             glBindTexture(GL_TEXTURE_2D, overlaytex->id);
675             gle::color(light);
676             rect_(x, y, xs, ys, 0);
677             if(title) text_(title, x + xs/12, y + ys - ys/12 - FONTH, hit ? 0xFF0000 : 0xFFFFFF, hit && actionon, hit);
678         }
679     }
680 
previewslotgui681     void previewslot(VSlot &vslot, bool overlaid, int x, int y, int size, bool hit)
682     {
683         Slot &slot = *vslot.slot;
684         if(slot.sts.empty()) return;
685         VSlot *layer = NULL;
686         Texture *t = NULL, *glowtex = NULL, *layertex = NULL;
687         if(slot.loaded)
688         {
689             t = slot.sts[0].t;
690             if(t == notexture) return;
691             Slot &slot = *vslot.slot;
692             if(slot.texmask&(1<<TEX_GLOW)) { loopvj(slot.sts) if(slot.sts[j].type==TEX_GLOW) { glowtex = slot.sts[j].t; break; } }
693             if(vslot.layer)
694             {
695                 layer = &lookupvslot(vslot.layer);
696                 if(!layer->slot->sts.empty()) layertex = layer->slot->sts[0].t;
697             }
698         }
699         else if(slot.thumbnail && slot.thumbnail != notexture) t = slot.thumbnail;
700         else return;
701         float xt = min(1.0f, t->xs/(float)t->ys), yt = min(1.0f, t->ys/(float)t->xs), xs = size, ys = size;
702         if(hit && actionon)
703         {
704             hudnotextureshader->set();
705             gle::colorf(0, 0, 0, 0.75f);
706             rect_(x+SHADOW, y+SHADOW, xs, ys);
707             hudshader->set();
708         }
709         SETSHADER(hudrgb);
710         gle::defvertex(2);
711         gle::deftexcoord0();
712         const vec &color = hit ? vec(1, 0.5f, 0.5f) : (overlaid ? vec(1, 1, 1) : light);
713         vec2 tc[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) };
714         float xoff = vslot.offset.x, yoff = vslot.offset.y;
715         if(vslot.rotation)
716         {
717             const texrotation &r = texrotations[vslot.rotation];
718             if(r.swapxy) { swap(xoff, yoff); loopk(4) swap(tc[k].x, tc[k].y); }
719             if(r.flipx) { xoff *= -1; loopk(4) tc[k].x *= -1; }
720             if(r.flipy) { yoff *= -1; loopk(4) tc[k].y *= -1; }
721         }
722         loopk(4) { tc[k].x = tc[k].x/xt - xoff/t->xs; tc[k].y = tc[k].y/yt - yoff/t->ys; }
723         if(slot.loaded) gle::color(vec(color).mul(vslot.colorscale));
724         else gle::color(color);
725         glBindTexture(GL_TEXTURE_2D, t->id);
726         gle::begin(GL_TRIANGLE_STRIP);
727         gle::attribf(x,    y);    gle::attrib(tc[0]);
728         gle::attribf(x+xs, y);    gle::attrib(tc[1]);
729         gle::attribf(x,    y+ys); gle::attrib(tc[3]);
730         gle::attribf(x+xs, y+ys); gle::attrib(tc[2]);
731         gle::end();
732         if(glowtex)
733         {
734             glBlendFunc(GL_SRC_ALPHA, GL_ONE);
735             glBindTexture(GL_TEXTURE_2D, glowtex->id);
736             if(hit || overlaid) gle::color(vec(vslot.glowcolor).mul(color));
737             else gle::color(vslot.glowcolor);
738             gle::begin(GL_TRIANGLE_STRIP);
739             gle::attribf(x,    y);    gle::attrib(tc[0]);
740             gle::attribf(x+xs, y);    gle::attrib(tc[1]);
741             gle::attribf(x,    y+ys); gle::attrib(tc[3]);
742             gle::attribf(x+xs, y+ys); gle::attrib(tc[2]);
743             gle::end();
744             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
745         }
746         if(layertex)
747         {
748             glBindTexture(GL_TEXTURE_2D, layertex->id);
749             gle::color(vec(color).mul(layer->colorscale));
750             gle::begin(GL_TRIANGLE_STRIP);
751             gle::attribf(x+xs/2, y+ys/2); gle::attrib(tc[0]);
752             gle::attribf(x+xs,   y+ys/2); gle::attrib(tc[1]);
753             gle::attribf(x+xs/2, y+ys);   gle::attrib(tc[3]);
754             gle::attribf(x+xs,   y+ys);   gle::attrib(tc[2]);
755             gle::end();
756         }
757 
758         hudshader->set();
759         if(overlaid)
760         {
761             if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3);
762             glBindTexture(GL_TEXTURE_2D, overlaytex->id);
763             gle::color(light);
764             rect_(x, y, xs, ys, 0);
765         }
766     }
767 
line_gui768     void line_(int size, float percent = 1.0f)
769     {
770         if(visible())
771         {
772             if(!slidertex) slidertex = textureload("data/guislider.png", 3);
773             glBindTexture(GL_TEXTURE_2D, slidertex->id);
774             if(percent < 0.99f)
775             {
776                 gle::colorf(light.x, light.y, light.z, 0.375f);
777                 if(ishorizontal())
778                     rect_(curx + FONTH/2 - size/2, cury, size, ysize, 0);
779                 else
780                     rect_(curx, cury + FONTH/2 - size/2, xsize, size, 1);
781             }
782             gle::color(light);
783             if(ishorizontal())
784                 rect_(curx + FONTH/2 - size/2, cury + ysize*(1-percent), size, ysize*percent, 0);
785             else
786                 rect_(curx, cury + FONTH/2 - size/2, xsize*percent, size, 1);
787         }
788         layout(ishorizontal() ? FONTH : 0, ishorizontal() ? 0 : FONTH);
789     }
790 
textboxgui791     void textbox(const char *text, int width, int height, int color)
792     {
793         width *= FONTW;
794         height *= FONTH;
795         int w, h;
796         text_bounds(text, w, h, width);
797         if(h > height) height = h;
798         if(visible()) draw_text(text, curx, cury, color>>16, (color>>8)&0xFF, color&0xFF, 0xFF, -1, width);
799         layout(width, height);
800     }
801 
button_gui802     int button_(const char *text, int color, const char *icon, bool clickable, bool center)
803     {
804         const int padding = 10;
805         int w = 0;
806         if(icon) w += ICON_SIZE;
807         if(icon && text) w += padding;
808         if(text) w += text_width(text);
809 
810         if(visible())
811         {
812             bool hit = ishit(w, FONTH);
813             if(hit && clickable) color = 0xFF0000;
814             int x = curx;
815             if(isvertical() && center) x += (xsize-w)/2;
816 
817             if(icon)
818             {
819                 if(icon[0] != ' ')
820                 {
821                     const char *ext = strrchr(icon, '.');
822                     defformatstring(tname, "packages/icons/%s%s", icon, ext ? "" : ".jpg");
823                     icon_(textureload(tname, 3), false, x, cury, ICON_SIZE, clickable && hit);
824                 }
825                 x += ICON_SIZE;
826             }
827             if(icon && text) x += padding;
828             if(text) text_(text, x, cury, color, center || (hit && clickable && actionon), hit && clickable);
829         }
830         return layout(w, FONTH);
831     }
832 
833     static Texture *skintex, *overlaytex, *slidertex;
834     static const int skinx[], skiny[];
835     static const struct patch { ushort left, right, top, bottom; uchar flags; } patches[];
836 
drawskingui837     static void drawskin(int x, int y, int gapw, int gaph, int start, int n, int passes = 1, const vec &light = vec(1, 1, 1), float alpha = 0.80f)//int vleft, int vright, int vtop, int vbottom, int start, int n)
838     {
839         if(!skintex) skintex = textureload("data/guiskin.png", 3);
840         glBindTexture(GL_TEXTURE_2D, skintex->id);
841         int gapx1 = INT_MAX, gapy1 = INT_MAX, gapx2 = INT_MAX, gapy2 = INT_MAX;
842         float wscale = 1.0f/(SKIN_W*SKIN_SCALE), hscale = 1.0f/(SKIN_H*SKIN_SCALE);
843 
844         loopj(passes)
845         {
846             bool quads = false;
847             if(passes>1) glDepthFunc(j ? GL_LEQUAL : GL_GREATER);
848             gle::color(j ? light : vec(1, 1, 1), passes<=1 || j ? alpha : alpha/2); //ghost when its behind something in depth
849             loopi(n)
850             {
851                 const patch &p = patches[start+i];
852                 int left = skinx[p.left]*SKIN_SCALE, right = skinx[p.right]*SKIN_SCALE,
853                     top = skiny[p.top]*SKIN_SCALE, bottom = skiny[p.bottom]*SKIN_SCALE;
854                 float tleft = left*wscale, tright = right*wscale,
855                       ttop = top*hscale, tbottom = bottom*hscale;
856                 if(p.flags&0x1)
857                 {
858                     gapx1 = left;
859                     gapx2 = right;
860                 }
861                 else if(left >= gapx2)
862                 {
863                     left += gapw - (gapx2-gapx1);
864                     right += gapw - (gapx2-gapx1);
865                 }
866                 if(p.flags&0x10)
867                 {
868                     gapy1 = top;
869                     gapy2 = bottom;
870                 }
871                 else if(top >= gapy2)
872                 {
873                     top += gaph - (gapy2-gapy1);
874                     bottom += gaph - (gapy2-gapy1);
875                 }
876 
877                 //multiple tiled quads if necessary rather than a single stretched one
878                 int ystep = bottom-top;
879                 int yo = y+top;
880                 while(ystep > 0)
881                 {
882                     if(p.flags&0x10 && yo+ystep-(y+top) > gaph)
883                     {
884                         ystep = gaph+y+top-yo;
885                         tbottom = ttop+ystep*hscale;
886                     }
887                     int xstep = right-left;
888                     int xo = x+left;
889                     float tright2 = tright;
890                     while(xstep > 0)
891                     {
892                         if(p.flags&0x01 && xo+xstep-(x+left) > gapw)
893                         {
894                             xstep = gapw+x+left-xo;
895                             tright = tleft+xstep*wscale;
896                         }
897                         if(!quads)
898                         {
899                             quads = true;
900                             gle::defvertex(2);
901                             gle::deftexcoord0();
902                             gle::begin(GL_QUADS);
903                         }
904                         gle::attribf(xo,       yo);       gle::attribf(tleft,  ttop);
905                         gle::attribf(xo+xstep, yo);       gle::attribf(tright, ttop);
906                         gle::attribf(xo+xstep, yo+ystep); gle::attribf(tright, tbottom);
907                         gle::attribf(xo,       yo+ystep); gle::attribf(tleft,  tbottom);
908                         if(!(p.flags&0x01)) break;
909                         xo += xstep;
910                     }
911                     tright = tright2;
912                     if(!(p.flags&0x10)) break;
913                     yo += ystep;
914                 }
915             }
916             if(quads) xtraverts += gle::end();
917             else break; //if it didn't happen on the first pass, it won't happen on the second..
918         }
919         if(passes>1) glDepthFunc(GL_ALWAYS);
920     }
921 
922     vec origin, scale, *savedorigin;
923     float dist;
924     g3d_callback *cb;
925     bool gui2d;
926 
927     static float basescale, maxscale;
928     static bool passthrough;
929     static float alpha;
930     static vec light;
931 
adjustscalegui932     void adjustscale()
933     {
934         int w = xsize + (skinx[2]-skinx[1])*SKIN_SCALE + (skinx[10]-skinx[9])*SKIN_SCALE, h = ysize + (skiny[9]-skiny[7])*SKIN_SCALE;
935         if(tcurrent) h += ((skiny[5]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE + FONTH-2*INSERT;
936         else h += (skiny[6]-skiny[3])*SKIN_SCALE;
937 
938         float aspect = forceaspect ? 1.0f/forceaspect : float(screenh)/float(screenw), fit = 1.0f;
939         if(w*aspect*basescale>1.0f) fit = 1.0f/(w*aspect*basescale);
940         if(h*basescale*fit>maxscale) fit *= maxscale/(h*basescale*fit);
941         origin = vec(0.5f-((w-xsize)/2 - (skinx[2]-skinx[1])*SKIN_SCALE)*aspect*scale.x*fit, 0.5f + (0.5f*h-(skiny[9]-skiny[7])*SKIN_SCALE)*scale.y*fit, 0);
942         scale = vec(aspect*scale.x*fit, scale.y*fit, 1);
943     }
944 
startgui945     void start(int starttime, float initscale, int *tab, bool allowinput)
946     {
947         if(gui2d)
948         {
949             initscale *= 0.025f;
950             if(allowinput) hascursor = true;
951         }
952         basescale = initscale;
953         if(layoutpass) scale.x = scale.y = scale.z = guifadein ? basescale*min((totalmillis-starttime)/300.0f, 1.0f) : basescale;
954         alpha = allowinput ? 0.80f : 0.60f;
955         passthrough = scale.x<basescale || !allowinput;
956         curdepth = -1;
957         curlist = -1;
958         tpos = 0;
959         tx = 0;
960         ty = 0;
961         tcurrent = tab;
962         tcolor = 0xFFFFFF;
963         pushlist();
964         if(layoutpass)
965         {
966             firstlist = nextlist = curlist;
967             memset(columns, 0, sizeof(columns));
968         }
969         else
970         {
971             if(tcurrent && !*tcurrent) tcurrent = NULL;
972             cury = -ysize;
973             curx = -xsize/2;
974 
975             if(gui2d)
976             {
977                 hudmatrix.ortho(0, 1, 1, 0, -1, 1);
978                 hudmatrix.translate(origin);
979                 hudmatrix.scale(scale);
980 
981                 light = vec(1, 1, 1);
982             }
983             else
984             {
985                 float yaw = atan2f(origin.y-camera1->o.y, origin.x-camera1->o.x);
986                 hudmatrix = camprojmatrix;
987                 hudmatrix.translate(origin);
988                 hudmatrix.rotate_around_z(yaw - 90*RAD);
989                 hudmatrix.rotate_around_x(-90*RAD);
990                 hudmatrix.scale(-scale.x, scale.y, scale.z);
991 
992                 vec dir;
993                 lightreaching(origin, light, dir, false, 0, 0.5f);
994                 float intensity = vec(yaw, 0.0f).dot(dir);
995                 light.mul(1.0f + max(intensity, 0.0f));
996             }
997 
998             resethudmatrix();
999             hudshader->set();
1000 
1001             drawskin(curx-skinx[2]*SKIN_SCALE, cury-skiny[6]*SKIN_SCALE, xsize, ysize, 0, 9, gui2d ? 1 : 2, light, alpha);
1002             if(!tcurrent) drawskin(curx-skinx[5]*SKIN_SCALE, cury-skiny[6]*SKIN_SCALE, xsize, 0, 9, 1, gui2d ? 1 : 2, light, alpha);
1003         }
1004     }
1005 
adjusthorizontalcolumngui1006     void adjusthorizontalcolumn(int col, int i)
1007     {
1008         int h = columns[col], dh = 0;
1009         for(int d = 1; i >= 0; d ^= 1)
1010         {
1011             list &p = lists[i];
1012             if(d&1) { dh = h - p.h; if(dh <= 0) break; p.h = h; }
1013             else { p.h += dh; h = p.h; }
1014             i = p.parent;
1015         }
1016         ysize += max(dh, 0);
1017     }
1018 
adjustverticalcolumngui1019     void adjustverticalcolumn(int col, int i)
1020     {
1021         int w = columns[col], dw = 0;
1022         for(int d = 0; i >= 0; d ^= 1)
1023         {
1024             list &p = lists[i];
1025             if(d&1) { p.w += dw; w = p.w; }
1026             else { dw = w - p.w; if(dw <= 0) break; p.w = w; }
1027             i = p.parent;
1028         }
1029         xsize = max(xsize, w);
1030     }
1031 
adjustcolumnsgui1032     void adjustcolumns()
1033     {
1034         if(lists.inrange(curlist))
1035         {
1036             list &l = lists[curlist];
1037             if(l.column >= 0) columns[l.column] = max(columns[l.column], ishorizontal() ? ysize : xsize);
1038         }
1039         int parent = -1, depth = 0;
1040         for(int i = firstlist; i < lists.length(); i++)
1041         {
1042             list &l = lists[i];
1043             if(l.parent > parent) { parent = l.parent; depth++; }
1044             else if(l.parent < parent)
1045             {
1046                 while(parent > l.parent && depth > 0)
1047                 {
1048                     parent = lists[parent].parent;
1049                     depth--;
1050                 }
1051             }
1052             if(l.column >= 0)
1053             {
1054                 if(depth&1) adjusthorizontalcolumn(l.column, i);
1055                 else adjustverticalcolumn(l.column, i);
1056             }
1057         }
1058     }
1059 
endgui1060     void end()
1061     {
1062         if(layoutpass)
1063         {
1064             adjustcolumns();
1065             xsize = max(tx, xsize);
1066             ysize = max(ty, ysize);
1067             ysize = max(ysize, (skiny[7]-skiny[6])*SKIN_SCALE);
1068             if(tcurrent) *tcurrent = max(1, min(*tcurrent, tpos));
1069             if(gui2d) adjustscale();
1070             if(!windowhit && !passthrough)
1071             {
1072                 float dist = 0;
1073                 if(gui2d)
1074                 {
1075                     hitx = (cursorx - origin.x)/scale.x;
1076                     hity = (cursory - origin.y)/scale.y;
1077                 }
1078                 else
1079                 {
1080                     plane p;
1081                     p.toplane(vec(origin).sub(camera1->o).set(2, 0).normalize(), origin);
1082                     if(p.rayintersect(camera1->o, camdir, dist) && dist>=0)
1083                     {
1084                         vec hitpos(camdir);
1085                         hitpos.mul(dist).add(camera1->o).sub(origin);
1086                         hitx = vec(-p.y, p.x, 0).dot(hitpos)/scale.x;
1087                         hity = -hitpos.z/scale.y;
1088                     }
1089                 }
1090                 if((mousebuttons & G3D_PRESSED) && (fabs(hitx-firstx) > 2 || fabs(hity - firsty) > 2)) mousebuttons |= G3D_DRAGGED;
1091                 if(dist>=0 && hitx>=-xsize/2 && hitx<=xsize/2 && hity<=0)
1092                 {
1093                     if(hity>=-ysize || (tcurrent && hity>=-ysize-(FONTH-2*INSERT)-((skiny[6]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE && hitx<=tx-xsize/2))
1094                         windowhit = this;
1095                 }
1096             }
1097         }
1098         else
1099         {
1100             if(tcurrent && tx<xsize) drawskin(curx+tx-skinx[5]*SKIN_SCALE, -ysize-skiny[6]*SKIN_SCALE, xsize-tx, FONTH, 9, 1, gui2d ? 1 : 2, light, alpha);
1101         }
1102         poplist();
1103     }
1104 
drawgui1105     void draw()
1106     {
1107         cb->gui(*this, layoutpass);
1108     }
1109 };
1110 
1111 Texture *gui::skintex = NULL, *gui::overlaytex = NULL, *gui::slidertex = NULL;
1112 
1113 //chop skin into a grid
1114 const int gui::skiny[] = {0, 7, 21, 34, 43, 48, 56, 104, 111, 117, 128},
1115           gui::skinx[] = {0, 11, 23, 37, 105, 119, 137, 151, 215, 229, 246, 256};
1116 //Note: skinx[3]-skinx[2] = skinx[7]-skinx[6]
1117 //      skinx[5]-skinx[4] = skinx[9]-skinx[8]
1118 const gui::patch gui::patches[] =
1119 { //arguably this data can be compressed - it depends on what else needs to be skinned in the future
1120     {1,2,3,6,  0},    // body
1121     {2,9,5,6,  0x01},
1122     {9,10,3,6, 0},
1123 
1124     {1,2,6,7,  0x10},
1125     {2,9,6,7,  0x11},
1126     {9,10,6,7, 0x10},
1127 
1128     {1,2,7,9,  0},
1129     {2,9,7,9,  0x01},
1130     {9,10,7,9, 0},
1131 
1132     {5,6,3,5, 0x01}, // top
1133 
1134     {2,3,1,2, 0},    // selected tab
1135     {3,4,1,2, 0x01},
1136     {4,5,1,2, 0},
1137     {2,3,2,3, 0x10},
1138     {3,4,2,3, 0x11},
1139     {4,5,2,3, 0x10},
1140     {2,3,3,5, 0},
1141     {3,4,3,5, 0x01},
1142     {4,5,3,5, 0},
1143 
1144     {6,7,1,2, 0},    // deselected tab
1145     {7,8,1,2, 0x01},
1146     {8,9,1,2, 0},
1147     {6,7,2,3, 0x10},
1148     {7,8,2,3, 0x11},
1149     {8,9,2,3, 0x10},
1150     {6,7,3,5, 0},
1151     {7,8,3,5, 0x01},
1152     {8,9,3,5, 0},
1153 };
1154 
1155 vector<gui::list> gui::lists;
1156 float gui::basescale, gui::maxscale = 1, gui::hitx, gui::hity, gui::alpha;
1157 bool gui::passthrough, gui::shouldmergehits = false, gui::shouldautotab = true;
1158 vec gui::light;
1159 int gui::curdepth, gui::curlist, gui::xsize, gui::ysize, gui::curx, gui::cury;
1160 int gui::ty, gui::tx, gui::tpos, *gui::tcurrent, gui::tcolor;
1161 static vector<gui> guis2d, guis3d;
1162 
1163 VARP(guipushdist, 1, 4, 64);
1164 
g3d_input(const char * str,int len)1165 bool g3d_input(const char *str, int len)
1166 {
1167     editor *e = currentfocus();
1168     if(fieldmode == FIELDKEY || fieldmode == FIELDSHOW || !e) return false;
1169 
1170     e->input(str, len);
1171     return true;
1172 }
1173 
g3d_key(int code,bool isdown)1174 bool g3d_key(int code, bool isdown)
1175 {
1176     editor *e = currentfocus();
1177     if(fieldmode == FIELDKEY)
1178     {
1179         switch(code)
1180         {
1181             case SDLK_ESCAPE:
1182                 if(isdown) fieldmode = FIELDCOMMIT;
1183                 return true;
1184         }
1185         const char *keyname = getkeyname(code);
1186         if(keyname && isdown)
1187         {
1188             if(e->lines.length()!=1 || !e->lines[0].empty()) e->insert(" ");
1189             e->insert(keyname);
1190         }
1191         return true;
1192     }
1193 
1194     if(code==-1 && g3d_windowhit(isdown, true)) return true;
1195     else if(code==-3 && g3d_windowhit(isdown, false)) return true;
1196 
1197     if(fieldmode == FIELDSHOW || !e)
1198     {
1199         if(windowhit) switch(code)
1200         {
1201             case -4: // window "management"
1202                 if(isdown)
1203                 {
1204                     if(windowhit->gui2d)
1205                     {
1206                         vec origin = *guis2d.last().savedorigin;
1207                         int i = windowhit - &guis2d[0];
1208                         for(int j = guis2d.length()-1; j > i; j--) *guis2d[j].savedorigin = *guis2d[j-1].savedorigin;
1209                         *windowhit->savedorigin = origin;
1210                         if(guis2d.length() > 1)
1211                         {
1212                             if(camera1->o.dist(*windowhit->savedorigin) <= camera1->o.dist(*guis2d.last().savedorigin))
1213                                 windowhit->savedorigin->add(camdir);
1214                         }
1215                     }
1216                     else windowhit->savedorigin->add(vec(camdir).mul(guipushdist));
1217                 }
1218                 return true;
1219             case -5:
1220                 if(isdown)
1221                 {
1222                     if(windowhit->gui2d)
1223                     {
1224                         vec origin = *guis2d[0].savedorigin;
1225                         loopj(guis2d.length()-1) *guis2d[j].savedorigin = *guis2d[j + 1].savedorigin;
1226                         *guis2d.last().savedorigin = origin;
1227                         if(guis2d.length() > 1)
1228                         {
1229                             if(camera1->o.dist(*guis2d.last().savedorigin) >= camera1->o.dist(*guis2d[0].savedorigin))
1230                                 guis2d.last().savedorigin->sub(camdir);
1231                         }
1232                     }
1233                     else windowhit->savedorigin->sub(vec(camdir).mul(guipushdist));
1234                 }
1235                 return true;
1236         }
1237 
1238         return false;
1239     }
1240     switch(code)
1241     {
1242         case SDLK_ESCAPE: //cancel editing without commit
1243             if(isdown) fieldmode = FIELDABORT;
1244             return true;
1245         case SDLK_RETURN:
1246         case SDLK_TAB:
1247             if(e->maxy != 1) break;
1248         case SDLK_KP_ENTER:
1249             if(isdown) fieldmode = FIELDCOMMIT; //signal field commit (handled when drawing field)
1250             return true;
1251     }
1252     if(isdown) e->key(code);
1253     return true;
1254 }
1255 
g3d_cursorpos(float & x,float & y)1256 void g3d_cursorpos(float &x, float &y)
1257 {
1258     if(guis2d.length()) { x = cursorx; y = cursory; }
1259     else x = y = 0.5f;
1260 }
1261 
g3d_resetcursor()1262 void g3d_resetcursor()
1263 {
1264     cursorx = cursory = 0.5f;
1265 }
1266 
1267 FVARP(guisens, 1e-3f, 1, 1e3f);
1268 
g3d_movecursor(int dx,int dy)1269 bool g3d_movecursor(int dx, int dy)
1270 {
1271     if(!guis2d.length() || !hascursor) return false;
1272     const float CURSORSCALE = 500.0f;
1273     cursorx = max(0.0f, min(1.0f, cursorx+guisens*dx*(screenh/(screenw*CURSORSCALE))));
1274     cursory = max(0.0f, min(1.0f, cursory+guisens*dy/CURSORSCALE));
1275     return true;
1276 }
1277 
1278 VARNP(guifollow, useguifollow, 0, 1, 1);
1279 VARNP(gui2d, usegui2d, 0, 1, 1);
1280 
g3d_addgui(g3d_callback * cb,vec & origin,int flags)1281 void g3d_addgui(g3d_callback *cb, vec &origin, int flags)
1282 {
1283     bool gui2d = flags&GUI_FORCE_2D || (flags&GUI_2D && usegui2d) || mainmenu;
1284     if(!gui2d && flags&GUI_FOLLOW && useguifollow) origin.z = player->o.z-(player->eyeheight-1);
1285     gui &g = (gui2d ? guis2d : guis3d).add();
1286     g.cb = cb;
1287     g.origin = origin;
1288     g.savedorigin = &origin;
1289     g.dist = flags&GUI_BOTTOM && gui2d ? 1e16f : camera1->o.dist(g.origin);
1290     g.gui2d = gui2d;
1291 }
1292 
g3d_limitscale(float scale)1293 void g3d_limitscale(float scale)
1294 {
1295     gui::maxscale = scale;
1296 }
1297 
g3d_sort(const gui & a,const gui & b)1298 static inline bool g3d_sort(const gui &a, const gui &b) { return a.dist < b.dist; }
1299 
g3d_windowhit(bool on,bool act)1300 bool g3d_windowhit(bool on, bool act)
1301 {
1302     extern int cleargui(int n);
1303     if(act)
1304     {
1305         if(actionon || windowhit)
1306         {
1307             if(on) { firstx = gui::hitx; firsty = gui::hity; }
1308             mousebuttons |= (actionon=on) ? G3D_DOWN : G3D_UP;
1309         }
1310     } else if(!on && windowhit) cleargui(1);
1311     return (guis2d.length() && hascursor) || (windowhit && !windowhit->gui2d);
1312 }
1313 
g3d_render()1314 void g3d_render()
1315 {
1316     windowhit = NULL;
1317     if(actionon) mousebuttons |= G3D_PRESSED;
1318 
1319     gui::reset();
1320     guis2d.shrink(0);
1321     guis3d.shrink(0);
1322 
1323     // call all places in the engine that may want to render a gui from here, they call g3d_addgui()
1324     extern void g3d_texturemenu();
1325 
1326     if(!mainmenu) g3d_texturemenu();
1327     g3d_mainmenu();
1328     if(!mainmenu) game::g3d_gamemenus();
1329 
1330     guis2d.sort(g3d_sort);
1331     guis3d.sort(g3d_sort);
1332 
1333     readyeditors();
1334     fieldsactive = false;
1335 
1336     hascursor = false;
1337 
1338     layoutpass = true;
1339     loopv(guis2d) guis2d[i].draw();
1340     loopv(guis3d) guis3d[i].draw();
1341     layoutpass = false;
1342 
1343     if(guis3d.length())
1344     {
1345         glEnable(GL_BLEND);
1346         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1347 
1348         glEnable(GL_DEPTH_TEST);
1349         glDepthFunc(GL_ALWAYS);
1350         glDepthMask(GL_FALSE);
1351 
1352         loopvrev(guis3d) guis3d[i].draw();
1353 
1354         glDepthFunc(GL_LESS);
1355         glDepthMask(GL_TRUE);
1356         glDisable(GL_DEPTH_TEST);
1357 
1358         glDisable(GL_BLEND);
1359     }
1360 }
1361 
g3d_render2d()1362 void g3d_render2d()
1363 {
1364     if(guis2d.length())
1365     {
1366         glEnable(GL_BLEND);
1367         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1368 
1369         loopvrev(guis2d) guis2d[i].draw();
1370 
1371         glDisable(GL_BLEND);
1372     }
1373 
1374     flusheditors();
1375     if(!fieldsactive) fieldmode = FIELDSHOW; //didn't draw any fields, so lose focus - mainly for menu closed
1376     textinput(fieldmode!=FIELDSHOW, TI_GUI);
1377     keyrepeat(fieldmode!=FIELDSHOW, KR_GUI);
1378 
1379     mousebuttons = 0;
1380 }
1381 
consolebox(int x1,int y1,int x2,int y2)1382 void consolebox(int x1, int y1, int x2, int y2)
1383 {
1384     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1385     float bw = x2 - x1, bh = y2 - y1, aspect = bw/bh, sh = bh, sw = sh*aspect;
1386     bw *= float(4*FONTH)/(SKIN_H*SKIN_SCALE);
1387     bh *= float(4*FONTH)/(SKIN_H*SKIN_SCALE);
1388     sw /= bw + (gui::skinx[2]-gui::skinx[1] + gui::skinx[10]-gui::skinx[9])*SKIN_SCALE;
1389     sh /= bh + (gui::skiny[9]-gui::skiny[7] + gui::skiny[6]-gui::skiny[4])*SKIN_SCALE;
1390     pushhudmatrix();
1391     hudmatrix.translate(x1, y1, 0);
1392     hudmatrix.scale(sw, sh, 1);
1393     flushhudmatrix();
1394     gui::drawskin(-gui::skinx[1]*SKIN_SCALE, -gui::skiny[4]*SKIN_SCALE, int(bw), int(bh), 0, 9, 1, vec(1, 1, 1), 0.60f);
1395     gui::drawskin((-gui::skinx[1] + gui::skinx[2] - gui::skinx[5])*SKIN_SCALE, -gui::skiny[4]*SKIN_SCALE, int(bw), 0, 9, 1, 1, vec(1, 1, 1), 0.60f);
1396     pophudmatrix();
1397 }
1398 
1399