675 lines
14 KiB
ActionScript
675 lines
14 KiB
ActionScript
import elements.IGuiElement;
|
|
import elements.Alignment;
|
|
from gui import get_nextGuiID, getRootGuiElement, setGuiFocus, setGuiAbsorb, getGuiFocus, isGuiFocusIn, clearGuiHovered, gui_root;
|
|
|
|
interface IGuiCallback {
|
|
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source);
|
|
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source);
|
|
bool onGuiEvent(const GuiEvent& event);
|
|
};
|
|
|
|
class GuiCallback {
|
|
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
|
|
return false;
|
|
}
|
|
|
|
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
|
|
return false;
|
|
}
|
|
|
|
bool onGuiEvent(const GuiEvent& event) {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class SimpleTooltip : ITooltip {
|
|
string text;
|
|
|
|
SimpleTooltip(const string& txt) {
|
|
text = txt;
|
|
}
|
|
|
|
float get_delay() {
|
|
return 0.5f;
|
|
}
|
|
|
|
bool get_persist() {
|
|
return false;
|
|
}
|
|
|
|
void update(const Skin@ skin, IGuiElement@ elem) {
|
|
text = elem.get_tooltip();
|
|
}
|
|
|
|
void show(const Skin@ skin, IGuiElement@ elem) {
|
|
}
|
|
|
|
void hide(const Skin@ skin, IGuiElement@ elem) {
|
|
}
|
|
|
|
void draw(const Skin@ skin, IGuiElement@ elem) {
|
|
const Font@ ft = skin.getFont(FT_Normal);
|
|
vec2i size = ft.getDimension(text);
|
|
vec2i pos(mousePos.x + 12, mousePos.y);
|
|
|
|
skin.draw(SS_Tooltip, SF_Normal, recti_area(pos, size + vec2i(8, 8)));
|
|
skin.draw(FT_Normal, pos + vec2i(4, 4), text);
|
|
}
|
|
};
|
|
|
|
class BaseGuiElement : IGuiElement {
|
|
int id = nextGuiID;
|
|
IGuiElement@ Parent;
|
|
IGuiElement@[] Children;
|
|
KeybindGroup@ keybinds;
|
|
const Skin@ skin = getSkin(settings::sSkinName);
|
|
Alignment@ Alignment;
|
|
IGuiCallback@ callback;
|
|
recti Position;
|
|
recti ClipRect;
|
|
bool NoClip = false;
|
|
bool Visible = true;
|
|
int TabIndex = -1;
|
|
recti AbsolutePosition;
|
|
recti AbsoluteClipRect;
|
|
ITooltip@ Tooltip;
|
|
bool Navigable = false;
|
|
bool StrictBounds = false;
|
|
|
|
BaseGuiElement(IGuiElement@ ParentElement, const recti& Rectangle) {
|
|
Position = Rectangle;
|
|
ClipRect = recti(vec2i(), Position.size);
|
|
|
|
_BaseGuiElement(ParentElement);
|
|
}
|
|
|
|
BaseGuiElement(IGuiElement@ ParentElement, Alignment@ align) {
|
|
@Alignment = align;
|
|
|
|
_BaseGuiElement(ParentElement);
|
|
}
|
|
|
|
BaseGuiElement(bool NoParent) {
|
|
if(!NoParent)
|
|
throw("NoParent must be true");
|
|
}
|
|
|
|
void _BaseGuiElement(IGuiElement@ ParentElement) {
|
|
if(ParentElement is null)
|
|
@Parent = getRootGuiElement();
|
|
else
|
|
@Parent = ParentElement;
|
|
|
|
if(Parent !is null)
|
|
Parent.addChild(this);
|
|
}
|
|
|
|
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
|
|
if(callback !is null)
|
|
if(callback.onMouseEvent(event, source))
|
|
return true;
|
|
|
|
if(Parent !is null)
|
|
return Parent.onMouseEvent(event,source);
|
|
else if(callback !is null)
|
|
return callback.onMouseEvent(event,source);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
|
|
if(callback !is null)
|
|
if(callback.onKeyEvent(event, source))
|
|
return true;
|
|
|
|
if(keybinds !is null && (event.type == KET_Key_Down || event.type == KET_Key_Up)) {
|
|
int key = modifyKey(event.key);
|
|
Keybind bind = keybinds.getBind(key);
|
|
|
|
if(bind != KB_NONE) {
|
|
GuiEvent evt;
|
|
evt.value = bind;
|
|
@evt.caller = this;
|
|
|
|
//Emit the right event
|
|
switch(event.type) {
|
|
case KET_Key_Down:
|
|
evt.type = GUI_Keybind_Down;
|
|
break;
|
|
case KET_Key_Up:
|
|
evt.type = GUI_Keybind_Up;
|
|
break;
|
|
}
|
|
onGuiEvent(evt);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(Parent !is null)
|
|
return Parent.onKeyEvent(event,source);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool onGuiEvent(const GuiEvent& event) {
|
|
if(callback !is null)
|
|
if(callback.onGuiEvent(event))
|
|
return true;
|
|
|
|
if(Parent !is null)
|
|
return Parent.onGuiEvent(event);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
string get_elementType() const {
|
|
return "unknown";
|
|
}
|
|
|
|
int get_id() const {
|
|
return id;
|
|
}
|
|
|
|
IGuiElement@ elementFromPosition(const vec2i& pos) {
|
|
if(StrictBounds) {
|
|
if(!AbsoluteClipRect.isWithin(pos))
|
|
return null;
|
|
}
|
|
|
|
uint cCnt = Children.length();
|
|
for(int i = cCnt - 1; i >= 0; --i) {
|
|
if(!Children[i].visible)
|
|
continue;
|
|
|
|
IGuiElement@ ele = Children[i].elementFromPosition(pos);
|
|
|
|
if(ele !is null)
|
|
return ele;
|
|
}
|
|
|
|
if(noClip) {
|
|
if(AbsolutePosition.isWithin(pos))
|
|
return this;
|
|
}
|
|
else {
|
|
if(AbsoluteClipRect.isWithin(pos))
|
|
return this;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void updateAbsolutePosition() {
|
|
if(Parent !is null) {
|
|
if(Alignment !is null) {
|
|
Position = Alignment.resolve(Parent.updatePosition.size);
|
|
ClipRect = recti(vec2i(), Position.size);
|
|
}
|
|
|
|
AbsolutePosition = Position + Parent.updatePosition.topLeft;
|
|
AbsoluteClipRect = (ClipRect + AbsolutePosition.topLeft).clipAgainst(Parent.absoluteClipRect);
|
|
}
|
|
else {
|
|
if(Alignment !is null) {
|
|
Position = Alignment.resolve(screenSize);
|
|
ClipRect = recti(vec2i(), Position.size);
|
|
}
|
|
|
|
AbsolutePosition = Position;
|
|
AbsoluteClipRect = ClipRect + Position.topLeft;
|
|
}
|
|
|
|
if(noClip)
|
|
AbsoluteClipRect = AbsolutePosition;
|
|
|
|
uint cCnt = Children.length();
|
|
for(uint i = 0; i != cCnt; ++i)
|
|
Children[i].updateAbsolutePosition();
|
|
}
|
|
|
|
recti get_absolutePosition() {
|
|
return AbsolutePosition;
|
|
}
|
|
|
|
recti get_updatePosition() {
|
|
return AbsolutePosition;
|
|
}
|
|
|
|
recti get_absoluteClipRect() {
|
|
return AbsoluteClipRect;
|
|
}
|
|
|
|
void swap(BaseGuiElement@ other) {
|
|
recti prevPos = Position;
|
|
recti prevClip = ClipRect;
|
|
recti prevAPos = AbsolutePosition;
|
|
recti prevAClip = AbsoluteClipRect;
|
|
|
|
Position = other.Position;
|
|
ClipRect = other.ClipRect;
|
|
AbsolutePosition = other.AbsolutePosition;
|
|
AbsoluteClipRect = other.AbsoluteClipRect;
|
|
|
|
other.AbsolutePosition = prevAPos;
|
|
other.AbsoluteClipRect = prevAClip;
|
|
other.Position = prevPos;
|
|
other.ClipRect = prevClip;
|
|
|
|
uint cCnt = Children.length();
|
|
for(uint i = 0; i != cCnt; ++i)
|
|
Children[i].updateAbsolutePosition();
|
|
|
|
cCnt = other.Children.length();
|
|
for(uint i = 0; i != cCnt; ++i)
|
|
other.Children[i].updateAbsolutePosition();
|
|
}
|
|
|
|
void set_position(const vec2i& pos) {
|
|
if(Position.topLeft == pos)
|
|
return;
|
|
move(pos - Position.topLeft);
|
|
}
|
|
|
|
void move(const vec2i& moveBy) {
|
|
Position += moveBy;
|
|
AbsolutePosition += moveBy;
|
|
AbsoluteClipRect = ClipRect + AbsolutePosition.topLeft;
|
|
if(Parent !is null)
|
|
AbsoluteClipRect = AbsoluteClipRect.clipAgainst(Parent.absoluteClipRect);
|
|
|
|
uint cCnt = Children.length();
|
|
for(uint i = 0; i != cCnt; ++i)
|
|
Children[i].abs_move(moveBy);
|
|
}
|
|
|
|
void abs_move(const vec2i& moveBy) {
|
|
AbsolutePosition += moveBy;
|
|
AbsoluteClipRect = ClipRect + AbsolutePosition.topLeft;
|
|
if(Parent !is null)
|
|
AbsoluteClipRect = AbsoluteClipRect.clipAgainst(Parent.absoluteClipRect);
|
|
|
|
uint cCnt = Children.length();
|
|
for(uint i = 0; i != cCnt; ++i)
|
|
Children[i].abs_move(moveBy);
|
|
}
|
|
|
|
vec2i get_position() const {
|
|
if(Alignment !is null)
|
|
if(Parent !is null)
|
|
return Alignment.resolve(Parent.updatePosition.size).topLeft;
|
|
else
|
|
return Alignment.resolve(screenSize).topLeft;
|
|
else
|
|
return Position.topLeft;
|
|
}
|
|
|
|
void set_size(const vec2i& _size) {
|
|
if(Position.get_size() == _size)
|
|
return;
|
|
Position = recti_area(Position.topLeft, _size);
|
|
ClipRect = recti(vec2i(), _size);
|
|
if(Alignment is null)
|
|
updateAbsolutePosition();
|
|
}
|
|
|
|
vec2i get_size() const {
|
|
if(Alignment !is null)
|
|
if(Parent !is null)
|
|
return Alignment.resolve(Parent.updatePosition.size).size;
|
|
else
|
|
return Alignment.resolve(screenSize).size;
|
|
else
|
|
return Position.size;
|
|
}
|
|
|
|
vec2i get_desiredSize() const {
|
|
return size;
|
|
}
|
|
|
|
Alignment@ get_alignment() {
|
|
if(Alignment is null)
|
|
@Alignment = Alignment(Position);
|
|
return Alignment;
|
|
}
|
|
|
|
void set_alignment(Alignment@ align) {
|
|
@Alignment = align;
|
|
updateAbsolutePosition();
|
|
}
|
|
|
|
bool get_visible() const {
|
|
return Visible;
|
|
}
|
|
|
|
bool get_actuallyVisible() const {
|
|
if(!Visible)
|
|
return false;
|
|
|
|
IGuiElement@ ele = Parent;
|
|
while(ele !is null && ele !is gui_root) {
|
|
if(!ele.visible)
|
|
return false;
|
|
@ele = ele.parent;
|
|
}
|
|
return ele !is null;
|
|
}
|
|
|
|
bool get_isRoot() const {
|
|
return false;
|
|
}
|
|
|
|
bool get_onScreen() const {
|
|
return AbsolutePosition.overlaps(recti(vec2i(), screenSize));
|
|
}
|
|
|
|
void set_visible(bool vis) {
|
|
bool prevVisible = Visible;
|
|
Visible = vis;
|
|
|
|
if(prevVisible && !Visible && isAncestorOf(getGuiFocus())) {
|
|
IGuiElement@ ele = parent;
|
|
while(ele !is null && !ele.visible)
|
|
@ele = @ele.parent;
|
|
setGuiFocus(ele);
|
|
}
|
|
}
|
|
|
|
void set_rect(const recti& rect) {
|
|
size = rect.size;
|
|
position = rect.topLeft;
|
|
}
|
|
|
|
recti get_rect() const {
|
|
return Position;
|
|
}
|
|
|
|
void draw() {
|
|
uint cCnt = Children.length();
|
|
for(uint i = 0; i != cCnt; ++i) {
|
|
IGuiElement@ ele = Children[i];
|
|
|
|
if(!ele.visible || !ele.onScreen)
|
|
continue;
|
|
|
|
if(ele.noClip)
|
|
clearClip();
|
|
else
|
|
setClip(ele.absoluteClipRect);
|
|
|
|
ele.draw();
|
|
}
|
|
clearClip();
|
|
}
|
|
|
|
IGuiElement@ get_parent() const {
|
|
return Parent;
|
|
}
|
|
|
|
void set_parent(IGuiElement@ NewParent) {
|
|
if(Parent is NewParent)
|
|
return;
|
|
if(Parent !is null)
|
|
Parent.removeChild(this);
|
|
@Parent = @NewParent;
|
|
if(NewParent !is null)
|
|
NewParent.addChild(this);
|
|
updateAbsolutePosition();
|
|
}
|
|
|
|
void addChild(IGuiElement@ ele) {
|
|
Children.insertLast(ele);
|
|
}
|
|
|
|
void preserveReparent(IGuiElement@ newParent) {
|
|
recti abs = absolutePosition;
|
|
@parent = newParent;
|
|
if(newParent !is null)
|
|
rect = abs - newParent.absolutePosition.topLeft;
|
|
}
|
|
|
|
void removeChild(IGuiElement@ ele) {
|
|
for(uint i = 0; i < Children.length(); ++i) {
|
|
if(Children[i] is ele) {
|
|
Children.removeAt(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void remove() {
|
|
if(Parent !is null) {
|
|
Parent.removeChild(this);
|
|
@Parent = null;
|
|
|
|
while(Children.length > 0)
|
|
Children[Children.length - 1].remove();
|
|
}
|
|
}
|
|
|
|
bool isAncestorOf(const IGuiElement@ ele) const {
|
|
while(ele !is null) {
|
|
if(ele is this)
|
|
return true;
|
|
@ele = @ele.parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isChildOf(const IGuiElement@ ele) const {
|
|
const IGuiElement@ us = this;
|
|
while(us !is null) {
|
|
if(us is ele)
|
|
return true;
|
|
@us = @us.parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isFront() {
|
|
if(Parent is null)
|
|
return true;
|
|
return (Parent.getChild(Parent.childCount - 1) is this);
|
|
}
|
|
|
|
bool isBack() {
|
|
if(Parent is null)
|
|
return true;
|
|
return (Parent.getChild(0) is this);
|
|
}
|
|
|
|
void bringToFront() {
|
|
if(Parent !is null)
|
|
Parent.bringToFront(this);
|
|
}
|
|
|
|
void sendToBack() {
|
|
if(Parent !is null)
|
|
Parent.sendToBack(this);
|
|
}
|
|
|
|
void bringToFront(IGuiElement@ ele) {
|
|
if(Children.length != 0)
|
|
if(Children[Children.length - 1] is ele)
|
|
return;
|
|
Children.remove(ele);
|
|
Children.insertLast(ele);
|
|
}
|
|
|
|
void sendToBack(IGuiElement@ ele) {
|
|
if(Children.length != 0 && Children[0] is ele)
|
|
return;
|
|
uint offset = 0;
|
|
for(int i = Children.length() - 1; i >= 0; --i) {
|
|
if(Children[i] is ele)
|
|
offset = 1;
|
|
else if(offset != 0)
|
|
@Children[i + offset] = Children[i];
|
|
}
|
|
@Children[0] = ele;
|
|
}
|
|
|
|
string get_tooltip() {
|
|
SimpleTooltip@ st = cast<SimpleTooltip@>(Tooltip);
|
|
if(st !is null)
|
|
return st.text;
|
|
else
|
|
return "";
|
|
}
|
|
|
|
void set_tooltip(const string& ToolText) {
|
|
SimpleTooltip@ st = cast<SimpleTooltip@>(Tooltip);
|
|
if(st !is null)
|
|
st.text = ToolText;
|
|
else
|
|
@Tooltip = SimpleTooltip(ToolText);
|
|
}
|
|
|
|
void set_tooltipObject(ITooltip@ tp) {
|
|
@Tooltip = tp;
|
|
}
|
|
|
|
ITooltip@ get_tooltipObject() const {
|
|
return Tooltip;
|
|
}
|
|
|
|
bool get_noClip() const {
|
|
return NoClip;
|
|
}
|
|
|
|
void set_noClip(bool noclip) {
|
|
NoClip = noclip;
|
|
}
|
|
|
|
int get_tabIndex() const {
|
|
return TabIndex;
|
|
}
|
|
|
|
void set_tabIndex(int ind) {
|
|
TabIndex = ind;
|
|
}
|
|
|
|
IGuiElement@ getChild(uint index) {
|
|
if(index >= Children.length())
|
|
return null;
|
|
return Children[index];
|
|
}
|
|
|
|
uint get_childCount() const {
|
|
return Children.length();
|
|
}
|
|
|
|
void emitChanged(int value = 0) {
|
|
GuiEvent evt;
|
|
evt.value = value;
|
|
evt.type = GUI_Changed;
|
|
@evt.caller = this;
|
|
onGuiEvent(evt);
|
|
}
|
|
|
|
void emitClicked(int value = 0) {
|
|
GuiEvent evt;
|
|
evt.type = GUI_Clicked;
|
|
evt.value = value;
|
|
@evt.caller = this;
|
|
onGuiEvent(evt);
|
|
}
|
|
|
|
void emitConfirmed(int value = 0) {
|
|
GuiEvent evt;
|
|
evt.type = GUI_Confirmed;
|
|
evt.value = value;
|
|
@evt.caller = this;
|
|
onGuiEvent(evt);
|
|
}
|
|
|
|
void emitHoverChanged(int value = 0) {
|
|
GuiEvent evt;
|
|
evt.type = GUI_Hover_Changed;
|
|
evt.value = value;
|
|
@evt.caller = this;
|
|
onGuiEvent(evt);
|
|
}
|
|
|
|
void set_navigable(bool value) {
|
|
Navigable = value;
|
|
}
|
|
|
|
bool isNavigable(NavigationMode mode) const {
|
|
switch(mode) {
|
|
case NM_None: return false;
|
|
case NM_Action: return Navigable;
|
|
case NM_Tooltip: return tooltipObject !is null;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
IGuiElement@ navigate(NavigationMode mode, const vec2d& line) {
|
|
return navigate(mode, absolutePosition, line);
|
|
}
|
|
|
|
void clipParent() {
|
|
if(parent is null)
|
|
clearClip();
|
|
else
|
|
setClip(parent.absoluteClipRect);
|
|
}
|
|
|
|
void clipParent(const recti& clip) {
|
|
if(parent is null)
|
|
setClip(clip);
|
|
else
|
|
setClip(clip.clipAgainst(parent.absoluteClipRect));
|
|
}
|
|
|
|
void resetClip() {
|
|
if(noClip)
|
|
clearClip();
|
|
else
|
|
setClip(absoluteClipRect);
|
|
}
|
|
|
|
IGuiElement@ navigate(NavigationMode mode, const recti& box, const vec2d&in line) {
|
|
//Bump the navigation upwards for overriding
|
|
if(Parent !is null)
|
|
return Parent.navigate(mode, box, line);
|
|
|
|
float closestDist = FLOAT_INFINITY;
|
|
IGuiElement@ closest;
|
|
closestToLine(mode, box, line.normalized(), closestDist, closest, getGuiFocus());
|
|
return closest;
|
|
}
|
|
|
|
void closestToLine(NavigationMode mode, const recti& box, const vec2d&in line, float& closestDist, IGuiElement@& closest, IGuiElement@ skip) {
|
|
vec2d origin = vec2d(box.center);
|
|
for(uint i = 0, cnt = Children.length; i < cnt; ++i) {
|
|
IGuiElement@ child = Children[i];
|
|
if(!child.visible)
|
|
continue;
|
|
if(child.isNavigable(mode)) {
|
|
if(child is skip)
|
|
continue;
|
|
recti pos = child.absolutePosition;
|
|
vec2d dir = vec2d(pos.center) - origin;
|
|
double t = (dir.x*line.x + dir.y*line.y);
|
|
if(t < 0 && skip !is null)
|
|
continue;
|
|
|
|
vec2d point = origin + line * t;
|
|
float dist = pos.distanceTo(vec2i(point));
|
|
dist += 0.001f * point.distanceTo(origin);
|
|
|
|
if(dist < closestDist) {
|
|
closestDist = dist;
|
|
@closest = child;
|
|
if(dist == 0.f)
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
child.closestToLine(mode, box, line, closestDist, closest, skip);
|
|
if(closestDist == 0.f)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|