OctaCore/src/engine/ui.cc

3586 lines
110 KiB
C++
Raw Normal View History

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)
{