engine/quakec/menusys/menusys/mitem_frame.qc

743 lines
21 KiB
Plaintext

/***************************************************************************
Basic frame, containing sub-items.
Logically the parent of menu objects. Also used for tabs.
*/
#ifndef MITEM_FRAME_QC__
#define MITEM_FRAME_QC__
class mitem_vslider : mitem
{
virtual float(vector pos, float scan, float char, float down) item_keypress;
virtual void(vector pos) item_draw;
void() mitem_vslider;
float val;
float minv;
float maxv;
float stride; //size of one 'notch'
};
class mitem_frame : mitem
{
virtual void(mitem newfocus, float changedflag) item_focuschange;
virtual float(vector pos, float scan, float char, float down) item_keypress;
virtual void() item_resized;
virtual void(vector pos) item_draw;
virtual void() item_remove;
virtual void(mitem fromitem, string cmd) item_execcommand;
mitem item_children;
mitem item_mactivechild;
mitem item_kactivechild;
mitem item_exclusive;
mitem_vslider vslider; //displayed if any client item's max[y] > our item_size[y]
vector item_framesize; //x=sides, y=top, z=bottom
float frame_stdheight; //adjust RS_Y_MIN_PARENT_MID to pinch inwards when the frame is smaller than this. Child items crossing the mid-point must size gracefully (like subframes).
float frame_hasscroll;
static mitem(string title) findchildtext;
static mitem(string title) findchildcmd;
static void(mitem newitem, float originflags, vector originmin, vector originmax) add;
static void(mitem newitem, vector pos) adda;
static void(mitem newitem, vector originmin, vector originmax) addm;
static void(mitem newitem, float originflags, vector originmin, vector originmax) addr;
static void(mitem newitem, float ypos) addc;
void() mitem_frame =
{
item_flags |= IF_ISFRAME;
};
};
void(mitem ch, vector parentsize) mitem_parentresized =
{
float f = ch.resizeflags;
if (f & RS_X_FRACTION)
ch.item_position[0] = parentsize[0] * ch.mins[0];
else if (f & RS_X_MIN_PARENT_MAX)
ch.item_position[0] = parentsize[0] + ch.mins[0];
else if (f & RS_X_MIN_PARENT_MID)
ch.item_position[0] = parentsize[0]/2 + ch.mins[0];
else //if (f & RS_X_MIN_PARENT_MIN)
ch.item_position[0] = ch.mins[0];
if (f & RS_X_FRACTION)
ch.item_position[0] = parentsize[0] * ch.maxs[0];
else if (f & RS_X_MAX_PARENT_MIN)
ch.item_size[0] = ch.maxs[0] - ch.item_position[0];
else if (f & RS_X_MAX_PARENT_MID)
ch.item_size[0] = ch.maxs[0]+parentsize[0]/2 - ch.item_position[0];
else if (f & RS_X_MAX_PARENT_MAX)
ch.item_size[0] = ch.maxs[0]+parentsize[0] - ch.item_position[0];
else //if (f & RS_X_MAX_OWN_MIN)
ch.item_size[0] = ch.maxs[0];
if (f & RS_Y_FRACTION)
ch.item_position[1] = parentsize[1] * ch.mins[1];
else if (f & RS_Y_MIN_PARENT_MAX)
ch.item_position[1] = parentsize[1] + ch.mins[1];
else if (f & RS_Y_MIN_PARENT_MID)
{
ch.item_position[1] = parentsize[1]/2 + ch.mins[1];
if (ch.item_parent.frame_stdheight)
if (parentsize[1] < ch.item_parent.frame_stdheight)
{
if (ch.item_position[1] < parentsize[1]*0.5)
ch.item_position[1] += (ch.item_parent.frame_stdheight-parentsize[1])*0.5;
else
ch.item_position[1] -= (ch.item_parent.frame_stdheight-parentsize[1])*0.5;
}
}
else //if (f & RS_Y_MIN_PARENT_MIN)
ch.item_position[1] = ch.mins[1];
if (f & RS_Y_FRACTION)
ch.item_position[1] = parentsize[1] * ch.maxs[1];
else if (f & RS_Y_MAX_PARENT_MIN)
ch.item_size[1] = ch.maxs[1] - ch.item_position[1];
else if (f & RS_Y_MAX_PARENT_MID)
{
ch.item_size[1] = ch.maxs[1]+parentsize[1]/2 - ch.item_position[1];
if (ch.item_parent.frame_stdheight)
if (parentsize[1] < ch.item_parent.frame_stdheight)
{
if (ch.item_position[1]+ch.item_size[1] < parentsize[1]*0.5)
ch.item_size[1] += (ch.item_parent.frame_stdheight-parentsize[1])*0.5;
else
ch.item_size[1] -= (ch.item_parent.frame_stdheight-parentsize[1])*0.5;
}
}
else if (f & RS_Y_MAX_PARENT_MAX)
ch.item_size[1] = ch.maxs[1]+parentsize[1] - ch.item_position[1];
else //if (f & RS_Y_MAX_OWN_MIN)
ch.item_size[1] = ch.maxs[1];
};
void(mitem fromitem, string cmd) mitem_frame::item_execcommand =
{
if (item_parent)
item_parent.item_execcommand(fromitem, cmd);
else
localcmd(strcat(cmd, "\n"));
};
mitem(string title) mitem_frame::findchildtext =
{
mitem ch;
for (ch = item_children; ch; ch = ch.item_next)
{
if (ch.item_text == title)
return ch;
}
return __NULL__;
};
mitem(string title) mitem_frame::findchildcmd =
{
mitem ch;
for (ch = item_children; ch; ch = ch.item_next)
{
if (ch.item_command == title)
return ch;
}
return __NULL__;
};
//adds an item with the desired origin settings
void(mitem newitem, float originflags, vector originmin, vector originmax) mitem_frame::add =
{
newitem.item_next = item_children;
item_children = newitem;
newitem.item_parent = this;
//set up position and resize
newitem.resizeflags = originflags;
newitem.mins = originmin;
newitem.maxs = originmax;
local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])];
mitem_parentresized(newitem, parentsize);
newitem.item_resized();
item_flags |= IF_CLIENTMOVED; //make sure it happens.
// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE))
// item_focuschange(newitem, IF_KFOCUSED);
};
//adds an item to the parent with an absolute position relative to the parent's top-left. objects are expected to already have a size specified.
void(mitem newitem, vector pos) mitem_frame::adda =
{
local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])];
newitem.item_next = item_children;
item_children = newitem;
newitem.item_parent = this;
newitem.resizeflags = RS_X_MIN_PARENT_MIN | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN;
newitem.mins = pos;
newitem.maxs = newitem.item_size;
mitem_parentresized(newitem, parentsize);
newitem.item_resized();
item_flags |= IF_CLIENTMOVED; //make sure it happens.
// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE))
// item_focuschange(newitem, IF_KFOCUSED);
};
//adds an item to the parent in reverse order (ie: at the tail, so the actual order in code)
void(mitem newitem, float originflags, vector originmin, vector originmax) mitem_frame::addr =
{
local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])];
if (item_children)
{
local mitem prev;
for (prev = item_children; prev.item_next; prev = prev.item_next)
;
prev.item_next = newitem;
newitem.item_next = __NULL__;
}
else
{
newitem.item_next = item_children;
item_children = newitem;
}
newitem.item_parent = this;
newitem.resizeflags = originflags;
newitem.mins = originmin;
newitem.maxs = originmax;
mitem_parentresized(newitem, parentsize);
newitem.item_resized();
item_flags |= IF_CLIENTMOVED; //make sure it happens.
// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE))
// item_focuschange(newitem, IF_KFOCUSED);
};
//adds an item on the parent with the x coord centered, and absolute y.
//if multiple items are at the same ypos, it will recenter all with respect to the others
void(mitem newitem, float ypos) mitem_frame::addc =
{
float w, c;
local mitem prev;
local vector parentsize = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])];
newitem.item_next = item_children;
item_children = newitem;
newitem.item_position[1] = ypos;
newitem.item_position[0] = (parentsize[0] - newitem.item_size[0])*0.5;
newitem.item_parent = this;
newitem.resizeflags = RS_X_MIN_PARENT_MID | RS_X_MAX_OWN_MIN | RS_Y_MIN_PARENT_MIN | RS_Y_MAX_OWN_MIN;
newitem.mins[0] = 0;
newitem.mins[1] = ypos;
newitem.maxs = newitem.item_size;
//count the width of the other items at this height.
w = 0;
c = 0;
for (prev = item_children; prev; prev = prev.item_next)
if (prev.resizeflags == newitem.resizeflags && prev.mins[1] == ypos && prev.maxs[1] == newitem.maxs[1])
{
w += prev.maxs[0];
c += 1;
}
//distribute them evenly (from the right, because its add-at-head)
w += (c-1)*16; //this much gap space
for (prev = item_children; prev; prev = prev.item_next)
{
if (prev.resizeflags == newitem.resizeflags && prev.mins[1] == ypos && prev.maxs[1] == newitem.maxs[1])
{
w -= prev.maxs[0];
prev.mins[0] = (w+prev.maxs[0])/-2;
mitem_parentresized(prev, parentsize);
w -= 16;
}
}
newitem.item_resized();
item_flags |= IF_CLIENTMOVED; //make sure it happens.
// if (!item_kactivechild && (newitem.item_flags & IF_SELECTABLE))
// item_focuschange(newitem, IF_KFOCUSED);
};
//Adds the item in the exact middle of the parent, in both axis
void(mitem newitem, vector min, vector max) mitem_frame::addm =
{
this.add(newitem, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, min, max);
};
//updates this.item_activechild, and calls focus notifications to ensure things get the message.
//flag should be IF_MFOCUSED or IF_KFOCUSED
void(mitem newfocus, float flag) mitem_frame::item_focuschange =
{
local mitem it;
if (newfocus == this)
{
//our focus didn't change, but the parent is telling us that *it* got changed while we're the focus
if (flag & IF_MFOCUSED)
{
if (item_parent.item_flags & IF_MFOCUSED && item_parent.item_mactivechild == this)
item_flags |= IF_MFOCUSED;
else
item_flags = item_flags - (item_flags&IF_MFOCUSED);
//and tell the child
it = item_mactivechild;
if (it)
if (it.item_focuschange)
it.item_focuschange(it, IF_MFOCUSED);
}
if (flag & IF_KFOCUSED)
{
if (item_parent.item_flags & IF_KFOCUSED && item_parent.item_kactivechild == this)
item_flags |= IF_KFOCUSED;
else
item_flags = item_flags - (item_flags&IF_KFOCUSED);
//and tell the child
it = item_kactivechild;
if (it)
if (it.item_focuschange)
it.item_focuschange(it, IF_KFOCUSED);
}
return;
}
if ((flag & IF_MFOCUSED) && item_mactivechild != newfocus)
{
//make key focus follow the mouse cursor. this should probably only apply to button menus or something? :s
if (newfocus && (item_flags & IF_FOCUSFOLLOWSMOUSE))
flag |= IF_KFOCUSED;
it = item_mactivechild;
item_mactivechild = newfocus;
if (it)
{
it.item_flags = it.item_flags - (it.item_flags&IF_MFOCUSED);
if (it.item_focuschange)
it.item_focuschange(it, IF_MFOCUSED);
}
it = item_mactivechild;
if (it)
{
it.item_flags = it.item_flags | (item_flags & IF_MFOCUSED);
if (it.item_focuschange)
it.item_focuschange(it, IF_MFOCUSED);
}
}
if ((flag & IF_KFOCUSED) && item_kactivechild != newfocus)
{
it = item_kactivechild;
item_kactivechild = newfocus;
if (it)
{
it.item_flags = it.item_flags - (it.item_flags&IF_KFOCUSED);
if (it.item_focuschange)
it.item_focuschange(it, IF_KFOCUSED);
}
it = item_kactivechild;
if (it)
{
it.item_flags = it.item_flags | (item_flags & IF_KFOCUSED);
if (it.item_focuschange)
it.item_focuschange(it, IF_KFOCUSED);
}
}
};
float(vector pos, float scan, float char, float down) mitem_vslider::item_keypress =
{
if (down != 1)
{
if (scan == K_MOUSE1 && ui.mgrabs == this)
ui.mgrabs = __NULL__;
return FALSE;
}
if (scan == K_MWHEELDOWN)
val = bound(minv, val+stride, maxv);
else if (scan == K_MWHEELUP)
val = bound(minv, val-stride, maxv);
else if (scan == K_MOUSE1)
ui.mgrabs = this;
else
return FALSE;
return TRUE;
};
void(vector pos) mitem_vslider::item_draw =
{
float f;
float w = item_size[0];
float h = item_size[1];
float isize = w;
vector v = dp_workarounds?'0 0 0':drawgetimagesize("scrollbars/slider.tga");
if (v != '0 0 0')
{
ui.drawpic(pos, "scrollbars/arrow_up.tga", [w, w], '1 1 1', item_alpha, 0); //top
pos_y += w;
h -= w*2;
ui.drawpic(pos + [0, h], "scrollbars/arrow_down.tga", [w, w], '1 1 1', item_alpha, 0); //bottom
ui.drawpic(pos, "scrollbars/slidebg.tga", [w, h], '1 1 1', item_alpha, 0); //back-middle
isize = (v_y * w) / (v_x);
if (isize > h/2)
isize = h/2;
}
else
{
// ui.drawfill(pos, [w, w], '1 1 1', item_alpha, 0); //top
// pos_y += w;
// h -= w*2;
// ui.drawfill(pos + [0, h], [w, w], '1 1 1', item_alpha, 0); //bottom
ui.drawfill(pos, [w, h], '0.5 0.5 0.5', item_alpha, 0); //back-middle
}
if (ui.mgrabs == this)
{
f = (ui.mousepos[1] - pos_y - (isize/2))/(h - isize);
f = bound(0, f, 1);
val = minv + (f * (maxv - minv));
}
else
f = bound(0, (val - minv) / (maxv - minv), 1);
h -= isize;
if (v != '0 0 0')
ui.drawpic(pos + [0, h*f], "scrollbars/slider.tga", [w, isize], '1 1 1', item_alpha, 0); //back-middle
else
ui.drawfill(pos + [0, h*f], [w, isize], '1 1 1', item_alpha, 0); //back-middle
};
void() mitem_vslider::mitem_vslider =
{
item_size[0] = 8;
item_size[1] = 128;
};
//does NOT wrap.
//does NOT pass go.
static mitem(mitem item, float upwards) menu_simplenextitem =
{
mitem_frame menu = item.item_parent;
mitem prev;
if (upwards)
{
if (item)
{
item = item.item_next;
if (!item)
return __NULL__;
return item;
}
else
return __NULL__;
}
else
{
for(prev = menu.item_children; prev.item_next; prev = prev.item_next)
{
if (prev.item_next == item)
return prev;
}
return __NULL__;
}
};
//finds the next/prev item through multiple children, returning NULL when it runs out of items in the sequence.
//call this with item==null to find the first item in the sequence (to handle wraps).
static mitem(mitem_frame menu, float upwards, mitem item) menu_findnextitem =
{
mitem_frame frame;
mitem citem;
if (item && (item.item_flags & IF_ISFRAME))
{
frame = (mitem_frame)item;
citem = menu_findnextitem(frame, upwards, frame.item_kactivechild?frame.item_kactivechild:frame.item_mactivechild);
if (citem)
return citem;
}
for(;;)
{
if (!item)
{ //we go for the opposite end here, as we assume to be starting/unfocused
item = menu.item_children;
if (!upwards && item)
{
while(item.item_next)
item = item.item_next;
}
}
else
item = menu_simplenextitem(item, upwards);
if (!item)
{ //we reached the end of the list, let the parent frame try its next
return __NULL__;
}
if (item.item_flags & IF_ISFRAME)
{ //if the next item is a frame, try and select its first element instead
frame = (mitem_frame)item;
citem = menu_findnextitem(frame, upwards, __NULL__);
if (citem)
return citem;
}
if (item.item_flags & IF_INVISIBLE)
continue;
if (item.item_flags & IF_SELECTABLE)
return item;
}
};
static void(mitem item) menu_deselectitem =
{
if (!item)
return;
if (item && (item.item_flags & IF_ISFRAME))
{
mitem_frame frame = (mitem_frame)item;
if (frame.item_kactivechild)
menu_deselectitem(frame.item_kactivechild);
}
item.item_focuschange(__NULL__, IF_KFOCUSED); //deselect the previous one
};
static void(mitem item) menu_selectitem =
{
if (!item)
return;
mitem_frame menu = item.item_parent;
if (menu)
{
if (menu.item_kactivechild != item)
menu_deselectitem(menu.item_kactivechild);
menu_selectitem(menu);
menu.item_focuschange(item, IF_KFOCUSED); //focus on the new
}
};
void(mitem_frame rootmenu, float upwards) menu_selectnextitem =
{
mitem item = menu_findnextitem(rootmenu, upwards, rootmenu.item_kactivechild?rootmenu.item_kactivechild:rootmenu.item_mactivechild);
if (!item)
item = menu_findnextitem(rootmenu, upwards, __NULL__);
menu_selectitem(item);
item.item_focuschange(item, IF_KFOCUSED); //focus on the new
};
float(vector pos, float scan, float char, float down) mitem_frame::item_keypress =
{
mitem ch;
float handled = FALSE;
//compensate for the frame
pos[0] += item_framesize[0];
pos[1] += item_framesize[1];
if (scan >= K_MOUSE1 && scan <= K_MOUSE5 && scan != K_MWHEELUP && scan != K_MWHEELDOWN)
{
if (item_exclusive)
ch = item_exclusive;
else
ch = item_mactivechild;
if (down) //keyboard focus follows on mouse click.
{
item_focuschange(ch, IF_KFOCUSED);
}
}
else
{
ch = item_kactivechild;
if (!ch && down)
{
//if there's no key child active, then go and pick one so you can just start using keyboard access etc.
if (item_exclusive)
ch = item_exclusive;
else if (item_mactivechild)
ch = item_mactivechild;
else
{
mitem c;
for (c = item_children; c; c = c.item_next)
{
if (c.item_flags & IF_SELECTABLE)
ch = c;
}
}
if (ch)
item_focuschange(ch, IF_KFOCUSED);
ch = item_kactivechild;
}
}
if (vslider)
pos[1] -= vslider.val;
if (ch)
{
if (ch.item_keypress)
handled = ch.item_keypress(pos + ch.item_position, scan, char, down);
}
if (vslider && !handled && (scan == K_MWHEELUP || scan == K_MWHEELDOWN)) //give mwheel to the slider if its visible.
handled = vslider.item_keypress(pos + vslider.item_position, scan, char, down);
return handled;
};
void() mitem_frame::item_remove =
{
local mitem ch;
while (this.item_children)
{
ch = this.item_children;
this.item_children = ch.item_next;
ch.item_remove();
}
super::item_remove();
};
void() mitem_frame::item_resized =
{
mitem ch;
vector framemax = [item_size[0] - item_framesize[0]*2, item_size[1] - (item_framesize[1] + item_framesize[2])];
for (ch = this.item_children; ch; ch = ch.item_next)
{
vector os = ch.item_size;
mitem_parentresized(ch, framemax);
if (ch.item_resized && ch.item_size != os)
ch.item_resized();
}
item_flags |= IF_CLIENTMOVED; //make sure it happens.
super::item_resized();
};
void(vector pos) mitem_frame::item_draw =
{
local vector omin = ui.drawrectmin, omax = ui.drawrectmax;
local mitem ch;
local vector clientpos;
local vector clientsize;
if (frame_hasscroll && (item_flags & IF_CLIENTMOVED))
{
//if a client object moved, make sure the scrollbar is updated
item_flags &~= IF_CLIENTMOVED;
clientsize= '0 0';
for(ch = item_children; ch; ch = ch.item_next)
{
if (clientsize[0] < ch.item_position[0] + ch.item_size[0])
clientsize[0] = ch.item_position[0] + ch.item_size[0];
if (clientsize[1] < ch.item_position[1] + ch.item_size[1])
clientsize[1] = ch.item_position[1] + ch.item_size[1];
}
// if (clientsize[0] > item_size[0] - item_framesize[0]*2)
// //add hscroll
if (clientsize[1] > item_size[1] - (item_framesize[1]+item_framesize[2]))
{
if (!vslider)
{
local mitem_vslider tmp;
tmp = spawn(mitem_vslider, val:0, stride:8);
vslider = tmp;
}
vslider.maxv = clientsize[1] - (item_size[1] - (item_framesize[1]+item_framesize[2]));
}
else if (vslider)
{
vslider.item_remove();
vslider = (mitem_vslider)__NULL__;
}
}
else if (!frame_hasscroll && this.vslider)
{
vslider.item_remove();
vslider = (mitem_vslider)__NULL__;
}
//compensate for the frame
pos[0] += item_framesize[0];
pos[1] += item_framesize[1];
clientpos = pos;
clientsize = this.item_size;
clientsize[0] -= item_framesize[0]*2;
clientsize[1] -= (item_framesize[1]+item_framesize[2]);
if (vslider)
{
//scroll+shrink the client area to fit the slider on it.
clientpos[1] -= vslider.val;
clientsize[0] -= vslider.item_size[0];
}
if (ui.mousepos != ui.oldmousepos)
{
local mitem newfocus = __NULL__;
//if the mouse moved, select the new item
if (item_exclusive)
newfocus = item_exclusive;
else
{
for(ch = item_children; ch; ch = ch.item_next)
{
if (ch.item_flags & IF_SELECTABLE)
if (mouseinbox(clientpos + ch.item_position, ch.item_size))
{
newfocus = ch;
}
}
}
if (vslider)
if (mouseinbox(pos + [clientsize[0], 0], vslider.item_size))
newfocus = vslider;
this.item_focuschange(newfocus, IF_MFOCUSED);
}
//clip the draw rect to our area, so children don't draw outside it. don't draw if its inverted.
if (pos_x > ui.drawrectmin[0])
ui.drawrectmin[0] = pos_x;
if (pos_y > ui.drawrectmin[1])
ui.drawrectmin[1] = pos_y;
if (pos_x+clientsize_x < ui.drawrectmax[0])
ui.drawrectmax[0] = pos_x+clientsize_x;
if (pos_y+clientsize_y < ui.drawrectmax[1])
ui.drawrectmax[1] = pos_y+clientsize[1];
if (ui.drawrectmax[0] > ui.drawrectmin[0] && ui.drawrectmax[1] > ui.drawrectmin[1])
{
ui.setcliparea(ui.drawrectmin[0], ui.drawrectmin[1], ui.drawrectmax[0] - ui.drawrectmin[0], ui.drawrectmax[1] - ui.drawrectmin[1]);
if (item_exclusive)
item_exclusive.item_draw(clientpos + ch.item_position);
else
{
for(ch = item_children; ch; ch = ch.item_next)
{
if not (ch.item_flags & IF_INVISIBLE)
ch.item_draw(clientpos + ch.item_position);
}
}
ui.setcliparea(omin_x, omin_y, omax_x - omin_x, omax_y - omin_y);
if (vslider)
{
vslider.item_size[1] = clientsize[1];
vslider.item_draw(pos + [clientsize[0], 0]);
}
}
ui.drawrectmin = omin;
ui.drawrectmax = omax;
};
#define menuitemframe_spawn(sz) spawn(mitem_frame, item_size:sz)
#endif //MITEM_FRAME_QC__