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