2020-04-16 18:28:40 +00:00
|
|
|
#include "engine.hh"
|
|
|
|
#include "textedit.hh"
|
2020-04-15 16:39:17 +00:00
|
|
|
|
|
|
|
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<ClipArea> 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<type>(); \
|
|
|
|
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<Object *> 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)<<ALIGN_HSHIFT;
|
|
|
|
adjust |= (clamp(yalign, -2, 1)+2)<<ALIGN_VSHIFT;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setclamp(int left, int right, int top, int bottom)
|
|
|
|
{
|
|
|
|
adjust &= ~CLAMP_MASK;
|
|
|
|
if(left) adjust |= CLAMP_LEFT;
|
|
|
|
if(right) adjust |= CLAMP_RIGHT;
|
|
|
|
if(top) adjust |= CLAMP_TOP;
|
|
|
|
if(bottom) adjust |= CLAMP_BOTTOM;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool target(float cx, float cy)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool rawkey(int code, bool isdown)
|
|
|
|
{
|
|
|
|
loopchildrenrev(o,
|
|
|
|
{
|
|
|
|
if(o->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<class T> 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<class T> T *buildtype()
|
|
|
|
{
|
|
|
|
T *t;
|
|
|
|
if(children.inrange(buildchild))
|
|
|
|
{
|
|
|
|
Object *o = children[buildchild];
|
|
|
|
if(o->istype<T>()) 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<Window *> 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<float> 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<type>(); \
|
|
|
|
setup; \
|
|
|
|
o->buildchildren(columndata, contents); \
|
|
|
|
} \
|
|
|
|
} while(0)
|
|
|
|
|
|
|
|
struct Table : Object
|
|
|
|
{
|
|
|
|
float spacew, spaceh, subw, subh;
|
|
|
|
vector<float> widths;
|
|
|
|
|
|
|
|
static const char *typestr() { return "#Table"; }
|
|
|
|
const char *gettype() const { return typestr(); }
|
|
|
|
|
|
|
|
void setup(float spacew_ = 0, float spaceh_ = 0)
|
|
|
|
{
|
|