#include "engine.hh" #include "textedit.hh" namespace UI { float cursorx = 0.499f, cursory = 0.499f; static void quads(float x, float y, float w, float h, float tx = 0, float ty = 0, float tw = 1, float th = 1) { gle::attribf(x, y); gle::attribf(tx, ty); gle::attribf(x+w, y); gle::attribf(tx+tw, ty); gle::attribf(x+w, y+h); gle::attribf(tx+tw, ty+th); gle::attribf(x, y+h); gle::attribf(tx, ty+th); } #if 0 static void quad(float x, float y, float w, float h, float tx = 0, float ty = 0, float tw = 1, float th = 1) { gle::defvertex(2); gle::deftexcoord0(); gle::begin(GL_TRIANGLE_STRIP); gle::attribf(x+w, y); gle::attribf(tx+tw, ty); gle::attribf(x, y); gle::attribf(tx, ty); gle::attribf(x+w, y+h); gle::attribf(tx+tw, ty+th); gle::attribf(x, y+h); gle::attribf(tx, ty+th); gle::end(); } #endif static void quad(float x, float y, float w, float h, const vec2 tc[4]) { gle::defvertex(2); gle::deftexcoord0(); gle::begin(GL_TRIANGLE_STRIP); gle::attribf(x+w, y); gle::attrib(tc[1]); gle::attribf(x, y); gle::attrib(tc[0]); gle::attribf(x+w, y+h); gle::attrib(tc[2]); gle::attribf(x, y+h); gle::attrib(tc[3]); gle::end(); } struct ClipArea { float x1, y1, x2, y2; ClipArea(float x, float y, float w, float h) : x1(x), y1(y), x2(x+w), y2(y+h) {} void intersect(const ClipArea &c) { x1 = max(x1, c.x1); y1 = max(y1, c.y1); x2 = max(x1, min(x2, c.x2)); y2 = max(y1, min(y2, c.y2)); } bool isfullyclipped(float x, float y, float w, float h) { return x1 == x2 || y1 == y2 || x >= x2 || y >= y2 || x+w <= x1 || y+h <= y1; } void scissor(); }; static vector clipstack; static void pushclip(float x, float y, float w, float h) { if(clipstack.empty()) glEnable(GL_SCISSOR_TEST); ClipArea &c = clipstack.add(ClipArea(x, y, w, h)); if(clipstack.length() >= 2) c.intersect(clipstack[clipstack.length()-2]); c.scissor(); } static void popclip() { clipstack.pop(); if(clipstack.empty()) glDisable(GL_SCISSOR_TEST); else clipstack.last().scissor(); } static inline bool isfullyclipped(float x, float y, float w, float h) { if(clipstack.empty()) return false; return clipstack.last().isfullyclipped(x, y, w, h); } enum { ALIGN_MASK = 0xF, ALIGN_HMASK = 0x3, ALIGN_HSHIFT = 0, ALIGN_HNONE = 0, ALIGN_LEFT = 1, ALIGN_HCENTER = 2, ALIGN_RIGHT = 3, ALIGN_VMASK = 0xC, ALIGN_VSHIFT = 2, ALIGN_VNONE = 0<<2, ALIGN_TOP = 1<<2, ALIGN_VCENTER = 2<<2, ALIGN_BOTTOM = 3<<2, CLAMP_MASK = 0xF0, CLAMP_LEFT = 0x10, CLAMP_RIGHT = 0x20, CLAMP_TOP = 0x40, CLAMP_BOTTOM = 0x80, NO_ADJUST = ALIGN_HNONE | ALIGN_VNONE, }; enum { STATE_HOVER = 1<<0, STATE_PRESS = 1<<1, STATE_HOLD = 1<<2, STATE_RELEASE = 1<<3, STATE_ALT_PRESS = 1<<4, STATE_ALT_HOLD = 1<<5, STATE_ALT_RELEASE = 1<<6, STATE_ESC_PRESS = 1<<7, STATE_ESC_HOLD = 1<<8, STATE_ESC_RELEASE = 1<<9, STATE_SCROLL_UP = 1<<10, STATE_SCROLL_DOWN = 1<<11, STATE_HIDDEN = 1<<12, STATE_HOLD_MASK = STATE_HOLD | STATE_ALT_HOLD | STATE_ESC_HOLD }; struct Object; static Object *buildparent = NULL; static int buildchild = -1; #define BUILD(type, o, setup, contents) do { \ if(buildparent) \ { \ type *o = buildparent->buildtype(); \ setup; \ o->buildchildren(contents); \ } \ } while(0) enum { CHANGE_SHADER = 1<<0, CHANGE_COLOR = 1<<1, CHANGE_BLEND = 1<<2 }; static int changed = 0; static Object *drawing = NULL; enum { BLEND_ALPHA, BLEND_MOD }; static int blendtype = BLEND_ALPHA; static inline void changeblend(int type, GLenum src, GLenum dst) { if(blendtype != type) { blendtype = type; glBlendFunc(src, dst); } } void resetblend() { changeblend(BLEND_ALPHA, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } void modblend() { changeblend(BLEND_MOD, GL_ZERO, GL_SRC_COLOR); } struct Object { Object *parent; float x, y, w, h; uchar adjust; ushort state, childstate; vector children; Object() : adjust(0), state(0), childstate(0) {} virtual ~Object() { clearchildren(); } void resetlayout() { x = y = w = h = 0; } void reset() { resetlayout(); parent = NULL; adjust = ALIGN_HCENTER | ALIGN_VCENTER; } virtual uchar childalign() const { return ALIGN_HCENTER | ALIGN_VCENTER; } void reset(Object *parent_) { resetlayout(); parent = parent_; adjust = parent->childalign(); } void setup() { } void clearchildren() { children.deletecontents(); } #define loopchildren(o, body) do { \ loopv(children) \ { \ Object *o = children[i]; \ body; \ } \ } while(0) #define loopchildrenrev(o, body) do { \ loopvrev(children) \ { \ Object *o = children[i]; \ body; \ } \ } while(0) #define loopchildrange(start, end, o, body) do { \ for(int i = start; i < end; i++) \ { \ Object *o = children[i]; \ body; \ } \ } while(0) #define loopinchildren(o, cx, cy, inbody, outbody) \ loopchildren(o, \ { \ float o##x = cx - o->x; \ float o##y = cy - o->y; \ if(o##x >= 0 && o##x < o->w && o##y >= 0 && o##y < o->h) \ { \ inbody; \ } \ outbody; \ }) #define loopinchildrenrev(o, cx, cy, inbody, outbody) \ loopchildrenrev(o, \ { \ float o##x = cx - o->x; \ float o##y = cy - o->y; \ if(o##x >= 0 && o##x < o->w && o##y >= 0 && o##y < o->h) \ { \ inbody; \ } \ outbody; \ }) virtual void layout() { w = h = 0; loopchildren(o, { o->x = o->y = 0; o->layout(); w = max(w, o->x + o->w); h = max(h, o->y + o->h); }); } void adjustchildrento(float px, float py, float pw, float ph) { loopchildren(o, o->adjustlayout(px, py, pw, ph)); } virtual void adjustchildren() { adjustchildrento(0, 0, w, h); } void adjustlayout(float px, float py, float pw, float ph) { switch(adjust&ALIGN_HMASK) { case ALIGN_LEFT: x = px; break; case ALIGN_HCENTER: x = px + (pw - w) / 2; break; case ALIGN_RIGHT: x = px + pw - w; break; } switch(adjust&ALIGN_VMASK) { case ALIGN_TOP: y = py; break; case ALIGN_VCENTER: y = py + (ph - h) / 2; break; case ALIGN_BOTTOM: y = py + ph - h; break; } if(adjust&CLAMP_MASK) { if(adjust&CLAMP_LEFT) { w += x - px; x = px; } if(adjust&CLAMP_RIGHT) w = px + pw - x; if(adjust&CLAMP_TOP) { h += y - py; y = py; } if(adjust&CLAMP_BOTTOM) h = py + ph - y; } adjustchildren(); } void setalign(int xalign, int yalign) { adjust &= ~ALIGN_MASK; adjust |= (clamp(xalign, -2, 1)+2)<rawkey(code, isdown)) return true; }); return false; } virtual bool key(int code, bool isdown) { loopchildrenrev(o, { if(o->key(code, isdown)) return true; }); return false; } virtual bool textinput(const char *str, int len) { loopchildrenrev(o, { if(o->textinput(str, len)) return true; }); return false; } virtual void startdraw() {} virtual void enddraw() {} void enddraw(int change) { enddraw(); changed &= ~change; if(changed) { if(changed&CHANGE_SHADER) hudshader->set(); if(changed&CHANGE_COLOR) gle::colorf(1, 1, 1); if(changed&CHANGE_BLEND) resetblend(); } } void changedraw(int change = 0) { if(!drawing) { startdraw(); changed = change; } else if(drawing->gettype() != gettype()) { drawing->enddraw(change); startdraw(); changed = change; } drawing = this; } virtual void draw(float sx, float sy) { loopchildren(o, { if(!isfullyclipped(sx + o->x, sy + o->y, o->w, o->h)) o->draw(sx + o->x, sy + o->y); }); } void resetstate() { state &= STATE_HOLD_MASK; childstate &= STATE_HOLD_MASK; } void resetchildstate() { resetstate(); loopchildren(o, o->resetchildstate()); } bool hasstate(int flags) const { return ((state & ~childstate) & flags) != 0; } bool haschildstate(int flags) const { return ((state | childstate) & flags) != 0; } #define DOSTATES \ DOSTATE(STATE_HOVER, hover) \ DOSTATE(STATE_PRESS, press) \ DOSTATE(STATE_HOLD, hold) \ DOSTATE(STATE_RELEASE, release) \ DOSTATE(STATE_ALT_HOLD, althold) \ DOSTATE(STATE_ALT_PRESS, altpress) \ DOSTATE(STATE_ALT_RELEASE, altrelease) \ DOSTATE(STATE_ESC_HOLD, eschold) \ DOSTATE(STATE_ESC_PRESS, escpress) \ DOSTATE(STATE_ESC_RELEASE, escrelease) \ DOSTATE(STATE_SCROLL_UP, scrollup) \ DOSTATE(STATE_SCROLL_DOWN, scrolldown) bool setstate(int state, float cx, float cy, int mask = 0, bool inside = true, int setflags = 0) { switch(state) { #define DOSTATE(flags, func) case flags: func##children(cx, cy, mask, inside, setflags | flags); return haschildstate(flags); DOSTATES #undef DOSTATE } return false; } void clearstate(int flags) { state &= ~flags; if(childstate & flags) { loopchildren(o, { if((o->state | o->childstate) & flags) o->clearstate(flags); }); childstate &= ~flags; } } #define propagatestate(o, cx, cy, mask, inside, body) \ loopchildrenrev(o, \ { \ if(((o->state | o->childstate) & mask) != mask) continue; \ float o##x = cx - o->x; \ float o##y = cy - o->y; \ if(!inside) \ { \ o##x = clamp(o##x, 0.0f, o->w); \ o##y = clamp(o##y, 0.0f, o->h); \ body; \ } \ else if(o##x >= 0 && o##x < o->w && o##y >= 0 && o##y < o->h) \ { \ body; \ } \ }) #define DOSTATE(flags, func) \ virtual void func##children(float cx, float cy, int mask, bool inside, int setflags) \ { \ propagatestate(o, cx, cy, mask, inside, \ { \ o->func##children(ox, oy, mask, inside, setflags); \ childstate |= (o->state | o->childstate) & (setflags); \ }); \ if(target(cx, cy)) state |= (setflags); \ func(cx, cy); \ } \ virtual void func(float cx, float cy) {} DOSTATES #undef DOSTATE static const char *typestr() { return "#Object"; } virtual const char *gettype() const { return typestr(); } virtual const char *getname() const { return gettype(); } virtual const char *gettypename() const { return gettype(); } template bool istype() const { return T::typestr() == gettype(); } bool isnamed(const char *name) const { return name[0] == '#' ? name == gettypename() : !strcmp(name, getname()); } Object *find(const char *name, bool recurse = true, const Object *exclude = NULL) const { loopchildren(o, { if(o != exclude && o->isnamed(name)) return o; }); if(recurse) loopchildren(o, { if(o != exclude) { Object *found = o->find(name); if(found) return found; } }); return NULL; } Object *findsibling(const char *name) const { for(const Object *prev = this, *cur = parent; cur; prev = cur, cur = cur->parent) { Object *o = cur->find(name, true, prev); if(o) return o; } return NULL; } template T *buildtype() { T *t; if(children.inrange(buildchild)) { Object *o = children[buildchild]; if(o->istype()) t = (T *)o; else { delete o; t = new T; children[buildchild] = t; } } else { t = new T; children.add(t); } t->reset(this); buildchild++; return t; } void buildchildren(uint *contents) { if((*contents&CODE_OP_MASK) == CODE_EXIT) children.deletecontents(); else { Object *oldparent = buildparent; int oldchild = buildchild; buildparent = this; buildchild = 0; executeret(contents); while(children.length() > buildchild) delete children.pop(); buildparent = oldparent; buildchild = oldchild; } resetstate(); } virtual int childcolumns() const { return children.length(); } }; static inline void stopdrawing() { if(drawing) { drawing->enddraw(0); drawing = NULL; } } struct Window; static Window *window = NULL; struct Window : Object { char *name; uint *contents, *onshow, *onhide; bool allowinput, eschide, abovehud; float px, py, pw, ph; vec2 sscale, soffset; Window(const char *name, const char *contents, const char *onshow, const char *onhide) : name(newstring(name)), contents(compilecode(contents)), onshow(onshow && onshow[0] ? compilecode(onshow) : NULL), onhide(onhide && onhide[0] ? compilecode(onhide) : NULL), allowinput(true), eschide(true), abovehud(false), px(0), py(0), pw(0), ph(0), sscale(1, 1), soffset(0, 0) { } ~Window() { delete[] name; freecode(contents); freecode(onshow); freecode(onhide); } static const char *typestr() { return "#Window"; } const char *gettype() const { return typestr(); } const char *getname() const { return name; } void build(); void hide() { if(onhide) execute(onhide); } void show() { state |= STATE_HIDDEN; clearstate(STATE_HOLD_MASK); if(onshow) execute(onshow); } void setup() { Object::setup(); allowinput = eschide = true; abovehud = false; px = py = pw = ph = 0; } void layout() { if(state&STATE_HIDDEN) { w = h = 0; return; } window = this; Object::layout(); window = NULL; } void draw(float sx, float sy) { if(state&STATE_HIDDEN) return; window = this; projection(); hudshader->set(); glEnable(GL_BLEND); blendtype = BLEND_ALPHA; glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); gle::colorf(1, 1, 1); changed = 0; drawing = NULL; Object::draw(sx, sy); stopdrawing(); glDisable(GL_BLEND); window = NULL; } void draw() { draw(x, y); } void adjustchildren() { if(state&STATE_HIDDEN) return; window = this; Object::adjustchildren(); window = NULL; } void adjustlayout() { float aspect = float(hudw)/hudh; ph = max(max(h, w/aspect), 1.0f); pw = aspect*ph; Object::adjustlayout(0, 0, pw, ph); } #define DOSTATE(flags, func) \ void func##children(float cx, float cy, int mask, bool inside, int setflags) \ { \ if(!allowinput || state&STATE_HIDDEN || pw <= 0 || ph <= 0) return; \ cx = cx*pw + px-x; \ cy = cy*ph + py-y; \ if(!inside || (cx >= 0 && cy >= 0 && cx < w && cy < h)) \ Object::func##children(cx, cy, mask, inside, setflags); \ } DOSTATES #undef DOSTATE void escrelease(float cx, float cy); void projection() { hudmatrix.ortho(px, px + pw, py + ph, py, -1, 1); resethudmatrix(); sscale = vec2(hudmatrix.a.x, hudmatrix.b.y).mul(0.5f); soffset = vec2(hudmatrix.d.x, hudmatrix.d.y).mul(0.5f).add(0.5f); } void calcscissor(float x1, float y1, float x2, float y2, int &sx1, int &sy1, int &sx2, int &sy2, bool clip = true) { vec2 s1 = vec2(x1, y2).mul(sscale).add(soffset), s2 = vec2(x2, y1).mul(sscale).add(soffset); sx1 = int(floor(s1.x*hudw + 0.5f)); sy1 = int(floor(s1.y*hudh + 0.5f)); sx2 = int(floor(s2.x*hudw + 0.5f)); sy2 = int(floor(s2.y*hudh + 0.5f)); if(clip) { sx1 = clamp(sx1, 0, hudw); sy1 = clamp(sy1, 0, hudh); sx2 = clamp(sx2, 0, hudw); sy2 = clamp(sy2, 0, hudh); } } float calcabovehud() { return 1 - (y*sscale.y + soffset.y); } }; static hashnameset windows; void ClipArea::scissor() { int sx1, sy1, sx2, sy2; window->calcscissor(x1, y1, x2, y2, sx1, sy1, sx2, sy2); glScissor(sx1, sy1, sx2-sx1, sy2-sy1); } struct World : Object { static const char *typestr() { return "#World"; } const char *gettype() const { return typestr(); } #define loopwindows(o, body) do { \ loopv(children) \ { \ Window *o = (Window *)children[i]; \ body; \ } \ } while(0) #define loopwindowsrev(o, body) do { \ loopvrev(children) \ { \ Window *o = (Window *)children[i]; \ body; \ } \ } while(0) void adjustchildren() { loopwindows(w, w->adjustlayout()); } #define DOSTATE(flags, func) \ void func##children(float cx, float cy, int mask, bool inside, int setflags) \ { \ loopwindowsrev(w, \ { \ if(((w->state | w->childstate) & mask) != mask) continue; \ w->func##children(cx, cy, mask, inside, setflags); \ int wflags = (w->state | w->childstate) & (setflags); \ if(wflags) { childstate |= wflags; break; } \ }); \ } DOSTATES #undef DOSTATE void build() { reset(); setup(); loopwindows(w, { w->build(); if(!children.inrange(i)) break; if(children[i] != w) i--; }); resetstate(); } bool show(Window *w) { if(children.find(w) >= 0) return false; w->resetchildstate(); children.add(w); w->show(); return true; } void hide(Window *w, int index) { children.remove(index); childstate = 0; loopchildren(o, childstate |= o->state | o->childstate); w->hide(); } bool hide(Window *w) { int index = children.find(w); if(index < 0) return false; hide(w, index); return true; } bool hidetop() { loopwindowsrev(w, { if(w->allowinput && !(w->state&STATE_HIDDEN)) { hide(w, i); return true; } }); return false; } int hideall() { int hidden = 0; loopwindowsrev(w, { hide(w, i); hidden++; }); return hidden; } bool allowinput() const { loopwindows(w, { if(w->allowinput && !(w->state&STATE_HIDDEN)) return true; }); return false; } void draw(float sx, float sy) {} void draw() { if(children.empty()) return; loopwindows(w, w->draw()); } float abovehud() { float y = 1; loopwindows(w, { if(w->abovehud && !(w->state&STATE_HIDDEN)) y = min(y, w->calcabovehud()); }); return y; } }; static World *world = NULL; void Window::escrelease(float cx, float cy) { if(eschide) world->hide(this); } void Window::build() { reset(world); setup(); window = this; buildchildren(contents); window = NULL; } struct HorizontalList : Object { float space, subw; static const char *typestr() { return "#HorizontalList"; } const char *gettype() const { return typestr(); } void setup(float space_ = 0) { Object::setup(); space = space_; } uchar childalign() const { return ALIGN_VCENTER; } void layout() { subw = h = 0; loopchildren(o, { o->x = subw; o->y = 0; o->layout(); subw += o->w; h = max(h, o->y + o->h); }); w = subw + space*max(children.length() - 1, 0); } void adjustchildren() { if(children.empty()) return; float offset = 0, sx = 0, cspace = (w - subw) / max(children.length() - 1, 1), cstep = (w - subw) / children.length(); for(int i = 0; i < children.length(); i++) { Object *o = children[i]; o->x = offset; offset += o->w + cspace; float sw = o->w + cstep; o->adjustlayout(sx, 0, sw, h); sx += sw; } } }; struct VerticalList : Object { float space, subh; static const char *typestr() { return "#VerticalList"; } const char *gettype() const { return typestr(); } void setup(float space_ = 0) { Object::setup(); space = space_; } uchar childalign() const { return ALIGN_HCENTER; } void layout() { w = subh = 0; loopchildren(o, { o->x = 0; o->y = subh; o->layout(); subh += o->h; w = max(w, o->x + o->w); }); h = subh + space*max(children.length() - 1, 0); } void adjustchildren() { if(children.empty()) return; float offset = 0, sy = 0, rspace = (h - subh) / max(children.length() - 1, 1), rstep = (h - subh) / children.length(); loopchildren(o, { o->y = offset; offset += o->h + rspace; float sh = o->h + rstep; o->adjustlayout(0, sy, w, sh); sy += sh; }); } }; struct Grid : Object { int columns; float spacew, spaceh, subw, subh; vector widths, heights; static const char *typestr() { return "#Grid"; } const char *gettype() const { return typestr(); } void setup(int columns_, float spacew_ = 0, float spaceh_ = 0) { Object::setup(); columns = columns_; spacew = spacew_; spaceh = spaceh_; } uchar childalign() const { return 0; } void layout() { widths.setsize(0); heights.setsize(0); int column = 0, row = 0; loopchildren(o, { o->layout(); if(column >= widths.length()) widths.add(o->w); else if(o->w > widths[column]) widths[column] = o->w; if(row >= heights.length()) heights.add(o->h); else if(o->h > heights[row]) heights[row] = o->h; column = (column + 1) % columns; if(!column) row++; }); subw = subh = 0; loopv(widths) subw += widths[i]; loopv(heights) subh += heights[i]; w = subw + spacew*max(widths.length() - 1, 0); h = subh + spaceh*max(heights.length() - 1, 0); } void adjustchildren() { if(children.empty()) return; int row = 0, column = 0; float offsety = 0, sy = 0, offsetx = 0, sx = 0, cspace = (w - subw) / max(widths.length() - 1, 1), cstep = (w - subw) / widths.length(), rspace = (h - subh) / max(heights.length() - 1, 1), rstep = (h - subh) / heights.length(); loopchildren(o, { o->x = offsetx; o->y = offsety; o->adjustlayout(sx, sy, widths[column] + cstep, heights[row] + rstep); offsetx += widths[column] + cspace; sx += widths[column] + cstep; column = (column + 1) % columns; if(!column) { offsetx = sx = 0; offsety += heights[row] + rspace; sy += heights[row] + rstep; row++; } }); } }; struct TableHeader : Object { int columns; TableHeader() : columns(-1) {} static const char *typestr() { return "#TableHeader"; } const char *gettype() const { return typestr(); } uchar childalign() const { return columns < 0 ? ALIGN_VCENTER : ALIGN_HCENTER | ALIGN_VCENTER; } int childcolumns() const { return columns; } void buildchildren(uint *columndata, uint *contents) { Object *oldparent = buildparent; int oldchild = buildchild; buildparent = this; buildchild = 0; executeret(columndata); if(columns != buildchild) while(children.length() > buildchild) delete children.pop(); columns = buildchild; if((*contents&CODE_OP_MASK) != CODE_EXIT) executeret(contents); while(children.length() > buildchild) delete children.pop(); buildparent = oldparent; buildchild = oldchild; resetstate(); } void adjustchildren() { loopchildrange(columns, children.length(), o, o->adjustlayout(0, 0, w, h)); } void draw(float sx, float sy) { loopchildrange(columns, children.length(), o, { if(!isfullyclipped(sx + o->x, sy + o->y, o->w, o->h)) o->draw(sx + o->x, sy + o->y); }); loopchildrange(0, columns, o, { if(!isfullyclipped(sx + o->x, sy + o->y, o->w, o->h)) o->draw(sx + o->x, sy + o->y); }); } }; struct TableRow : TableHeader { static const char *typestr() { return "#TableRow"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { return true; } }; #define BUILDCOLUMNS(type, o, setup, columndata, contents) do { \ if(buildparent) \ { \ type *o = buildparent->buildtype(); \ setup; \ o->buildchildren(columndata, contents); \ } \ } while(0) struct Table : Object { float spacew, spaceh, subw, subh; vector widths; static const char *typestr() { return "#Table"; } const char *gettype() const { return typestr(); } void setup(float spacew_ = 0, float spaceh_ = 0) { Object::setup(); spacew = spacew_; spaceh = spaceh_; } uchar childalign() const { return 0; } void layout() { widths.setsize(0); w = subh = 0; loopchildren(o, { o->layout(); int cols = o->childcolumns(); while(widths.length() < cols) widths.add(0); loopj(cols) { Object *c = o->children[j]; if(c->w > widths[j]) widths[j] = c->w; } w = max(w, o->w); subh += o->h; }); subw = 0; loopv(widths) subw += widths[i]; w = max(w, subw + spacew*max(widths.length() - 1, 0)); h = subh + spaceh*max(children.length() - 1, 0); } void adjustchildren() { if(children.empty()) return; float offsety = 0, sy = 0, cspace = (w - subw) / max(widths.length() - 1, 1), cstep = (w - subw) / widths.length(), rspace = (h - subh) / max(children.length() - 1, 1), rstep = (h - subh) / children.length(); loopchildren(o, { o->x = 0; o->y = offsety; o->w = w; offsety += o->h + rspace; float sh = o->h + rstep; o->adjustlayout(0, sy, w, sh); sy += sh; float offsetx = 0; float sx = 0; int cols = o->childcolumns(); loopj(cols) { Object *c = o->children[j]; c->x = offsetx; offsetx += widths[j] + cspace; float sw = widths[j] + cstep; c->adjustlayout(sx, 0, sw, o->h); sx += sw; } }); } }; struct Spacer : Object { float spacew, spaceh; void setup(float spacew_, float spaceh_) { Object::setup(); spacew = spacew_; spaceh = spaceh_; } static const char *typestr() { return "#Spacer"; } const char *gettype() const { return typestr(); } void layout() { w = spacew; h = spaceh; loopchildren(o, { o->x = spacew; o->y = spaceh; o->layout(); w = max(w, o->x + o->w); h = max(h, o->y + o->h); }); w += spacew; h += spaceh; } void adjustchildren() { adjustchildrento(spacew, spaceh, w - 2*spacew, h - 2*spaceh); } }; struct Offsetter : Object { float offsetx, offsety; void setup(float offsetx_, float offsety_) { Object::setup(); offsetx = offsetx_; offsety = offsety_; } static const char *typestr() { return "#Offsetter"; } const char *gettype() const { return typestr(); } void layout() { Object::layout(); loopchildren(o, { o->x += offsetx; o->y += offsety; }); w += offsetx; h += offsety; } void adjustchildren() { adjustchildrento(offsetx, offsety, w - offsetx, h - offsety); } }; struct Filler : Object { float minw, minh; void setup(float minw_, float minh_) { Object::setup(); minw = minw_; minh = minh_; } static const char *typestr() { return "#Filler"; } const char *gettype() const { return typestr(); } void layout() { Object::layout(); w = max(w, minw); h = max(h, minh); } }; struct Target : Filler { static const char *typestr() { return "#Target"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { return true; } }; struct Color { uchar r, g, b, a; Color() {} Color(uint c) : r((c>>16)&0xFF), g((c>>8)&0xFF), b(c&0xFF), a(c>>24 ? c>>24 : 0xFF) {} Color(uint c, uchar a) : r((c>>16)&0xFF), g((c>>8)&0xFF), b(c&0xFF), a(a) {} Color(uchar r, uchar g, uchar b, uchar a = 255) : r(r), g(g), b(b), a(a) {} void init() { gle::colorub(r, g, b, a); } void attrib() { gle::attribub(r, g, b, a); } static void def() { gle::defcolor(4, GL_UNSIGNED_BYTE); } }; struct FillColor : Target { enum { SOLID = 0, MODULATE }; int type; Color color; void setup(int type_, const Color &color_, float minw_ = 0, float minh_ = 0) { Target::setup(minw_, minh_); type = type_; color = color_; } static const char *typestr() { return "#FillColor"; } const char *gettype() const { return typestr(); } void startdraw() { hudnotextureshader->set(); gle::defvertex(2); } void draw(float sx, float sy) { changedraw(CHANGE_SHADER | CHANGE_COLOR | CHANGE_BLEND); if(type==MODULATE) modblend(); else resetblend(); color.init(); gle::begin(GL_TRIANGLE_STRIP); gle::attribf(sx+w, sy); gle::attribf(sx, sy); gle::attribf(sx+w, sy+h); gle::attribf(sx, sy+h); gle::end(); Object::draw(sx, sy); } }; struct Gradient : FillColor { enum { VERTICAL, HORIZONTAL }; int dir; Color color2; void setup(int type_, int dir_, const Color &color_, const Color &color2_, float minw_ = 0, float minh_ = 0) { FillColor::setup(type_, color_, minw_, minh_); dir = dir_; color2 = color2_; } static const char *typestr() { return "#Gradient"; } const char *gettype() const { return typestr(); } void startdraw() { hudnotextureshader->set(); gle::defvertex(2); Color::def(); } void draw(float sx, float sy) { changedraw(CHANGE_SHADER | CHANGE_COLOR | CHANGE_BLEND); if(type==MODULATE) modblend(); else resetblend(); gle::begin(GL_TRIANGLE_STRIP); gle::attribf(sx+w, sy); (dir == HORIZONTAL ? color2 : color).attrib(); gle::attribf(sx, sy); color.attrib(); gle::attribf(sx+w, sy+h); color2.attrib(); gle::attribf(sx, sy+h); (dir == HORIZONTAL ? color : color2).attrib(); gle::end(); Object::draw(sx, sy); } }; struct Line : Filler { Color color; void setup(const Color &color_, float minw_ = 0, float minh_ = 0) { Filler::setup(minw_, minh_); color = color_; } static const char *typestr() { return "#Line"; } const char *gettype() const { return typestr(); } void startdraw() { hudnotextureshader->set(); gle::defvertex(2); } void draw(float sx, float sy) { changedraw(CHANGE_SHADER | CHANGE_COLOR); color.init(); gle::begin(GL_LINES); gle::attribf(sx, sy); gle::attribf(sx+w, sy+h); gle::end(); Object::draw(sx, sy); } }; struct Outline : Filler { Color color; void setup(const Color &color_, float minw_ = 0, float minh_ = 0) { Filler::setup(minw_, minh_); color = color_; } static const char *typestr() { return "#Outline"; } const char *gettype() const { return typestr(); } void startdraw() { hudnotextureshader->set(); gle::defvertex(2); } void draw(float sx, float sy) { changedraw(CHANGE_SHADER | CHANGE_COLOR); color.init(); gle::begin(GL_LINE_LOOP); gle::attribf(sx, sy); gle::attribf(sx+w, sy); gle::attribf(sx+w, sy+h); gle::attribf(sx, sy+h); gle::end(); Object::draw(sx, sy); } }; static inline bool checkalphamask(Texture *tex, float x, float y) { if(!tex->alphamask) { loadalphamask(tex); if(!tex->alphamask) return true; } int tx = clamp(int(x*tex->xs), 0, tex->xs-1), ty = clamp(int(y*tex->ys), 0, tex->ys-1); if(tex->alphamask[ty*((tex->xs+7)/8) + tx/8] & (1<<(tx%8))) return true; return false; } struct Image : Filler { static Texture *lasttex; Texture *tex; void setup(Texture *tex_, float minw_ = 0, float minh_ = 0) { Filler::setup(minw_, minh_); tex = tex_; } static const char *typestr() { return "#Image"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { return !(tex->type&Texture::ALPHA) || checkalphamask(tex, cx/w, cy/h); } void startdraw() { lasttex = NULL; gle::defvertex(2); gle::deftexcoord0(); gle::begin(GL_QUADS); } void enddraw() { gle::end(); } void bindtex() { changedraw(); if(lasttex != tex) { if(lasttex) gle::end(); lasttex = tex; glBindTexture(GL_TEXTURE_2D, tex->id); } } void draw(float sx, float sy) { if(tex != notexture) { bindtex(); quads(sx, sy, w, h); } Object::draw(sx, sy); } }; Texture *Image::lasttex = NULL; struct CroppedImage : Image { float cropx, cropy, cropw, croph; void setup(Texture *tex_, float minw_ = 0, float minh_ = 0, float cropx_ = 0, float cropy_ = 0, float cropw_ = 1, float croph_ = 1) { Image::setup(tex_, minw_, minh_); cropx = cropx_; cropy = cropy_; cropw = cropw_; croph = croph_; } static const char *typestr() { return "#CroppedImage"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { return !(tex->type&Texture::ALPHA) || checkalphamask(tex, cropx + cx/w*cropw, cropy + cy/h*croph); } void draw(float sx, float sy) { if(tex == notexture) { Object::draw(sx, sy); return; } bindtex(); quads(sx, sy, w, h, cropx, cropy, cropw, croph); Object::draw(sx, sy); } }; struct StretchedImage : Image { static const char *typestr() { return "#StretchedImage"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { if(!(tex->type&Texture::ALPHA)) return true; float mx, my; if(w <= minw) mx = cx/w; else if(cx < minw/2) mx = cx/minw; else if(cx >= w - minw/2) mx = 1 - (w - cx) / minw; else mx = 0.5f; if(h <= minh) my = cy/h; else if(cy < minh/2) my = cy/minh; else if(cy >= h - minh/2) my = 1 - (h - cy) / minh; else my = 0.5f; return checkalphamask(tex, mx, my); } void draw(float sx, float sy) { if(tex == notexture) { Object::draw(sx, sy); return; } bindtex(); float splitw = (minw ? min(minw, w) : w) / 2, splith = (minh ? min(minh, h) : h) / 2, vy = sy, ty = 0; loopi(3) { float vh = 0, th = 0; switch(i) { case 0: if(splith < h - splith) { vh = splith; th = 0.5f; } else { vh = h; th = 1; } break; case 1: vh = h - 2*splith; th = 0; break; case 2: vh = splith; th = 0.5f; break; } float vx = sx, tx = 0; loopj(3) { float vw = 0, tw = 0; switch(j) { case 0: if(splitw < w - splitw) { vw = splitw; tw = 0.5f; } else { vw = w; tw = 1; } break; case 1: vw = w - 2*splitw; tw = 0; break; case 2: vw = splitw; tw = 0.5f; break; } quads(vx, vy, vw, vh, tx, ty, tw, th); vx += vw; tx += tw; if(tx >= 1) break; } vy += vh; ty += th; if(ty >= 1) break; } Object::draw(sx, sy); } }; struct BorderedImage : Image { float texborder, screenborder; void setup(Texture *tex_, float texborder_, float screenborder_) { Image::setup(tex_); texborder = texborder_; screenborder = screenborder_; } static const char *typestr() { return "#BorderedImage"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { if(!(tex->type&Texture::ALPHA)) return true; float mx, my; if(cx < screenborder) mx = cx/screenborder*texborder; else if(cx >= w - screenborder) mx = 1-texborder + (cx - (w - screenborder))/screenborder*texborder; else mx = texborder + (cx - screenborder)/(w - 2*screenborder)*(1 - 2*texborder); if(cy < screenborder) my = cy/screenborder*texborder; else if(cy >= h - screenborder) my = 1-texborder + (cy - (h - screenborder))/screenborder*texborder; else my = texborder + (cy - screenborder)/(h - 2*screenborder)*(1 - 2*texborder); return checkalphamask(tex, mx, my); } void draw(float sx, float sy) { if(tex == notexture) { Object::draw(sx, sy); return; } bindtex(); float vy = sy, ty = 0; loopi(3) { float vh = 0, th = 0; switch(i) { case 0: vh = screenborder; th = texborder; break; case 1: vh = h - 2*screenborder; th = 1 - 2*texborder; break; case 2: vh = screenborder; th = texborder; break; } float vx = sx, tx = 0; loopj(3) { float vw = 0, tw = 0; switch(j) { case 0: vw = screenborder; tw = texborder; break; case 1: vw = w - 2*screenborder; tw = 1 - 2*texborder; break; case 2: vw = screenborder; tw = texborder; break; } quads(vx, vy, vw, vh, tx, ty, tw, th); vx += vw; tx += tw; } vy += vh; ty += th; } Object::draw(sx, sy); } }; struct TiledImage : Image { float tilew, tileh; void setup(Texture *tex_, float minw_ = 0, float minh_ = 0, float tilew_ = 0, float tileh_ = 0) { Image::setup(tex_, minw_, minh_); tilew = tilew_; tileh = tileh_; } static const char *typestr() { return "#TiledImage"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { if(!(tex->type&Texture::ALPHA)) return true; return checkalphamask(tex, fmod(cx/tilew, 1), fmod(cy/tileh, 1)); } void draw(float sx, float sy) { if(tex == notexture) { Object::draw(sx, sy); return; } bindtex(); if(tex->clamp) { for(float dy = 0; dy < h; dy += tileh) { float dh = min(tileh, h - dy); for(float dx = 0; dx < w; dx += tilew) { float dw = min(tilew, w - dx); quads(sx + dx, sy + dy, dw, dh, 0, 0, dw / tilew, dh / tileh); } } } else quads(sx, sy, w, h, 0, 0, w/tilew, h/tileh); Object::draw(sx, sy); } }; struct Shape : Filler { enum { SOLID = 0, OUTLINE, MODULATE }; int type; Color color; void setup(const Color &color_, int type_ = SOLID, float minw_ = 0, float minh_ = 0) { Filler::setup(minw_, minh_); color = color_; type = type_; } void startdraw() { hudnotextureshader->set(); gle::defvertex(2); } }; struct Triangle : Shape { vec2 a, b, c; void setup(const Color &color_, float w = 0, float h = 0, int angle = 0, int type_ = SOLID) { a = vec2(0, -h*2.0f/3); b = vec2(-w/2, h/3); c = vec2(w/2, h/3); if(angle) { vec2 rot = sincosmod360(-angle); a.rotate_around_z(rot); b.rotate_around_z(rot); c.rotate_around_z(rot); } vec2 bbmin = vec2(a).min(b).min(c); a.sub(bbmin); b.sub(bbmin); c.sub(bbmin); vec2 bbmax = vec2(a).max(b).max(c); Shape::setup(color_, type_, bbmax.x, bbmax.y); } static const char *typestr() { return "#Triangle"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { if(type == OUTLINE) return false; bool side = vec2(cx, cy).sub(b).cross(vec2(a).sub(b)) < 0; return (vec2(cx, cy).sub(c).cross(vec2(b).sub(c)) < 0) == side && (vec2(cx, cy).sub(a).cross(vec2(c).sub(a)) < 0) == side; } void draw(float sx, float sy) { Object::draw(sx, sy); changedraw(CHANGE_SHADER | CHANGE_COLOR | CHANGE_BLEND); if(type==MODULATE) modblend(); else resetblend(); color.init(); gle::begin(type == OUTLINE ? GL_LINE_LOOP : GL_TRIANGLES); gle::attrib(vec2(sx, sy).add(a)); gle::attrib(vec2(sx, sy).add(b)); gle::attrib(vec2(sx, sy).add(c)); gle::end(); } }; struct Circle : Shape { float radius; void setup(const Color &color_, float size, int type_ = SOLID) { Shape::setup(color_, type_, size, size); radius = size/2; } static const char *typestr() { return "#Circle"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { if(type == OUTLINE) return false; float r = radius <= 0 ? min(w, h)/2 : radius; return vec2(cx, cy).sub(r).squaredlen() <= r*r; } void draw(float sx, float sy) { Object::draw(sx, sy); changedraw(CHANGE_SHADER | CHANGE_COLOR | CHANGE_BLEND); if(type==MODULATE) modblend(); else resetblend(); float r = radius <= 0 ? min(w, h)/2 : radius; color.init(); vec2 center(sx + r, sy + r); if(type == OUTLINE) { gle::begin(GL_LINE_LOOP); for(int angle = 0; angle < 360; angle += 360/15) gle::attrib(vec2(sincos360[angle]).mul(r).add(center)); gle::end(); } else { gle::begin(GL_TRIANGLE_FAN); gle::attrib(center); gle::attribf(center.x + r, center.y); for(int angle = 360/15; angle < 360; angle += 360/15) { vec2 p = vec2(sincos360[angle]).mul(r).add(center); gle::attrib(p); gle::attrib(p); } gle::attribf(center.x + r, center.y); gle::end(); } } }; // default size of text in terms of rows per screenful VARP(uitextrows, 1, 24, 200); FVAR(uitextscale, 1, 0, 0); #define SETSTR(dst, src) do { \ if(dst) { if(dst != src && strcmp(dst, src)) { delete[] dst; dst = newstring(src); } } \ else dst = newstring(src); \ } while(0) struct Text : Object { float scale, wrap; Color color; void setup(float scale_ = 1, const Color &color_ = Color(255, 255, 255), float wrap_ = -1) { Object::setup(); scale = scale_; color = color_; wrap = wrap_; } static const char *typestr() { return "#Text"; } const char *gettype() const { return typestr(); } float drawscale() const { return scale / FONTH; } virtual const char *getstr() const { return ""; } void draw(float sx, float sy) { Object::draw(sx, sy); changedraw(CHANGE_SHADER | CHANGE_COLOR); float oldscale = textscale; textscale = drawscale(); draw_text(getstr(), sx/textscale, sy/textscale, color.r, color.g, color.b, color.a, -1, wrap >= 0 ? int(wrap/textscale) : -1); textscale = oldscale; } void layout() { Object::layout(); float k = drawscale(), tw, th; text_boundsf(getstr(), tw, th, wrap >= 0 ? int(wrap/k) : -1); w = max(w, tw*k); h = max(h, th*k); } }; struct TextString : Text { char *str; TextString() : str(NULL) {} ~TextString() { delete[] str; } void setup(const char *str_, float scale_ = 1, const Color &color_ = Color(255, 255, 255), float wrap_ = -1) { Text::setup(scale_, color_, wrap_); SETSTR(str, str_); } static const char *typestr() { return "#TextString"; } const char *gettype() const { return typestr(); } const char *getstr() const { return str; } }; struct TextInt : Text { int val; char str[20]; TextInt() : val(0) { str[0] = '0'; str[1] = '\0'; } void setup(int val_, float scale_ = 1, const Color &color_ = Color(255, 255, 255), float wrap_ = -1) { Text::setup(scale_, color_, wrap_); if(val != val_) { val = val_; intformat(str, val, sizeof(str)); } } static const char *typestr() { return "#TextInt"; } const char *gettype() const { return typestr(); } const char *getstr() const { return str; } }; struct TextFloat : Text { float val; char str[20]; TextFloat() : val(0) { memcpy(str, "0.0", 4); } void setup(float val_, float scale_ = 1, const Color &color_ = Color(255, 255, 255), float wrap_ = -1) { Text::setup(scale_, color_, wrap_); if(val != val_) { val = val_; floatformat(str, val, sizeof(str)); } } static const char *typestr() { return "#TextFloat"; } const char *gettype() const { return typestr(); } const char *getstr() const { return str; } }; struct Font : Object { ::font *font; Font() : font(NULL) {} void setup(const char *name) { Object::setup(); if(!font || !strcmp(font->name, name)) font = findfont(name); } void layout() { pushfont(); setfont(font); Object::layout(); popfont(); } void draw(float sx, float sy) { pushfont(); setfont(font); Object::draw(sx, sy); popfont(); } void buildchildren(uint *contents) { pushfont(); setfont(font); Object::buildchildren(contents); popfont(); } #define DOSTATE(flags, func) \ void func##children(float cx, float cy, int mask, bool inside, int setflags) \ { \ pushfont(); \ setfont(font); \ Object::func##children(cx, cy, mask, inside, setflags); \ popfont(); \ } DOSTATES #undef DOSTATE bool rawkey(int code, bool isdown) { pushfont(); setfont(font); bool result = Object::rawkey(code, isdown); popfont(); return result; } bool key(int code, bool isdown) { pushfont(); setfont(font); bool result = Object::key(code, isdown); popfont(); return result; } bool textinput(const char *str, int len) { pushfont(); setfont(font); bool result = Object::textinput(str, len); popfont(); return result; } }; float uicontextscale = 0; ICOMMAND(uicontextscale, "", (), floatret(FONTH*uicontextscale)); struct Console : Filler { void setup(float minw_ = 0, float minh_ = 0) { Filler::setup(minw_, minh_); } static const char *typestr() { return "#Console"; } const char *gettype() const { return typestr(); } float drawscale() const { return uicontextscale; } void draw(float sx, float sy) { Object::draw(sx, sy); changedraw(CHANGE_SHADER | CHANGE_COLOR); float k = drawscale(); pushhudtranslate(sx, sy, k); renderfullconsole(w/k, h/k); pophudmatrix(); } }; struct Clipper : Object { float clipw, cliph, virtw, virth; void setup(float clipw_ = 0, float cliph_ = 0) { Object::setup(); clipw = clipw_; cliph = cliph_; virtw = virth = 0; } static const char *typestr() { return "#Clipper"; } const char *gettype() const { return typestr(); } void layout() { Object::layout(); virtw = w; virth = h; if(clipw) w = min(w, clipw); if(cliph) h = min(h, cliph); } void adjustchildren() { adjustchildrento(0, 0, virtw, virth); } void draw(float sx, float sy) { if((clipw && virtw > clipw) || (cliph && virth > cliph)) { stopdrawing(); pushclip(sx, sy, w, h); Object::draw(sx, sy); stopdrawing(); popclip(); } else Object::draw(sx, sy); } }; struct Scroller : Clipper { float offsetx, offsety; Scroller() : offsetx(0), offsety(0) {} void setup(float clipw_ = 0, float cliph_ = 0) { Clipper::setup(clipw_, cliph_); } static const char *typestr() { return "#Scroller"; } const char *gettype() const { return typestr(); } void layout() { Clipper::layout(); offsetx = min(offsetx, hlimit()); offsety = min(offsety, vlimit()); } #define DOSTATE(flags, func) \ void func##children(float cx, float cy, int mask, bool inside, int setflags) \ { \ cx += offsetx; \ cy += offsety; \ if(cx < virtw && cy < virth) Clipper::func##children(cx, cy, mask, inside, setflags); \ } DOSTATES #undef DOSTATE void draw(float sx, float sy) { if((clipw && virtw > clipw) || (cliph && virth > cliph)) { stopdrawing(); pushclip(sx, sy, w, h); Object::draw(sx - offsetx, sy - offsety); stopdrawing(); popclip(); } else Object::draw(sx, sy); } float hlimit() const { return max(virtw - w, 0.0f); } float vlimit() const { return max(virth - h, 0.0f); } float hoffset() const { return offsetx / max(virtw, w); } float voffset() const { return offsety / max(virth, h); } float hscale() const { return w / max(virtw, w); } float vscale() const { return h / max(virth, h); } void addhscroll(float hscroll) { sethscroll(offsetx + hscroll); } void addvscroll(float vscroll) { setvscroll(offsety + vscroll); } void sethscroll(float hscroll) { offsetx = clamp(hscroll, 0.0f, hlimit()); } void setvscroll(float vscroll) { offsety = clamp(vscroll, 0.0f, vlimit()); } void scrollup(float cx, float cy); void scrolldown(float cx, float cy); }; struct ScrollButton : Object { static const char *typestr() { return "#ScrollButton"; } const char *gettype() const { return typestr(); } }; struct ScrollBar : Object { float offsetx, offsety; ScrollBar() : offsetx(0), offsety(0) {} static const char *typestr() { return "#ScrollBar"; } const char *gettype() const { return typestr(); } const char *gettypename() const { return typestr(); } bool target(float cx, float cy) { return true; } virtual void scrollto(float cx, float cy, bool closest = false) {} void hold(float cx, float cy) { ScrollButton *button = (ScrollButton *)find(ScrollButton::typestr(), false); if(button && button->haschildstate(STATE_HOLD)) movebutton(button, offsetx, offsety, cx - button->x, cy - button->y); } void press(float cx, float cy) { ScrollButton *button = (ScrollButton *)find(ScrollButton::typestr(), false); if(button && button->haschildstate(STATE_PRESS)) { offsetx = cx - button->x; offsety = cy - button->y; } else scrollto(cx, cy, true); } virtual void addscroll(Scroller *scroller, float dir) = 0; void addscroll(float dir) { Scroller *scroller = (Scroller *)findsibling(Scroller::typestr()); if(scroller) addscroll(scroller, dir); } void arrowscroll(float dir) { addscroll(dir*curtime/1000.0f); } void wheelscroll(float step); virtual int wheelscrolldirection() const { return 1; } void scrollup(float cx, float cy) { wheelscroll(-wheelscrolldirection()); } void scrolldown(float cx, float cy) { wheelscroll(wheelscrolldirection()); } virtual void movebutton(Object *o, float fromx, float fromy, float tox, float toy) = 0; }; void Scroller::scrollup(float cx, float cy) { ScrollBar *scrollbar = (ScrollBar *)findsibling(ScrollBar::typestr()); if(scrollbar) scrollbar->wheelscroll(-scrollbar->wheelscrolldirection()); } void Scroller::scrolldown(float cx, float cy) { ScrollBar *scrollbar = (ScrollBar *)findsibling(ScrollBar::typestr()); if(scrollbar) scrollbar->wheelscroll(scrollbar->wheelscrolldirection()); } struct ScrollArrow : Object { float arrowspeed; void setup(float arrowspeed_ = 0) { Object::setup(); arrowspeed = arrowspeed_; } static const char *typestr() { return "#ScrollArrow"; } const char *gettype() const { return typestr(); } void hold(float cx, float cy) { ScrollBar *scrollbar = (ScrollBar *)findsibling(ScrollBar::typestr()); if(scrollbar) scrollbar->arrowscroll(arrowspeed); } }; VARP(uiscrollsteptime, 0, 50, 1000); void ScrollBar::wheelscroll(float step) { ScrollArrow *arrow = (ScrollArrow *)findsibling(ScrollArrow::typestr()); if(arrow) addscroll(arrow->arrowspeed*step*uiscrollsteptime/1000.0f); } struct HorizontalScrollBar : ScrollBar { static const char *typestr() { return "#HorizontalScrollBar"; } const char *gettype() const { return typestr(); } void addscroll(Scroller *scroller, float dir) { scroller->addhscroll(dir); } void scrollto(float cx, float cy, bool closest = false) { Scroller *scroller = (Scroller *)findsibling(Scroller::typestr()); if(!scroller) return; ScrollButton *button = (ScrollButton *)find(ScrollButton::typestr(), false); if(!button) return; float bscale = (w - button->w) / (1 - scroller->hscale()), offset = bscale > 1e-3f ? (closest && cx >= button->x + button->w ? cx - button->w : cx)/bscale : 0; scroller->sethscroll(offset*scroller->virtw); } void adjustchildren() { Scroller *scroller = (Scroller *)findsibling(Scroller::typestr()); if(!scroller) return; ScrollButton *button = (ScrollButton *)find(ScrollButton::typestr(), false); if(!button) return; float bw = w*scroller->hscale(); button->w = max(button->w, bw); float bscale = scroller->hscale() < 1 ? (w - button->w) / (1 - scroller->hscale()) : 1; button->x = scroller->hoffset()*bscale; button->adjust &= ~ALIGN_HMASK; ScrollBar::adjustchildren(); } void movebutton(Object *o, float fromx, float fromy, float tox, float toy) { scrollto(o->x + tox - fromx, o->y + toy); } }; struct VerticalScrollBar : ScrollBar { static const char *typestr() { return "#VerticalScrollBar"; } const char *gettype() const { return typestr(); } void addscroll(Scroller *scroller, float dir) { scroller->addvscroll(dir); } void scrollto(float cx, float cy, bool closest = false) { Scroller *scroller = (Scroller *)findsibling(Scroller::typestr()); if(!scroller) return; ScrollButton *button = (ScrollButton *)find(ScrollButton::typestr(), false); if(!button) return; float bscale = (h - button->h) / (1 - scroller->vscale()), offset = bscale > 1e-3f ? (closest && cy >= button->y + button->h ? cy - button->h : cy)/bscale : 0; scroller->setvscroll(offset*scroller->virth); } void adjustchildren() { Scroller *scroller = (Scroller *)findsibling(Scroller::typestr()); if(!scroller) return; ScrollButton *button = (ScrollButton *)find(ScrollButton::typestr(), false); if(!button) return; float bh = h*scroller->vscale(); button->h = max(button->h, bh); float bscale = scroller->vscale() < 1 ? (h - button->h) / (1 - scroller->vscale()) : 1; button->y = scroller->voffset()*bscale; button->adjust &= ~ALIGN_VMASK; ScrollBar::adjustchildren(); } void movebutton(Object *o, float fromx, float fromy, float tox, float toy) { scrollto(o->x + tox, o->y + toy - fromy); } int wheelscrolldirection() const { return -1; } }; struct SliderButton : Object { static const char *typestr() { return "#SliderButton"; } const char *gettype() const { return typestr(); } }; static double getfval(ident *id, double val = 0) { switch(id->type) { case ID_VAR: val = *id->storage.i; break; case ID_FVAR: val = *id->storage.f; break; case ID_SVAR: val = parsenumber(*id->storage.s); break; case ID_ALIAS: val = id->getnumber(); break; case ID_COMMAND: { tagval t; executeret(id, NULL, 0, true, t); val = t.getnumber(); t.cleanup(); break; } } return val; } static void setfval(ident *id, double val, uint *onchange = NULL) { switch(id->type) { case ID_VAR: setvarchecked(id, int(clamp(val, double(INT_MIN), double(INT_MAX)))); break; case ID_FVAR: setfvarchecked(id, val); break; case ID_SVAR: setsvarchecked(id, numberstr(val)); break; case ID_ALIAS: alias(id->name, numberstr(val)); break; case ID_COMMAND: { tagval t; t.setnumber(val); execute(id, &t, 1); break; } } if(onchange && (*onchange&CODE_OP_MASK) != CODE_EXIT) execute(onchange); } struct Slider : Object { ident *id; double val, vmin, vmax, vstep; bool changed; Slider() : id(NULL), val(0), vmin(0), vmax(0), vstep(0), changed(false) {} void setup(ident *id_, double vmin_ = 0, double vmax_ = 0, double vstep_ = 1, uint *onchange = NULL) { Object::setup(); if(!vmin_ && !vmax_) switch(id_->type) { case ID_VAR: vmin_ = id_->minval; vmax_ = id_->maxval; break; case ID_FVAR: vmin_ = id_->minvalf; vmax_ = id_->maxvalf; break; } if(id != id_) changed = false; id = id_; vmin = vmin_; vmax = vmax_; vstep = vstep_ > 0 ? vstep_ : 1; if(changed) { setfval(id, val, onchange); changed = false; } else val = getfval(id, vmin); } static const char *typestr() { return "#Slider"; } const char *gettype() const { return typestr(); } const char *gettypename() const { return typestr(); } bool target(float cx, float cy) { return true; } void arrowscroll(double dir) { double newval = val + dir*vstep; newval += vstep * (newval < 0 ? -0.5 : 0.5); newval -= fmod(newval, vstep); newval = clamp(newval, min(vmin, vmax), max(vmin, vmax)); if(val != newval) changeval(newval); } void wheelscroll(float step); virtual int wheelscrolldirection() const { return 1; } void scrollup(float cx, float cy) { wheelscroll(-wheelscrolldirection()); } void scrolldown(float cx, float cy) { wheelscroll(wheelscrolldirection()); } virtual void scrollto(float cx, float cy) {} void hold(float cx, float cy) { scrollto(cx, cy); } void changeval(double newval) { val = newval; changed = true; } }; VARP(uislidersteptime, 0, 50, 1000); struct SliderArrow : Object { double stepdir; int laststep; SliderArrow() : laststep(0) {} void setup(double dir_ = 0) { Object::setup(); stepdir = dir_; } static const char *typestr() { return "#SliderArrow"; } const char *gettype() const { return typestr(); } void press(float cx, float cy) { laststep = totalmillis + 2*uislidersteptime; Slider *slider = (Slider *)findsibling(Slider::typestr()); if(slider) slider->arrowscroll(stepdir); } void hold(float cx, float cy) { if(totalmillis < laststep + uislidersteptime) return; laststep = totalmillis; Slider *slider = (Slider *)findsibling(Slider::typestr()); if(slider) slider->arrowscroll(stepdir); } }; void Slider::wheelscroll(float step) { SliderArrow *arrow = (SliderArrow *)findsibling(SliderArrow::typestr()); if(arrow) step *= arrow->stepdir; arrowscroll(step); } struct HorizontalSlider : Slider { static const char *typestr() { return "#HorizontalSlider"; } const char *gettype() const { return typestr(); } void scrollto(float cx, float cy) { SliderButton *button = (SliderButton *)find(SliderButton::typestr(), false); if(!button) return; float offset = w > button->w ? clamp((cx - button->w/2)/(w - button->w), 0.0f, 1.0f) : 0.0f; int step = int((val - vmin) / vstep), bstep = int(offset * (vmax - vmin) / vstep); if(step != bstep) changeval(bstep * vstep + vmin); } void adjustchildren() { SliderButton *button = (SliderButton *)find(SliderButton::typestr(), false); if(!button) return; int step = int((val - vmin) / vstep), bstep = int(button->x / (w - button->w) * (vmax - vmin) / vstep); if(step != bstep) button->x = (w - button->w) * step * vstep / (vmax - vmin); button->adjust &= ~ALIGN_HMASK; Slider::adjustchildren(); } }; struct VerticalSlider : Slider { static const char *typestr() { return "#VerticalSlider"; } const char *gettype() const { return typestr(); } void scrollto(float cx, float cy) { SliderButton *button = (SliderButton *)find(SliderButton::typestr(), false); if(!button) return; float offset = h > button->h ? clamp((cy - button->h/2)/(h - button->h), 0.0f, 1.0f) : 0.0f; int step = int((val - vmin) / vstep), bstep = int(offset * (vmax - vmin) / vstep); if(step != bstep) changeval(bstep * vstep + vmin); } void adjustchildren() { SliderButton *button = (SliderButton *)find(SliderButton::typestr(), false); if(!button) return; int step = int((val - vmin) / vstep), bstep = int(button->y / (h - button->h) * (vmax - vmin) / vstep); if(step != bstep) button->y = (h - button->h) * step * vstep / (vmax - vmin); button->adjust &= ~ALIGN_VMASK; Slider::adjustchildren(); } int wheelscrolldirection() const { return -1; } }; struct TextEditor : Object { static TextEditor *focus; float scale, offsetx, offsety; editor *edit; char *keyfilter; TextEditor() : edit(NULL), keyfilter(NULL) {} void setup(const char *name, int length, int height, float scale_ = 1, const char *initval = NULL, int mode = EDITORUSED, const char *keyfilter_ = NULL) { Object::setup(); editor *edit_ = useeditor(name, mode, false, initval); if(edit_ != edit) { if(edit) clearfocus(); edit = edit_; } else if(isfocus() && !hasstate(STATE_HOVER)) commit(); if(initval && edit->mode == EDITORFOCUSED && !isfocus()) edit->init(initval); edit->active = true; edit->linewrap = length < 0; edit->maxx = edit->linewrap ? -1 : length; edit->maxy = height <= 0 ? 1 : -1; edit->pixelwidth = abs(length)*FONTW; if(edit->linewrap && edit->maxy == 1) edit->updateheight(); else edit->pixelheight = FONTH*max(height, 1); scale = scale_; if(keyfilter_) SETSTR(keyfilter, keyfilter_); else DELETEA(keyfilter); } ~TextEditor() { clearfocus(); DELETEA(keyfilter); } static void setfocus(TextEditor *e) { if(focus == e) return; focus = e; bool allowtextinput = focus!=NULL && focus->allowtextinput(); ::textinput(allowtextinput, TI_GUI); ::keyrepeat(allowtextinput, KR_GUI); } void setfocus() { setfocus(this); } void clearfocus() { if(focus == this) setfocus(NULL); } bool isfocus() const { return focus == this; } static const char *typestr() { return "#TextEditor"; } const char *gettype() const { return typestr(); } bool target(float cx, float cy) { return true; } float drawscale() const { return scale / FONTH; } void draw(float sx, float sy) { changedraw(CHANGE_SHADER | CHANGE_COLOR); edit->rendered = true; float k = drawscale(); pushhudtranslate(sx, sy, k); edit->draw(FONTW/2, 0, 0xFFFFFF, isfocus()); pophudmatrix(); Object::draw(sx, sy); } void layout() { Object::layout(); float k = drawscale(); w = max(w, (edit->pixelwidth + FONTW)*k); h = max(h, edit->pixelheight*k); } virtual void resetmark(float cx, float cy) { edit->mark(false); offsetx = cx; offsety = cy; } void press(float cx, float cy) { setfocus(); resetmark(cx, cy); } void hold(float cx, float cy) { if(isfocus()) { float k = drawscale(); bool dragged = max(fabs(cx - offsetx), fabs(cy - offsety)) > (FONTH/8.0f)*k; edit->hit(int(floor(cx/k - FONTW/2)), int(floor(cy/k)), dragged); } } void scrollup(float cx, float cy) { edit->scrollup(); } void scrolldown(float cx, float cy) { edit->scrolldown(); } virtual void cancel() { clearfocus(); } virtual void commit() { clearfocus(); } bool key(int code, bool isdown) { if(Object::key(code, isdown)) return true; if(!isfocus()) return false; switch(code) { case SDLK_ESCAPE: if(isdown) cancel(); return true; case SDLK_RETURN: case SDLK_TAB: if(edit->maxy != 1) break; // fall-through case SDLK_KP_ENTER: if(isdown) commit(); return true; } if(isdown) edit->key(code); return true; } virtual bool allowtextinput() const { return true; } bool textinput(const char *str, int len) { if(Object::textinput(str, len)) return true; if(!isfocus() || !allowtextinput()) return false; if(!keyfilter) edit->input(str, len); else while(len > 0) { int accept = min(len, (int)strspn(str, keyfilter)); if(accept > 0) edit->input(str, accept); str += accept + 1; len -= accept + 1; if(len <= 0) break; int reject = (int)strcspn(str, keyfilter); str += reject; str -= reject; } return true; } }; TextEditor *TextEditor::focus = NULL; static const char *getsval(ident *id, bool &shouldfree, const char *val = "") { switch(id->type) { case ID_VAR: val = intstr(*id->storage.i); break; case ID_FVAR: val = floatstr(*id->storage.f); break; case ID_SVAR: val = *id->storage.s; break; case ID_ALIAS: val = id->getstr(); break; case ID_COMMAND: val = executestr(id, NULL, 0, true); shouldfree = true; break; } return val; } static void setsval(ident *id, const char *val, uint *onchange = NULL) { switch(id->type) { case ID_VAR: setvarchecked(id, parseint(val)); break; case ID_FVAR: setfvarchecked(id, parsefloat(val)); break; case ID_SVAR: setsvarchecked(id, val); break; case ID_ALIAS: alias(id->name, val); break; case ID_COMMAND: { tagval t; t.setstr(newstring(val)); execute(id, &t, 1); break; } } if(onchange && (*onchange&CODE_OP_MASK) != CODE_EXIT) execute(onchange); } struct Field : TextEditor { ident *id; bool changed; Field() : id(NULL), changed(false) {} void setup(ident *id_, int length, uint *onchange, float scale = 1, const char *keyfilter_ = NULL) { if(isfocus() && !hasstate(STATE_HOVER)) commit(); if(changed) { if(id == id_) setsval(id, edit->lines[0].text, onchange); changed = false; } bool shouldfree = false; const char *initval = id != id_ || !isfocus() ? getsval(id_, shouldfree) : NULL; TextEditor::setup(id_->name, length, 0, scale, initval, EDITORFOCUSED, keyfilter_); if(shouldfree) delete[] initval; id = id_; } static const char *typestr() { return "#Field"; } const char *gettype() const { return typestr(); } void commit() { TextEditor::commit(); changed = true; } void cancel() { TextEditor::cancel(); changed = false; } }; struct KeyField : Field { static const char *typestr() { return "#KeyField"; } const char *gettype() const { return typestr(); } void resetmark(float cx, float cy) { edit->clear(); Field::resetmark(cx, cy); } void insertkey(int code) { const char *keyname = getkeyname(code); if(keyname) { if(!edit->empty()) edit->insert(" "); edit->insert(keyname); } } bool rawkey(int code, bool isdown) { if(Object::rawkey(code, isdown)) return true; if(!isfocus() || !isdown) return false; if(code == SDLK_ESCAPE) commit(); else insertkey(code); return true; } bool allowtextinput() const { return false; } }; struct Preview : Target { void startdraw() { glDisable(GL_BLEND); if(clipstack.length()) glDisable(GL_SCISSOR_TEST); } void enddraw() { glEnable(GL_BLEND); if(clipstack.length()) glEnable(GL_SCISSOR_TEST); } }; struct ModelPreview : Preview { char *name; int anim; ModelPreview() : name(NULL) {} ~ModelPreview() { delete[] name; } void setup(const char *name_, const char *animspec, float minw_, float minh_) { Preview::setup(minw_, minh_); SETSTR(name, name_); anim = ANIM_ALL; if(animspec[0]) { if(isdigit(animspec[0])) { anim = parseint(animspec); if(anim >= 0) anim %= ANIM_INDEX; else anim = ANIM_ALL; } else { vector anims; game::findanims(animspec, anims); if(anims.length()) anim = anims[0]; } } anim |= ANIM_LOOP; } static const char *typestr() { return "#ModelPreview"; } const char *gettype() const { return typestr(); } void draw(float sx, float sy) { Object::draw(sx, sy); changedraw(CHANGE_SHADER); int sx1, sy1, sx2, sy2; window->calcscissor(sx, sy, sx+w, sy+h, sx1, sy1, sx2, sy2, false); modelpreview::start(sx1, sy1, sx2-sx1, sy2-sy1, false, clipstack.length() > 0); model *m = loadmodel(name); if(m) { vec center, radius; m->boundbox(center, radius); float yaw; vec o = calcmodelpreviewpos(radius, yaw).sub(center); rendermodel(name, anim, o, yaw, 0, 0, 0, NULL, NULL, 0); } if(clipstack.length()) clipstack.last().scissor(); modelpreview::end(); } }; struct PlayerPreview : Preview { int model, color, team, weapon; void setup(int model_, int color_, int team_, int weapon_, float minw_, float minh_) { Preview::setup(minw_, minh_); model = model_; color = color_; team = team_; weapon = weapon_; } static const char *typestr() { return "#PlayerPreview"; } const char *gettype() const { return typestr(); } void draw(float sx, float sy) { Object::draw(sx, sy); changedraw(CHANGE_SHADER); int sx1, sy1, sx2, sy2; window->calcscissor(sx, sy, sx+w, sy+h, sx1, sy1, sx2, sy2, false); modelpreview::start(sx1, sy1, sx2-sx1, sy2-sy1, false, clipstack.length() > 0); game::renderplayerpreview(model, color, team, weapon); if(clipstack.length()) clipstack.last().scissor(); modelpreview::end(); } }; struct PrefabPreview : Preview { char *name; vec color; PrefabPreview() : name(NULL) {} ~PrefabPreview() { delete[] name; } void setup(const char *name_, int color_, float minw_, float minh_) { Preview::setup(minw_, minh_); SETSTR(name, name_); color = vec::hexcolor(color_); } static const char *typestr() { return "#PrefabPreview"; } const char *gettype() const { return typestr(); } void draw(float sx, float sy) { Object::draw(sx, sy); changedraw(CHANGE_SHADER); int sx1, sy1, sx2, sy2; window->calcscissor(sx, sy, sx+w, sy+h, sx1, sy1, sx2, sy2, false); modelpreview::start(sx1, sy1, sx2-sx1, sy2-sy1, false, clipstack.length() > 0); previewprefab(name, color); if(clipstack.length()) clipstack.last().scissor(); modelpreview::end(); } }; VARP(uislotviewtime, 0, 25, 1000); static int lastthumbnail = 0; struct SlotViewer : Target { int index; void setup(int index_, float minw_ = 0, float minh_ = 0) { Target::setup(minw_, minh_); index = index_; } static const char *typestr() { return "#SlotViewer"; } const char *gettype() const { return typestr(); } void previewslot(Slot &slot, VSlot &vslot, float x, float y) { if(slot.sts.empty()) return; VSlot *layer = NULL, *detail = NULL; Texture *t = NULL, *glowtex = NULL, *layertex = NULL, *detailtex = NULL; if(slot.loaded) { t = slot.sts[0].t; if(t == notexture) return; Slot &slot = *vslot.slot; if(slot.texmask&(1<slot->sts.empty()) layertex = layer->slot->sts[0].t; } if(vslot.detail) { detail = &lookupvslot(vslot.detail); if(!detail->slot->sts.empty()) detailtex = detail->slot->sts[0].t; } } else { if(!slot.thumbnail) { if(totalmillis - lastthumbnail < uislotviewtime) return; slot.loadthumbnail(); lastthumbnail = totalmillis; } if(slot.thumbnail != notexture) t = slot.thumbnail; else return; } changedraw(CHANGE_SHADER | CHANGE_COLOR); SETSHADER(hudrgb); vec2 tc[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) }; int xoff = vslot.offset.x, yoff = vslot.offset.y; if(vslot.rotation) { if((vslot.rotation&5) == 1) { swap(xoff, yoff); loopk(4) swap(tc[k].x, tc[k].y); } if(vslot.rotation >= 2 && vslot.rotation <= 4) { xoff *= -1; loopk(4) tc[k].x *= -1; } if(vslot.rotation <= 2 || vslot.rotation == 5) { yoff *= -1; loopk(4) tc[k].y *= -1; } } float xt = min(1.0f, t->xs/float(t->ys)), yt = min(1.0f, t->ys/float(t->xs)); loopk(4) { tc[k].x = tc[k].x/xt - float(xoff)/t->xs; tc[k].y = tc[k].y/yt - float(yoff)/t->ys; } glBindTexture(GL_TEXTURE_2D, t->id); if(slot.loaded) gle::color(vslot.colorscale); else gle::colorf(1, 1, 1); quad(x, y, w, h, tc); if(detailtex) { glBindTexture(GL_TEXTURE_2D, detailtex->id); quad(x + w/2, y + h/2, w/2, h/2, tc); } if(glowtex) { glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBindTexture(GL_TEXTURE_2D, glowtex->id); gle::color(vslot.glowcolor); quad(x, y, w, h, tc); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } if(layertex) { glBindTexture(GL_TEXTURE_2D, layertex->id); gle::color(layer->colorscale); quad(x, y, w/2, h/2, tc); } } void draw(float sx, float sy) { Slot &slot = lookupslot(index, false); previewslot(slot, *slot.variants, sx, sy); Object::draw(sx, sy); } }; struct VSlotViewer : SlotViewer { static const char *typestr() { return "#VSlotViewer"; } const char *gettype() const { return typestr(); } void draw(float sx, float sy) { VSlot &vslot = lookupvslot(index, false); previewslot(*vslot.slot, vslot, sx, sy); Object::draw(sx, sy); } }; ICOMMAND(newui, "ssss", (char *name, char *contents, char *onshow, char *onhide), { Window *window = windows.find(name, NULL); if(window) { if (window == UI::window) return; world->hide(window); windows.remove(name); delete window; } windows[name] = new Window(name, contents, onshow, onhide); }); ICOMMAND(uiallowinput, "b", (int *val), { if(window) { if(*val >= 0) window->allowinput = *val!=0; intret(window->allowinput ? 1 : 0); } }); ICOMMAND(uieschide, "b", (int *val), { if(window) { if(*val >= 0) window->eschide = *val!=0; intret(window->eschide ? 1 : 0); } }); bool showui(const char *name) { Window *window = windows.find(name, NULL); return window && world->show(window); } bool hideui(const char *name) { if(!name) return world->hideall() > 0; Window *window = windows.find(name, NULL); return window && world->hide(window); } bool toggleui(const char *name) { if(showui(name)) return true; hideui(name); return false; } void holdui(const char *name, bool on) { if(on) showui(name); else hideui(name); } bool uivisible(const char *name) { if(!name) return world->children.length() > 0; Window *window = windows.find(name, NULL); return window && world->children.find(window) >= 0; } ICOMMAND(showui, "s", (char *name), intret(showui(name) ? 1 : 0)); ICOMMAND(hideui, "s", (char *name), intret(hideui(name) ? 1 : 0)); ICOMMAND(hidetopui, "", (), intret(world->hidetop() ? 1 : 0)); ICOMMAND(hideallui, "", (), intret(world->hideall())); ICOMMAND(toggleui, "s", (char *name), intret(toggleui(name) ? 1 : 0)); ICOMMAND(holdui, "sD", (char *name, int *down), holdui(name, *down!=0)); ICOMMAND(uivisible, "s", (char *name), intret(uivisible(name) ? 1 : 0)); ICOMMAND(uiname, "", (), { if(window) result(window->name); }); #define IFSTATEVAL(state,t,f) { if(state) { if(t->type == VAL_NULL) intret(1); else result(*t); } else if(f->type == VAL_NULL) intret(0); else result(*f); } #define DOSTATE(flags, func) \ ICOMMANDNS("ui!" #func, uinot##func##_, "ee", (uint *t, uint *f), \ executeret(buildparent && buildparent->hasstate(flags) ? t : f)); \ ICOMMANDNS("ui" #func, ui##func##_, "ee", (uint *t, uint *f), \ executeret(buildparent && buildparent->haschildstate(flags) ? t : f)); \ ICOMMANDNS("ui!" #func "?", uinot##func##__, "tt", (tagval *t, tagval *f), \ IFSTATEVAL(buildparent && buildparent->hasstate(flags), t, f)); \ ICOMMANDNS("ui" #func "?", ui##func##__, "tt", (tagval *t, tagval *f), \ IFSTATEVAL(buildparent && buildparent->haschildstate(flags), t, f)); \ ICOMMANDNS("ui!" #func "+", uinextnot##func##_, "ee", (uint *t, uint *f), \ executeret(buildparent && buildparent->children.inrange(buildchild) && buildparent->children[buildchild]->hasstate(flags) ? t : f)); \ ICOMMANDNS("ui" #func "+", uinext##func##_, "ee", (uint *t, uint *f), \ executeret(buildparent && buildparent->children.inrange(buildchild) && buildparent->children[buildchild]->haschildstate(flags) ? t : f)); \ ICOMMANDNS("ui!" #func "+?", uinextnot##func##__, "tt", (tagval *t, tagval *f), \ IFSTATEVAL(buildparent && buildparent->children.inrange(buildchild) && buildparent->children[buildchild]->hasstate(flags), t, f)); \ ICOMMANDNS("ui" #func "+?", uinext##func##__, "tt", (tagval *t, tagval *f), \ IFSTATEVAL(buildparent && buildparent->children.inrange(buildchild) && buildparent->children[buildchild]->haschildstate(flags), t, f)); DOSTATES #undef DOSTATE ICOMMANDNS("uifocus", uifocus_, "ee", (uint *t, uint *f), executeret(buildparent && TextEditor::focus == buildparent ? t : f)); ICOMMANDNS("uifocus?", uifocus__, "tt", (tagval *t, tagval *f), IFSTATEVAL(buildparent && TextEditor::focus == buildparent, t, f)); ICOMMANDNS("uifocus+", uinextfocus_, "ee", (uint *t, uint *f), executeret(buildparent && buildparent->children.inrange(buildchild) && TextEditor::focus == buildparent->children[buildchild] ? t : f)); ICOMMANDNS("uifocus+?", uinextfocus__, "tt", (tagval *t, tagval *f), IFSTATEVAL(buildparent && buildparent->children.inrange(buildchild) && TextEditor::focus == buildparent->children[buildchild], t, f)); ICOMMAND(uialign, "ii", (int *xalign, int *yalign), { if(buildparent) buildparent->setalign(*xalign, *yalign); }); ICOMMANDNS("uialign-", uialign_, "ii", (int *xalign, int *yalign), { if(buildparent && buildchild > 0) buildparent->children[buildchild-1]->setalign(*xalign, *yalign); }); ICOMMANDNS("uialign*", uialign__, "ii", (int *xalign, int *yalign), { if(buildparent) loopi(buildchild) buildparent->children[i]->setalign(*xalign, *yalign); }); ICOMMAND(uiclamp, "iiii", (int *left, int *right, int *top, int *bottom), { if(buildparent) buildparent->setclamp(*left, *right, *top, *bottom); }); ICOMMANDNS("uiclamp-", uiclamp_, "iiii", (int *left, int *right, int *top, int *bottom), { if(buildparent && buildchild > 0) buildparent->children[buildchild-1]->setclamp(*left, *right, *top, *bottom); }); ICOMMANDNS("uiclamp*", uiclamp__, "iiii", (int *left, int *right, int *top, int *bottom), { if(buildparent) loopi(buildchild) buildparent->children[i]->setclamp(*left, *right, *top, *bottom); }); ICOMMAND(uigroup, "e", (uint *children), BUILD(Object, o, o->setup(), children)); ICOMMAND(uihlist, "fe", (float *space, uint *children), BUILD(HorizontalList, o, o->setup(*space), children)); ICOMMAND(uivlist, "fe", (float *space, uint *children), BUILD(VerticalList, o, o->setup(*space), children)); ICOMMAND(uilist, "fe", (float *space, uint *children), { for(Object *parent = buildparent; parent && !parent->istype(); parent = parent->parent) { if(parent->istype()) { BUILD(VerticalList, o, o->setup(*space), children); return; } } BUILD(HorizontalList, o, o->setup(*space), children); }); ICOMMAND(uigrid, "iffe", (int *columns, float *spacew, float *spaceh, uint *children), BUILD(Grid, o, o->setup(*columns, *spacew, *spaceh), children)); ICOMMAND(uitableheader, "ee", (uint *columndata, uint *children), BUILDCOLUMNS(TableHeader, o, o->setup(), columndata, children)); ICOMMAND(uitablerow, "ee", (uint *columndata, uint *children), BUILDCOLUMNS(TableRow, o, o->setup(), columndata, children)); ICOMMAND(uitable, "ffe", (float *spacew, float *spaceh, uint *children), BUILD(Table, o, o->setup(*spacew, *spaceh), children)); ICOMMAND(uispace, "ffe", (float *spacew, float *spaceh, uint *children), BUILD(Spacer, o, o->setup(*spacew, *spaceh), children)); ICOMMAND(uioffset, "ffe", (float *offsetx, float *offsety, uint *children), BUILD(Offsetter, o, o->setup(*offsetx, *offsety), children)); ICOMMAND(uifill, "ffe", (float *minw, float *minh, uint *children), BUILD(Filler, o, o->setup(*minw, *minh), children)); ICOMMAND(uitarget, "ffe", (float *minw, float *minh, uint *children), BUILD(Target, o, o->setup(*minw, *minh), children)); ICOMMAND(uiclip, "ffe", (float *clipw, float *cliph, uint *children), BUILD(Clipper, o, o->setup(*clipw, *cliph), children)); ICOMMAND(uiscroll, "ffe", (float *clipw, float *cliph, uint *children), BUILD(Scroller, o, o->setup(*clipw, *cliph), children)); ICOMMAND(uihscrolloffset, "", (), { if(buildparent && buildparent->istype()) { Scroller *scroller = (Scroller *)buildparent; floatret(scroller->offsetx); } }); ICOMMAND(uivscrolloffset, "", (), { if(buildparent && buildparent->istype()) { Scroller *scroller = (Scroller *)buildparent; floatret(scroller->offsety); } }); ICOMMAND(uihscrollbar, "e", (uint *children), BUILD(HorizontalScrollBar, o, o->setup(), children)); ICOMMAND(uivscrollbar, "e", (uint *children), BUILD(VerticalScrollBar, o, o->setup(), children)); ICOMMAND(uiscrollarrow, "fe", (float *dir, uint *children), BUILD(ScrollArrow, o, o->setup(*dir), children)); ICOMMAND(uiscrollbutton, "e", (uint *children), BUILD(ScrollButton, o, o->setup(), children)); ICOMMAND(uihslider, "rfffee", (ident *var, float *vmin, float *vmax, float *vstep, uint *onchange, uint *children), BUILD(HorizontalSlider, o, o->setup(var, *vmin, *vmax, *vstep, onchange), children)); ICOMMAND(uivslider, "rfffee", (ident *var, float *vmin, float *vmax, float *vstep, uint *onchange, uint *children), BUILD(VerticalSlider, o, o->setup(var, *vmin, *vmax, *vstep, onchange), children)); ICOMMAND(uisliderarrow, "fe", (float *dir, uint *children), BUILD(SliderArrow, o, o->setup(*dir), children)); ICOMMAND(uisliderbutton, "e", (uint *children), BUILD(SliderButton, o, o->setup(), children)); ICOMMAND(uicolor, "iffe", (int *c, float *minw, float *minh, uint *children), BUILD(FillColor, o, o->setup(FillColor::SOLID, Color(*c), *minw, *minh), children)); ICOMMAND(uimodcolor, "iffe", (int *c, float *minw, float *minh, uint *children), BUILD(FillColor, o, o->setup(FillColor::MODULATE, Color(*c), *minw, *minh), children)); ICOMMAND(uivgradient, "iiffe", (int *c, int *c2, float *minw, float *minh, uint *children), BUILD(Gradient, o, o->setup(Gradient::SOLID, Gradient::VERTICAL, Color(*c), Color(*c2), *minw, *minh), children)); ICOMMAND(uimodvgradient, "iiffe", (int *c, int *c2, float *minw, float *minh, uint *children), BUILD(Gradient, o, o->setup(Gradient::MODULATE, Gradient::VERTICAL, Color(*c), Color(*c2), *minw, *minh), children)); ICOMMAND(uihgradient, "iiffe", (int *c, int *c2, float *minw, float *minh, uint *children), BUILD(Gradient, o, o->setup(Gradient::SOLID, Gradient::HORIZONTAL, Color(*c), Color(*c2), *minw, *minh), children)); ICOMMAND(uimodhgradient, "iiffe", (int *c, int *c2, float *minw, float *minh, uint *children), BUILD(Gradient, o, o->setup(Gradient::MODULATE, Gradient::HORIZONTAL, Color(*c), Color(*c2), *minw, *minh), children)); ICOMMAND(uioutline, "iffe", (int *c, float *minw, float *minh, uint *children), BUILD(Outline, o, o->setup(Color(*c), *minw, *minh), children)); ICOMMAND(uiline, "iffe", (int *c, float *minw, float *minh, uint *children), BUILD(Line, o, o->setup(Color(*c), *minw, *minh), children)); ICOMMAND(uitriangle, "iffie", (int *c, float *minw, float *minh, int *angle, uint *children), BUILD(Triangle, o, o->setup(Color(*c), *minw, *minh, *angle, Triangle::SOLID), children)); ICOMMAND(uitriangleoutline, "iffie", (int *c, float *minw, float *minh, int *angle, uint *children), BUILD(Triangle, o, o->setup(Color(*c), *minw, *minh, *angle, Triangle::OUTLINE), children)); ICOMMAND(uimodtriangle, "iffie", (int *c, float *minw, float *minh, int *angle, uint *children), BUILD(Triangle, o, o->setup(Color(*c), *minw, *minh, *angle, Triangle::MODULATE), children)); ICOMMAND(uicircle, "ife", (int *c, float *size, uint *children), BUILD(Circle, o, o->setup(Color(*c), *size, Circle::SOLID), children)); ICOMMAND(uicircleoutline, "ife", (int *c, float *size, uint *children), BUILD(Circle, o, o->setup(Color(*c), *size, Circle::OUTLINE), children)); ICOMMAND(uimodcircle, "ife", (int *c, float *size, uint *children), BUILD(Circle, o, o->setup(Color(*c), *size, Circle::MODULATE), children)); static inline void buildtext(tagval &t, float scale, float scalemod, const Color &color, float wrap, uint *children) { if(scale <= 0) scale = 1; scale *= scalemod; switch(t.type) { case VAL_INT: BUILD(TextInt, o, o->setup(t.i, scale, color, wrap), children); break; case VAL_FLOAT: BUILD(TextFloat, o, o->setup(t.f, scale, color, wrap), children); break; case VAL_CSTR: case VAL_MACRO: case VAL_STR: if(t.s[0]) { BUILD(TextString, o, o->setup(t.s, scale, color, wrap), children); break; } // fall-through default: BUILD(Object, o, o->setup(), children); break; } } ICOMMAND(uicolortext, "tife", (tagval *text, int *c, float *scale, uint *children), buildtext(*text, *scale, uitextscale, Color(*c), -1, children)); ICOMMAND(uitext, "tfe", (tagval *text, float *scale, uint *children), buildtext(*text, *scale, uitextscale, Color(255, 255, 255), -1, children)); ICOMMAND(uitextfill, "ffe", (float *minw, float *minh, uint *children), BUILD(Filler, o, o->setup(*minw * uitextscale*0.5f, *minh * uitextscale), children)); ICOMMAND(uiwrapcolortext, "tfife", (tagval *text, float *wrap, int *c, float *scale, uint *children), buildtext(*text, *scale, uitextscale, Color(*c), *wrap, children)); ICOMMAND(uiwraptext, "tffe", (tagval *text, float *wrap, float *scale, uint *children), buildtext(*text, *scale, uitextscale, Color(255, 255, 255), *wrap, children)); ICOMMAND(uicolorcontext, "tife", (tagval *text, int *c, float *scale, uint *children), buildtext(*text, *scale, FONTH*uicontextscale, Color(*c), -1, children)); ICOMMAND(uicontext, "tfe", (tagval *text, float *scale, uint *children), buildtext(*text, *scale, FONTH*uicontextscale, Color(255, 255, 255), -1, children)); ICOMMAND(uicontextfill, "ffe", (float *minw, float *minh, uint *children), BUILD(Filler, o, o->setup(*minw * FONTH*uicontextscale*0.5f, *minh * FONTH*uicontextscale), children)); ICOMMAND(uiwrapcolorcontext, "tfife", (tagval *text, float *wrap, int *c, float *scale, uint *children), buildtext(*text, *scale, FONTH*uicontextscale, Color(*c), *wrap, children)); ICOMMAND(uiwrapcontext, "tffe", (tagval *text, float *wrap, float *scale, uint *children), buildtext(*text, *scale, FONTH*uicontextscale, Color(255, 255, 255), *wrap, children)); ICOMMAND(uitexteditor, "siifsie", (char *name, int *length, int *height, float *scale, char *initval, int *mode, uint *children), BUILD(TextEditor, o, o->setup(name, *length, *height, (*scale <= 0 ? 1 : *scale) * uitextscale, initval, *mode <= 0 ? EDITORFOREVER : *mode), children)); ICOMMAND(uifont, "se", (char *name, uint *children), BUILD(Font, o, o->setup(name), children)); ICOMMAND(uiabovehud, "", (), { if(window) window->abovehud = true; }); ICOMMAND(uiconsole, "ffe", (float *minw, float *minh, uint *children), BUILD(Console, o, o->setup(*minw, *minh), children)); ICOMMAND(uifield, "riefe", (ident *var, int *length, uint *onchange, float *scale, uint *children), BUILD(Field, o, o->setup(var, *length, onchange, (*scale <= 0 ? 1 : *scale) * uitextscale), children)); ICOMMAND(uikeyfield, "riefe", (ident *var, int *length, uint *onchange, float *scale, uint *children), BUILD(KeyField, o, o->setup(var, *length, onchange, (*scale <= 0 ? 1 : *scale) * uitextscale), children)); ICOMMAND(uiimage, "sffe", (char *texname, float *minw, float *minh, uint *children), BUILD(Image, o, o->setup(textureload(texname, 3, true, false), *minw, *minh), children)); ICOMMAND(uistretchedimage, "sffe", (char *texname, float *minw, float *minh, uint *children), BUILD(StretchedImage, o, o->setup(textureload(texname, 3, true, false), *minw, *minh), children)); static inline float parsepixeloffset(const tagval *t, int size) { switch(t->type) { case VAL_INT: return t->i; case VAL_FLOAT: return t->f; case VAL_NULL: return 0; default: { const char *s = t->getstr(); char *end; float val = strtod(s, &end); return *end == 'p' ? val/size : val; } } } ICOMMAND(uicroppedimage, "sfftttte", (char *texname, float *minw, float *minh, tagval *cropx, tagval *cropy, tagval *cropw, tagval *croph, uint *children), BUILD(CroppedImage, o, { Texture *tex = textureload(texname, 3, true, false); o->setup(tex, *minw, *minh, parsepixeloffset(cropx, tex->xs), parsepixeloffset(cropy, tex->ys), parsepixeloffset(cropw, tex->xs), parsepixeloffset(croph, tex->ys)); }, children)); ICOMMAND(uiborderedimage, "stfe", (char *texname, tagval *texborder, float *screenborder, uint *children), BUILD(BorderedImage, o, { Texture *tex = textureload(texname, 3, true, false); o->setup(tex, parsepixeloffset(texborder, tex->xs), *screenborder); }, children)); ICOMMAND(uitiledimage, "sffffe", (char *texname, float *tilew, float *tileh, float *minw, float *minh, uint *children), BUILD(TiledImage, o, { Texture *tex = textureload(texname, 0, true, false); o->setup(tex, *minw, *minh, *tilew <= 0 ? 1 : *tilew, *tileh <= 0 ? 1 : *tileh); }, children)); ICOMMAND(uimodelpreview, "ssffe", (char *model, char *animspec, float *minw, float *minh, uint *children), BUILD(ModelPreview, o, o->setup(model, animspec, *minw, *minh), children)); ICOMMAND(uiplayerpreview, "iiiiffe", (int *model, int *color, int *team, int *weapon, float *minw, float *minh, uint *children), BUILD(PlayerPreview, o, o->setup(*model, *color, *team, *weapon, *minw, *minh), children)); ICOMMAND(uiprefabpreview, "siffe", (char *prefab, int *color, float *minw, float *minh, uint *children), BUILD(PrefabPreview, o, o->setup(prefab, *color, *minw, *minh), children)); ICOMMAND(uislotview, "iffe", (int *index, float *minw, float *minh, uint *children), BUILD(SlotViewer, o, o->setup(*index, *minw, *minh), children)); ICOMMAND(uivslotview, "iffe", (int *index, float *minw, float *minh, uint *children), BUILD(VSlotViewer, o, o->setup(*index, *minw, *minh), children)); FVARP(uisensitivity, 1e-4f, 1, 1e4f); bool hascursor() { return world->allowinput(); } void getcursorpos(float &x, float &y) { if(hascursor()) { x = cursorx; y = cursory; } else x = y = 0.5f; } void resetcursor() { cursorx = cursory = 0.5f; } bool movecursor(int dx, int dy) { if(!hascursor()) return false; cursorx = clamp(cursorx + dx*uisensitivity/hudw, 0.0f, 1.0f); cursory = clamp(cursory + dy*uisensitivity/hudh, 0.0f, 1.0f); return true; } bool keypress(int code, bool isdown) { if(world->rawkey(code, isdown)) return true; int action = 0, hold = 0; switch(code) { case -1: action = isdown ? STATE_PRESS : STATE_RELEASE; hold = STATE_HOLD; break; case -2: action = isdown ? STATE_ALT_PRESS : STATE_ALT_RELEASE; hold = STATE_ALT_HOLD; break; case -3: action = isdown ? STATE_ESC_PRESS : STATE_ESC_RELEASE; hold = STATE_ESC_HOLD; break; case -4: action = STATE_SCROLL_UP; break; case -5: action = STATE_SCROLL_DOWN; break; } if(action) { if(isdown) { if(hold) world->clearstate(hold); if(world->setstate(action, cursorx, cursory, 0, true, action|hold)) return true; } else if(hold) { if(world->setstate(action, cursorx, cursory, hold, true, action)) { world->clearstate(hold); return true; } world->clearstate(hold); } return world->allowinput(); } return world->key(code, isdown); } bool textinput(const char *str, int len) { return world->textinput(str, len); } void setup() { world = new World; } void cleanup() { world->children.setsize(0); enumerate(windows, Window *, w, delete w); windows.clear(); DELETEP(world); } void calctextscale() { uitextscale = 1.0f/uitextrows; int tw = hudw, th = hudh; if(forceaspect) tw = int(ceil(th*forceaspect)); gettextres(tw, th); uicontextscale = conscale/th; } void update() { readyeditors(); world->setstate(STATE_HOVER, cursorx, cursory, world->childstate&STATE_HOLD_MASK); if(world->childstate&STATE_HOLD) world->setstate(STATE_HOLD, cursorx, cursory, STATE_HOLD, false); if(world->childstate&STATE_ALT_HOLD) world->setstate(STATE_ALT_HOLD, cursorx, cursory, STATE_ALT_HOLD, false); if(world->childstate&STATE_ESC_HOLD) world->setstate(STATE_ESC_HOLD, cursorx, cursory, STATE_ESC_HOLD, false); calctextscale(); world->build(); flusheditors(); } void render() { world->layout(); world->adjustchildren(); world->draw(); } float abovehud() { return world->abovehud(); } }