Files
starruler-linux/scripts/toolkit/gui.as
T
2018-07-17 14:15:37 +02:00

846 lines
18 KiB
ActionScript

import elements.IGuiElement;
RootElement gui_root;
int next_gui_id = 1;
int get_nextGuiID() {
return next_gui_id++;
}
IGuiElement@ getRootGuiElement() {
return gui_root;
}
void setGuiFocus(IGuiElement@ elem) {
@gui_root.focus = elem;
}
void clearGuiHovered() {
@gui_root.hovered = null;
}
void setGuiAbsorb(IGuiElement@ elem) {
@gui_root.AbsorbTo = elem;
@gui_root.focus = elem;
}
IGuiElement@ getGuiFocus() {
return gui_root.Focus;
}
bool isGuiFocusIn(IGuiElement@ elem) {
if(gui_root.Focus is null)
return false;
return gui_root.Focus is elem || gui_root.Focus.isChildOf(elem);
}
bool isGuiHovered() {
if(gui_root.AbsorbTo !is null)
return true;
if(gui_root.Hovered is null)
return false;
return !gui_root.Hovered.isRoot;
}
void animate_speed(IGuiElement@ ele, const recti& dest, double speed, int value = 0) {
GuiAnim@ anim;
uint len = animations.length();
for(uint i = 0; i < len; ++i) {
if(animations[i].ele is ele) {
@anim = animations[i];
break;
}
}
if(anim is null) {
animations.resize(len+1);
@anim = animations[len];
}
@anim.ele = @ele;
recti startRect = ele.rect;
anim.from = rectf(startRect);
anim.to = dest;
anim.byTime = false;
anim.arg = speed;
anim.value = value;
anim.completed = false;
}
void animate_time(IGuiElement@ ele, const recti& dest, double time, int value = 0) {
GuiAnim@ anim;
uint len = animations.length();
for(uint i = 0; i < len; ++i) {
if(animations[i].ele is ele) {
@anim = animations[i];
break;
}
}
if(anim is null) {
animations.resize(len+1);
@anim = animations[len];
}
@anim.ele = @ele;
recti startRect = ele.rect;
anim.from = rectf(startRect);
anim.to = dest;
anim.byTime = true;
anim.arg = time;
anim.value = value;
anim.completed = false;
}
void animate_retarget(IGuiElement@ ele, const recti& dest) {
uint len = animations.length();
for(uint i = 0; i < len; ++i) {
if(animations[i].ele is ele) {
animations[i].to = dest;
return;
}
}
}
void animate_remove(IGuiElement@ ele) {
for(int i = animations.length()-1; i >= 0; --i)
if(animations[i].ele is ele)
animations.removeAt(i);
}
void animate_snap(IGuiElement@ ele) {
uint len = animations.length();
for(uint i = 0; i < len; ++i) {
if(animations[i].ele is ele) {
ele.rect = animations[i].to;
animations.removeAt(i);
return;
}
}
}
ITooltip@ getTooltip(IGuiElement@ ele) {
ITooltip@ tt;
while(tt is null && ele !is null) {
@tt = ele.tooltipObject;
@ele = ele.parent;
}
return tt;
}
void clearTooltip() {
if(gui_root.Tooltip !is null) {
gui_root.Tooltip.hide(gui_root.skin, gui_root.Hovered);
@gui_root.Tooltip = null;
gui_root.updateHover();
}
}
void tick(double time) {
if(gui_root.AbsorbTo !is null && gui_root.AbsorbTo.parent is null)
@gui_root.AbsorbTo = null;
if(gui_root.Hovered !is null && gui_root.Tooltip is null && gui_root.AbsorbTo is null) {
@gui_root.Tooltip = getTooltip(gui_root.Hovered);
if(gui_root.Tooltip !is null) {
gui_root.showTooltipDelay = gui_root.Tooltip.delay;
if(gui_root.showTooltipDelay <= 0.f)
gui_root.Tooltip.show(gui_root.skin, gui_root.Hovered);
}
}
if(gui_root.Tooltip !is null) {
if(gui_root.showTooltipDelay > 0.f) {
gui_root.showTooltipDelay = max(gui_root.showTooltipDelay - float(time), 0.f);
if(gui_root.showTooltipDelay <= 0.f)
gui_root.Tooltip.show(gui_root.skin, gui_root.Hovered);
}
}
if(gui_root.Hovered !is null) {
if(gui_root.Hovered.parent is null)
gui_root.updateHover();
}
}
void draw() {
for(int i = animations.length()-1; i >= 0; --i)
if(animations[i].update(frameLength))
animations.removeAt(i);
gui_root.draw();
}
bool onGuiEvent(const GuiEvent& evt) {
return gui_root.postGuiEvent(evt);
}
bool onMouseEvent(const MouseEvent& event) {
return gui_root.postMouseEvent(event);
}
bool onKeyboardEvent(const KeyboardEvent& event) {
return gui_root.postKeyEvent(event);
}
void onGuiNavigate(vec2d direction) {
gui_root.navigateTo(direction);
}
IGuiElement@ getNavChild(IGuiElement@ ele, NavigationMode mode) {
for(uint i = 0, cnt = ele.childCount; i < cnt; ++i) {
IGuiElement@ child = ele.getChild(i);
if(!child.visible)
continue;
if(child.isNavigable(mode))
return child;
@child = getNavChild(child, mode);
if(child !is null)
return child;
}
return null;
}
void navigateInto(IGuiElement@ elem) {
if(elem.isNavigable(gui_root.NavMode)) {
setGuiFocus(elem);
}
else {
IGuiElement@ child = getNavChild(elem, gui_root.NavMode);
if(child !is null)
setGuiFocus(child);
else
setGuiFocus(elem);
}
}
//A special element that has only children, no parent
//Acts as the parent of all elements
class RootElement : IGuiElement {
IGuiElement@[] Children;
IGuiElement@ Focus;
IGuiElement@ Hovered;
IGuiElement@ AbsorbTo;
recti absRect;
ITooltip@ Tooltip;
const Skin@ skin = getSkin(settings::sSkinName);
float showTooltipDelay = -1;
IGuiElement@ prevTabFocus;
bool Visible = true;
bool NoClip = true;
NavigationMode NavMode = NM_None;
GuiEvent guiEvent;
RootElement() {
updateAbsolutePosition();
}
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;
}
int get_id() const {
return 0;
}
bool postGuiEvent(const GuiEvent& event) {
if(Focus !is null && Focus.actuallyVisible && Focus.onGuiEvent(event))
return true;
return false;
}
void updateHover() {
vec2i pos = mousePos;
@hovered = elementFromPosition(vec2i(pos.x, pos.y));
}
bool postMouseEvent(const MouseEvent& event) {
if(Tooltip !is null) {
if(Tooltip.persist) {
if(event.type == MET_Moved && showTooltipDelay > 0.f)
showTooltipDelay = Tooltip.delay;
}
else {
if(showTooltipDelay <= 0.f)
Tooltip.hide(skin, Hovered);
if(event.type == MET_Moved)
showTooltipDelay = Tooltip.delay;
else
@Tooltip = null;
}
}
if(event.type == MET_Moved)
@hovered = elementFromPosition(vec2i(event.x, event.y));
if(event.type == MET_Button_Down) {
if(NavMode != NM_None)
NavMode = NM_None;
@focus = elementFromPosition(vec2i(event.x, event.y));
}
if(Hovered !is null && Hovered !is Focus && Hovered.onMouseEvent(event,Hovered)) {
return true;
}
if(Focus !is null && Focus.actuallyVisible && Focus.onMouseEvent(event,Focus)) {
return true;
}
return Hovered !is null && !Hovered.isRoot;
}
bool postKeyEvent(const KeyboardEvent& event) {
if(Focus !is null && Focus.actuallyVisible && Focus.onKeyEvent(event,Focus))
return true;
if(Hovered !is null && Hovered !is Focus && Hovered.onKeyEvent(event,Hovered))
return true;
if(event.type == KET_Key_Down && event.key == KEY_TAB) {
if(prevTabFocus !is null) {
if(shiftKey)
@focus = prevTabElement(prevTabFocus);
else
@focus = nextTabElement(prevTabFocus);
return true;
}
}
return false;
}
void set_focus(IGuiElement@ ele) {
if(Focus is ele)
return;
if(Focus !is null) {
guiEvent.type = GUI_Focus_Lost;
@guiEvent.caller = Focus;
@guiEvent.other = ele;
if(Focus.onGuiEvent(guiEvent))
return;
}
if(ele !is null) {
guiEvent.type = GUI_Focused;
@guiEvent.caller = ele;
@guiEvent.other = Focus;
if(ele.onGuiEvent(guiEvent))
return;
}
@Focus = @ele;
if(Focus !is null) {
if(Focus.tabIndex >= 0) {
@prevTabFocus = Focus;
}
else if(prevTabFocus !is null) {
IGuiElement@ oldGroup = findParentGroup(prevTabFocus);
@prevTabFocus = findParentGroup(Focus);
if(prevTabFocus !is oldGroup)
@prevTabFocus = null;
}
}
}
void set_hovered(IGuiElement@ ele) {
if(Hovered is ele)
return;
if(Hovered !is null) {
if(ele is null || !ele.isChildOf(Hovered)) {
guiEvent.type = GUI_Mouse_Left;
@guiEvent.caller = Hovered;
@guiEvent.other = ele;
if(Hovered.onGuiEvent(guiEvent))
return;
if(ele !is null) {
IGuiElement@ check = Hovered.parent;
while(check !is null) {
if(check.isAncestorOf(ele))
break;
@guiEvent.caller = check;
if(check.onGuiEvent(guiEvent))
return;
@check = check.parent;
}
}
}
}
if(ele !is null) {
if(Hovered is null || !Hovered.isChildOf(ele)) {
guiEvent.type = GUI_Mouse_Entered;
@guiEvent.caller = ele;
@guiEvent.other = Hovered;
if(ele.onGuiEvent(guiEvent))
return;
IGuiElement@ check = ele.parent;
while(check !is null) {
if(check.isAncestorOf(Hovered))
break;
@guiEvent.caller = check;
if(check.onGuiEvent(guiEvent))
return;
@check = check.parent;
}
}
ITooltip@ prevTooltip = Tooltip;
@Tooltip = getTooltip(ele);
if(prevTooltip !is Tooltip) {
if(prevTooltip !is null)
prevTooltip.hide(skin, Hovered);
if(Tooltip !is null) {
showTooltipDelay = Tooltip.delay;
if(showTooltipDelay <= 0.f)
Tooltip.show(skin, ele);
}
}
}
else {
if(Tooltip !is null)
Tooltip.hide(skin, Hovered);
@Tooltip = null;
}
@Hovered = @ele;
}
IGuiElement@ get_parent() const {
return null;
}
void set_parent(IGuiElement@ NewParent) {
throw("Attempted to set parent of Root GUI Element");
}
void updateAbsolutePosition() {
vec2i newSize = screenSize;
if(newSize != absRect.size && newSize.width != 0 && newSize.height != 0) {
absRect = recti_area(vec2i(0,0), screenSize);
uint cCnt = Children.length();
for(uint i = 0; i != cCnt; ++i)
Children[i].updateAbsolutePosition();
}
}
IGuiElement@ elementFromPosition(const vec2i& pos) {
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;
}
return null;
}
recti get_absolutePosition() {
return absRect;
}
recti get_updatePosition() {
return absRect;
}
recti get_absoluteClipRect() {
return absRect;
}
string get_elementType() const {
return "root";
}
bool get_isRoot() const {
return true;
}
bool get_visible() const {
return Visible;
}
bool get_actuallyVisible() const {
return Visible;
}
bool get_onScreen() const {
return true;
}
void set_visible(bool vis) {
Visible = vis;
}
void addChild(IGuiElement@ ele) {
Children.insertLast(ele);
}
void removeChild(IGuiElement@ ele) {
for(uint i = 0; i < Children.length(); ++i) {
if(Children[i] is ele) {
Children.removeAt(i);
return;
}
}
}
void remove() {
throw("Error: attempting to remove gui root.");
}
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 {
return false;
}
bool isFront() { return true; }
void bringToFront() {}
void bringToFront(IGuiElement@ ele) {
if(Children.length != 0)
if(Children[Children.length - 1] is ele)
return;
Children.remove(ele);
Children.insertLast(ele);
}
bool isBack() { return true; }
void sendToBack() {}
void sendToBack(IGuiElement@ ele) {
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;
}
void draw() {
updateAbsolutePosition();
if(!Visible)
return;
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();
if(!ele.noClip)
clearClip();
}
if(Focus !is null && NavMode != NM_None && Focus.isNavigable(NavMode) && Focus.actuallyVisible) {
clearClip();
drawRectangle(Focus.absolutePosition.padded(-8), Color(0xffffff40));
setClip(Focus.absoluteClipRect);
Focus.draw();
clearClip();
}
if(Tooltip !is null && showTooltipDelay <= 0.f)
Tooltip.draw(skin, Hovered);
}
void set_position(const vec2i& pos) {}
void move(const vec2i& moveBy) {}
void abs_move(const vec2i& moveBy) {}
vec2i get_position() const {
return absRect.topLeft;
}
void set_size(const vec2i& _size) {}
vec2i get_size() const {
return absRect.get_size();
}
vec2i get_desiredSize() const {
return size;
}
void set_rect(const recti& rect) {}
recti get_rect() const {
return absRect;
}
void set_tooltip(const string& ToolText) {
}
string get_tooltip() {
return "";
}
void set_tooltipObject(ITooltip@ tp) {
}
ITooltip@ get_tooltipObject() const {
return null;
}
bool get_noClip() const {
return NoClip;
}
void set_noClip(bool noclip) {
NoClip = noclip;
}
int get_tabIndex() const {
return -1;
}
void set_tabIndex(int ind) {
}
IGuiElement@ getChild(uint index) {
if(index >= Children.length())
return null;
return Children[index];
}
uint get_childCount() const {
return Children.length();
}
//Find the first parent with a tab index (or the root)
IGuiElement@ findParentGroup(IGuiElement@ ele) {
IGuiElement@ parent = ele.parent;
while(parent !is null && parent.tabIndex == -1)
@parent = parent.parent;
if(parent is null)
return this;
else
return parent;
}
//Within the same tab group parent, find the next element to tab to
IGuiElement@ nextTabElement(IGuiElement@ from) {
int minIndex = from.tabIndex;
IGuiElement@ minElement;
IGuiElement@ parent = findParentGroup(from);
nextTabElement(parent, minIndex, minIndex, minElement);
return minElement;
}
void nextTabElement(IGuiElement@ ele, int index, int& minIndex, IGuiElement@& minElement) {
int ind = ele.tabIndex;
if(ind >= 0) {
if(minIndex > index) {
if(ind > index && ind < minIndex) {
minIndex = ind;
@minElement = ele;
}
}
else if(ind > index || ind < minIndex) {
minIndex = ind;
@minElement = ele;
}
}
for(uint i = 0, cnt = ele.childCount; i < cnt; ++i)
nextTabElement(ele.getChild(i), index, minIndex, minElement);
}
//Within the same tab group parent, find the previous element to tab to
IGuiElement@ prevTabElement(IGuiElement @from) {
int maxIndex = from.tabIndex;
IGuiElement@ maxElement;
IGuiElement@ parent = findParentGroup(from);
prevTabElement(parent, maxIndex, maxIndex, maxElement);
return maxElement;
}
void prevTabElement(IGuiElement@ ele, int index, int& maxIndex, IGuiElement@& maxElement) {
int ind = ele.tabIndex;
if(ind >= 0) {
if(maxIndex < index) {
if(ind < index && ind > maxIndex) {
maxIndex = ind;
@maxElement = ele;
}
}
else if(ind < index || ind > maxIndex) {
maxIndex = ind;
@maxElement = ele;
}
}
for(uint i = 0, cnt = ele.childCount; i < cnt; ++i)
prevTabElement(ele.getChild(i), index, maxIndex, maxElement);
}
bool isNavigable(NavigationMode mode) const {
return false;
}
void navigateTo(vec2d line) {
//Jump into the right navigation mode
if(NavMode != NM_Action) {
@focus = null;
NavMode = NM_Action;
}
//Find element to navigate to
IGuiElement@ nav;
if(Focus !is null && Focus.actuallyVisible && Focus.parent !is null)
@nav = Focus.navigate(NavMode, line);
else
@nav = navigate(NavMode, line);
//Focus it
GuiEvent evt;
if(nav !is null && nav !is Focus) {
if(Focus !is null) {
evt.type = GUI_Navigation_Leave;
@evt.caller = Focus;
@evt.other = nav;
Focus.onGuiEvent(evt);
}
@focus = nav;
evt.type = GUI_Navigation_Enter;
@evt.caller = Focus;
Focus.onGuiEvent(evt);
}
}
IGuiElement@ navigate(NavigationMode mode, const vec2d& line) {
return navigate(mode, absolutePosition, line);
}
IGuiElement@ navigate(NavigationMode mode, const recti& box, const vec2d&in line) {
float closestDist = FLOAT_INFINITY;
IGuiElement@ closest;
closestToLine(mode, box, line.normalized(), closestDist, closest, Focus);
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;
}
}
}
};
class GuiAnim {
IGuiElement@ ele;
rectf from;
recti to;
bool byTime;
double arg;
int value;
bool completed;
bool update(double time) {
double interp;
if(byTime) {
interp = time / arg;
arg -= time;
}
else {
rectf to_f(to);
float dist = max(to_f.topLeft.distanceTo(from.topLeft), to_f.topLeft.distanceTo(from.topLeft));
if(dist > 0)
interp = (arg * time) / dist;
else
interp = 1.0;
}
if(interp >= 1.0) {
ele.rect = to;
ele.updateAbsolutePosition();
completed = true;
GuiEvent evt;
evt.type = GUI_Animation_Complete;
evt.value = value;
@evt.caller = ele;
ele.onGuiEvent(evt);
return completed;
}
from = from.interpolate(rectf(to), interp);
ele.rect = recti(from);
ele.updateAbsolutePosition();
return false;
}
};
GuiAnim[] animations;