Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+271
View File
@@ -0,0 +1,271 @@
enum AlignmentSide {
AS_Left,
AS_Right,
AS_Add,
AS_Top = AS_Left,
AS_Bottom = AS_Right,
AS_Width = AS_Add,
AS_Height = AS_Add
}
AlignedPoint@ get_Left() {
return AlignedPoint(AS_Left);
}
AlignedPoint@ get_Right() {
return AlignedPoint(AS_Right);
}
AlignedPoint@ get_Top() {
return AlignedPoint(AS_Top);
}
AlignedPoint@ get_Bottom() {
return AlignedPoint(AS_Bottom);
}
final tidy class AlignedPoint {
int type;
float percent;
int pixels;
AlignedPoint() {
type = AS_Left;
pixels = 0;
percent = 0;
}
AlignedPoint(AlignmentSide side) {
type = side;
pixels = 0;
percent = 0;
}
AlignedPoint(int px) {
pixels = px;
percent = 0;
type = AS_Left;
}
AlignedPoint(float perc, int px) {
pixels = px;
percent = perc;
type = AS_Left;
}
AlignedPoint(int tp, float perc, int px) {
type = tp;
pixels = px;
percent = perc;
}
AlignedPoint(int tp, float perc, int px, int anch) {
type = tp;
pixels = px;
percent = perc;
}
void set(int px) {
pixels = px;
percent = 0;
type = AS_Left;
}
void set(float perc, int px) {
pixels = px;
percent = perc;
type = AS_Left;
}
void set(int tp, float perc, int px) {
type = tp;
pixels = px;
percent = perc;
}
void set(int tp, float perc, int px, int anch) {
type = tp;
pixels = px;
percent = perc;
}
int resolve(int size, int addFrom = 0) const {
if(type == AS_Left)
return percent * size + pixels;
else if(type == AS_Right)
return size - percent * size - pixels;
else if(type == AS_Add)
return addFrom + percent * size + pixels;
return 0;
}
AlignedPoint& opAssign(const AlignedPoint& other) {
type = other.type;
percent = other.percent;
pixels = other.pixels;
return this;
}
AlignedPoint& opAdd(int px) {
if(type == AS_Left)
pixels += px;
else
pixels -= px;
return this;
}
AlignedPoint& opSub(int px) {
if(type == AS_Left)
pixels -= px;
else
pixels += px;
return this;
}
AlignedPoint& opAdd(float pct) {
if(type == AS_Left)
percent += pct;
else
percent -= pct;
return this;
}
AlignedPoint& opSub(float pct) {
if(type == AS_Left)
percent -= pct;
else
percent += pct;
return this;
}
void dump() {
print(""+type+" : "+pixels+" : "+percent+"%");
}
};
final tidy class Alignment {
AlignedPoint@ left;
AlignedPoint@ right;
AlignedPoint@ top;
AlignedPoint@ bottom;
Alignment() {
@left = AlignedPoint(AS_Left, 0.f, 0);
@right = AlignedPoint(AS_Right, 0.f, 0);
@top = AlignedPoint(AS_Top, 0.f, 0);
@bottom = AlignedPoint(AS_Bottom, 0.f, 0);
}
Alignment(AlignedPoint@ Left, AlignedPoint@ Top, AlignedPoint@ Right, AlignedPoint@ Bot) {
@left = Left;
@right = Right;
@top = Top;
@bottom = Bot;
}
Alignment(AlignedPoint@ Left, AlignedPoint@ Top, int Width, int Height) {
@left = Left;
@top = Top;
@right = AlignedPoint(AS_Add, 0.f, Width);
@bottom = AlignedPoint(AS_Add, 0.f, Height);
}
Alignment(AlignedPoint@ Left, AlignedPoint@ Top, AlignedPoint@ Right, int Height) {
@left = Left;
@top = Top;
@right = Right;
@bottom = AlignedPoint(AS_Add, 0.f, Height);
}
Alignment& fill() {
left.set(AS_Left, 0.f, 0);
right.set(AS_Right, 0.f, 0);
top.set(AS_Top, 0.f, 0);
bottom.set(AS_Bottom, 0.f, 0);
return this;
}
Alignment& padded(int padding) {
left.pixels += padding;
right.pixels += padding;
top.pixels += padding;
bottom.pixels += padding;
return this;
}
Alignment& padded(int x, int y) {
left.pixels += x;
right.pixels += x;
top.pixels += y;
bottom.pixels += y;
return this;
}
Alignment& padded(int x, int y, int x2, int y2) {
left.pixels += x;
right.pixels += x2;
top.pixels += y;
bottom.pixels += y2;
return this;
}
Alignment(int lt, float lp, int lx,
int tt, float tp, int tx,
int rt, float rp, int rx,
int bt, float bp, int bx) {
left.set(lt, lp, lx);
top.set(tt, tp, tx);
right.set(rt, rp, rx);
bottom.set(bt, bp, bx);
}
Alignment(recti from) {
@left = AlignedPoint(from.topLeft.x);
@right = AlignedPoint(from.topLeft.y);
@top = AlignedPoint(from.botRight.x);
@bottom = AlignedPoint(from.botRight.y);
}
void set(int lt, float lp, int lx,
int tt, float tp, int tx,
int rt, float rp, int rx,
int bt, float bp, int bx) {
left.set(lt, lp, lx);
top.set(tt, tp, tx);
right.set(rt, rp, rx);
bottom.set(bt, bp, bx);
}
void set(AlignedPoint@ Left, AlignedPoint@ Top, AlignedPoint@ Right, AlignedPoint@ Bot) {
@left = Left;
@right = Right;
@top = Top;
@bottom = Bot;
}
recti resolve(const vec2i& within) const {
//Get covered rectangle
recti res;
res.topLeft.x = left.resolve(within.width);
res.topLeft.y = top.resolve(within.height);
res.botRight.x = right.resolve(within.width, res.topLeft.x);
res.botRight.y = bottom.resolve(within.height, res.topLeft.y);
return res;
}
void dump() {
print("Left: ");
left.dump();
print("Top: ");
top.dump();
print("Right: ");
right.dump();
print("Bottom: ");
bottom.dump();
}
};
Alignment@ Alignment_Fill() {
return Alignment(Left, Top, Right, Bottom);
}
+674
View File
@@ -0,0 +1,674 @@
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;
}
}
}
};
+381
View File
@@ -0,0 +1,381 @@
#section disable menu
import elements.BaseGuiElement;
import planet_types;
import orbitals;
import pickups;
import artifacts;
import civilians;
#section gui
from nodes.PlanetNode import getPlanetMaterial;
#section all
from planets.PlanetSurface import preparePlanetShader;
from util.draw_model import drawLitModel;
export Gui3DDisplay;
export ObjectAction;
export Gui3DObject;
export Draw3D, makeDrawMode;
int ringworldType = -1;
void init() {
auto@ rw = getPlanetType("Ringworld");
if(rw !is null)
ringworldType = rw.id;
}
interface Draw3D {
void preRender(Object@ obj);
void draw(const recti &in pos, quaterniond rotation);
};
class DrawModel : Draw3D {
const Material@ mat;
const Model@ model;
quaterniond extraRotation;
DrawModel(const Model@ Model, const Material@ Mat) {
@mat = Mat;
@model = Model;
}
DrawModel(const Model@ Model, const Material@ Mat, const quaterniond& rot) {
@mat = Mat;
@model = Model;
extraRotation = rot;
}
void preRender(Object@ obj) {
}
void draw(const recti &in pos, quaterniond rotation) {
drawLitModel(model, mat, pos, extraRotation * rotation);
}
};
class DrawPlanet : Draw3D {
const Material@ mat, atmos;
Planet@ pl;
Node@ node;
DrawPlanet(Planet@ pl) {
const PlanetType@ type = getPlanetType(cast<Planet>(pl).PlanetType);
@mat = type.emptyMat;
@atmos = type.atmosMat;
@this.pl = pl;
@node = pl.getNode();
}
void preRender(Object@ obj) {
}
void draw(const recti &in pos, quaterniond rotation) {
@renderingNode = node;
#section gui
auto@ dyntex = getPlanetMaterial(pl, mat);
auto@ material = dyntex.material;
if(material is null)
return;
auto@ plType = getPlanetType(pl.PlanetType);
preparePlanetShader(pl);
if(atmos !is null)
drawLitModel(plType.model, material, pos, rotation, 1/1.025);
else
drawLitModel(plType.model, material, pos, rotation);
if(atmos !is null) {
shader::MODEL_SCALE = double(min(pos.height, pos.width)) / 2 / 1.025;
quaterniond atmosRot;
if(settings::bRotateUIObjects)
atmosRot = quaterniond_fromAxisAngle(vec3d_up(), fraction(gameTime / 120.0) * twopi);
drawLitModel(model::Sphere_max, material::FlatAtmosphere, pos, rotation * atmosRot);
}
@renderingNode = null;
#section all
}
};
class DrawRingworld : Draw3D {
DrawRingworld(Planet@ pl) {
}
void preRender(Object@ obj) {
}
void draw(const recti &in pos, quaterniond rotation) {
quaterniond outerRot;
outerRot *= quaterniond_fromAxisAngle(vec3d_front(), -0.25 * pi);
outerRot *= rotation;
drawLitModel(model::RingworldOuter, material::GenericPBR_RingworldOuter, pos, outerRot);
quaterniond innerRot;
innerRot *= quaterniond_fromAxisAngle(vec3d_front(), -0.25 * pi);
innerRot *= rotation.inverted();
drawLitModel(model::RingworldInner, material::GenericPBR_RingworldInner, pos, innerRot);
drawLitModel(model::RingworldLiving, material::RingworldSurface, pos, outerRot);
// drawLitModel(model::RingworldAtmosphere, material::RingworldAtmo, pos, outerRot);
}
};
class DrawBlackhole : Draw3D {
Node@ node;
DrawBlackhole(Star@ star) {
@node = star.getNode();
}
void preRender(Object@ obj) {
}
void draw(const recti &in pos, quaterniond rotation) {
@renderingNode = node;
recti square = pos.aspectAligned(1.0);
square = square.padded(-square.width*0.2, -square.height*0.2);
model::Sphere_max.draw(material::Blackhole, square, rotation, 0.3);
@renderingNode = null;
}
};
class DrawStar : Draw3D {
Node@ node;
double temp;
DrawStar(Star@ star) {
temp = star.temperature;
@node = star.getNode();
}
void preRender(Object@ obj) {
shader::STAR_TEMP = temp;
}
void draw(const recti &in pos, quaterniond rotation) {
@renderingNode = node;
recti square = pos.aspectAligned(1.0);
square = square.padded(-square.width*0.2, -square.height*0.2);
model::Sphere_max.draw(material::PopupStarSurface, square, rotation, 1/1.5);
material::Corona.draw(square);
@renderingNode = null;
}
};
class Gui3DDisplay : BaseGuiElement {
Draw3D@ drawMode;
quaterniond rotation;
Gui3DDisplay(BaseGuiElement@ parent, recti pos) {
super(parent, pos);
}
Gui3DDisplay(BaseGuiElement@ parent, Alignment@ pos) {
super(parent, pos);
}
void draw() {
if(drawMode !is null)
drawMode.draw(AbsolutePosition, rotation);
BaseGuiElement::draw();
}
};
enum ObjectAction {
OA_LeftClick,
OA_RightClick,
OA_DoubleClick,
OA_MiddleClick,
};
Draw3D@ makeDrawMode(Object@ obj) {
if(obj is null || !obj.valid || !obj.initialized)
return null;
switch(obj.type) {
case OT_Planet: {
Planet@ pl = cast<Planet>(obj);
if(pl.PlanetType == ringworldType)
return DrawRingworld(pl);
else
return DrawPlanet(pl);
}
case OT_Orbital: {
const OrbitalModule@ def = getOrbitalModule(cast<Orbital>(obj).coreModule);
return DrawModel(def.model, def.material);
}
case OT_Ship: {
const Hull@ hull = cast<Ship>(obj).blueprint.design.hull;
return DrawModel(hull.model, hull.material);
}
case OT_ColonyShip: {
const Model@ model;
const Material@ material;
const Shipset@ ss = obj.owner.shipset;
const ShipSkin@ skin;
if(ss !is null)
@skin = ss.getSkin("Colonizer");
if(obj.owner.ColonizerModel.length != 0) {
@model = getModel(obj.owner.ColonizerModel);
@material = getMaterial(obj.owner.ColonizerMaterial);
}
else if(skin !is null) {
@model = skin.model;
@material = skin.material;
}
else {
@model = model::ColonyShip;
@material = material::VolkurGenericPBR;
}
return DrawModel(model, material);
}
case OT_Freighter: {
const Model@ model = model::Fighter;
const Material@ material = material::Ship10;
const Shipset@ ss = obj.owner.shipset;
if(ss !is null) {
auto@ skin = ss.getSkin(cast<Freighter>(obj).skin);
if(skin !is null) {
@model = skin.model;
@material = skin.material;
}
}
return DrawModel(model, material);
}
case OT_Asteroid: {
const Model@ model;
const Material@ material;
switch(obj.id % 4) {
case 0:
@model = model::Asteroid1; break;
case 1:
@model = model::Asteroid2; break;
case 2:
@model = model::Asteroid3; break;
case 3:
@model = model::Asteroid4; break;
}
switch(obj.id % 3) {
case 0:
@material = material::AsteroidPegmatite; break;
case 1:
@material = material::AsteroidMagnetite; break;
case 2:
@material = material::AsteroidTonalite; break;
}
return DrawModel(model, material);
}
case OT_Anomaly: {
Anomaly@ anom = cast<Anomaly>(obj);
return DrawModel(getModel(anom.model), getMaterial(anom.material));
}
case OT_Artifact: {
Artifact@ art = cast<Artifact>(obj);
auto@ type = getArtifactType(art.ArtifactType);
return DrawModel(type.model, type.material, quaterniond_fromAxisAngle(vec3d_up(), 0.5*pi));
}
case OT_Star: {
Star@ star = cast<Star>(obj);
if(star.temperature > 0)
return DrawStar(star);
else
return DrawBlackhole(star);
}
case OT_Pickup: {
const PickupType@ type = getPickupType(cast<Pickup>(obj).PickupType);
return DrawModel(type.model, type.material);
}
case OT_Civilian: {
uint type = cast<Civilian>(obj).getCivilianType();
return DrawModel(getCivilianModel(obj.owner, type, obj.radius), getCivilianMaterial(obj.owner, type, obj.radius));
}
}
return null;
}
class Gui3DObject : Gui3DDisplay {
Object@ obj;
double dblClick = 0;
quaterniond internalRotation;
bool objectRotation = settings::bRotateUIObjects;
Gui3DObject(BaseGuiElement@ parent, recti pos, Object@ Obj = null) {
super(parent, pos);
@object = Obj;
}
Gui3DObject(BaseGuiElement@ parent, Alignment@ pos, Object@ Obj = null) {
super(parent, pos);
@object = Obj;
}
void set_object(Object@ Obj) {
if(Obj is obj)
return;
@obj = Obj;
@drawMode = makeDrawMode(Obj);
}
Object@ get_object() {
return obj;
}
void draw() {
if(obj is null)
return;
//Update from object values
if(objectRotation) {
rotation = internalRotation * obj.node_rotation;
rotation.normalize();
}
else {
rotation = internalRotation;
}
if(drawMode !is null)
drawMode.preRender(obj);
Empire@ owner = obj.owner;
if(owner !is null)
NODE_COLOR = owner.color;
Gui3DDisplay::draw();
}
bool onMouseEvent(const MouseEvent& evt, IGuiElement@ source) {
switch(evt.type) {
case MET_Button_Up:
if(evt.button == 0) {
if(frameTime < dblClick) {
emitClicked(OA_DoubleClick);
}
else {
emitClicked(OA_LeftClick);
dblClick = frameTime + 0.2;
}
return true;
}
else if(evt.button == 1) {
emitClicked(OA_RightClick);
return true;
}
else if(evt.button == 2) {
emitClicked(OA_MiddleClick);
return true;
}
break;
}
return BaseGuiElement::onMouseEvent(evt, source);
}
};
+214
View File
@@ -0,0 +1,214 @@
import elements.BaseGuiElement;
import elements.GuiButton;
import elements.GuiPanel;
import elements.GuiText;
export GuiAccordion;
class GuiAccordion : BaseGuiElement {
SkinStyle style = SS_NULL;
int maxItemHeight = -1;
int spacing = 4;
bool expandItems = true;
bool multiple = false;
bool required = false;
bool clickableHeaders = true;
bool animate = true;
double animSpeed = 3.0;
IGuiElement@[] headers;
IGuiElement@[] items;
bool[] opened;
double[] animSize;
GuiAccordion(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
}
GuiAccordion(IGuiElement@ ParentElement, Alignment@ Align) {
super(ParentElement, Align);
}
void draw() {
if(style != SS_NULL) {
uint flags = SF_Normal;
if(isGuiFocusIn(this))
flags |= SF_Focused;
skin.draw(style, flags, AbsolutePosition);
}
updateAnimation(frameLength);
BaseGuiElement::draw();
}
uint addSection(const string& header, IGuiElement@ item) {
GuiButton btn(this, recti(0, 0, 100, 29), header);
btn.font = FT_Medium;
btn.style = SS_AccordionHeader;
btn.horizAlign = 0.0;
if(!clickableHeaders) {
btn.disabled = true;
btn.textColor = colors::White;
}
return addSection(btn, item);
}
uint get_sectionCount() {
return items.length;
}
uint addSection(IGuiElement@ header, IGuiElement@ item) {
@header.parent = this;
@item.parent = this;
headers.insertLast(header);
items.insertLast(item);
opened.insertLast(false);
animSize.insertLast(0.0);
updatePositions();
return headers.length - 1;
}
uint addSection_r(IGuiElement@ header, IGuiElement@ item) {
@header.parent = this;
@item.parent = this;
headers.insertLast(header);
items.insertLast(item);
opened.insertLast(false);
animSize.insertLast(0.0);
return headers.length - 1;
}
void clearSections() {
for(uint i = 0, cnt = headers.length; i < cnt; ++i) {
headers[i].remove();
items[i].remove();
}
headers.length = 0;
items.length = 0;
animSize.length = 0;
}
void openSection(uint num, bool snap = true) {
if(!multiple) {
for(uint i = 0, cnt = headers.length; i < cnt; ++i) {
opened[i] = false;
if(snap)
animSize[i] = 0.0;
}
}
opened[num] = true;
if(snap)
animSize[num] = 1.0;
updatePositions();
}
void closeSection(uint num, bool snap = true) {
if(required)
return;
opened[num] = false;
if(snap)
animSize[num] = 0.0;
updatePositions();
}
void toggleSection(uint num, bool snap = false) {
if(opened[num])
closeSection(num, snap);
else
openSection(num, snap);
updatePositions();
}
void updateAnimation(double time) {
bool animating = false;
for(uint i = 0, cnt = animSize.length; i < cnt; ++i) {
if(opened[i]) {
if(animSize[i] < 1.0) {
animSize[i] = clamp(animSize[i] + time * animSpeed, 0.0, 1.0);
animating = true;
auto@ p = cast<GuiPanel>(items[i]);
if(p !is null)
p.setAnimating(animSize[i] < 1.0);
}
}
else {
if(animSize[i] > 0.0) {
animSize[i] = clamp(animSize[i] - time * animSpeed, 0.0, 1.0);
animating = true;
auto@ p = cast<GuiPanel>(items[i]);
if(p !is null)
p.setAnimating(animSize[i] > 0.0);
}
}
}
if(animating)
updatePositions();
}
void updatePositions() {
int y = 0, w = size.width, hh = 0;
if(expandItems && !multiple) {
for(uint i = 0, cnt = headers.length; i < cnt; ++i)
hh += headers[i].size.height + spacing;
}
for(uint i = 0, cnt = headers.length; i < cnt; ++i) {
IGuiElement@ header = headers[i];
IGuiElement@ item = items[i];
//Position header
int h = header.size.height;
header.size = vec2i(w, h);
header.position = vec2i(0, y);
y += h;
//Position item
if(opened[i] || animSize[i] > 0) {
item.visible = true;
if(!expandItems)
h = item.size.height;
else if(!multiple)
h = size.height - hh;
else if(maxItemHeight == -1)
h = item.desiredSize.height;
else
h = min(maxItemHeight, item.desiredSize.height);
h *= animSize[i];
item.size = vec2i(w, h);
item.position = vec2i(0, y);
y += h;
}
else {
item.visible = false;
}
y += spacing;
}
size = vec2i(w, y);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.type == GUI_Clicked && event.caller.parent is this) {
for(uint i = 0, cnt = headers.length; i < cnt; ++i) {
if(headers[i] is event.caller) {
toggleSection(i);
return true;
}
}
}
return BaseGuiElement::onGuiEvent(event);
}
void updateAbsolutePosition() {
BaseGuiElement::updateAbsolutePosition();
updatePositions();
}
};
+52
View File
@@ -0,0 +1,52 @@
import elements.BaseGuiElement;
export GuiAligned;
class GuiAligned : BaseGuiElement {
double horizAlign = 0.5;
double vertAlign = 0.5;
GuiAligned(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
updateAbsolutePosition();
}
GuiAligned(IGuiElement@ ParentElement, Alignment@ Align) {
super(ParentElement, Align);
updateAbsolutePosition();
}
void updateAbsolutePosition() {
BaseGuiElement::updateAbsolutePosition();
vec2i _min = size;
vec2i _max;
for(uint i = 0, cnt = Children.length; i < cnt; ++i) {
IGuiElement@ child = Children[i];
if(!child.visible)
continue;
recti pos = child.rect;
_min.x = min(_min.x, pos.topLeft.x);
_min.y = min(_min.y, pos.topLeft.y);
_max.x = max(_max.x, pos.botRight.x);
_max.y = max(_max.y, pos.botRight.y);
}
int w = _max.x - _min.x;
int h = _max.y - _min.y;
vec2i sz = size;
vec2i off;
off.x = double(sz.width - w) * horizAlign;
off.y = double(sz.height - h) * vertAlign;
for(uint i = 0, cnt = Children.length; i < cnt; ++i) {
IGuiElement@ child = Children[i];
vec2i pos = child.position;
pos = (pos - _min) + off;
child.position = pos;
}
}
};
@@ -0,0 +1,82 @@
import elements.BaseGuiElement;
from elements.GuiMarkupText import GuiMarkupText;
export GuiBackgroundPanel;
class GuiBackgroundPanel : BaseGuiElement {
string Title;
FontType titleFont = FT_Medium;
Color titleColor;
Sprite picture;
Color pictureColor(0xffffff10);
GuiMarkupText@ markupBox;
SkinStyle titleStyle = SS_PanelTitle;
float titleWidth = 0.5f;
int titleHeight = 30;
vec2f maxSize(2.f, 2.f);
GuiBackgroundPanel(IGuiElement@ Parent, recti pos) {
super(Parent, pos);
}
GuiBackgroundPanel(IGuiElement@ Parent, Alignment@ align) {
super(Parent, align);
}
void set_markup(bool value) {
if(value) {
if(markupBox is null) {
@markupBox = GuiMarkupText(this, Alignment(Left+12, Top, Left+0.7f-12, Height=28));
markupBox.text = Title;
markupBox.visible = Title.length != 0;
markupBox.defaultFont = FT_Medium;
}
}
else {
if(markupBox !is null) {
markupBox.remove();
@markupBox = null;
}
}
}
void set_title(const string& text) {
Title = text;
if(markupBox !is null) {
markupBox.text = text;
markupBox.visible = text.length != 0;
}
}
const string& get_title() {
return Title;
}
void draw() {
skin.draw(SS_Panel, SF_Normal, AbsolutePosition);
if(title.length != 0) {
skin.draw(titleStyle, SF_Normal,
recti_area(AbsolutePosition.topLeft + vec2i(1,1),
vec2i(size.width - 3, titleHeight)), titleColor);
if(markupBox is null) {
const Font@ ft = skin.getFont(titleFont);
ft.draw(pos=recti_area(AbsolutePosition.topLeft+vec2i(12, 4), vec2i(size.width*titleWidth, titleHeight-10)),
ellipsis=locale::ELLIPSIS, text=title, vertAlign=0.5);
}
}
if(picture.valid) {
vec2i psize = picture.size;
recti pos = recti(AbsolutePosition.botRight - vec2i(
min(size.width * 0.5, float(psize.x) * maxSize.x - 8),
min(float(size.height - 40), float(psize.y) * maxSize.y - 8)),
AbsolutePosition.botRight - vec2i(8, 8));
pos = pos.aspectAligned(float(psize.width) / float(psize.height), 1.0, 1.0);
picture.draw(pos, pictureColor);
}
BaseGuiElement::draw();
}
};
File diff suppressed because it is too large Load Diff
+371
View File
@@ -0,0 +1,371 @@
import elements.BaseGuiElement;
import elements.GuiSprite;
export GuiButton, onButtonClick;
interface onButtonClick {
bool onClick(GuiButton@ btn);
};
class GuiButton : BaseGuiElement {
string Text;
FontType TextFont = FT_Button;
vec2i textOffset;
vec2i textSize;
double HorizAlign = 0.5;
double VertAlign = 0.5;
bool Pressed = false;
bool Hovered = false;
bool Focused = false;
bool disabled = false;
bool Toggle = false;
bool allowOtherButtons = false;
Color textColor = colors::Invisible;
Color color;
Sprite Icon;
GuiSprite@ fullIcon;
onButtonClick@ onClick;
SkinStyle style = SS_Button;
Sprite spriteStyle;
GuiButton(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
updateAbsolutePosition();
navigable = true;
}
GuiButton(IGuiElement@ ParentElement, const recti& Rectangle, const string&in DefaultText) {
super(ParentElement, Rectangle);
updateAbsolutePosition();
text = DefaultText;
navigable = true;
}
GuiButton(IGuiElement@ ParentElement, Alignment@ Align) {
super(ParentElement, Align);
updateAbsolutePosition();
navigable = true;
}
GuiButton(IGuiElement@ ParentElement, Alignment@ Align, const string& DefaultText) {
super(ParentElement, Align);
updateAbsolutePosition();
text = DefaultText;
navigable = true;
}
GuiButton(IGuiElement@ ParentElement, const recti& Rectangle, const Sprite& sprt) {
super(ParentElement, Rectangle);
updateAbsolutePosition();
setIcon(sprt);
navigable = true;
}
GuiButton(IGuiElement@ ParentElement, Alignment@ Align, const Sprite& sprt) {
super(ParentElement, Align);
updateAbsolutePosition();
setIcon(sprt);
navigable = true;
}
GuiButton(bool NoParent) {
super(NoParent);
}
string get_elementType() const {
return "button";
}
bool onGuiEvent(const GuiEvent& event) {
switch(event.type) {
case GUI_Mouse_Entered:
if(event.caller is this) {
if(!skin.isIrregular(style))
Hovered = true;
}
break;
case GUI_Mouse_Left:
if(event.caller is this) {
Hovered = false;
if(!Toggle)
Pressed = false;
}
break;
case GUI_Focused:
Focused = true;
break;
case GUI_Focus_Lost:
if(!isAncestorOf(event.other)) {
Focused = false;
if(!Toggle)
Pressed = false;
}
break;
case GUI_Controller_Down:
if(event.caller is this) {
if(event.value == GP_A) {
if(Toggle) {
Pressed = !Pressed;
emitClick();
}
else {
Pressed = true;
}
return true;
}
}
break;
case GUI_Controller_Up:
if(Focused) {
if(event.value == GP_A) {
if(Pressed && !Toggle) {
Pressed = false;
emitClick();
}
return true;
}
}
break;
}
return BaseGuiElement::onGuiEvent(event);
}
void emitClick(int btn = 0) {
if(onClick !is null && onClick.onClick(this))
return;
GuiEvent evt;
evt.type = GUI_Clicked;
@evt.caller = this;
evt.value = btn;
onGuiEvent(evt);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this || source.isChildOf(this)) {
if(disabled) {
if(event.type == MET_Button_Down && (event.button == 0 || allowOtherButtons))
return true;
if(event.type == MET_Button_Up && (event.button == 0 || allowOtherButtons)) {
sound::error.play(priority=true);
return true;
}
}
else switch(event.type) {
case MET_Moved:
if(skin.isIrregular(style)) {
if(AbsolutePosition.isWithin(mousePos)) {
Hovered = skin.isPixelActive(
style, flags, AbsolutePosition,
mousePos - AbsolutePosition.topLeft);
}
}
break;
case MET_Button_Down:
if(!Hovered)
return true;
if(event.button == 0 || allowOtherButtons) {
if(Toggle) {
Pressed = !Pressed;
emitClick(event.button);
}
else {
Pressed = true;
}
return true;
}
break;
case MET_Button_Up:
if(Focused) {
if(event.button == 0 || allowOtherButtons) {
if(Pressed && !Toggle) {
Pressed = false;
if(Hovered)
emitClick(event.button);
}
return true;
}
}
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
if(source is this || source.isChildOf(this)) {
if(disabled) {
if(event.key == KEY_ENTER) {
if(event.type == KET_Key_Up) {
sound::error.play(priority=true);
return true;
}
else if(event.type == KET_Key_Down) {
return true;
}
}
}
else switch(event.type) {
case KET_Key_Down:
if(event.key == KEY_ENTER) {
if(Toggle) {
Pressed = !Pressed;
emitClick();
}
else {
Pressed = true;
}
return true;
}
break;
case KET_Key_Up:
if(event.key == KEY_ENTER) {
if(Pressed && !Toggle) {
Pressed = false;
emitClick();
}
return true;
}
break;
}
}
return BaseGuiElement::onKeyEvent(event, source);
}
void set_horizAlign(double align) {
HorizAlign = align;
centerText();
}
void set_vertAlign(double align) {
VertAlign = align;
centerText();
}
void centerText() {
const Font@ font = skin.getFont(TextFont);
vec2i textSize = font.getDimension(Text);
int metric = min(size.height - 6, textSize.y + 8);
if(Icon.valid)
textSize.x += metric + 6;
textOffset = Position.size - textSize;
textOffset.x = double(textOffset.x - 8) * HorizAlign + 4;
textOffset.y = double(textOffset.y) * VertAlign;
textOffset.y += (textSize.y - font.getBaseline()) / 2;
if(Icon.valid)
textOffset.x += metric + 4;
}
void set_font(FontType type) {
if(TextFont == type)
return;
TextFont = type;
centerText();
}
void set_buttonIcon(const Sprite& sprt) {
Icon = sprt;
centerText();
}
GuiSprite@ setIcon(const Sprite& sprt, int padding = 5) {
if(Text.length != 0) {
buttonIcon = sprt;
return null;
}
else {
if(!sprt.valid) {
if(fullIcon !is null)
fullIcon.remove();
return null;
}
if(fullIcon is null)
@fullIcon = GuiSprite(this, Alignment().padded(padding));
fullIcon.desc = sprt;
return fullIcon;
}
}
void set_text(const string& text) {
Text = text;
centerText();
}
string get_text() {
return Text;
}
vec2i get_textSize() {
const Font@ font = skin.getFont(TextFont);
return font.getDimension(Text);
}
void set_toggleButton(bool t) {
Toggle = t;
Pressed = false;
}
bool get_toggleButton() {
return Toggle;
}
bool get_pressed() {
return Toggle ? Pressed : false;
}
void set_pressed(bool p) {
if(Toggle)
Pressed = p;
}
void updateAbsolutePosition() {
BaseGuiElement::updateAbsolutePosition();
if(Text.length() > 0)
centerText();
}
uint get_flags() {
uint flags = SF_Normal;
if(disabled)
flags |= SF_Disabled;
if(Pressed)
flags |= SF_Active;
if(Hovered)
flags |= SF_Hovered;
if(Focused)
flags |= SF_Focused;
return flags;
}
void draw() {
if(spriteStyle.valid) {
spriteStyle.index = 0;
if(Hovered)
spriteStyle.index = 1;
spriteStyle.draw(AbsolutePosition, color);
}
else {
skin.draw(style, flags, AbsolutePosition, color);
}
Color textCol = textColor;
if(textCol.a == 0) {
if(disabled)
textCol = skin.getColor(SC_Disabled);
else
textCol = skin.getColor(SC_ButtonText);
}
if(Icon.valid) {
int metric = min(size.height - 6, textSize.y + 8);
recti pos = recti_area(AbsolutePosition.topLeft +
vec2i(textOffset.x - metric - 6, (AbsolutePosition.height - metric) / 2 + 1 ), vec2i(metric, metric));
Icon.draw(pos);
}
skin.draw(TextFont, AbsolutePosition.topLeft + textOffset, Text, textCol);
BaseGuiElement::draw();
}
};
@@ -0,0 +1,59 @@
#section game
import elements.BaseGuiElement;
import elements.GuiSprite;
import elements.GuiText;
import elements.MarkupTooltip;
import cargo;
class GuiCargoDisplay : BaseGuiElement {
array<GuiSprite@> icons;
array<GuiText@> values;
int padding = 2;
GuiCargoDisplay(IGuiElement@ parent, Alignment@ align) {
super(parent, align);
}
GuiCargoDisplay(IGuiElement@ parent, const recti& pos) {
super(parent, pos);
}
void update(Object& obj) {
uint oldCnt = icons.length;
uint newCnt = obj.cargoTypes;
for(uint i = newCnt; i < oldCnt; ++i) {
icons[i].remove();
values[i].remove();
}
icons.length = newCnt;
values.length = newCnt;
for(uint i = oldCnt; i < newCnt; ++i) {
@icons[i] = GuiSprite(this, recti());
@values[i] = GuiText(this, recti());
}
const Font@ ft = skin.getFont(FT_Normal);
int x = padding, s = size.height-padding-padding;
for(uint i = 0; i < newCnt; ++i) {
auto@ type = getCargoType(obj.cargoType[i]);
if(type is null)
continue;
double amount = obj.getCargoStored(type.id);
string ttip = format("[font=Medium]$1[/font]\n$2", type.name, type.description);
icons[i].rect = recti_area(x, padding, s, s);
icons[i].desc = type.icon;
setMarkupTooltip(icons[i], ttip);
x += s + padding;
string txt = standardize(amount, true);
int w = ft.getDimension(txt).x + 3;
values[i].rect = recti_area(x, padding, w, s);
values[i].text = txt;
setMarkupTooltip(values[i], ttip);
x += w + padding;
}
}
};
+119
View File
@@ -0,0 +1,119 @@
import elements.BaseGuiElement;
export GuiCheckbox;
interface onCheckboxChange {
bool onChange(GuiCheckbox@ btn);
};
class GuiCheckbox : BaseGuiElement {
string text;
FontType font = FT_Normal;
Color textColor;
int vertPadding = 2;
int separation = 8;
bool checked;
bool Hovered = false;
bool Focused = false;
SkinStyle style = SS_Checkbox;
onCheckboxChange@ onChange;
GuiCheckbox(IGuiElement@ ParentElement, const recti& Rectangle, const string &in txt, bool Default = false) {
checked = Default;
text = txt;
super(ParentElement, Rectangle);
updateAbsolutePosition();
}
GuiCheckbox(IGuiElement@ ParentElement, Alignment@ align, const string& txt, bool Default = false) {
checked = Default;
text = txt;
super(ParentElement, align);
updateAbsolutePosition();
}
void emitChange() {
if(onChange !is null && onChange.onChange(this))
return;
GuiEvent evt;
evt.type = GUI_Changed;
@evt.caller = this;
onGuiEvent(evt);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this) {
switch(event.type) {
case MET_Button_Down:
return true;
case MET_Button_Up:
if(Focused) {
if(event.button == 0) {
if(Hovered) {
checked = !checked;
emitChange();
}
}
return true;
}
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is this) {
switch(event.type) {
case GUI_Mouse_Entered:
Hovered = true; break;
case GUI_Mouse_Left:
Hovered = false; break;
case GUI_Focused:
Focused = true;
break;
case GUI_Focus_Lost:
Focused = false;
break;
}
}
return BaseGuiElement::onGuiEvent(event);
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
if(source is this && event.type == KET_Key_Up) {
if(event.key == KEY_ENTER) {
checked = !checked;
emitChange();
return true;
}
}
return BaseGuiElement::onKeyEvent(event, source);
}
void draw() {
const Font@ fnt = skin.getFont(font);
//Draw checkbox
uint flags = SF_Normal;
if(checked)
flags |= SF_Active;
if(Hovered)
flags |= SF_Hovered;
int size = AbsolutePosition.height - vertPadding * 2;
recti box = recti_area(
AbsolutePosition.topLeft + vec2i(0, vertPadding),
vec2i(size, size));
skin.draw(style, flags, box);
//Draw text
int center = (AbsolutePosition.height - fnt.getBaseline()) / 2;
vec2i pos = vec2i(size + separation, center);
skin.draw(font, pos + AbsolutePosition.topLeft, text, textColor);
BaseGuiElement::draw();
}
};
+361
View File
@@ -0,0 +1,361 @@
import elements.BaseGuiElement;
import elements.GuiListbox;
import elements.GuiMarkupText;
from gui import clearTooltip;
export GuiContextOption, GuiSubMenu, GuiContextMenu;
export GuiMarkupContextOption;
const uint CTX_LEFT_OFF = 12;
const uint CTX_RIGHT_OFF = 12;
class GuiContextOption : GuiListElement {
Sprite icon;
Color color;
string text;
int height = -1;
int value = 0;
bool selectable = true;
bool separator = false;
GuiContextOption() {
}
GuiContextOption(const string& txt, int val = 0) {
text = txt;
value = val;
}
void set(const string& txt) {
text = txt;
height = -1;
}
string get() {
return text;
}
bool get_isSelectable() {
return selectable;
}
void call(GuiContextMenu@ menu) {
}
int getWidth(GuiListbox@ ele) {
const Font@ font = ele.skin.getFont(ele.TextFont);
int w = font.getDimension(text).x + ele.horizPadding * 2;
if(height == -1)
height = font.getDimension(text).height;
if(icon.valid) {
vec2i iconSize = icon.size;
if(iconSize.y > height + 8) {
iconSize.x = double(height + 8) / double(iconSize.y) * double(iconSize.x);
iconSize.y = height + 8;
}
w += iconSize.x + 8;
}
return w;
}
void draw(GuiListbox@ ele, uint flags, const recti& absPos) override {
const Font@ font = ele.skin.getFont(ele.TextFont);
if(height == -1)
height = font.getDimension(text).height;
vec2i textOffset(ele.horizPadding, (ele.lineHeight - height) / 2);
if(icon.valid) {
vec2i iconSize = icon.size;
if(iconSize.y > height + 8) {
iconSize.x = double(height + 8) / double(iconSize.y) * double(iconSize.x);
iconSize.y = height + 8;
}
vec2i iconOffset(ele.horizPadding, (ele.lineHeight - iconSize.height) / 2);
icon.draw(recti_area(absPos.topLeft + iconOffset, iconSize));
textOffset.x += iconSize.width + 8;
}
font.draw(absPos.topLeft + textOffset, text, color);
if(separator)
drawRectangle(
recti_area(absPos.topLeft + vec2i(6, absPos.size.height / 2 - 1),
vec2i(absPos.size.width - 12, 2)),
Color(0x88888888));
}
};
class GuiMarkupContextOption : GuiContextOption {
MarkupRenderer renderer;
GuiMarkupContextOption() {
}
GuiMarkupContextOption(const string& txt, int val = 0) {
set(txt);
value = val;
}
void set(const string& txt) {
text = txt;
height = -1;
renderer.parseTree(txt);
}
int getWidth(GuiListbox@ ele) {
renderer.update(ele.skin, recti_area(0,0,3000,100));
return renderer.width + 24;
}
void draw(GuiListbox@ ele, uint flags, const recti& absPos) override {
const Font@ font = ele.skin.getFont(ele.TextFont);
if(height == -1)
height = ele.lineHeight;
vec2i textOffset(ele.horizPadding, (ele.lineHeight - height) / 2);
if(icon.valid) {
vec2i iconSize = icon.size;
if(iconSize.y > height + 8) {
iconSize.x = double(height + 8) / double(iconSize.y) * double(iconSize.x);
iconSize.y = height + 8;
}
vec2i iconOffset(ele.horizPadding, (ele.lineHeight - iconSize.height) / 2);
icon.draw(recti_area(absPos.topLeft + iconOffset, iconSize));
textOffset.x += iconSize.width + 8;
}
renderer.draw(ele.skin, absPos.padded(textOffset.x, textOffset.y, 4, 4));
}
};
class GuiSubMenu : GuiContextOption {
GuiContextMenu@ menu;
GuiSubMenu(GuiContextMenu@ parent, const string& txt) {
@menu = GuiContextMenu(parent);
text = txt;
}
void set(const string& txt) {
text = txt;
}
string get() {
return text;
}
void call(GuiContextMenu@ m) {
menu.open();
}
};
class GuiContextMenu : BaseGuiElement {
GuiListbox@ list;
GuiContextMenu@ parentMenu;
vec2i origin;
bool flexWidth = true;
GuiContextMenu(const vec2i& orig, int width = 200, bool Flex = true) {
origin = orig;
flexWidth = Flex;
super(null, Alignment_Fill());
@list = GuiListbox(this, recti_area(orig, vec2i(width, 0)));
list.style = SS_ContextMenu;
list.itemStyle = SS_ContextMenuItem;
list.itemHeight = 28;
setGuiAbsorb(this);
clearTooltip();
}
GuiContextMenu(GuiContextMenu@ parent) {
super(null, Alignment_Fill());
@parentMenu = parent;
@list = GuiListbox(parent.top, recti());
list.style = SS_ContextMenu;
list.itemStyle = SS_ContextMenuItem;
list.itemHeight = parent.list.itemHeight;
visible = false;
}
GuiContextMenu@ get_top() {
GuiContextMenu@ p = this;
while(p.parentMenu !is null)
@p = p.parentMenu;
return p;
}
void open() {
visible = true;
int hov = parentMenu.list.hovered;
vec2i offset = parentMenu.list.getItemOffset(hov);
list.size = vec2i(parentMenu.list.size.width, 0);
list.position = parentMenu.position + offset + vec2i(parentMenu.list.size.width);
}
void set_width(int width) {
list.size = vec2i(width, list.size.height);
}
void clear() {
list.clearItems();
}
void addOption(const string& text, int value = 0) {
list.addItem(GuiContextOption(text, value));
}
void addOption(GuiContextOption@ opt) {
list.addItem(opt);
}
void addOption(GuiContextOption@ opt, const string& text, int value = 0) {
opt.text = text;
opt.value = value;
opt.set(text);
list.addItem(opt);
}
void addOption(GuiContextOption@ opt, const string& text, const Sprite& sprt, int value = 0) {
opt.text = text;
opt.value = value;
opt.icon = sprt;
opt.set(text);
list.addItem(opt);
}
void finalize() {
if(list.itemCount == 0)
remove();
else
updateAbsolutePosition();
}
void addLabel(const string& text) {
GuiContextOption opt(text);
opt.selectable = false;
list.addItem(opt);
}
void addSeparator() {
GuiContextOption opt;
opt.selectable = false;
opt.separator = true;
list.addItem(opt);
}
GuiContextMenu@ addSubMenu(const string& text) {
GuiSubMenu sub(this, text);
addOption(sub);
return sub.menu;
}
void remove() {
if(parentMenu !is null)
parentMenu.remove();
list.remove();
BaseGuiElement::remove();
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(event.type == MET_Button_Up && source is this) {
remove();
return true;
}
return BaseGuiElement::onMouseEvent(event, source);
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
switch(event.type) {
case KET_Key_Down:
if(event.key == KEY_ESC)
return true;
break;
case KET_Key_Up:
if(event.key == KEY_ESC) {
remove();
return true;
}
break;
}
return BaseGuiElement::onKeyEvent(event, source);
}
bool onGuiEvent(const GuiEvent& evt) {
switch(evt.type) {
case GUI_Changed:
if(evt.caller is list) {
int sel = list.selected;
if(sel != -1) {
GuiContextOption@ opt = cast<GuiContextOption>(list.getItemElement(sel));
opt.call(this);
emitClicked();
}
remove();
return true;
}
break;
}
return BaseGuiElement::onGuiEvent(evt);
}
void set_itemHeight(int value) {
list.itemHeight = value;
}
void updateAbsolutePosition() {
BaseGuiElement::updateAbsolutePosition();
//Calculate correct width
int width = list.size.width;
int wantWidth = width;
if(flexWidth) {
for(uint i = 0, cnt = list.itemCount; i < cnt; ++i) {
int w = cast<GuiContextOption>(list.getItemElement(i)).getWidth(list);
if(w > wantWidth)
wantWidth = w;
}
}
//Calculate correct height
int height = list.size.height;
int wantHeight = list.itemCount * list.lineHeight;
if(wantHeight > parent.size.height - 40) {
wantWidth += 20;
wantHeight = parent.size.height - 40;
}
if(height == wantHeight && width == wantWidth)
return;
//Resize it to the correct height
list.size = vec2i(wantWidth, wantHeight);
width = wantWidth;
height = wantHeight;
//Position on the horizontal axis
vec2i pos;
vec2i psize = size;
if(origin.x + CTX_RIGHT_OFF + width > psize.width &&
origin.x - width - CTX_LEFT_OFF > 0)
pos.x = origin.x - width - CTX_LEFT_OFF;
else
pos.x = origin.x + CTX_RIGHT_OFF;
//Position on the vertical axis
if(origin.y + height > psize.y) {
if(origin.y - height > 0) {
pos.y = origin.y - height;
}
else {
pos.y = psize.y - height;
}
}
else {
pos.y = origin.y;
}
list.position = pos;
}
};
@@ -0,0 +1,201 @@
#section disable menu
import elements.BaseGuiElement;
import tile_resources;
export DistributionElement, GuiDistributionBar, initResourceDistribution;
final class DistributionElement {
Sprite picture;
Color color;
Color textColor;
string tooltip;
string text;
float amount;
DistributionElement() {
}
DistributionElement(const Color& col, string tt, const Sprite& pic) {
picture = pic;
tooltip = tt;
color = col;
}
};
class GuiDistributionBar : BaseGuiElement {
DistributionElement@[] elements;
FontType font = FT_Normal;
int hovered = -1;
int padding = 1;
GuiDistributionBar(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
}
GuiDistributionBar(IGuiElement@ ParentElement, Alignment@ align) {
super(ParentElement, align);
}
string get_tooltip() override {
if(hovered < 0 || uint(hovered) >= elements.length)
return "";
return elements[hovered].tooltip;
}
int getOffsetItem(const vec2i& off) {
int width = size.width - 4;
int x = off.x;
if(off.x < 2 || off.y < 0)
return -1;
if(off.x > size.width - 2 || off.y > size.height)
return -1;
for(uint i = 0, cnt = elements.length; i < cnt; ++i) {
DistributionElement@ ele = elements[i];
int w = 0;
if(i == cnt - 1 && ele.amount != 0.f)
w = width - x;
else
w = floor(float(width) * ele.amount);
if(w == 0)
continue;
if(x < w)
return int(i);
x -= w;
}
return -1;
}
bool onGuiEvent(const GuiEvent& event) override {
if(event.caller is this) {
switch(event.type) {
case GUI_Mouse_Left:
hovered = -1;
break;
}
}
return BaseGuiElement::onGuiEvent(event);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) override {
if(source is this) {
switch(event.type) {
case MET_Moved: {
int prevHovered = hovered;
hovered = getOffsetItem(mousePos - AbsolutePosition.topLeft);
if(prevHovered != hovered && tooltipObject !is null)
tooltipObject.update(skin, this);
} break;
case MET_Button_Down:
if(hovered != -1)
return true;
break;
case MET_Button_Up:
if(hovered != -1) {
emitClicked(event.button);
return true;
}
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
void draw() {
const Font@ ft = skin.getFont(font);
//Draw background
skin.draw(SS_DistributionBar, SF_Normal, AbsolutePosition);
//Draw bars
int width = size.width - padding*2;
int x = padding;
for(uint i = 0, cnt = elements.length; i < cnt; ++i) {
DistributionElement@ ele = elements[i];
int w = 0;
if(i == cnt - 1 && ele.amount != 0.f)
w = width - x;
else
w = floor(float(width) * ele.amount);
if(w == 0)
continue;
//Bar background
uint flags = SF_Normal;
if(hovered == int(i))
flags |= SF_Hovered;
skin.draw(SS_DistributionElement, flags,
recti_area(AbsolutePosition.topLeft + vec2i(x, 1),
vec2i(w, size.height - 2)), ele.color);
//Calculate label sizes
int labelOffset = 0;
int labelSize = 0;
vec2i psize; float aspect; vec2i nsize;
if(ele.picture.valid) {
psize = ele.picture.size;
aspect = float(psize.width) / float(psize.height);
nsize = vec2i(float(size.height - 2) * aspect, size.height - 2);
labelSize += nsize.x+6;
}
vec2i tsize;
if(ele.text.length != 0) {
tsize = ft.getDimension(ele.text);
labelSize += tsize.x;
}
labelOffset = max(x + (w - labelSize) / 2, x);
//Draw icon
if(ele.picture.valid) {
vec2i pos = vec2i(labelOffset, (size.height - nsize.height) / 2);
pos += AbsolutePosition.topLeft;
ele.picture.draw(recti_area(pos, nsize));
labelOffset += nsize.x+6;
}
//Draw label
if(ele.text.length != 0) {
vec2i pos = vec2i(min(labelOffset, x + w - tsize.x - 4), (size.height - tsize.height) / 2);
pos += AbsolutePosition.topLeft;
ft.draw(pos, ele.text, ele.textColor);
}
x += w;
}
BaseGuiElement::draw();
}
};
const string[] RESOURCE_TOOLTIPS = {
locale::PLANET_INCOME_TIP,
locale::PLANET_INFLUENCE_TIP,
locale::PLANET_ENERGY_TIP,
locale::PLANET_DEFENSE_TIP,
locale::PLANET_LABOR_TIP,
locale::PLANET_RESEARCH_TIP
};
void initResourceDistribution(GuiDistributionBar@ bar) {
bar.elements.length = TR_COUNT;
for(uint i = 0, cnt = TR_COUNT; i < cnt; ++i) {
DistributionElement ele;
ele.picture = getTileResourceSprite(i);
ele.tooltip = RESOURCE_TOOLTIPS[i];
ele.color = getTileResourceColor(i);
ele.amount = 1.f / float(TR_COUNT);
@bar.elements[i] = ele;
}
}
+77
View File
@@ -0,0 +1,77 @@
import elements.BaseGuiElement;
export GuiDraggable;
class GuiDraggable : BaseGuiElement {
bool Dragging;
bool allowPassthrough;
bool btnDown = false;
vec2i dragPos;
vec2i dragOffset;
GuiDraggable(IGuiElement@ ParentElement, const recti& Rectangle) {
Dragging = false;
allowPassthrough = true;
super(ParentElement, Rectangle);
updateAbsolutePosition();
}
void emitChange() {
GuiEvent evt;
evt.type = GUI_Changed;
@evt.caller = this;
onGuiEvent(evt);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this || allowPassthrough) {
switch(event.type) {
case MET_Button_Down:
if(event.button == 0) {
btnDown = true;
dragPos = Position.topLeft;
dragOffset = mousePos;
bringToFront();
return true;
}
break;
case MET_Moved:
if(Dragging) {
position = mousePos - dragOffset + dragPos;
return true;
}
else if(btnDown) {
if((mousePos - dragOffset).length > 2)
Dragging = true;
return true;
}
break;
case MET_Button_Up:
if(event.button == 0) {
btnDown = false;
if(Dragging) {
if(Position.topLeft != dragPos)
emitChange();
Dragging = false;
return true;
}
}
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is this) {
switch(event.type) {
case GUI_Mouse_Left:
case GUI_Focus_Lost:
if(Dragging)
return true;
break;
}
}
return BaseGuiElement::onGuiEvent(event);
}
};
+426
View File
@@ -0,0 +1,426 @@
import elements.BaseGuiElement;
import elements.GuiListbox;
import elements.GuiButton;
export DropdownAlignment, GuiDropdown;
export from elements.GuiListbox;
enum DropdownAlignment {
DA_Equal,
DA_Left,
DA_Right,
DA_Center
};
class GuiDropdown : BaseGuiElement, IGuiCallback {
GuiListbox@ list;
GuiButton@ arrow;
int maxItemWidth;
FontType TextFont;
bool Hovered;
bool Focused;
bool Disabled;
DropdownAlignment DropAlign;
bool sizeDown;
int maxHeight;
int prevSelected;
int horizPadding = 6;
int vertPadding = 3;
bool showOnHover = false;
GuiListElement@ showElement;
SkinStyle style;
SkinStyle arrowStyle;
GuiDropdown(IGuiElement@ ParentElement, const recti& Rectangle) {
_GuiDropdown();
super(ParentElement, Rectangle);
@arrow.parent = this;
}
GuiDropdown(IGuiElement@ ParentElement, Alignment@ Align) {
_GuiDropdown();
super(ParentElement, Align);
@arrow.parent = this;
}
void _GuiDropdown() {
@list = GuiListbox(null, recti());
list.required = true;
list.visible = false;
list.style = SS_DropdownList;
list.itemStyle = SS_DropdownListItem;
@list.callback = this;
style = SS_Dropdown;
maxItemWidth = 0;
maxHeight = 220;
DropAlign = DA_Equal;
Hovered = false;
prevSelected = -1;
sizeDown = false;
Focused = false;
Disabled = false;
TextFont = FT_Normal;
@arrow = GuiButton(null, Alignment(Right-20, Top, Right, Bottom));
arrow.style = SS_DropdownArrow;
}
void set_itemHeight(int it) {
list.itemHeight = it;
}
void set_disabled(bool dis) {
Disabled = dis;
list.disabled = dis;
}
bool get_disabled() {
return Disabled;
}
void emitChange() {
GuiEvent evt;
evt.type = GUI_Changed;
@evt.caller = this;
onGuiEvent(evt);
}
void addItem(const string& item) {
list.addItem(item);
updateItemLength();
}
void addItem(GuiListElement@ elem) {
list.addItem(elem);
updateItemLength();
}
void setItem(uint index, const string& item) {
list.setItem(index, item);
updateItemLength();
}
void setItem(uint index, GuiListElement@ elem) {
list.setItem(index, elem);
updateItemLength();
}
void removeItem(uint index) {
list.removeItem(index);
updateItemLength();
}
void removeItemsFrom(uint index) {
list.removeItemsFrom(index);
updateItemLength();
}
void clearItems() {
list.clearItems();
updateItemLength();
}
string getItem(uint index) {
return list.getItem(index);
}
GuiListElement@ getItemElement(uint index) {
return list.getItemElement(index);
}
int get_selected() {
return list.selected;
}
void set_selected(int sel) {
list.selected = sel;
}
void set_font(FontType type) {
if(TextFont == type)
return;
TextFont = type;
list.font = type;
updateItemLength();
}
uint get_itemCount() {
return list.itemCount;
}
FontType get_font() const {
return TextFont;
}
void set_dropAlign(DropdownAlignment align) {
if(DropAlign == align)
return;
DropAlign = align;
updateAbsolutePosition();
}
DropdownAlignment get_dropAlign() const {
return DropAlign;
}
void toggleList() {
list.visible = !list.visible;
if(list.visible) {
prevSelected = selected;
list.selected = -1;
list.required = false;
list.bringToFront();
updateAbsolutePosition();
}
else {
if(list.selected == -1 && prevSelected != -1) {
list.selected = prevSelected;
list.required = true;
}
else {
emitChange();
}
}
}
GuiListElement@ get_selectedItem() {
return list.selectedItem;
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is this) {
switch(event.type) {
case GUI_Mouse_Entered:
Hovered = true; break;
case GUI_Mouse_Left:
Hovered = false; break;
case GUI_Focused:
Focused = true;
break;
case GUI_Focus_Lost:
Focused = false;
if(event.other !is list && list.visible)
toggleList();
break;
}
}
else if(event.caller is list) {
switch(event.type) {
case GUI_Changed:
if(list.searchTime < frameTime - 0.5) {
list.visible = false;
prevSelected = list.selected;
}
emitChange();
break;
case GUI_Focus_Lost:
if(event.other !is this && list.visible) {
Focused = false;
toggleList();
}
break;
}
}
else if(event.caller is arrow) {
switch(event.type) {
case GUI_Clicked:
toggleList();
return true;
}
}
return BaseGuiElement::onGuiEvent(event);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this) {
switch(event.type) {
case MET_Button_Down:
return true;
case MET_Button_Up:
if(!showOnHover || !disabled)
toggleList();
else
emitClicked();
return true;
case MET_Scrolled:
if(!list.visible && !Disabled && !showOnHover) {
if(event.y > 0) {
if(list.selected > 0) {
list.selected -= 1;
emitChange();
}
}
else {
if(list.selected < int(list.itemCount) - 1) {
list.selected += 1;
emitChange();
}
}
return true;
}
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
if(Focused && source is this) {
if(event.key == KEY_BACKSPACE) {
return list.onKeyEvent(event, list);
}
else if(event.type== KET_Key_Down) {
switch(event.key) {
case KEY_ENTER:
return true;
}
}
else if(event.type == KET_Key_Up) {
switch(event.key) {
case KEY_ENTER:
toggleList();
return true;
case KEY_DOWN:
if(Disabled)
return true;
if(list.selected == -1)
list.selected = 0;
else
list.nextItem();
return true;
case KEY_UP:
if(Disabled)
return true;
if(list.selected == -1)
list.selected = list.itemCount - 1;
else
list.prevItem();
return true;
}
}
else if(event.type == KET_Key_Typed) {
return list.onKeyEvent(event, list);
}
}
else if(source is list) {
switch(event.key) {
case KEY_ENTER:
toggleList();
return true;
}
}
return BaseGuiElement::onKeyEvent(event, source);
}
void updateItemLength() {
const Font@ font = skin.getFont(TextFont);
maxItemWidth = 0;
for(uint i = 0, cnt = list.itemCount; i < cnt; ++i) {
int length = font.getDimension(list.getItem(i)).width;
if(length > maxItemWidth)
maxItemWidth = length;
}
maxItemWidth += 20 + list.horizPadding * 2;
updateAbsolutePosition();
}
void updateAbsolutePosition() {
arrow.alignment.left.pixels = size.height;
BaseGuiElement::updateAbsolutePosition();
int height = min(maxHeight, list.lineHeight * list.itemCount);
vec2i pos, size;
switch(DropAlign) {
case DA_Equal:
pos = vec2i(0, AbsolutePosition.height);
size = vec2i(AbsolutePosition.width, height);
break;
case DA_Left: {
int width = min(maxItemWidth, screenSize.width - AbsolutePosition.topLeft.x);
if(!sizeDown)
width = max(AbsolutePosition.width, width);
pos = vec2i(0, AbsolutePosition.height);
size = vec2i(width, height);
} break;
case DA_Right: {
int width = min(maxItemWidth, AbsolutePosition.botRight.x);
if(!sizeDown)
width = max(AbsolutePosition.width, width);
pos = vec2i(AbsolutePosition.width - width, AbsolutePosition.height);
size = vec2i(width, height);
} break;
case DA_Center: {
int width = min(maxItemWidth, AbsolutePosition.center.x * 2);
width = min(width, (screenSize.width - AbsolutePosition.center.x) * 2);
if(!sizeDown)
width = max(AbsolutePosition.width, width);
pos = vec2i((AbsolutePosition.width - width) / 2, AbsolutePosition.height);
size = vec2i(width, height);
} break;
}
list.position = vec2i(
clamp(pos.x + AbsolutePosition.topLeft.x, 0, screenSize.width-size.x),
clamp(pos.y + AbsolutePosition.topLeft.y, 0, screenSize.height-size.y));
list.size = size;
list.updateAbsolutePosition();
}
void move(const vec2i& moveBy) {
list.abs_move(moveBy);
BaseGuiElement::move(moveBy);
}
void abs_move(const vec2i& moveBy) {
list.abs_move(moveBy);
BaseGuiElement::abs_move(moveBy);
}
void draw() {
const Font@ font = skin.getFont(TextFont);
int sel = list.visible ? prevSelected : selected;
uint flags = SF_Normal;
if(Hovered)
flags |= SF_Hovered;
if(list.visible)
flags |= SF_Active;
if(Disabled)
flags |= SF_Disabled;
if(!showOnHover || ((Hovered || list.visible) && !Disabled)) {
arrow.visible = true;
skin.draw(style, flags, AbsolutePosition);
}
else {
arrow.visible = false;
}
if(showElement !is null) {
showElement.draw(list, SF_Normal, AbsolutePosition.padded(horizPadding, vertPadding));
}
else if(sel >= 0) {
auto@ elem = list.getItemElement(sel);
if(elem !is null)
elem.draw(list, SF_Normal, AbsolutePosition.padded(horizPadding, vertPadding));
}
BaseGuiElement::draw();
}
};
+107
View File
@@ -0,0 +1,107 @@
import elements.BaseGuiElement;
import settings.game_settings;
import empire_data;
export drawEmpirePicture, GuiEmpire;
void drawEmpirePicture(Empire@ emp, recti pos) {
if(emp is null)
return;
if(emp.background !is null) {
int s = max(pos.width, pos.height);
emp.background.draw(recti_area(pos.topLeft - vec2i((s-pos.width)/2, (s-pos.height)/2), vec2i(s, s)));
}
if(emp.flag !is null) {
int flagSize = min(min(pos.width * 0.6, pos.height * 0.6), 128.0);
emp.flag.draw(recti_area(vec2i(pos.width - flagSize - 4, 4) + pos.topLeft,
vec2i(flagSize, flagSize)), emp.color);
}
if(emp.portrait !is null) {
int portraitSize = min(pos.width, pos.height);
float aspect = float(emp.portrait.size.x) / float(emp.portrait.size.y);
emp.portrait.draw(recti_area(vec2i(0, pos.height - portraitSize) + pos.topLeft,
vec2i(portraitSize, portraitSize)).aspectAligned(aspect, 0.5, 1.0));
}
}
void drawEmpirePicture(EmpireSettings@ emp, recti pos) {
if(emp is null)
return;
auto@ bg = getEmpireColor(emp.color);
if(bg !is null) {
int s = max(pos.width, pos.height);
bg.background.draw(recti_area(pos.topLeft - vec2i((s-pos.width)/2, (s-pos.height)/2), vec2i(s, s)));
}
auto@ flag = getEmpireFlag(emp.flag);
if(flag !is null) {
int flagSize = min(min(pos.width * 0.6, pos.height * 0.6), 128.0);
flag.flag.draw(recti_area(vec2i(pos.width - flagSize - 4, 4) + pos.topLeft,
vec2i(flagSize, flagSize)), bg.color);
}
auto@ portrait = getEmpirePortrait(emp.portrait);
if(portrait !is null) {
int portraitSize = min(pos.width, pos.height);
float aspect = float(portrait.portrait.size.x) / float(portrait.portrait.size.y);
portrait.portrait.draw(recti_area(vec2i(0, pos.height - portraitSize) + pos.topLeft,
vec2i(portraitSize, portraitSize)).aspectAligned(aspect, 0.5, 1.0));
}
}
class GuiEmpire : BaseGuiElement {
Empire@ empire;
EmpireSettings@ settings;
bool showName = false;
FontType nameFont = FT_Bold;
SkinStyle background = SS_NULL;
int padding = 0;
GuiEmpire(IGuiElement@ parent, Alignment@ pos, Empire@ emp = null) {
@empire = emp;
super(parent, pos);
updateAbsolutePosition();
}
GuiEmpire(IGuiElement@ parent, const recti& pos, Empire@ emp = null) {
@empire = emp;
super(parent, pos);
updateAbsolutePosition();
}
void draw() {
if(background != SS_NULL) {
Color col;
if(empire !is null)
col = empire.color;
skin.draw(background, SF_Normal, AbsolutePosition, col);
}
if(empire !is null) {
drawEmpirePicture(empire, AbsolutePosition.padded(padding));
if(showName) {
const Font@ ft = skin.getFont(nameFont);
//Because fuck the police
ft.draw(pos=AbsolutePosition.padded(-1,-1,1,1), horizAlign=0.5, vertAlign=0.95,
color=colors::Black, text=empire.name);
ft.draw(pos=AbsolutePosition.padded(1,1,-1,-1), horizAlign=0.5, vertAlign=0.95,
color=colors::Black, text=empire.name);
ft.draw(pos=AbsolutePosition.padded(-1,1,1,-1), horizAlign=0.5, vertAlign=0.95,
color=colors::Black, text=empire.name);
ft.draw(pos=AbsolutePosition.padded(1,-1,-1,1), horizAlign=0.5, vertAlign=0.95,
color=colors::Black, text=empire.name);
ft.draw(pos=AbsolutePosition, horizAlign=0.5, vertAlign=0.95,
color=empire.color.interpolate(colors::White, 0.3), text=empire.name);
}
}
else if(settings !is null)
drawEmpirePicture(settings, AbsolutePosition.padded(padding));
BaseGuiElement::draw();
}
};
+366
View File
@@ -0,0 +1,366 @@
import elements.BaseGuiElement;
import elements.GuiListbox;
import elements.GuiText;
import elements.GuiTextbox;
import elements.GuiButton;
import dialogs.QuestionDialog;
import dialogs.InputDialog;
export ChooseFileMode, GuiFileChooser;
enum ChooseFileMode {
CFM_Filename,
CFM_Single,
CFM_Multiple,
};
enum FileType {
FT_Other,
FT_Design,
FT_Directory,
FT_Back,
};
class FileChooserElement : GuiListElement {
string path;
string extension;
FileType type;
FileChooserElement() {
path = "../";
type = FT_Back;
}
FileChooserElement(FileList@ flist, uint num) {
path = flist.basename[num];
if(flist.isDirectory[num]) {
type = FT_Directory;
path += "/";
}
else {
extension = flist.extension[num];
if(extension == "design")
type = FT_Design;
else
type = FT_Other;
}
}
void draw(GuiListbox@ ele, uint flags, const recti& absPos) override {
const Font@ font = ele.skin.getFont(ele.TextFont);
int baseLine = font.getBaseline();
vec2i textOffset(ele.horizPadding + 24, (ele.lineHeight - baseLine) / 2);
if(ele.itemStyle == SS_NULL)
ele.skin.draw(SS_ListboxItem, flags, absPos);
spritesheet::FileIcons.draw(type, recti_area(absPos.topLeft + vec2i(ele.horizPadding, 2), vec2i(20, 20)));
font.draw(absPos.topLeft + textOffset, path);
}
};
class FileChooserConfirm : QuestionDialogCallback {
GuiFileChooser@ chooser;
FileChooserConfirm(GuiFileChooser@ Chooser) {
@chooser = Chooser;
}
void questionCallback(QuestionDialog@ dialog, int answer) {
if(answer == QA_Yes)
chooser.emitConfirmed();
}
};
class FileChooserConfirmDelete : QuestionDialogCallback {
GuiFileChooser@ chooser;
FileChooserConfirmDelete(GuiFileChooser@ Chooser) {
@chooser = Chooser;
}
void questionCallback(QuestionDialog@ dialog, int answer) {
if(answer == QA_Yes)
chooser.deleteFiles();
}
};
class FileChooserConfirmMkdir : InputDialogCallback {
GuiFileChooser@ chooser;
FileChooserConfirmMkdir(GuiFileChooser@ Chooser) {
@chooser = Chooser;
}
void inputCallback(InputDialog@ dialog, bool accepted) {
if(!accepted)
return;
makeDirectory(chooser.realPath(dialog.getTextInput(0)));
chooser.updateFiles();
}
};
class GuiFileChooser : BaseGuiElement {
string filter = "*";
string root;
string current;
ChooseFileMode mode;
bool PromptOverwrite = true;
GuiListbox@ list;
FileList flist;
GuiText@ label;
GuiTextbox@ filename;
GuiButton@ confirm;
GuiButton@ deleteButton;
GuiButton@ mkdirButton;
GuiFileChooser(IGuiElement@ Parent, Alignment@ align, const string& Root, const string&in Initial, ChooseFileMode Mode, bool Management = true) {
super(Parent, align);
_GuiFileChooser(Root, Initial, Mode, Management);
}
GuiFileChooser(IGuiElement@ Parent, const recti& pos, const string&in Root, const string&in Initial, ChooseFileMode Mode, bool Management = true) {
super(Parent, pos);
_GuiFileChooser(Root, Initial, Mode, Management);
}
void _GuiFileChooser(const string& Root, const string&in Initial, ChooseFileMode Mode, bool Management) {
root = Root;
current = Initial;
mode = Mode;
if(mode == CFM_Filename) {
@list = GuiListbox(this, Alignment(Left, Top+(Management ? 26 : 0), Right, Bottom-28));
@label = GuiText(this, Alignment(Left+4, Bottom-24, Left+0.25f, Bottom-2), locale::FILENAME_LABEL);
@filename = GuiTextbox(this, Alignment(Left+0.25f+4, Bottom-24, Left+0.75f, Bottom-2));
@confirm = GuiButton(this, Alignment(Left+0.75f+4, Bottom-24, Right, Bottom-2), locale::SAVE);
}
else {
@list = GuiListbox(this, Alignment(Left, Top+(Management ? 26 : 0), Right, Bottom));
if(mode == CFM_Multiple)
list.multiple = true;
}
list.DblClickConfirm = true;
list.autoMultiple = false;
list.itemHeight = 24;
if(Management) {
@deleteButton = GuiButton(this, Alignment(Left, Top, Left+0.2f, Top+22), locale::DELETE);
deleteButton.disabled = true;
@mkdirButton = GuiButton(this, Alignment(Right-0.3f, Top, Right, Top+22), locale::NEW_DIRECTORY);
}
updateFiles();
updateAbsolutePosition();
}
string realPath(string file) {
return path_join(path_join(root, current), file);
}
void deleteFiles() {
uint cnt = list.itemCount;
for(uint i = 0; i < cnt; ++i) {
if(!list.isSelected(i))
continue;
FileChooserElement@ elem = cast<FileChooserElement>(list.getItemElement(i));
if(elem is null || elem.type == FT_Back)
continue;
deleteFile(realPath(elem.path));
}
updateFiles();
}
void updateFiles() {
//Navigate to new directory
string dir = path_join(root, current);
flist.navigate(dir, filter);
//Fill file list
list.clearItems();
if(current.length != 0)
list.addItem(FileChooserElement());
uint cnt = flist.length;
for(uint i = 0; i < cnt; ++i)
if(flist.isDirectory[i])
list.addItem(FileChooserElement(flist, i));
for(uint i = 0; i < cnt; ++i)
if(!flist.isDirectory[i])
list.addItem(FileChooserElement(flist, i));
}
uint get_selectedCount() {
return list.selectedCount;
}
void getSelectedFiles(array<string>@ output, bool fullPath = true) {
uint cnt = list.itemCount;
for(uint i = 0; i < cnt; ++i) {
if(!list.isSelected(i))
continue;
FileChooserElement@ elem = cast<FileChooserElement>(list.getItemElement(i));
if(elem is null)
continue;
if(elem.type == FT_Directory || elem.type == FT_Back)
continue;
if(fullPath)
output.insertLast(path_join(path_join(root, current), elem.path));
else
output.insertLast(path_join(current, elem.path));
}
}
string getSelectedPath(bool fullPath = true) {
string fname;
if(filename !is null) {
fname = filename.text;
if(fname.length == 0)
return fname;
}
else {
if(list.selectedCount == 0)
return "";
fname = cast<FileChooserElement>(list.selectedItem).path;
}
if(fullPath)
return path_join(path_join(root, current), fname);
else
return path_join(current, fname);
}
string get_selectedFilename() {
return filename.text;
}
void set_selectedFilename(string fname) {
filename.text = fname;
}
void clickConfirm() {
if(mode == CFM_Filename && PromptOverwrite) {
string path = getSelectedPath(true);
if(path.length != 0 && fileExists(path)) {
string basepath = getSelectedPath(false);
string text = format(locale::CONFIRM_OVERWRITE, basepath);
question(text, locale::OVERWRITE, locale::CANCEL, FileChooserConfirm(this));
}
else {
emitConfirmed();
}
}
else {
emitConfirmed();
}
}
void promptMkdir() {
InputDialog@ dialog = InputDialog(FileChooserConfirmMkdir(this), this);
dialog.accept.text = locale::CREATE_DIRECTORY;
dialog.addTextInput(locale::DIRECTORY_LABEL, "");
addDialog(dialog);
dialog.focusInput();
}
void promptDelete() {
if(selectedCount == 0)
return;
string text = locale::CONFIRM_DELETE_FILES+"\n";
uint cnt = list.itemCount;
for(uint i = 0; i < cnt; ++i) {
if(!list.isSelected(i))
continue;
FileChooserElement@ elem = cast<FileChooserElement>(list.getItemElement(i));
if(elem is null || elem.type == FT_Back)
continue;
text += "\n"+elem.path;
}
question(text, locale::DELETE, locale::CANCEL, FileChooserConfirmDelete(this));
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
switch(event.type) {
case KET_Key_Down:
if(event.key == KEY_DEL) {
return true;
}
break;
case KET_Key_Up:
if(event.key == KEY_DEL) {
promptDelete();
return true;
}
break;
}
if(source !is list && !source.isChildOf(list))
return list.onKeyEvent(event, list);
else
return BaseGuiElement::onKeyEvent(event, source);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is list) {
switch(event.type) {
case GUI_Changed:
if(mode == CFM_Filename) {
FileChooserElement@ elem = cast<FileChooserElement>(list.selectedItem);
if(elem is null || elem.type == FT_Directory || elem.type == FT_Back)
return true;
filename.text = elem.path;
}
deleteButton.disabled = list.selectedCount == 0;
emitChanged();
break;
case GUI_Confirmed: {
FileChooserElement@ elem = cast<FileChooserElement>(list.getItemElement(event.value));
if(elem is null)
return true;
if(elem.type == FT_Back) {
if(current.length != 0) {
current = path_up(current);
updateFiles();
}
return true;
}
else if(elem.type == FT_Directory) {
current = path_join(current, elem.path);
updateFiles();
return true;
}
else {
clickConfirm();
return true;
}
}
}
}
else if(event.type == GUI_Clicked) {
if(event.caller is confirm) {
clickConfirm();
return true;
}
else if(event.caller is deleteButton) {
promptDelete();
return true;
}
else if(event.caller is mkdirButton) {
promptMkdir();
return true;
}
}
else if(event.caller is filename) {
if(event.type == GUI_Confirmed) {
clickConfirm();
return true;
}
}
return BaseGuiElement::onGuiEvent(event);
}
};
+158
View File
@@ -0,0 +1,158 @@
#section disable menu
#section client
import elements.BaseGuiElement;
import elements.MarkupTooltip;
import elements.GuiIconGrid;
import planet_types;
import ship_groups;
export GuiGroupDisplay;
const Color GHOST_COLOR(0x000000ff);
const Color ORDERED_COLOR(0xaaaaaaff);
class GuiGroupDisplay : GuiIconGrid {
GroupData[] groups;
Object@ obj;
Object@ leader;
Color color;
GuiGroupDisplay(IGuiElement@ parent, Alignment@ align) {
super(parent, align);
iconSize = vec2i(26, 18);
MarkupTooltip tt(250, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = false;
tt.Padding = 4;
@tooltipObject = tt;
}
void update(Object@ forObject) {
//Figure out which objects to use
@obj = forObject;
if(obj.hasLeaderAI) {
@leader = obj;
}
else {
Ship@ ship = cast<Ship>(obj);
if(ship !is null)
@leader = ship.Leader;
}
if(leader is null) {
groups.length = 0;
hovered = -1;
return;
}
//Sync the data
groups.syncFrom(leader.getSupportGroups());
//Update static data
Empire@ owner = leader.owner;
if(owner !is null)
color = owner.color;
else
color = Color(0xffffffff);
//Remove anything the player shouldn't be able to see
//TODO: Handle this from the server instead
if(owner !is playerEmpire) {
for(int i = int(groups.length) - 1; i >= 0; --i) {
GroupData@ group = groups[i];
if(group.amount == 0) {
groups.removeAt(i);
}
else {
group.ghost = 0;
group.ordered = 0;
group.waiting = 0;
}
}
}
//Make sure our hovered isn't invalid
hovered = clamp(hovered, -1, groups.length+1);
}
uint get_length() override {
return groups.length + 1;
}
string get_tooltip() override {
if(hovered < 0 || hovered > int(groups.length))
return "";
if(hovered == 0) {
if(leader !is null)
return format(locale::TT_SHIP_LEADER, leader.name);
return "";
}
else {
GroupData@ dat = groups[hovered-1];
return format(locale::TT_SHIP_GROUP,
dat.dsg.name, toString(dat.dsg.size, 0),
toString(dat.amount), toString(dat.ghost), toString(dat.ordered));
}
}
void drawLeader(const recti& pos) {
if(obj is leader)
spritesheet::ShipIconMods.draw(0, pos.padded(2, -2, 2, -2));
Ship@ ship = cast<Ship>(leader);
if(ship !is null) {
const Design@ dsg = ship.blueprint.design;
dsg.icon.draw(pos.padded(4, 0, 4, 0), dsg.color);
}
Planet@ pl = cast<Planet>(leader);
if(pl !is null) {
const PlanetType@ type = getPlanetType(pl.PlanetType);
type.icon.draw(pos.padded(4, 0, 4, 0));
}
spritesheet::ShipIconMods.draw(1, pos.padded(6, -2, 0, -2));
}
void drawGroup(GroupData@ dat, const recti& pos) {
const Font@ ft = skin.getFont(FT_Small);
Color col = color;
Color tcol(0xffffffff);
string num;
uint total = dat.totalSize;
if(total > 0) {
if(dat.ghost > dat.ordered) {
col = col.interpolate(GHOST_COLOR, float(dat.ghost) / float(total));
tcol = tcol.interpolate(Color(0xff0000ff), float(dat.ghost) / float(total));
}
else if(dat.ordered > 0) {
col = col.interpolate(ORDERED_COLOR, float(dat.ordered) / float(total));
}
}
if(dat.amount > 0)
num = toString(dat.amount);
else if(dat.ordered > 0)
num = toString(dat.ordered);
else if(dat.ghost > 0)
num = toString(dat.ghost);
Ship@ cur = cast<Ship>(obj);
if(cur !is null && cur.blueprint.design is dat.dsg)
spritesheet::ShipIconMods.draw(0, pos.padded(-2, -2, 6, -2));
dat.dsg.icon.draw(pos.padded(0, 0, 8, 0), dat.dsg.color);
ft.draw(pos=pos.padded(-1), text=num, ellipsis=locale::SHORT_ELLIPSIS,
color=tcol, horizAlign=0.9, vertAlign=1.0, stroke=colors::Black);
}
void drawElement(uint index, const recti& pos) override {
if(index == 0)
drawLeader(pos);
else
drawGroup(groups[index-1], pos);
}
};
+239
View File
@@ -0,0 +1,239 @@
import elements.BaseGuiElement;
import elements.MarkupTooltip;
export GuiIconGrid;
export GuiSpriteGrid;
class GuiIconGrid : BaseGuiElement {
int hovered = -1;
vec2i iconSize(18, 18);
vec2i spacing(3, 3);
vec2i padding(2, 2);
double horizAlign = 0.5;
double vertAlign = 0.5;
bool fallThrough = true;
bool clickable = true;
bool forceHover = false;
GuiIconGrid(IGuiElement@ ParentElement, Alignment@ align) {
super(ParentElement, align);
}
GuiIconGrid(IGuiElement@ ParentElement, const recti& pos) {
super(ParentElement, pos);
}
bool onGuiEvent(const GuiEvent& event) override {
if(event.caller is this) {
switch(event.type) {
case GUI_Mouse_Left:
if(hovered != -1) {
int prev = hovered;
hovered = -1;
setHovered(prev, -1);
}
break;
}
}
return BaseGuiElement::onGuiEvent(event);
}
IGuiElement@ elementFromPosition(const vec2i& pos) override {
IGuiElement@ elem = BaseGuiElement::elementFromPosition(pos);
if(elem is this && fallThrough) {
int item = getOffsetItem(pos - AbsolutePosition.topLeft);
if(item == -1 || !clickable)
return null;
}
return elem;
}
void setHovered(int previous, int current) {
}
void updateHover() {
int prevHovered = hovered;
hovered = getOffsetItem(mousePos - AbsolutePosition.topLeft);
if(prevHovered != hovered && tooltipObject !is null)
tooltipObject.update(skin, this);
setHovered(prevHovered, hovered);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) override {
if(source is this) {
switch(event.type) {
case MET_Moved:
updateHover();
break;
case MET_Button_Down:
if(uint(hovered) < length)
return true;
break;
case MET_Button_Up:
if(uint(hovered) < length) {
emitClicked(event.button);
return true;
}
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
int getOffsetItem(const vec2i& off) {
int def = -1;
uint len = length;
if(forceHover && len >= 1)
def = 0;
vec2i start, step, offset = off;
uint perRow = 0;
calcPositions(start, step, perRow);
uint rows = ceil(double(len) / double(perRow));
if(offset.x < start.x || offset.y < start.y || step.x == 0 || step.y == 0)
return def;
offset -= start;
int x = offset.x / step.x;
int y = offset.y / step.y;
if(x < 0 || uint(x) >= perRow)
return def;
if(y < 0 || uint(y) >= rows)
return def;
if(offset.x - (step.x * x) >= iconSize.x)
return def;
if(offset.y - (step.y * y) >= iconSize.y)
return def;
uint index = y*perRow + x;
if(index >= len)
return def;
return index;
}
recti getItemPosition(uint index) {
vec2i start, step;
uint perRow = 0;
calcPositions(start, step, perRow);
uint y = index / perRow;
uint x = index % perRow;
return recti_area(start + vec2i(step.x*x, step.y*y), iconSize);
}
void calcPositions(vec2i& start, vec2i& step, uint& perRow) {
perRow = max((size.width - spacing.x) / (iconSize.width + spacing.x), 1);
uint maxRows = max((size.height - spacing.y) / (iconSize.height + spacing.y), 1);
start = padding;
step = vec2i();
step.y = iconSize.height;
uint amt = length;
if(maxRows * perRow < amt) {
perRow = ceil(double(amt) / double(maxRows));
step.x = size.width / perRow;
}
else {
step.x = iconSize.width + spacing.x;
}
uint rows = ceil(double(length) / double(perRow));
if(rows == 0)
return;
double excess = double(size.height - 4 - (rows * step.y + spacing.y));
if(excess > 0) {
step.y += excess / double(rows);
start.y += excess / double(rows) * vertAlign;
}
if(amt > perRow)
start.x += double(size.width - 4 - (perRow * step.x + spacing.x)) * horizAlign;
else
start.x += double(size.width - 4 - (amt * step.x + spacing.x)) * horizAlign;
}
uint get_length() {
return 0;
}
void drawElement(uint index, const recti& pos) {
}
void draw() override {
vec2i start, step;
uint perRow = 0;
calcPositions(start, step, perRow);
recti hovPos;
recti pos = recti_area(start+AbsolutePosition.topLeft, iconSize);
uint n = 0;
for(uint i = 0, cnt = length; i < cnt; ++i) {
if(n >= perRow) {
n -= perRow;
pos.topLeft.x = start.x + AbsolutePosition.topLeft.x;
pos.botRight.x = start.x + iconSize.x + AbsolutePosition.topLeft.x;
pos.topLeft.y += step.y;
pos.botRight.y += step.y;
}
if(int(i) == hovered)
hovPos = pos;
else
drawElement(i, pos);
pos.topLeft.x += step.x;
pos.botRight.x += step.x;
++n;
}
if(hovered != -1 && uint(hovered) < length)
drawElement(hovered, hovPos);
BaseGuiElement::draw();
}
};
class GuiSpriteGrid : GuiIconGrid {
array<Sprite> sprites;
array<string> tooltips;
array<Color> colors;
GuiSpriteGrid(IGuiElement@ parent, Alignment@ align, const vec2i& size = vec2i(24, 24)) {
super(parent, align);
iconSize = size;
}
void clear() {
sprites.length = 0;
tooltips.length = 0;
colors.length = 0;
}
void add(const Sprite& sprite, const string& tooltip = "", const Color& color = colors::White) {
sprites.insertLast(sprite);
tooltips.insertLast(tooltip);
colors.insertLast(color);
if(tooltip.length > 0 && Tooltip is null)
addLazyMarkupTooltip(this, width=350);
}
uint get_length() override {
return sprites.length;
}
void drawElement(uint index, const recti& pos) override {
sprites[index].draw(pos, color=colors[index]);
}
string get_tooltip() override {
if(hovered < 0 || hovered >= int(length))
return "";
return tooltips[hovered];
}
};
+45
View File
@@ -0,0 +1,45 @@
import elements.BaseGuiElement;
export GuiImage;
class GuiImage : BaseGuiElement {
const Material@ mat;
recti source;
Color topleft;
Color topright;
Color botright;
Color botleft;
void set_material(const Material@ m) {
@mat = m;
if(m !is null)
source = recti(vec2i(), mat.size);
}
GuiImage(IGuiElement@ ParentElement, const recti& Rectangle, const Material@ Material) {
@material = Material;
super(ParentElement, Rectangle);
}
GuiImage(IGuiElement@ ParentElement, Alignment@ align, const Material@ Material) {
@material = Material;
super(ParentElement, align);
}
void set_color(Color c) {
topleft = c;
topright = c;
botright = c;
botleft = c;
}
void draw() {
if(mat !is null)
mat.draw(AbsolutePosition, topleft);
//TODO: Broken shit
// mat.draw(AbsolutePosition, source, topleft, topright, botright, botleft);
BaseGuiElement::draw();
}
};
File diff suppressed because it is too large Load Diff
+805
View File
@@ -0,0 +1,805 @@
import elements.BaseGuiElement;
import elements.GuiScrollbar;
import elements.GuiMarkupText;
export GuiListElement, GuiListText, GuiListbox;
export GuiMarkupListText;
class GuiListElement {
void set(const string& txt) {
throw("Setting item text on a non-textual list element.");
}
string get() {
return "";
}
string get_tooltipText() {
return "";
}
ITooltip@ get_tooltip() {
return null;
}
bool get_isSelectable() {
return true;
}
bool onMouseEvent(const MouseEvent& event) {
return false;
}
void onSelect() {
}
void draw(GuiListbox@ ele, uint flags, const recti& absPos) {
}
int opCmp(const GuiListElement@ other) const {
return 0;
}
};
class GuiListText : GuiListElement {
string text;
Sprite icon;
GuiListText(const string& txt, const Sprite& icon = Sprite()) {
text = txt;
this.icon = icon;
}
void set(const string& txt) {
text = txt;
}
string get() {
return text;
}
void draw(GuiListbox@ ele, uint flags, const recti& absPos) {
const Font@ font = ele.skin.getFont(ele.TextFont);
int baseLine = font.getBaseline();
vec2i textOffset(ele.horizPadding, (absPos.size.height - baseLine) / 2);
if(ele.itemStyle == SS_NULL)
ele.skin.draw(SS_ListboxItem, flags, absPos);
if(icon.valid) {
vec2i iconSize = icon.size;
if(iconSize.y > absPos.height) {
iconSize.x = double(absPos.height) / double(iconSize.y) * double(iconSize.x);
iconSize.y = absPos.height;
}
vec2i iconOffset(ele.horizPadding, (absPos.height - iconSize.height) / 2);
icon.draw(recti_area(absPos.topLeft + iconOffset, iconSize));
textOffset.x += iconSize.width + 8;
}
font.draw(absPos.topLeft + textOffset, text);
}
};
class GuiMarkupListText : GuiListElement {
string text;
string basicText;
MarkupRenderer renderer;
GuiMarkupListText(const string& txt, FontType font = FT_Normal) {
text = txt;
renderer.parseTree(txt);
renderer.defaultFont = font;
}
void set(const string& txt) {
text = txt;
renderer.parseTree(txt);
}
string get() {
return basicText.length == 0 ? text : basicText;
}
void draw(GuiListbox@ ele, uint flags, const recti& absPos) {
if(ele.itemStyle == SS_NULL)
ele.skin.draw(SS_ListboxItem, flags, absPos);
renderer.draw(ele.skin, absPos.padded(ele.horizPadding, ele.vertPadding));
}
};
class GuiListbox : BaseGuiElement {
FontType TextFont = FT_Normal;
GuiListElement@[] items;
int selected = -1;
int hovered = -1;
int lineHeight = 0;
int horizPadding = 6;
int vertPadding = 5;
bool Required = false;
bool hoverSelect = false;
bool ElementHovered = false;
bool ElementFocused = false;
bool Multiple = false;
bool autoMultiple = true;
bool[] SelectedItems;
bool DblClickConfirm = true;
int SelectedCount = 0;
int PrevSelected = -1;
int itemHeight = -1;
bool disabled = false;
string search;
double searchTime = 0;
double doubleTime = 0;
SkinStyle style = SS_Listbox;
SkinStyle itemStyle = SS_NULL;
GuiScrollbar@ scroll;
GuiListbox(IGuiElement@ ParentElement, const recti& Rectangle) {
_GuiListbox();
super(ParentElement, Rectangle);
@scroll.parent = this;
}
GuiListbox(IGuiElement@ ParentElement, Alignment@ Align) {
_GuiListbox();
super(ParentElement, Align);
@scroll.parent = this;
}
void sortAsc() {
items.sortAsc();
}
void sortDesc() {
items.sortDesc();
}
void scrollToEnd() {
scroll.pos = max(0.0, scroll.end - scroll.page);
}
void _GuiListbox() {
@scroll = GuiScrollbar(null, recti());
scroll.alignment.set(
AS_Right, 0.0, 20, AS_Top, 0.0, 0,
AS_Right, 0.0, 0, AS_Bottom, 0.0, 0);
}
void addItem(GuiListElement@ elem, uint where) {
items.insertAt(where, elem);
if(multiple) {
if(Required && SelectedCount <= 0) {
SelectedItems.insertLast(true);
SelectedCount = 1;
}
else
SelectedItems.insertLast(false);
}
else if(Required && selected == -1) {
selected = 0;
}
updateAbsolutePosition();
}
void addItem(GuiListElement@ elem) {
addItem(elem, items.length);
}
void addItem(const string& item, uint where) {
addItem(GuiListText(item), where);
}
void addItem(const string& item) {
addItem(GuiListText(item), items.length);
}
void removeItem(uint index) {
if(index < items.length()) {
items.removeAt(index);
if(multiple) {
if(SelectedItems[index])
SelectedCount -= 1;
SelectedItems.removeAt(index);
}
else {
if(selected >= int(items.length()))
selected = items.length() - 1;
}
}
updateAbsolutePosition();
}
void removeItemsFrom(uint index) {
if(items.length > index)
items.length = index;
if(selected > 0 && selected >= int(index))
selected = index - 1;
updateAbsolutePosition();
}
void clearItems() {
SelectedItems.length = 0;
items.length = 0;
selected = -1;
hovered = -1;
if(Tooltip !is null)
Tooltip.update(skin, this);
updateAbsolutePosition();
}
void setItem(uint index, const string& item) {
if(index < items.length())
items[index].set(item);
else
addItem(item);
}
void setItem(uint index, GuiListElement@ elem) {
if(index < items.length())
@items[index] = elem;
else
addItem(elem);
}
string getItem(uint index) {
if(index < items.length())
return items[index].get();
return "";
}
GuiListElement@ getItemElement(uint index) {
if(index < items.length())
return items[index];
return null;
}
GuiListElement@ get_selectedItem() {
if(selected == -1 || Multiple)
return null;
return items[selected];
}
GuiListElement@ get_hoveredItem() {
if(hovered == -1)
return null;
return items[hovered];
}
uint get_itemCount() {
return items.length();
}
bool isSelected(uint index) {
if(index >= items.length())
return false;
if(multiple)
return SelectedItems[index];
else
return int(index) == selected;
}
uint get_selectedCount() {
if(multiple)
return SelectedCount;
else
return selected == -1 ? 0 : 1;
}
void clearSelection() {
if(multiple) {
for(uint i = 0, cnt = items.length(); i < cnt; ++i)
SelectedItems[i] = false;
if(Required && SelectedItems.length() > 0) {
SelectedCount = 1;
SelectedItems[0] = true;
}
else {
SelectedCount = 0;
}
}
else {
selected = -1;
}
}
ITooltip@ get_tooltipObject() {
if(hovered >= 0 && hovered < int(items.length)) {
ITooltip@ tt = items[hovered].tooltip;
if(tt !is null)
return tt;
}
return Tooltip;
}
string get_tooltip() {
if(hovered != -1 && uint(hovered) < items.length) {
string tt = items[hovered].tooltipText;
if(tt.length != 0)
return tt;
}
return BaseGuiElement::get_tooltip();
}
void set_multiple(bool mult) {
if(Multiple == mult)
return;
Multiple = mult;
if(Multiple) {
SelectedItems.resize(items.length());
for(uint i = 0, cnt = items.length(); i < cnt; ++i)
SelectedItems[i] = false;
if(selected != -1) {
SelectedItems[selected] = true;
SelectedCount = 1;
}
else {
if(Required && SelectedItems.length() > 0) {
SelectedItems[0] = true;
SelectedCount = 1;
}
else {
SelectedCount = 0;
}
}
}
else {
SelectedItems.resize(0);
if(Required && items.length() > 0)
selected = 0;
else
selected = -1;
}
}
bool get_multiple() {
return Multiple;
}
void set_font(FontType type) {
if(TextFont == type)
return;
TextFont = type;
updateAbsolutePosition();
}
FontType get_font() const {
return TextFont;
}
void set_required(bool req) {
Required = req;
if(Required) {
if(Multiple) {
if(SelectedCount <= 0 && items.length() > 0) {
SelectedCount = 1;
SelectedItems[0] = true;
}
}
else {
if(selected == -1 && items.length() > 0)
selected = 0;
}
}
}
bool get_required() const {
return Required;
}
void emitChange() {
GuiEvent evt;
evt.type = GUI_Changed;
@evt.caller = this;
onGuiEvent(evt);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is this) {
switch(event.type) {
case GUI_Mouse_Entered:
ElementHovered = true;
break;
case GUI_Mouse_Left:
ElementHovered = false;
break;
case GUI_Focused:
ElementFocused = true;
break;
case GUI_Focus_Lost:
ElementFocused = false;
break;
}
}
return BaseGuiElement::onGuiEvent(event);
}
void updateHover() {
vec2i offset = mousePos - AbsolutePosition.topLeft;
int prevHovered = hovered;
hovered = getOffsetItem(offset);
if(prevHovered != hovered)
emitHoverChanged(hovered);
if(hovered != prevHovered && Tooltip !is null)
Tooltip.update(skin, this);
if(hoverSelect && hovered >= 0 && !disabled)
selected = hovered;
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this) {
updateHover();
if(hovered >= 0 && items[hovered].onMouseEvent(event))
return true;
switch(event.type) {
case MET_Button_Down:
if(event.button == 0)
return true;
break;
case MET_Button_Up: {
if(disabled)
return true;
if(hoverSelect || !ElementFocused || event.button != 0)
break;
bool wasConfirmation = false;
if(hovered == -1 || !items[hovered].isSelectable) {
if(!Required)
clearSelection();
}
else if(Multiple) {
if(shiftKey && PrevSelected != -1) {
int from = PrevSelected <= hovered ? PrevSelected : hovered;
int to = PrevSelected > hovered ? PrevSelected : hovered;
bool doSelected = SelectedItems[PrevSelected];
for(int i = from; i <= to; ++i) {
if(doSelected) {
if(!SelectedItems[i]) {
SelectedItems[i] = true;
SelectedCount += 1;
}
}
else {
if(SelectedItems[i] && (!Required || SelectedCount > 1)) {
SelectedItems[i] = false;
SelectedCount -= 1;
}
}
}
}
else {
PrevSelected = hovered;
if(SelectedItems[hovered]) {
if(!ctrlKey && DblClickConfirm && frameTime < doubleTime + double(settings::iDoubleClickMS) * 0.001) {
wasConfirmation = true;
emitConfirmed(hovered);
doubleTime = -INFINITY;
}
else if(ctrlKey || autoMultiple) {
if(!Required || SelectedCount > 1) {
SelectedItems[hovered] = false;
SelectedCount -= 1;
}
}
else {
for(uint i = 0, cnt = items.length(); i < cnt; ++i)
SelectedItems[i] = false;
if(Required || SelectedCount > 1) {
SelectedItems[hovered] = true;
SelectedCount = 1;
}
else {
SelectedCount = 0;
}
}
}
else {
if(ctrlKey || autoMultiple) {
SelectedItems[hovered] = true;
SelectedCount += 1;
doubleTime = frameTime;
}
else {
for(uint i = 0, cnt = items.length(); i < cnt; ++i)
SelectedItems[i] = false;
SelectedItems[hovered] = true;
SelectedCount = 1;
doubleTime = frameTime;
}
}
}
}
else {
if(selected == hovered && DblClickConfirm && frameTime < doubleTime + double(settings::iDoubleClickMS) * 0.001) {
wasConfirmation = true;
emitConfirmed(hovered);
doubleTime = -INFINITY;
}
else if(selected == hovered && !Required)
selected = -1;
else if(!Required || hovered != -1)
selected = hovered;
}
if(!wasConfirmation)
doubleTime = frameTime;
if(selected >= 0 && !wasConfirmation)
items[selected].onSelect();
emitChange();
} return true;
case MET_Scrolled:
if(scroll.visible)
return scroll.onMouseEvent(event, scroll);
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
void nextItem() {
if(selected >= 0 && selected < int(items.length()) - 1) {
selected += 1;
if((selected + 1) * lineHeight - scroll.pos >= AbsolutePosition.height)
scroll.pos = min(scroll.end - scroll.page, scroll.pos + lineHeight);
}
}
void prevItem() {
if(selected > 0) {
selected -= 1;
if(selected * lineHeight - scroll.pos < 0)
scroll.pos = max(0.0, scroll.pos - lineHeight);
}
}
void updateScroll() {
if(selected * lineHeight - scroll.pos < 0)
scroll.pos = max(0.0, double(selected * lineHeight));
else if((selected + 1) * lineHeight - scroll.pos >= AbsolutePosition.height)
scroll.pos = min(scroll.end - scroll.page, double(selected * lineHeight));
}
void doSearch() {
searchTime = frameTime;
uint start = selected == -1 ? 0 : uint(selected);
int found = -1;
for(uint i = 0, cnt = items.length; i < cnt; ++i) {
int index = int((start + i) % cnt);
string text = items[index].get();
if(text.length == 0)
continue;
if(text.startswith_nocase(search)) {
found = index;
break;
}
}
if(found != -1 && selected != found) {
selected = found;
emitChange();
updateScroll();
}
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
if(source is this) {
if(!Multiple) {
if(event.type == KET_Key_Down) {
int pageItems = ceil(double(AbsolutePosition.height) / double(lineHeight));
switch(event.key) {
case KEY_UP:
if(!disabled)
prevItem();
return true;
case KEY_DOWN:
if(!disabled)
nextItem();
return true;
case KEY_ENTER:
return true;
case KEY_PAGEUP:
if(selected > 0) {
if(disabled)
return true;
selected = max(0, selected - pageItems);
scroll.pos = max(0.0, scroll.pos - AbsolutePosition.height);
return true;
}
break;
case KEY_PAGEDOWN:
if(selected >= 0) {
if(disabled)
return true;
selected = min(items.length() - 1, selected + pageItems);
scroll.pos = min(scroll.end - scroll.page, scroll.pos + AbsolutePosition.height);
return true;
}
break;
case KEY_BACKSPACE: {
if(disabled)
return true;
int len = search.length;
int curs = len;
int prevPos = len;
int tmp = 0;
if(prevPos == 0)
return true;
u8prev(search, prevPos, tmp);
string newText;
if(prevPos > 0)
newText += search.substr(0, prevPos);
if(len - curs > 0)
newText += search.substr(curs, len - curs);
search = newText;
doSearch();
return true;
}
}
}
else if(event.type == KET_Key_Up) {
switch(event.key) {
case KEY_UP:
return true;
case KEY_DOWN:
return true;
case KEY_ENTER:
emitChange();
emitConfirmed();
return true;
case KEY_PAGEUP:
if(selected > 0)
return true;
break;
case KEY_PAGEDOWN:
if(selected >= 0)
return true;
break;
case KEY_BACKSPACE:
return true;
}
}
else if(event.type == KET_Key_Typed) {
if(disabled)
return true;
if(searchTime < frameTime - 0.5)
search = "";
u8append(search, event.key);
doSearch();
return true;
}
}
return scroll.onKeyEvent(event, scroll);
}
return BaseGuiElement::onKeyEvent(event, source);
}
void updateAbsolutePosition() {
BaseGuiElement::updateAbsolutePosition();
if(skin is null)
return;
const Font@ font = skin.getFont(TextFont);
if(itemHeight == -1)
lineHeight = font.getLineHeight() + vertPadding * 2;
else
lineHeight = itemHeight;
scroll.scroll = lineHeight * 2;
scroll.page = AbsolutePosition.height;
scroll.bar = scroll.page;
scroll.end = lineHeight * items.length();
scroll.pos = max(0.0, min(scroll.end - scroll.page, scroll.pos));
scroll.visible = scroll.end > scroll.page;
scroll.updateAbsolutePosition();
}
vec2i get_desiredSize() const {
return vec2i(size.width, lineHeight * items.length);
}
int getOffsetItem(const vec2i& offset) {
if(offset.x < 0 || offset.x > AbsolutePosition.width)
return -1;
int item = floor(double(offset.y + scroll.pos) / double(lineHeight));
if(item < 0 || item >= int(items.length()))
return -1;
else
return item;
}
vec2i getItemOffset(int item) {
if(item < 0 || item >= int(items.length))
return vec2i();
return vec2i(0, (item * lineHeight) - int(scroll.pos));
}
void draw() {
//Draw element background
uint flags = SF_Normal;
if(ElementHovered)
flags |= SF_Hovered;
if(ElementFocused)
flags |= SF_Focused;
if(disabled)
flags |= SF_Disabled;
skin.draw(style, SF_Normal, AbsoluteClipRect);
//Draw items
int itemWidth = AbsolutePosition.width;
if(scroll.visible)
itemWidth -= 20;
recti itemPos = recti_area(
AbsolutePosition.topLeft - vec2i(0, scroll.pos),
vec2i(itemWidth, lineHeight));
for(uint i = 0, cnt = items.length(); i < cnt; ++i) {
//Don't display completely invisible items
if(itemPos.botRight.y < AbsolutePosition.topLeft.y) {
itemPos += vec2i(0, lineHeight);
continue;
}
if(itemPos.topLeft.y > AbsolutePosition.botRight.y)
break;
//Figure correct style to use
uint itFlags = SF_Normal;
if(items[i].isSelectable) {
if(int(i) == hovered && !disabled) {
if(ElementHovered)
itFlags |= SF_Hovered;
if(ElementFocused)
itFlags |= SF_Focused;
}
if(Multiple) {
if(SelectedItems[i])
itFlags |= SF_Active;
}
else if(int(i) == selected) {
itFlags |= SF_Active;
}
}
//Draw item
if(itemStyle != SS_NULL)
skin.draw(itemStyle, itFlags, itemPos);
items[i].draw(this, itFlags, itemPos);
itemPos += vec2i(0, lineHeight);
}
BaseGuiElement::draw();
}
};
File diff suppressed because it is too large Load Diff
+567
View File
@@ -0,0 +1,567 @@
#section game
import influence;
import elements.BaseGuiElement;
import elements.GuiButton;
import elements.GuiPanel;
import elements.GuiText;
import elements.GuiSprite;
import elements.GuiSpinbox;
import elements.GuiIconGrid;
import elements.GuiInfluenceCard;
import elements.GuiDropdown;
import elements.MarkupTooltip;
import icons;
import util.formatting;
import util.icon_view;
import targeting.ObjectTarget;
import artifacts;
from tabs.GalaxyTab import zoomTo;
from tabs.tabbar import findTab;
export GuiOfferList;
export drawDiplomacyOffer;
export getDiplomacyOfferTooltip;
export GuiOfferGrid;
class GuiOfferList : BaseGuiElement {
GuiPanel@ panel;
array<GuiOffer@> offers;
GuiButton@ moneyButton;
GuiButton@ energyButton;
GuiButton@ cardButton;
GuiButton@ fleetButton;
GuiButton@ planetButton;
GuiButton@ artifButton;
GuiOfferList(IGuiElement@ parent, Alignment@ align, const string& prefix = "OFFER") {
super(parent, align);
@panel = GuiPanel(this, Alignment().padded(0,90,0,0));
float x = 0.f, w = 0.33f;
int y = 0;
@moneyButton = GuiButton(this, Alignment(Left+4+x, Top+y, Left-4+x+w, Height=34), localize("#"+prefix+"_MONEY"));
moneyButton.color = colors::Money;
moneyButton.buttonIcon = icons::Money;
x += w;
@energyButton = GuiButton(this, Alignment(Left+4+x, Top+y, Left-4+x+w, Height=34), localize("#"+prefix+"_ENERGY"));
energyButton.color = colors::Energy;
energyButton.buttonIcon = icons::Energy;
x += w;
@cardButton = GuiButton(this, Alignment(Left+4+x, Top+y, Left-4+x+w, Height=34), localize("#"+prefix+"_CARD"));
cardButton.color = colors::Influence;
cardButton.buttonIcon = icons::Action;
x += w;
x = 0.f;
y += 38;
@fleetButton = GuiButton(this, Alignment(Left+4+x, Top+y, Left-4+x+w, Height=34), localize("#"+prefix+"_FLEET"));
fleetButton.color = colors::Defense;
fleetButton.buttonIcon = icons::Strength;
x += w;
@planetButton = GuiButton(this, Alignment(Left+4+x, Top+y, Left-4+x+w, Height=34), localize("#"+prefix+"_PLANET"));
planetButton.color = colors::Planet;
planetButton.buttonIcon = icons::Planet;
x += w;
@artifButton = GuiButton(this, Alignment(Left+4+x, Top+y, Left-4+x+w, Height=34), localize("#"+prefix+"_ARTIFACT"));
artifButton.color = colors::Artifact;
artifButton.buttonIcon = icons::Artifact;
x += w;
updateAbsolutePosition();
}
uint get_length() const {
return offers.length;
}
void updateAbsolutePosition() {
BaseGuiElement::updateAbsolutePosition();
uint cnt = offers.length;
for(uint i = 0; i < cnt; ++i)
offers[i].rect = recti_area(vec2i(8, i*38), vec2i(size.width-36, 34));
panel.updateAbsolutePosition();
}
void update(array<DiplomacyOffer>& list) {
uint cnt = list.length;
offers.length = cnt;
for(uint i = 0; i < cnt; ++i) {
GuiOffer@ off = offers[i];
if(off is null || !off.supports(list[i])) {
if(off !is null)
off.remove();
@off = createOffer(panel, list[i]);
off.load(list[i]);
@offers[i] = off;
}
}
updateAbsolutePosition();
}
void changed() {
moneyButton.disabled = false;
energyButton.disabled = false;
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
if(offers[i].offer.type == DOT_Money)
moneyButton.disabled = true;
if(offers[i].offer.type == DOT_Energy)
energyButton.disabled = true;
}
}
void addOffer(GuiOffer@ off) {
offers.insertLast(off);
updateAbsolutePosition();
changed();
emitChanged();
}
bool onGuiEvent(const GuiEvent& evt) {
if(evt.type == GUI_Changed && evt.caller !is this) {
changed();
emitChanged();
return true;
}
else if(evt.type == GUI_Confirmed) {
if(evt.value == 1) {
for(uint i = 0, cnt = offers.length; i < cnt; ++i) {
auto@ off = offers[i];
if(evt.caller.isChildOf(off)) {
off.remove();
offers.removeAt(i);
changed();
emitChanged();
return true;
}
}
}
}
else if(evt.type == GUI_Clicked) {
if(evt.caller is moneyButton) {
addOffer(MoneyOffer(panel));
return true;
}
if(evt.caller is energyButton) {
addOffer(EnergyOffer(panel));
return true;
}
if(evt.caller is cardButton) {
addOffer(CardOffer(panel));
return true;
}
if(evt.caller is planetButton) {
targetObject(PlanetOfferTarget(this), findTab(this));
return true;
}
if(evt.caller is fleetButton) {
targetObject(FleetOfferTarget(this), findTab(this));
return true;
}
if(evt.caller is artifButton) {
targetObject(ArtifactOfferTarget(this), findTab(this));
return true;
}
}
return BaseGuiElement::onGuiEvent(evt);
}
void apply(array<DiplomacyOffer>& list) {
list.length = offers.length;
for(uint i = 0, cnt = offers.length; i < cnt; ++i)
offers[i].apply(list[i]);
}
};
class GuiOffer : BaseGuiElement {
DiplomacyOffer offer;
GuiButton@ removeButton;
GuiOffer(IGuiElement@ parent) {
super(parent, recti());
@removeButton = GuiButton(this, Alignment(Right-34, Top, Right, Bottom));
removeButton.color = colors::Red;
GuiSprite(removeButton, Alignment().padded(4), icons::Remove);
updateAbsolutePosition();
}
bool supports(DiplomacyOffer& input) {
return input.type == offer.type;
}
void load(DiplomacyOffer& input) {
offer = input;
}
void apply(DiplomacyOffer& output) {
apply();
output = offer;
}
void apply() {
}
bool onGuiEvent(const GuiEvent& evt) {
if(evt.type == GUI_Clicked && evt.caller is removeButton) {
emitConfirmed(1);
return true;
}
return BaseGuiElement::onGuiEvent(evt);
}
};
class MoneyOffer : GuiOffer {
GuiSprite@ icon;
GuiText@ label;
GuiSpinbox@ input;
MoneyOffer(IGuiElement@ parent) {
super(parent);
offer.type = DOT_Money;
@icon = GuiSprite(this, recti_area(4,4, 28,28), icons::Money);
@label = GuiText(this, recti_area(40,6, 100,26), locale::OFFER_MONEY);
label.font = FT_Bold;
@input = GuiSpinbox(this, Alignment(Left+150, Top+4, Right-40, Bottom),
num=200, min=100, max=INFINITY, step=100, decimals=0);
input.color = colors::Money;
updateAbsolutePosition();
apply();
}
void apply() {
offer.value = input.value;
}
bool onGuiEvent(const GuiEvent& evt) {
if(evt.type == GUI_Changed && evt.caller is input) {
apply();
emitChanged();
return true;
}
return GuiOffer::onGuiEvent(evt);
}
};
class EnergyOffer : GuiOffer {
GuiSprite@ icon;
GuiText@ label;
GuiSpinbox@ input;
EnergyOffer(IGuiElement@ parent) {
super(parent);
offer.type = DOT_Energy;
@icon = GuiSprite(this, recti_area(4,4, 28,28), icons::Energy);
@label = GuiText(this, recti_area(40,6, 100,26), locale::OFFER_ENERGY);
label.font = FT_Bold;
@input = GuiSpinbox(this, Alignment(Left+150, Top+4, Right-40, Bottom),
num=200, min=100, max=INFINITY, step=100, decimals=0);
input.color = colors::Energy;
updateAbsolutePosition();
apply();
}
void apply() {
offer.value = input.value;
}
bool onGuiEvent(const GuiEvent& evt) {
if(evt.type == GUI_Changed && evt.caller is input) {
apply();
emitChanged();
return true;
}
return GuiOffer::onGuiEvent(evt);
}
};
class CardOffer : GuiOffer {
GuiSprite@ icon;
GuiText@ label;
GuiDropdown@ selection;
GuiSpinbox@ input;
array<InfluenceCard> cards;
CardOffer(IGuiElement@ parent) {
super(parent);
offer.type = DOT_Card;
@icon = GuiSprite(this, recti_area(4,4, 28,28), icons::Influence);
@label = GuiText(this, recti_area(40,6, 100,26), locale::OFFER_CARD);
label.font = FT_Bold;
@selection = GuiDropdown(this, Alignment(Left+150, Top+4, Right-140, Bottom));
cards.syncFrom(playerEmpire.getInfluenceCards());
for(uint i = 0, cnt = cards.length; i < cnt; ++i)
selection.addItem(GuiMarkupListText(cards[i].formatBlurb()));
@input = GuiSpinbox(this, Alignment(Right-136, Top+4, Right-40, Bottom),
num=1, min=1, max=INFINITY, step=1, decimals=0);
input.font = FT_Bold;
updateAbsolutePosition();
apply();
}
void apply() {
if(uint(selection.selected) >= selection.itemCount) {
offer.id = -1;
}
else {
offer.id = cards[selection.selected].id;
offer.value = int(input.value);
icon.desc = cards[selection.selected].type.icon;
}
}
bool onGuiEvent(const GuiEvent& evt) {
if(evt.type == GUI_Changed && (evt.caller is input || evt.caller is selection)) {
apply();
emitChanged();
return true;
}
return GuiOffer::onGuiEvent(evt);
}
};
class ObjectOffer : GuiOffer {
GuiText@ label;
ObjectOffer(IGuiElement@ parent, Object@ obj) {
super(parent);
if(obj.isPlanet)
offer.type = DOT_Planet;
else if(obj.isShip)
offer.type = DOT_Fleet;
else if(obj.isArtifact)
offer.type = DOT_Artifact;
@offer.obj = obj;
@label = GuiText(this, Alignment(Left+50, Top, Right-40, Bottom));
label.font = FT_Bold;
label.stroke = colors::Black;
label.color = obj.owner.color;
label.text = obj.name;
updateAbsolutePosition();
}
void load(DiplomacyOffer& input) {
offer = input;
label.color = offer.obj.owner.color;
label.text = offer.obj.name;
}
void draw() override {
drawObjectIcon(offer.obj, recti_area(AbsolutePosition.topLeft, vec2i(size.height, size.height)));
GuiOffer::draw();
}
};
class PlanetOfferTarget : ObjectTargeting {
GuiOfferList@ list;
PlanetOfferTarget(GuiOfferList@ list) {
@this.list = list;
allowMultiple = true;
}
bool valid(Object@ target) {
return target !is null && target.isPlanet && target.owner is playerEmpire;
}
void call(Object@ target) {
list.addOffer(ObjectOffer(list.panel, target));
}
string message(Object@ target, bool valid) {
return locale::OFFER_PLANET;
}
};
class FleetOfferTarget : ObjectTargeting {
GuiOfferList@ list;
FleetOfferTarget(GuiOfferList@ list) {
@this.list = list;
allowMultiple = true;
}
bool valid(Object@ target) {
Ship@ ship = cast<Ship>(target);
if(ship is null || !ship.valid)
return false;
if(!ship.hasLeaderAI || ship.owner !is playerEmpire)
return false;
if(ship.blueprint.design.hasTag(ST_CannotDonate))
return false;
return true;
}
void call(Object@ target) {
list.addOffer(ObjectOffer(list.panel, target));
}
string message(Object@ target, bool valid) {
return locale::OFFER_FLEET;
}
};
class ArtifactOfferTarget : ObjectTargeting {
GuiOfferList@ list;
ArtifactOfferTarget(GuiOfferList@ list) {
@this.list = list;
allowMultiple = true;
}
bool valid(Object@ target) {
if(target is null || !target.isArtifact)
return false;
Region@ region = target.region;
return target.valid && target.owner !is null
&& (!target.owner.valid || target.owner is playerEmpire)
&& region !is null && region.TradeMask & playerEmpire.mask != 0
&& getArtifactType(cast<Artifact>(target).ArtifactType).canDonate;
}
void call(Object@ target) {
list.addOffer(ObjectOffer(list.panel, target));
}
string message(Object@ target, bool valid) {
return locale::OFFER_ARTIFACT;
}
};
GuiOffer@ createOffer(IGuiElement@ parent, DiplomacyOffer@ input) {
if(input.type == DOT_Money)
return MoneyOffer(parent);
if(input.type == DOT_Energy)
return EnergyOffer(parent);
if(input.type == DOT_Planet)
return ObjectOffer(parent, input.obj);
if(input.type == DOT_Fleet)
return ObjectOffer(parent, input.obj);
return GuiOffer(parent);
}
class GuiOfferGrid : GuiIconGrid {
array<DiplomacyOffer>@ list;
GuiOfferGrid(IGuiElement@ parent, const recti& pos) {
super(parent, recti());
iconSize = vec2i(64, 34);
addLazyMarkupTooltip(this, width=300);
}
uint get_length() override {
if(list is null)
return 0;
return list.length;
}
string get_tooltip() override {
if(hovered < 0 || hovered >= int(length))
return "";
return getDiplomacyOfferTooltip(list[hovered]);
}
void drawElement(uint i, const recti& pos) override {
if(list is null)
return;
drawDiplomacyOffer(list[i], pos);
}
bool onGuiEvent(const GuiEvent& evt) override {
if(evt.caller is this && evt.type == GUI_Clicked) {
if(hovered < 0 || hovered >= int(length))
return true;
auto@ off = list[hovered];
zoomTo(off.obj);
return true;
}
return GuiIconGrid::onGuiEvent(evt);
}
};
void drawDiplomacyOffer(DiplomacyOffer& offer, const recti& position) {
switch(offer.type) {
case DOT_Money:
{
icons::Money.draw(recti_area(position.topLeft+vec2i(5,5), vec2i(position.height-10, position.height-10)));
font::DroidSans_11_Bold.draw(
pos=position, horizAlign=0.95, vertAlign=0.5,
text=formatMoney(offer.value), stroke=colors::Black);
}
break;
case DOT_Energy:
{
icons::Energy.draw(recti_area(position.topLeft+vec2i(5,5), vec2i(position.height-10, position.height-10)));
font::DroidSans_11_Bold.draw(
pos=position, horizAlign=0.95, vertAlign=0.5,
text=standardize(offer.value, true), stroke=colors::Black);
}
break;
case DOT_Planet:
{
drawObjectIcon(offer.obj, position.aspectAligned(1.0));
font::DroidSans_8.draw(
pos=position, horizAlign=0.5, vertAlign=0.0,
text=offer.obj.name, stroke=colors::Black);
}
break;
case DOT_Fleet:
{
drawObjectIcon(offer.obj, position.aspectAligned(1.0));
font::DroidSans_8.draw(
pos=position, horizAlign=0.5, vertAlign=0.0,
text=formatShipName(cast<Ship>(offer.obj)), stroke=colors::Black);
}
break;
case DOT_Artifact:
{
icons::Artifact.draw(position.aspectAligned(1.0));
font::DroidSans_8.draw(
pos=position, horizAlign=0.5, vertAlign=0.0,
text=formatObjectName(offer.obj), stroke=colors::Black);
}
break;
case DOT_Card:
{
InfluenceCard card;
if(offer.bound !is null && receive(offer.bound.getInfluenceCard(offer.id), card)) {
drawCardIcon(card, position, int(offer.value));
font::DroidSans_8.draw(
pos=position, horizAlign=0.5, vertAlign=0.0,
text=card.formatTitle(pretty=false), stroke=colors::Black);
if(int(offer.value) != 0) {
font::DroidSans_8.draw(
pos=position, horizAlign=0.5, vertAlign=1.0,
text=toString(int(offer.value),0)+"x", stroke=colors::Black,
color=colors::Red);
}
}
}
break;
}
}
string getDiplomacyOfferTooltip(DiplomacyOffer& offer) {
switch(offer.type) {
case DOT_Planet:
case DOT_Fleet:
return format(locale::OFFER_TT_OBJ, offer.blurb);
}
return format(locale::OFFER_TT, offer.blurb);
}
+97
View File
@@ -0,0 +1,97 @@
import elements.BaseGuiElement;
export GuiOverlay;
class GuiOverlay : BaseGuiElement {
Color fade(0x00000090);
IGuiElement@ prevFocus;
bool closeSelf = true;
bool pressed = false;
GuiOverlay(IGuiElement@ ParentElement, bool grabHover = true) {
@prevFocus = getGuiFocus();
super(ParentElement, Alignment_Fill());
updateAbsolutePosition();
bringToFront();
if(grabHover)
clearGuiHovered();
setGuiFocus(this);
}
void close() {
remove();
setGuiFocus(prevFocus);
}
bool onGuiEvent(const GuiEvent& event) override {
switch(event.type) {
case GUI_Navigation_Leave:
if(!isAncestorOf(event.other)) {
close();
return false;
}
break;
case GUI_Controller_Down:
if(event.value == GP_B)
return true;
break;
case GUI_Controller_Up:
if(event.value == GP_B) {
close();
return true;
}
break;
}
return BaseGuiElement::onGuiEvent(event);
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) override {
switch(event.type) {
case KET_Key_Down:
if(event.key == KEY_ESC)
return true;
break;
case KET_Key_Up:
if(event.key == KEY_ESC) {
close();
return true;
}
break;
}
return BaseGuiElement::onKeyEvent(event, source);
}
IGuiElement@ navigate(NavigationMode mode, const recti& box, const vec2d&in line) override {
float closestDist = FLOAT_INFINITY;
IGuiElement@ closest;
closestToLine(mode, box, line.normalized(), closestDist, closest, getGuiFocus());
return closest;
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) override {
if(event.type == MET_Button_Down) {
pressed = true;
return true;
}
if(event.type == MET_Button_Up) {
if(!pressed)
return false;
pressed = false;
if(event.button != 0 && event.button != 1)
return BaseGuiElement::onMouseEvent(event, source);
if((!closeSelf && event.button == 0) && source !is this && isAncestorOf(source))
return true;
close();
return true;
}
return BaseGuiElement::onMouseEvent(event, source);
}
void draw() override {
if(fade.a != 0)
drawRectangle(AbsolutePosition, fade);
BaseGuiElement::draw();
}
};
+434
View File
@@ -0,0 +1,434 @@
import elements.BaseGuiElement;
import elements.GuiScrollbar;
export GuiPanel, ScrollType;
enum ScrollType {
ST_Always,
ST_Never,
ST_Auto,
};
class GuiPanel : BaseGuiElement {
recti RealAbsolutePosition;
vec2i virtualSize;
vec2d scrollOffset;
vec2i Origin;
recti minPanelSize;
recti sizePadding;
ScrollType horizType;
ScrollType vertType;
GuiScrollbar@ horiz;
GuiScrollbar@ vert;
bool ScrollPane = false;
bool ShowPaneScroll = false;
int dragThreshold = 5;
bool allowScrollDrag = true;
vec2i dragStart;
bool heldLeft = false;
bool isDragging = false;
bool animating = false;
bool LeftDrag = true;
bool MiddleDrag = true;
GuiPanel(IGuiElement@ Parent, const recti& Rectangle) {
super(Parent, Rectangle);
_GuiPanel();
}
GuiPanel(IGuiElement@ Parent, Alignment@ Align) {
super(Parent, Align);
_GuiPanel();
}
void _GuiPanel() {
@horiz = GuiScrollbar(null, recti());
horiz.orientation = SO_Horizontal;
horiz.scroll = 40;
horiz.alignment.left.set(AS_Left, 0.0, 0);
horiz.alignment.right.set(AS_Right, 0.0, 0);
horiz.alignment.top.set(AS_Bottom, 0.0, 0);
horiz.alignment.bottom.set(AS_Bottom, 0.0, -20);
@vert = GuiScrollbar(null, recti());
vert.scroll = 40;
vert.alignment.left.set(AS_Right, 0.0, 0);
vert.alignment.right.set(AS_Right, 0.0, -20);
vert.alignment.top.set(AS_Top, 0.0, 0);
vert.alignment.bottom.set(AS_Bottom, 0.0, 0);
horizType = ST_Auto;
vertType = ST_Auto;
@horiz.parent = this;
@vert.parent = this;
}
void addChild(IGuiElement@ ele) {
BaseGuiElement::addChild(ele);
updateAbsolutePosition();
}
void removeChild(IGuiElement@ ele) {
BaseGuiElement::removeChild(ele);
updateAbsolutePosition();
}
void scrollToVert(int position) {
vert.pos = clamp(position, vert.start, vert.end-vert.page);
updateAbsolutePosition();
}
void setScrollPane(bool value, bool showScrollbars = false) {
ShowPaneScroll = showScrollbars;
if(ScrollPane == value)
return;
ScrollPane = value;
scrollOffset = vec2d();
updateAbsolutePosition();
}
void center() {
updateAbsolutePosition();
scrollOffset = (vec2d(virtualSize) - vec2d(size)) / 2 + vec2d(Origin);
updateAbsolutePosition();
}
void centerAround(const vec2i& pos) {
updateAbsolutePosition();
scrollOffset = vec2d(pos) - vec2d(size) / 2;
updateAbsolutePosition();
}
void stopDrag() {
heldLeft = false;
isDragging = false;
dragStart = vec2i();
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(ScrollPane) {
if(source is this || isAncestorOf(source)) {
switch(event.type) {
case MET_Button_Down:
if((LeftDrag && event.button == 0) || (MiddleDrag && event.button == 2)) {
heldLeft = true;
dragStart = mousePos;
return true;
}
break;
case MET_Moved: {
vec2i d = mousePos - dragStart;
if(isDragging) {
scroll(d.x, d.y);
dragStart = mousePos;
}
else if(heldLeft) {
if(abs(d.x) >= dragThreshold || abs(d.y) >= dragThreshold) {
isDragging = true;
scroll(d.x, d.y);
dragStart = mousePos;
}
}
} break;
case MET_Button_Up:
if((LeftDrag && event.button == 0) || (MiddleDrag && event.button == 2)) {
heldLeft = false;
dragStart = vec2i();
if(isDragging) {
isDragging = false;
return true;
}
}
break;
}
}
if(event.type == MET_Scrolled && allowScrollDrag) {
if(virtualSize.height > size.height) {
if(ctrlKey && virtualSize.width > size.width)
scroll(event.y * 30, 0);
else
scroll(0, event.y * 30);
return true;
}
else if(virtualSize.width > size.width) {
scroll(event.y * 30, 0);
return true;
}
}
}
else {
if(event.type == MET_Scrolled) {
if(horiz.visible && (horiz.AbsolutePosition.isWithin(mousePos) || !vert.visible))
return horiz.onMouseEvent(event, horiz);
else if(vert.visible)
return vert.onMouseEvent(event, vert);
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
if(source !is horiz && source !is vert) {
switch(event.key) {
case KEY_LEFT:
case KEY_RIGHT:
if(horiz.visible)
return horiz.onKeyEvent(event, horiz);
break;
case KEY_UP:
case KEY_DOWN:
case KEY_PAGEUP:
case KEY_PAGEDOWN:
if(vert.visible)
return vert.onKeyEvent(event, vert);
break;
}
}
return BaseGuiElement::onKeyEvent(event, source);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.type == GUI_Changed) {
if(event.caller is vert || event.caller is horiz) {
updateAbsolutePosition();
return true;
}
}
return BaseGuiElement::onGuiEvent(event);
}
bool get_onScreen() const {
return RealAbsolutePosition.overlaps(recti(vec2i(), screenSize));
}
void scroll(double dx, double dy) {
vec2i delta = vec2i(scrollOffset);
scrollOffset -= vec2d(dx, dy);
scrollOffset.x = clamp(scrollOffset.x, double(Origin.x),
max(double(virtualSize.width - size.width + Origin.x), double(Origin.x)));
scrollOffset.y = clamp(scrollOffset.y, double(Origin.y),
max(double(virtualSize.height - size.height + Origin.y), double(Origin.y)));
delta -= vec2i(scrollOffset);
if(delta.x != 0 || delta.y != 0) {
AbsolutePosition += delta;
for(uint i = 0, cnt = Children.length; i < cnt; ++i) {
auto@ chld = Children[i];
if(chld is vert || chld is horiz)
continue;
chld.abs_move(delta);
}
}
if(ShowPaneScroll) {
horiz.pos = scrollOffset.x - Origin.x;
vert.pos = scrollOffset.y - Origin.y;
}
}
void setAnimating(bool value) {
animating = value;
}
void updateAbsolutePosition() {
if(horiz is null || vert is null)
return;
//Resize actual panel
if(Parent !is null) {
if(Alignment !is null) {
Position = Alignment.resolve(Parent.absolutePosition.size);
ClipRect = recti(vec2i(), Position.size);
}
AbsolutePosition = Position + Parent.absolutePosition.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;
}
//Figure out virtual size of canvas
uint cCnt = Children.length();
Origin = AbsolutePosition.topLeft + minPanelSize.topLeft;
vec2i realSize = AbsolutePosition.size;
virtualSize = minPanelSize.size;
if(vert.visible)
AbsolutePosition.botRight.x -= 20;
if(horiz.visible)
AbsolutePosition.botRight.y -= 20;
for(uint i = 0; i != cCnt; ++i) {
IGuiElement@ child = Children[i];
if(child is horiz || child is vert || !child.visible)
continue;
child.updateAbsolutePosition();
vec2i topLeft = child.absolutePosition.topLeft;
if(topLeft.x < Origin.x)
Origin.x = topLeft.x;
if(topLeft.y < Origin.y)
Origin.y = topLeft.y;
}
for(uint i = 0; i != cCnt; ++i) {
IGuiElement@ child = Children[i];
if(child is horiz || child is vert || !child.visible)
continue;
vec2i botRight = child.absolutePosition.botRight;
virtualSize.width = max(virtualSize.width, botRight.x - Origin.x);
virtualSize.height = max(virtualSize.height, botRight.y - Origin.y);
}
Origin -= sizePadding.topLeft;
virtualSize += sizePadding.topLeft;
virtualSize += sizePadding.botRight;
//Update scrollbars
Origin -= AbsolutePosition.topLeft;
if(!ScrollPane || ShowPaneScroll) {
horiz.end = virtualSize.width;
horiz.page = AbsolutePosition.width;
horiz.bar = horiz.page;
horiz.pos = max(horiz.start, min(horiz.end-horiz.page, horiz.pos));
vert.end = virtualSize.height;
vert.page = AbsolutePosition.height;
vert.bar = vert.page;
vert.pos = max(vert.start, min(vert.end-vert.page, vert.pos));
scrollOffset = vec2d(horiz.pos + Origin.x, vert.pos + Origin.y);
//Check which scrollbars should be visible
bool prevHoriz = horiz.visible;
bool prevVert = vert.visible;
if(animating) {
vert.visible = false;
horiz.visible = false;
}
else {
vert.visible = (virtualSize.height > realSize.height && vertType != ST_Never) || vertType == ST_Always;
horiz.visible = (virtualSize.width > realSize.width && horizType != ST_Never) || horizType == ST_Always;
if(horiz.visible && !vert.visible)
vert.visible = (virtualSize.height > realSize.height - 20 && vertType != ST_Never) || vertType == ST_Always;
}
if(prevHoriz != horiz.visible || prevVert != vert.visible) {
updateAbsolutePosition();
return;
}
//Update scrollbar positions
horiz.updateAbsolutePosition();
vert.updateAbsolutePosition();
}
else {
scroll(0, 0);
horiz.visible = false;
vert.visible = false;
}
//Fake absolute position and reposition children
RealAbsolutePosition = AbsolutePosition;
AbsolutePosition -= vec2i(scrollOffset);
if(horiz.visible)
AbsoluteClipRect.botRight.y -= 20;
if(vert.visible)
AbsoluteClipRect.botRight.x -= 20;
for(uint i = 0; i != cCnt; ++i) {
if(Children[i] is horiz || Children[i] is vert)
continue;
Children[i].updateAbsolutePosition();
}
}
void move(const vec2i& moveBy) {
Position += moveBy;
AbsolutePosition += moveBy;
RealAbsolutePosition += moveBy;
AbsoluteClipRect = ClipRect + RealAbsolutePosition.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;
RealAbsolutePosition += moveBy;
AbsoluteClipRect = ClipRect + RealAbsolutePosition.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 set_horizPosition(int pos) {
horiz.pos = min(double(pos), horiz.end - horiz.page);
updateAbsolutePosition();
}
void set_vertPosition(int pos) {
vert.pos = min(double(pos), vert.end - vert.page);
updateAbsolutePosition();
}
recti get_absolutePosition() {
return RealAbsolutePosition;
}
void draw() {
if(!visible)
return;
clearClip();
if(horiz.visible)
horiz.draw();
if(vert.visible)
vert.draw();
uint cCnt = Children.length();
for(uint i = 0; i != cCnt; ++i) {
if(Children[i] is horiz || Children[i] is vert)
continue;
IGuiElement@ ele = Children[i];
if(!ele.visible || !RealAbsolutePosition.overlaps(ele.absolutePosition))
continue;
if(ele.noClip)
clearClip();
else
setClip(ele.absoluteClipRect);
ele.draw();
if(!ele.noClip)
clearClip();
}
}
};
@@ -0,0 +1,419 @@
#section disable menu
import elements.BaseGuiElement;
import elements.MarkupTooltip;
import planets.PlanetSurface;
import biomes;
from traits import getTraitID;
export GuiPlanetSurface, drawHoverBuilding;
class GuiPlanetSurface : BaseGuiElement {
PlanetSurface@ surface;
Object@ obj;
vec2i hovered(-1, -1);
int maxSize = 32;
bool showTooltip = true;
bool showHoverBiome = true;
DynamicTexture@ surfTex;
const Material@ developedMat = material::DevelopedTile;
double horizAlign = 0.5;
double vertAlign = 0.5;
GuiPlanetSurface(IGuiElement@ ParentElement, Alignment@ Align) {
super(ParentElement, Align);
_GuiPlanetSurface();
}
GuiPlanetSurface(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
_GuiPlanetSurface();
}
void _GuiPlanetSurface() {
MarkupTooltip tt(400, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = true;
tt.Padding = 4;
@tooltipObject = tt;
}
string get_tooltip() {
if(showTooltip && hovered.x != -1 && hovered.y != -1) {
SurfaceBuilding@ bld = surface.getBuilding(hovered.x, hovered.y);
if(bld is null)
return "";
string output = bld.getTooltip(obj);
if(bld.completion < 1.f) {
output += format("\n\n"+locale::BUILDING_CONSTRUCTING_PCT,
toString(bld.completion * 100.f, 0));
}
if(bld.disabled)
output += format("\n\n[color=#ff0000]$1[/color]", locale::BUILDING_DISABLED_POP);
return output;
}
return "";
}
vec2i getGridSize() {
recti area = AbsolutePosition.aspectAligned(float(surface.size.width)
/ float(surface.size.height), horizAlign, vertAlign);
int space = min(int(float(area.width) / float(surface.size.width)), maxSize);
return vec2i(space, space);
}
vec2i getTilePosition(const vec2i& tile) {
recti area = AbsolutePosition.aspectAligned(float(surface.size.width)
/ float(surface.size.height));
int space = min(int(float(area.width) / float(surface.size.width)), maxSize);
area += vec2i((area.width - space*surface.size.width) / 2,
(area.height - space*surface.size.height) / 2);
return vec2i(space * tile.x, space * tile.y) + area.topLeft;
}
vec2i getOffsetItem(const vec2i& offset) {
if(surface is null || surface.size.x == 0 || surface.size.y == 0)
return vec2i(-1, -1);
recti area = AbsolutePosition.aspectAligned(float(surface.size.width)
/ float(surface.size.height), horizAlign, vertAlign) - AbsolutePosition.topLeft;
int space = min(int(float(area.width) / float(surface.size.width)), maxSize);
area += vec2i((area.width - space*surface.size.width) / 2,
(area.height - space*surface.size.height) / 2);
if(offset.x < area.topLeft.x || offset.x > area.botRight.x)
return vec2i(-1, -1);
if(offset.y < area.topLeft.y || offset.y > area.botRight.y)
return vec2i(-1, -1);
vec2i pos;
pos.x = (offset.x - area.topLeft.x) / space;
pos.y = (offset.y - area.topLeft.y) / space;
if(pos.x >= int(surface.size.width) || pos.y >= int(surface.size.height))
return vec2i(-1, -1);
return pos;
}
vec2u prevSurfSize;
void drawSurfaceData() {
@surfTex = DynamicTexture();
vec2u size = vec2u(surface.size);
Image@ img = Image(size, 4);
prevSurfSize = size;
if(img is null) {
error("Bad image");
return;
}
renderSurfaceData(obj, surface, img);
@surfTex.image[0] = img;
@surfTex.material.texture1 = material::GuiPlanetSurface.texture0;
@surfTex.material.texture2 = material::GuiPlanetSurface.texture1;
@surfTex.material.shader = shader::PlanetSurfaceGeneric;
}
void remove() override {
@surfTex = null;
BaseGuiElement::remove();
}
bool pressed = false;
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this) {
switch(event.type) {
case MET_Moved: {
vec2i mouse = vec2i(event.x, event.y);
vec2i offset = mouse - AbsolutePosition.topLeft;
vec2i prevHovered = hovered;
hovered = getOffsetItem(offset);
if(hovered != prevHovered && Tooltip !is null)
Tooltip.update(skin, this);
} break;
case MET_Button_Down:
if(surface.isValidPosition(hovered)) {
pressed = true;
return true;
}
break;
case MET_Button_Up: {
if(pressed && surface.isValidPosition(hovered)) {
emitClicked(event.button);
pressed = false;
return true;
}
}
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.type == GUI_Mouse_Left)
hovered = vec2i(-1, -1);
return BaseGuiElement::onGuiEvent(event);
}
void draw() {
auto size = surface.size;
if(surface is null || size.x == 0 || size.y == 0)
return;
if(surfTex is null || size != prevSurfSize)
drawSurfaceData();
recti area = AbsolutePosition.aspectAligned(float(size.width)
/ float(size.height), horizAlign, vertAlign);
int space = min(int(float(area.width) / float(size.width)), maxSize);
area += vec2i((area.width - space * size.width) / 2,
(area.height - space * size.height) / 2);
recti surfArea = recti_area(area.topLeft, vec2i(space * size.width, space * size.height));
preparePlanetShader(obj);
if(surfTex.stream()) {
vec2i tl = surfArea.topLeft;
vec2i br = surfArea.botRight;
vec2i bl(tl.x, br.y);
vec2i tr(br.x, tl.y);
vec2i tc(tl.x+surfArea.width/2, tl.y);
vec2i bc(tc.x, br.y);
drawPolygonStart(PT_Quads, 2, surfTex.material);
drawPolygonPoint(tl, vec2f(0.f,0.f), Color(0x00000000));
drawPolygonPoint(tc, vec2f(0.5f,0.f), Color(0xffffffff));
drawPolygonPoint(bc, vec2f(0.5f,1.f), Color(0xffffffff));
drawPolygonPoint(bl, vec2f(0.f,1.f), Color(0x00000000));
drawPolygonPoint(tc, vec2f(0.5f,0.f), Color(0x00000000));
drawPolygonPoint(tr, vec2f(1.f,0.f), Color(0xffffffff));
drawPolygonPoint(br, vec2f(1.f,1.f), Color(0xffffffff));
drawPolygonPoint(bc, vec2f(0.5f,1.f), Color(0x00000000));
drawPolygonEnd();
}
/*surfTex.draw(surfArea, Color());*/
if(obj !is null && obj.owner.hasTrait(verdantTrait))
@developedMat = material::GrownTile;
else
@developedMat = material::DevelopedTile;
bool hasDevelopment = obj is null || obj.owner is null || obj.owner.HasPopulation != 0;
recti pos = recti_area(area.topLeft, vec2i(space, space));
vec2i spaceX(space, 0);
vec2i spaceY(-space * size.width, space);
for(uint y = 0, height = size.height; y < height; ++y) {
for(uint x = 0, width = size.width; x < width; ++x) {
const Biome@ biome = surface.getBiome(x, y);
uint8 flags = surface.getFlags(x, y);
//Draw biome background
//biome.tile.draw(pos);
//Draw building
SurfaceBuilding@ bld = surface.getBuilding(x, y);
if(bld !is null) {
Color col(0xffffffff);
if(bld.disabled)
col.r = 0xff;
col.a = (bld.completion * 0xbf) + 0x40;
if(bld.type.size.x == 1 && bld.type.size.y == 1) {
bld.type.sprite.draw(pos, col);
//Draw developed border
if(flags & SuF_Usable != 0 && hasDevelopment)
developedMat.draw(pos.padded(-1));
if(bld.disabled)
drawRectangle(pos, Color(0xff000040));
}
else {
int relX = x - bld.position.x;
int relY = y - bld.position.y;
vec2u center = bld.type.getCenter();
if(relX == int(bld.type.size.x - center.x - 1)
&& relY == int(bld.type.size.y - center.y - 1)) {
vec2i ssize(bld.type.size.x * space, bld.type.size.y * space);
recti spos = recti_area(pos.topLeft - ssize + vec2i(space,space), ssize);
bld.type.sprite.draw(spos, col);
}
//Draw undeveloped overlay
if(hasDevelopment || bld.disabled) {
for(uint rx = 0; rx < bld.type.size.x; ++rx) {
uint px = bld.position.x - center.x + rx;
for(uint ry = 0; ry < bld.type.size.y; ++ry) {
uint py = bld.position.y - center.y + ry;
uint8 tileFlags = surface.getFlags(px, py);
if(bld.disabled) {
drawRectangle(recti_area(area.topLeft + vec2i(space * px, space * py),
vec2i(space, space)), Color(0xff000040));
}
else if(tileFlags & SuF_Usable == 0 && bld.type.getMaintenanceFor(surface.getBiome(px, py)) != 0 && hasDevelopment) {
drawRectangle(recti_area(area.topLeft + vec2i(space * px, space * py),
vec2i(space, space)), Color(0x80800040));
}
}
}
}
}
}
else {
//Draw developed border
if(flags & SuF_Usable != 0 && hasDevelopment)
developedMat.draw(pos.padded(-1));
}
pos += spaceX;
}
pos += spaceY;
}
if(showHoverBiome && hovered.x >= 0 && hovered.y >= 0 && surface.getBuilding(hovered.x, hovered.y) is null) {
auto@ biome = surface.getBiome(hovered.x, hovered.y);
if(biome !is null) {
recti tilePos = recti_area(area.topLeft + vec2i(space * hovered.x, space * hovered.y), vec2i(space, space));
const Font@ ft = skin.getFont(FT_Normal);
string txt;
if(biome.isVoid || !hasDevelopment) {
txt = biome.name;
}
else if(obj !is null && obj.owner.hasTrait(verdantTrait)) {
if(surface.getFlags(hovered.x, hovered.y) & SuF_Usable != 0)
txt = format(locale::TILE_BIOME_GROWN, biome.name);
else
txt = format(locale::TILE_BIOME_UNGROWN, biome.name);
}
else {
if(surface.getFlags(hovered.x, hovered.y) & SuF_Usable != 0)
txt = format(locale::TILE_BIOME_DEVELOPED, biome.name);
else
txt = format(locale::TILE_BIOME_UNDEVELOPED, biome.name);
}
material::DevelopedTile.draw(tilePos, Color(0x000000ff));
ft.draw(pos=surfArea.padded(4),
text=txt, horizAlign=1.0, vertAlign=1.0,
stroke=colors::Black, color=biome.color.interpolate(colors::White, 0.5));
}
}
BaseGuiElement::draw();
}
};
void drawHoverBuilding(Planet& pl, const Skin@ skin, const BuildingType@ type, const vec2i& mousePos, const vec2i&in gridPos, GuiPlanetSurface@ surface = null) {
vec2i gridSize(26, 26);
if(surface !is null)
gridSize = surface.getGridSize();
vec2i pos;
if(surface is null || gridPos.x == -1 || gridPos.y == -1) {
//Draw at mouse position
pos = mousePos;
pos.x -= gridSize.x / 2;
pos.y -= gridSize.y / 2;
}
else {
//Draw on surface
pos = surface.getTilePosition(gridPos);
}
//Draw each tile
double costFactor = pl.owner.BuildingCostFactor;
int buildCost = ceil(double(type.baseBuildCost) * costFactor);
int maintainCost = type.baseMaintainCost;
vec2i center = vec2i(type.getCenter());
vec2i bldSize = vec2i(gridSize.x * type.size.x, gridSize.y * type.size.y);
if(type.size.x > 0 || type.size.y > 0) {
type.sprite.draw(recti_area(pos - vec2i(center.x * gridSize.x, center.y * gridSize.y), bldSize));
}
for(int y = 0; y < int(type.size.y); ++y) {
for(int x = 0; x < int(type.size.x); ++x) {
recti tpos = recti_area(pos - vec2i((center.x - x) * gridSize.x, (center.y - y) * gridSize.y), gridSize);
Color color(0x00000080);
if(surface !is null && gridPos.x != -1 && gridPos.y != -1) {
vec2i rpos = gridPos - (center - vec2i(x, y));
if(rpos.x < 0 || rpos.y < 0
|| uint(rpos.x) >= surface.surface.size.x
|| uint(rpos.y) >= surface.surface.size.y)
{
buildCost += ceil(double(type.tileBuildCost) * costFactor);
maintainCost += type.tileMaintainCost;
color = Color(0xff000080);
}
else {
if(!surface.surface.getBiome(rpos.x, rpos.y).buildable) {
color = Color(0xff0000ff);
}
else if(surface.surface.getFlags(rpos.x, rpos.y) & SuF_Usable == 0) {
auto@ biome = surface.surface.getBiome(rpos.x, rpos.y);
double bld = type.tileBuildCost * biome.buildCost * costFactor;
for(uint n = 0, ncnt = type.buildAffinities.length; n < ncnt; ++n) {
auto@ aff = type.buildAffinities[n];
if(aff.biome is biome)
bld *= aff.factor;
}
double mnt = type.tileMaintainCost;
for(uint n = 0, ncnt = type.maintainAffinities.length; n < ncnt; ++n) {
auto@ aff = type.maintainAffinities[n];
if(aff.biome is biome)
mnt *= aff.factor;
}
buildCost += ceil(bld);
maintainCost += ceil(mnt);
if(bld > type.tileBuildCost)
color = Color(0xff006180);
else if(bld != 0)
color = Color(0xffc80080);
}
SurfaceBuilding@ bld = surface.surface.getBuilding(rpos.x, rpos.y);
if(bld !is null) {
if(bld.type.civilian)
color = Color(0xff9d0080);
else
color = Color(0xff000080);
}
}
}
if(type.size.y == 1 && type.size.x == 1) {
type.sprite.draw(tpos.padded(2), color);
}
else {
color.a = 0x40;
drawRectangle(tpos, color);
}
}
}
if(buildCost != 0 || maintainCost != 0) {
const Font@ small = skin.getFont(FT_Normal);
string cost = formatMoney(buildCost, maintainCost);
vec2i corner = pos-vec2i(center.x*gridSize.x, center.y*gridSize.y);
small.draw(pos=recti_area(corner-vec2i(300,0), vec2i(600+bldSize.x,bldSize.y)),
text=cost, color=Color(0xffffffff), stroke=colors::Black, horizAlign=0.5, vertAlign=0.5);
}
}
uint verdantTrait = uint(-1);
void init() {
verdantTrait = getTraitID("Verdant");
}
@@ -0,0 +1,66 @@
import elements.BaseGuiElement;
export GuiProgressbar;
class GuiProgressbar : BaseGuiElement {
const Material@ frontMaterial;
const Material@ backMaterial;
SkinStyle frontStyle = SS_ProgressBar;
SkinStyle backStyle = SS_ProgressBarBG;
Color frontColor;
Color backColor;
float progress = 0.5f;
double textHorizAlign = 0.5;
double textVertAlign = 0.5;
int padding = 1;
bool invert = false;
FontType font = FT_Normal;
string text;
Color textColor;
Color strokeColor(0x00000000);
GuiProgressbar(IGuiElement@ ParentElement, const recti& Rectangle, float pct = 0.5f) {
progress = pct;
super(ParentElement, Rectangle);
}
GuiProgressbar(IGuiElement@ ParentElement, Alignment@ align, float pct = 0.5f) {
progress = pct;
super(ParentElement, align);
}
void set_color(Color c) {
frontColor = c;
backColor = c;
}
void draw() {
recti backPos = AbsolutePosition;
recti frontPos = backPos.padded(padding);
frontPos.botRight.x -= float(frontPos.width) * (1.f - progress);
if(invert){
frontPos.topLeft.x = backPos.botRight.x - (frontPos.botRight.x - frontPos.topLeft.x);
frontPos.botRight.x = backPos.botRight.x;
}
if(backMaterial !is null)
backMaterial.draw(backPos, backColor);
else
skin.draw(backStyle, FT_Normal, backPos, backColor);
if(frontMaterial !is null)
frontMaterial.draw(frontPos, frontColor);
else
skin.draw(frontStyle, FT_Normal, frontPos, frontColor);
if(text.length != 0)
skin.getFont(font).draw(pos=backPos, text=text, color=textColor, horizAlign=textHorizAlign, vertAlign=textVertAlign, stroke=strokeColor);
BaseGuiElement::draw();
}
};
@@ -0,0 +1,60 @@
import elements.BaseGuiElement;
export GuiResizeHandle;
class GuiResizeHandle : BaseGuiElement {
IGuiElement@ element;
bool dragging = false;
vec2i startPos;
vec2i startSize;
vec2i minSize(0, 0);
vec2i maxSize(INT_MAX, INT_MAX);
SkinStyle style = SS_ResizeHandle;
GuiResizeHandle(IGuiElement@ ParentElement, Alignment@ Align, IGuiElement@ drag = null) {
super(ParentElement, Align);
if(drag !is null)
@element = drag;
else
@element = ParentElement;
updateAbsolutePosition();
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) override {
switch(event.type) {
case MET_Button_Down:
if(event.button == 0) {
dragging = true;
startPos = mousePos;
startSize = element.size;
return true;
}
break;
case MET_Button_Up:
if(event.button == 0) {
dragging = false;
return true;
}
break;
case MET_Moved:
if(dragging) {
vec2i diff = mousePos - startPos;
vec2i newSize = startSize + diff;
newSize.x = clamp(newSize.x, minSize.x, maxSize.x);
newSize.y = clamp(newSize.y, minSize.y, maxSize.y);
element.size = newSize;
return true;
}
break;
}
return BaseGuiElement::onMouseEvent(event, source);
}
void draw() {
uint flags = SF_Normal;
if(dragging)
flags |= SF_Active;
skin.draw(style, flags, AbsolutePosition);
BaseGuiElement::draw();
}
};
+902
View File
@@ -0,0 +1,902 @@
#section disable menu
import elements.BaseGuiElement;
import elements.GuiText;
import elements.MarkupTooltip;
import elements.GuiIconGrid;
import resources;
import util.formatting;
import planet_levels;
export GuiResources, GuiResourceRequirements;
export GuiResourceGrid, GuiResourceReqGrid;
export drawResource, drawSmallResource;
export getAutoImportIcon;
export getRequirementIcon;
const ResourceClass@ foodClass;
const ResourceClass@ waterClass;
const ResourceType@ waterType;
const ResourceClass@ scalableClass;
void init() {
@foodClass = getResourceClass("Food");
@scalableClass = getResourceClass("Scalable");
@waterClass = getResourceClass("WaterType");
@waterType = getResource("Water");
}
void drawResource(const ResourceType@ type, const recti& pos) {
type.icon.draw(pos);
//Affinities
uint affCnt = type.affinities.length;
for(uint i = 0; i < affCnt; ++i) {
recti affPos = recti_area(vec2i(pos.topLeft.x, pos.botRight.y-i*20-20), vec2i(20, 20));
getAffinitySprite(type.affinities[i]).draw(affPos);
}
//Resource level icon
if(type.level > 0 && type.level <= 3)
spritesheet::ResourceIconsMods.draw(2+type.level, pos);
//Resource class icon
else if(type.cls is foodClass)
spritesheet::ResourceIconsMods.draw(6, pos);
}
void drawResource(const Resource@ r, const recti& pos, Object@ drawFrom = null, const Font@ ft = null) {
const ResourceType@ type = r.type;
//Origin underlay
if(r.origin is drawFrom && r.origin !is null) {
spritesheet::ResourceIconsMods.draw(0, pos);
//Rarity
if(type.rarity > 0)
spritesheet::ResourceIconsMods.draw(6+type.rarity, pos);
}
drawResource(r.type, pos);
if(r.origin is null || r.origin.owner is playerEmpire) {
//Disabled overlay
if(!r.usable)
spritesheet::ResourceIconsMods.draw(2, pos);
}
//Export tick
if(r.origin is drawFrom && r.exportedTo !is null && r.origin !is null && drawFrom.visible)
spritesheet::ResourceIconsMods.draw(1, pos);
//Vanish timer
if(r.type.vanishMode != VM_Never && drawFrom !is null) {
double timeLeft = r.type.vanishTime - r.vanishTime;
if(r.exportedTo !is null)
timeLeft /= r.exportedTo.resourceVanishRate;
else if(r.origin !is null)
timeLeft /= r.origin.resourceVanishRate;
font::DroidSans_11_Bold.draw(pos.topLeft+vec2i(4, 4),
formatShortTime(timeLeft),
Color(0xffbb00ff));
}
}
void drawSmallResource(const ResourceType@ type, const Resource@ r, const recti& pos, Object@ drawFrom = null, bool onPlanet = false) {
if(r !is null) {
//Origin underlay
if(r.origin is drawFrom && r.origin !is null && !onPlanet) {
spritesheet::ResourceIconsSmallMods.draw(0, pos.padded(-2));
//Rarity
if(type.rarity > 0)
spritesheet::ResourceIconsSmallMods.draw(10, pos.padded(-2), getResourceRarityColor(type.rarity));
}
if(drawFrom !is null && !r.usable && r.origin is null) {
if(r.type.cls is foodClass) {
FOOD_REQ.draw(pos);
}
else if(r.type.cls is waterClass) {
WATER_REQ.draw(pos);
}
else if(r.type.level < LEVEL_REQ.length) {
LEVEL_REQ[r.type.level].draw(pos);
}
else {
UNKNOWN_REQ.draw(pos);
}
}
else {
type.smallIcon.draw(pos);
}
bool lowPop = false;
if(drawFrom !is null) {
if(!r.usable) {
if(r.origin is null) {
//Queued auto-import
spritesheet::ResourceIconsSmallMods.draw(13, pos.padded(-2), Color(0xf800ffff));
}
else if(drawFrom.owner is playerEmpire || drawFrom !is r.origin) {
if(r.origin.owner !is playerEmpire) {
//Queued import
spritesheet::ResourceIconsSmallMods.draw(13, pos.padded(-2), Color(0xffe400ff));
}
else {
if(r.origin.hasSurfaceComponent && r.origin.resourceLevel >= r.type.level &&
r.origin.population < getPlanetLevelRequiredPop(r.origin, r.type.level)) {
//Insufficient population
spritesheet::ResourceIconsSmallMods.draw(13, pos.padded(-4), Color(0xff6300ff));
lowPop = true;
}
else {
//Disabled
spritesheet::ResourceIconsSmallMods.draw(2, pos.padded(-4));
}
}
}
}
else if(r.origin is drawFrom && r.origin.hasSurfaceComponent) {
lowPop = r.origin.resourceLevel > r.origin.level;
if(lowPop && !r.type.artificial) {
//Insufficient population
spritesheet::ResourceIconsSmallMods.draw(13, pos.padded(-4), Color(0xff6300ff));
}
}
}
//Resource level icon
if(type.level > 0 && type.level <= 3)
spritesheet::ResourceIconsSmallMods.draw(3+type.level, pos.padded(-2));
//Resource class icon
else if(type.cls is foodClass)
spritesheet::ResourceIconsSmallMods.draw(7, pos.padded(-2));
else if(type.cls is scalableClass)
spritesheet::ResourceIconsSmallMods.draw(16, pos.padded(-2));
//Export tick
if(r.origin !is null && r.origin is drawFrom && r.exportedTo !is null && drawFrom.visible)
spritesheet::ResourceIconsSmallMods.draw(1, pos.padded(-2));
//Excess tick
if(r.exportedTo is null && r.origin !is null && r.usable && !lowPop) {
if(r.origin.owner is playerEmpire && r.origin.hasSurfaceComponent && !r.type.isMaterial(r.origin.level) && r.origin.exportEnabled)
spritesheet::ResourceIconsSmallMods.draw(3, pos.padded(-2));
}
//Lock icon
if(drawFrom is null || r.origin is drawFrom) {
if(r.locked)
spritesheet::ResourceIconsSmallMods.draw(15, pos.padded(-2));
else if(type.willLock)
spritesheet::ResourceIconsSmallMods.draw(14, pos.padded(-2));
}
}
else {
type.smallIcon.draw(pos);
//Resource level icon
if(type.level > 0 && type.level <= 3)
spritesheet::ResourceIconsSmallMods.draw(3+type.level, pos.padded(-2));
//Resource class icon
else if(type.cls is foodClass)
spritesheet::ResourceIconsSmallMods.draw(7, pos.padded(-2));
else if(type.cls is scalableClass)
spritesheet::ResourceIconsSmallMods.draw(16, pos.padded(-2));
//Lock icon
if(type.willLock)
spritesheet::ResourceIconsSmallMods.draw(14, pos.padded(-2));
}
}
class GuiResourceRequirements : BaseGuiElement {
GuiText@[] text;
GuiResources@[] resources;
GuiResourceRequirements(IGuiElement@ ParentElement, Alignment@ align) {
super(ParentElement, align);
}
GuiResourceRequirements(IGuiElement@ ParentElement, const recti& pos) {
super(ParentElement, pos);
}
void set(const ResourceRequirements@ reqs, const Resources@ available = null, bool allowUniversal = true) {
//Do calculations
array<int>@ remaining = null;
if(available !is null) {
@remaining = array<int>();
reqs.satisfiedBy(available, null, allowUniversal, remaining);
}
//Remove previous
for(uint i = 0, cnt = resources.length; i < cnt; ++i)
resources[i].remove();
resources.length = 0;
for(uint i = 0, cnt = text.length; i < cnt; ++i)
text[i].remove();
text.length = 0;
//Add all groups
uint rCnt = reqs.reqs.length;
int x = 0;
int rsize = size.height - 15;
for(uint i = 0; i < rCnt; ++i) {
const ResourceRequirement@ req = reqs.reqs[i];
int amount = req.amount;
if(remaining !is null)
amount = remaining[i];
if(amount == 0)
continue;
GuiResources r(this, recti());
string caption;
//Find which resources and caption to display
switch(req.type) {
case RRT_Resource:
r.types.insertLast(req.resource);
caption = toString(amount)+"x";
break;
case RRT_Class:
for(uint i = 0, cnt = req.cls.types.length; i < cnt; ++i) {
const ResourceType@ restype = req.cls.types[i];
if(!restype.exportable || restype.mode != RM_Normal || !restype.requirementDisplay)
continue;
r.types.insertLast(restype);
}
if(r.types.length == 1)
caption = toString(amount)+"x";
else
caption = format(locale::RESOURCES_OF, toString(amount));
break;
case RRT_Class_Types:
for(uint i = 0, cnt = req.cls.types.length; i < cnt; ++i) {
const ResourceType@ restype = req.cls.types[i];
if(!restype.exportable || restype.mode != RM_Normal || !restype.requirementDisplay)
continue;
if(available !is null && available.getAmount(restype) != 0)
continue;
r.types.insertLast(restype);
}
if(r.types.length == 1)
caption = toString(amount)+"x";
else
caption = format(locale::TYPES_OF, toString(amount));
break;
case RRT_Level:
for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) {
const ResourceType@ res = getResource(i);
if(!res.exportable || res.mode != RM_Normal || !res.requirementDisplay)
continue;
if(res.level == req.level)
r.types.insertLast(res);
}
if(r.types.length == 1)
caption = toString(amount)+"x";
else
caption = format(locale::RESOURCES_OF, toString(amount));
break;
case RRT_Level_Types:
for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) {
const ResourceType@ res = getResource(i);
if(res.level != req.level)
continue;
if(!res.exportable || res.mode != RM_Normal || !res.requirementDisplay)
continue;
if(available !is null && available.getAmount(res) != 0)
continue;
r.types.insertLast(res);
}
if(r.types.length == 1)
caption = toString(amount)+"x";
else
caption = format(locale::TYPES_OF, toString(amount));
break;
}
if(r.types.length == 0) {
r.remove();
continue;
}
//Scrunch icons if multiple
int w = 0;
if(r.types.length == 1)
w = rsize;
else
w = (float(rsize) * 0.9f) * float(r.types.length);
//Create the caption
GuiText ctext(this, Alignment(Left+x, Top, Left+x+w, Top+15));
ctext.horizAlign = 0.5;
ctext.font = FT_Small;
ctext.text = caption;
text.insertLast(ctext);
//Position the resources element
r.typeMode = true;
@r.alignment = Alignment(Left+x, Top+15, Left+x+w, Bottom);
resources.insertLast(r);
x += w+16;
}
updateAbsolutePosition();
}
void draw() {
BaseGuiElement::draw();
}
};
class GuiResources : BaseGuiElement {
Object@ drawFrom;
array<const ResourceType@> types;
Resource[] resources;
bool typeMode = false;
bool vertical = false;
bool shrink = false;
double horizAlign = 0.5;
double vertAlign = 0.5;
int spacing = 0;
int hovered = -1;
GuiResources(IGuiElement@ ParentElement, Alignment@ align) {
super(ParentElement, align);
_GuiResources();
}
GuiResources(IGuiElement@ ParentElement, const recti& pos) {
super(ParentElement, pos);
_GuiResources();
}
void _GuiResources() {
MarkupTooltip tt(350, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = false;
tt.Padding = 4;
@tooltipObject = tt;
}
uint get_length() {
if(typeMode)
return types.length;
else
return resources.length;
}
string get_tooltip() {
if(hovered < 0 || hovered >= int(length))
return "";
Resource@ r;
const ResourceType@ type;
if(typeMode) {
@type = types[hovered];
}
else {
@r = resources[hovered];
@type = r.type;
}
return getResourceTooltip(type, r, drawFrom);
}
void calcOffset(vec2i& pos, vec2i& offset, vec2i& rsize) {
uint cnt = length;
uint isize = min(size.width, size.height);
rsize = vec2i(isize, isize);
pos = vec2i();
offset = vec2i();
if(vertical) {
pos.x = double(size.width - isize) * horizAlign;
int tot = (isize + spacing) * cnt - spacing;
if(tot < size.height) {
pos.y = double(size.height - tot) * vertAlign;
offset.y = isize + spacing;
}
else {
if(shrink) {
isize = double(size.height) / double(cnt);
rsize.x = isize;
rsize.y = isize;
offset.y = isize;
}
else if(cnt > 1) {
offset.y = double(size.height - isize) / double(cnt - 1);
}
else {
offset.y = isize;
}
}
}
else {
pos.y = double(size.height - isize) * vertAlign;
int tot = (isize + spacing) * cnt - spacing;
if(tot < size.width) {
pos.x = double(size.width - tot) * horizAlign;
offset.x = isize + spacing;
}
else {
if(shrink) {
isize = double(size.width) / double(cnt);
rsize.x = isize;
rsize.y = isize;
offset.x = isize;
}
else if(cnt > 1) {
offset.x = double(size.width - isize) / double(cnt - 1);
}
else {
offset.x = isize;
}
}
}
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is this) {
switch(event.type) {
case GUI_Mouse_Left:
hovered = -1;
break;
}
}
return BaseGuiElement::onGuiEvent(event);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this) {
switch(event.type) {
case MET_Moved: {
int prevHovered = hovered;
hovered = getOffsetItem(mousePos - AbsolutePosition.topLeft);
if(prevHovered != hovered)
tooltipObject.update(skin, this);
} break;
case MET_Button_Down:
if(hovered != -1)
return true;
break;
case MET_Button_Up:
if(hovered != -1) {
if(event.button == 2 && uint(hovered) < resources.length)
zoomTo(resources[hovered].origin);
emitClicked(event.button);
return true;
}
break;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
int getOffsetItem(const vec2i& off) {
vec2i pos, ioff, rsize, offset = off;
calcOffset(pos, ioff, rsize);
offset -= pos;
if(offset.x < 0 || offset.y < 0)
return -1;
int num = 0;
if(vertical) {
num = floor(double(offset.y) / double(ioff.y));
if(offset.y - (num * ioff.y) > rsize.y)
return -1;
}
else {
num = floor(double(offset.x) / double(ioff.x));
if(offset.y - (num * ioff.y) > rsize.y)
return -1;
}
if(num >= int(length))
return -1;
return num;
}
void draw() {
vec2i pos, offset, rsize;
calcOffset(pos, offset, rsize);
vec2i hovPos;
const Font@ ft = skin.getFont(FT_Normal);
if(rsize.width < 32)
@ft = skin.getFont(FT_Small);
//Draw everything but the hovered resource
for(uint i = 0, cnt = length; i < cnt; ++i) {
if(hovered != int(i)) {
if(typeMode)
drawResource(types[i], recti_area(pos + AbsolutePosition.topLeft, rsize));
else
drawResource(resources[i], recti_area(pos + AbsolutePosition.topLeft, rsize), drawFrom, ft);
}
else
hovPos = pos;
pos += offset;
}
//Draw hovered last so it's on top
if(hovered != -1 && uint(hovered) < this.length) {
if(typeMode)
drawResource(types[hovered], recti_area(hovPos + AbsolutePosition.topLeft, rsize));
else
drawResource(resources[hovered], recti_area(hovPos + AbsolutePosition.topLeft, rsize), drawFrom, ft);
}
BaseGuiElement::draw();
}
};
class GuiResourceGrid : GuiIconGrid {
Object@ drawFrom;
array<const ResourceType@> types;
Resource[] resources;
bool typeMode = false;
bool singleMode = false;
GuiResourceGrid(IGuiElement@ ParentElement, Alignment@ align) {
super(ParentElement, align);
_GuiResourceGrid();
}
GuiResourceGrid(IGuiElement@ ParentElement, const recti& pos) {
super(ParentElement, pos);
_GuiResourceGrid();
}
void _GuiResourceGrid() {
iconSize = vec2i(21, 21);
spacing = vec2i(5, 5);
MarkupTooltip tt(350, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = false;
tt.Padding = 4;
@tooltipObject = tt;
}
uint get_length() override {
if(typeMode)
return types.length;
else
return resources.length;
}
void setSingleMode(bool mode = true, double align = 0.5) {
singleMode = mode && length == 1;
forceHover = singleMode;
horizAlign = singleMode ? 0.0 : align;
}
bool onGuiEvent(const GuiEvent& event) override {
if(event.caller is this) {
switch(event.type) {
case GUI_Clicked:
if(uint(hovered) < resources.length) {
if(event.value == 2) {
zoomTo(resources[hovered].origin);
return true;
}
}
break;
}
}
return BaseGuiElement::onGuiEvent(event);
}
string get_tooltip() override {
if(hovered < 0 || hovered >= int(length))
return "";
Resource@ r;
const ResourceType@ type;
if(typeMode) {
@type = types[hovered];
}
else {
@r = resources[hovered];
@type = r.type;
}
return getResourceTooltip(type, r, drawFrom);
}
void drawElement(uint i, const recti& pos) override {
clearClip();
if(typeMode)
drawSmallResource(types[i], null, pos, drawFrom);
else
drawSmallResource(resources[i].type, resources[i], pos, drawFrom);
}
void draw() override {
GuiIconGrid::draw();
if(singleMode && length == 1) {
Resource@ r;
const ResourceType@ type;
if(typeMode) {
@type = types[0];
}
else {
@r = resources[0];
@type = r.type;
}
if(typeMode || drawFrom is null || r.origin is drawFrom) {
uint affCnt = type.affinities.length;
Color textColor;
if(!typeMode && !r.usable && r.origin.owner is playerEmpire) {
if(r.exportedTo !is null) {
float pct = abs((frameTime % 1.0) - 0.5f) * 2.f;
textColor = colors::Red.interpolate(colors::Orange, pct);
}
else {
textColor = colors::Red;
}
}
const Font@ ft = skin.getFont(FT_Bold);
recti pos = AbsolutePosition.padded(34, 0, 0, 0);
ft.draw(text=type.name, ellipsis=locale::ELLIPSIS,
pos=pos.padded(0, 0, affCnt*28, 0), vertAlign=0.5, color=textColor);
int x = 28;
for(uint i = 0; i < affCnt; ++i) {
uint aff = type.affinities[i];
getAffinitySprite(aff).draw(recti_area(vec2i(pos.botRight.x-x, pos.topLeft.y), vec2i(24, 24)));
x += 28;
}
}
}
}
};
const Sprite FOOD_REQ(spritesheet::ResourceClassIcons, 3);
const Sprite WATER_REQ(spritesheet::ResourceClassIcons, 4);
const Sprite UNKNOWN_REQ(spritesheet::ResourceClassIcons, 5);
const Sprite[] LEVEL_REQ = {
Sprite(),
Sprite(spritesheet::ResourceClassIcons, 0),
Sprite(spritesheet::ResourceClassIcons, 1),
Sprite(spritesheet::ResourceClassIcons, 2)
};
Sprite getAutoImportIcon(const AutoImportDesc& desc) {
if(desc.type !is null) {
if(desc.type is waterType)
return WATER_REQ;
return desc.type.smallIcon;
}
else if(desc.cls !is null) {
if(desc.cls is foodClass)
return FOOD_REQ;
else if(desc.cls is waterClass)
return WATER_REQ;
return Sprite();
}
else if(desc.level != -1 && uint(desc.level) < LEVEL_REQ.length) {
return LEVEL_REQ[desc.level];
}
return UNKNOWN_REQ;
}
Sprite getRequirementIcon(const ResourceRequirement@ req) {
switch(req.type) {
case RRT_Resource:
if(req.resource is waterType)
return WATER_REQ;
else
return req.resource.smallIcon;
case RRT_Class:
case RRT_Class_Types:
if(req.cls is foodClass)
return FOOD_REQ;
else if(req.cls is waterClass)
return WATER_REQ;
else
return UNKNOWN_REQ;
case RRT_Level:
case RRT_Level_Types:
if(req.level < LEVEL_REQ.length)
return LEVEL_REQ[req.level];
else
return UNKNOWN_REQ;
}
return Sprite();
}
class GuiResourceReqGrid : GuiIconGrid {
Sprite[] sprites;
string[] tooltips;
GuiResourceReqGrid(IGuiElement@ ParentElement, Alignment@ align) {
super(ParentElement, align);
MarkupTooltip tt(350, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = false;
tt.Padding = 4;
@tooltipObject = tt;
iconSize = vec2i(26,26);
padding = vec2i(-2);
spacing = vec2i(1);
noClip = true;
}
GuiResourceReqGrid(IGuiElement@ ParentElement, const recti& pos) {
super(ParentElement, pos);
MarkupTooltip tt(350, 0.f, true, true);
tt.Lazy = true;
tt.LazyUpdate = false;
tt.Padding = 4;
@tooltipObject = tt;
iconSize = vec2i(26,26);
padding = vec2i(-2);
spacing = vec2i(1);
noClip = true;
}
string get_tooltip() override {
if(hovered < 0 || uint(hovered) >= tooltips.length)
return "";
return tooltips[hovered];
}
uint get_length() override {
return sprites.length;
}
void drawElement(uint i, const recti& pos) override {
sprites[i].draw(pos.padded(-2));
}
void set(const ResourceRequirements@ reqs, const Resources@ available = null, bool allowUniversal = true) {
//Do calculations
array<int>@ remaining = null;
if(available !is null) {
@remaining = array<int>();
reqs.satisfiedBy(available, null, allowUniversal, remaining);
}
sprites.length = 0;
tooltips.length = 0;
//Add all groups
uint rCnt = reqs.reqs.length;
for(uint i = 0; i < rCnt; ++i) {
const ResourceRequirement@ req = reqs.reqs[i];
int amount = req.amount;
if(remaining !is null)
amount = remaining[i];
if(amount == 0)
continue;
//Find which icon to display
switch(req.type) {
case RRT_Resource:
for(int i = 0; i < amount; ++i) {
if(req.resource is waterType)
sprites.insertLast(WATER_REQ);
else
sprites.insertLast(req.resource.smallIcon);
tooltips.insertLast(req.resource.name);
}
break;
case RRT_Class: {
Sprite sprt;
if(req.cls is foodClass)
sprt = FOOD_REQ;
else if(req.cls is waterClass)
sprt = WATER_REQ;
else
sprt = UNKNOWN_REQ;
string tip = format(locale::REQ_TYPE, req.cls.name, getSpriteDesc(sprt))+"\n";
for(uint i = 0, cnt = req.cls.types.length; i < cnt; ++i) {
const ResourceType@ restype = req.cls.types[i];
if(!restype.exportable || restype.mode != RM_Normal || !restype.requirementDisplay)
continue;
if(restype.frequency == 0.0 || restype.rarity >= RR_Rare)
continue;
tip += format("[img=$1;28/]", getSpriteDesc(restype.smallIcon));
}
for(int i = 0; i < amount; ++i) {
sprites.insertLast(sprt);
tooltips.insertLast(tip);
}
} break;
case RRT_Class_Types: {
Sprite sprt;
if(req.cls is foodClass)
sprt = FOOD_REQ;
else if(req.cls is waterClass)
sprt = WATER_REQ;
else
sprt = UNKNOWN_REQ;
string tip = format(locale::REQ_TYPE_UNIQUE, req.cls.name, getSpriteDesc(sprt))+"\n";
for(uint i = 0, cnt = req.cls.types.length; i < cnt; ++i) {
const ResourceType@ restype = req.cls.types[i];
if(!restype.exportable || restype.mode != RM_Normal || !restype.requirementDisplay)
continue;
if(restype.frequency == 0.0 || restype.rarity >= RR_Rare)
continue;
if(available !is null && available.getAmount(restype) != 0)
continue;
tip += format("[img=$1;28/]", getSpriteDesc(restype.smallIcon));
}
for(int i = 0; i < amount; ++i) {
sprites.insertLast(sprt);
tooltips.insertLast(tip);
}
} break;
case RRT_Level: {
Sprite sprt;
if(req.level < LEVEL_REQ.length)
sprt = LEVEL_REQ[req.level];
else
sprt = UNKNOWN_REQ;
string tip = format(locale::REQ_LEVEL, toString(req.level), getSpriteDesc(sprt))+"\n";
for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) {
const ResourceType@ res = getResource(i);
if(res.level != req.level)
continue;
if(res.frequency == 0.0 || res.rarity >= RR_Rare)
continue;
if(!res.exportable || res.mode != RM_Normal || !res.requirementDisplay)
continue;
tip += format("[img=$1;28/]", getSpriteDesc(res.smallIcon));
}
for(int i = 0; i < amount; ++i) {
sprites.insertLast(sprt);
tooltips.insertLast(tip);
}
} break;
case RRT_Level_Types: {
Sprite sprt;
if(req.level < LEVEL_REQ.length)
sprt = LEVEL_REQ[req.level];
else
sprt = UNKNOWN_REQ;
string tip = format(locale::REQ_LEVEL_UNIQUE, toString(req.level), getSpriteDesc(sprt))+"\n";
for(uint i = 0, cnt = getResourceCount(); i < cnt; ++i) {
const ResourceType@ res = getResource(i);
if(res.level != req.level)
continue;
if(!res.exportable || res.mode != RM_Normal || !res.requirementDisplay)
continue;
if(res.frequency == 0.0 || res.rarity >= RR_Rare)
continue;
if(available !is null && available.getAmount(res) != 0)
continue;
tip += format("[img=$1;28/]", getSpriteDesc(res.smallIcon));
}
for(int i = 0; i < amount; ++i) {
sprites.insertLast(sprt);
tooltips.insertLast(tip);
}
} break;
}
}
updateAbsolutePosition();
}
};
#section game
import void zoomTo(Object@ obj) from "tabs.GalaxyTab";
#section menu
void zoomTo(Object@ obj) {}
#section all
+316
View File
@@ -0,0 +1,316 @@
import elements.BaseGuiElement;
import elements.GuiButton;
export ScrollOrientation, GuiScrollbar;
enum ScrollOrientation {
SO_Vertical,
SO_Horizontal,
};
class GuiScrollbar : BaseGuiElement {
bool Focused;
bool Dragging;
int Orientation;
vec2i DragFrom;
GuiButton@ up;
GuiButton@ down;
SkinStyle style;
SkinStyle handleStyle;
double start;
double end;
double page;
double scroll;
double pos;
double bar;
GuiScrollbar(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
start = 0.0;
end = 1.0;
page = 0.0;
bar = 0.2;
pos = 0.5;
scroll = 40.0;
Focused = false;
Dragging = false;
@up = GuiButton(this, recti());
@down = GuiButton(this, recti());
Orientation = SO_Vertical;
updateAbsolutePosition();
}
void set_orientation(int orient) {
Orientation = orient;
if(orient == SO_Vertical) {
up.alignment.top.set(AS_Top, 0.0, 0);
up.alignment.left.set(AS_Left, 0.0, 0);
up.alignment.right.set(AS_Right, 0.0, 0);
up.alignment.bottom.set(AS_Top, 0.0, AbsolutePosition.width);
down.alignment.top.set(AS_Bottom, 0.0, AbsolutePosition.width);
down.alignment.left.set(AS_Left, 0.0, 0);
down.alignment.right.set(AS_Right, 0.0, 0);
down.alignment.bottom.set(AS_Bottom, 0.0, 0);
up.style = SS_ScrollUp;
down.style = SS_ScrollDown;
style = SS_ScrollVert;
handleStyle = SS_ScrollVertHandle;
}
else {
up.alignment.top.set(AS_Top, 0.0, 0);
up.alignment.left.set(AS_Left, 0.0, 0);
up.alignment.right.set(AS_Left, 0.0, AbsolutePosition.height);
up.alignment.bottom.set(AS_Bottom, 0.0, 0);
down.alignment.top.set(AS_Top, 0.0, 0);
down.alignment.left.set(AS_Right, 0.0, AbsolutePosition.height);
down.alignment.right.set(AS_Right, 0.0, 0);
down.alignment.bottom.set(AS_Bottom, 0.0, 0);
up.style = SS_ScrollLeft;
down.style = SS_ScrollRight;
style = SS_ScrollHoriz;
handleStyle = SS_ScrollHorizHandle;
}
up.updateAbsolutePosition();
down.updateAbsolutePosition();
}
int get_orientation() {
return Orientation;
}
void emitChange() {
GuiEvent evt;
evt.type = GUI_Changed;
@evt.caller = this;
onGuiEvent(evt);
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is this) {
switch(event.type) {
case GUI_Focused:
Focused = true;
break;
case GUI_Focus_Lost:
Focused = false;
Dragging = false;
break;
}
}
else if(event.type == GUI_Clicked) {
if(event.caller is up) {
if(shiftKey)
pos = max(start, pos - page);
else
pos = max(start, pos - scroll);
emitChange();
}
else if(event.caller is down) {
if(shiftKey)
pos = min(end - page, pos + page);
else
pos = min(end - page, pos + scroll);
emitChange();
}
}
return BaseGuiElement::onGuiEvent(event);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this) {
vec2i mouse = vec2i(event.x, event.y);
switch(event.type) {
case MET_Button_Down: {
recti handlePos = getBarPosition();
vec2i offset = mouse - AbsolutePosition.topLeft;
Dragging = true;
DragFrom = mouse;
if(!handlePos.isWithin(offset)) {
pos = min(end - page, max(start, getOffsetPosition(offset)));
emitChange();
}
} return true;
case MET_Moved: {
if(Dragging) {
vec2i diff = mouse - DragFrom;
if(Orientation == SO_Vertical) {
int barSize = getBarPosition().height;
pos += double(diff.y) * (end - start - page) / double(AbsolutePosition.height - barSize - 2 * AbsolutePosition.width);
}
else {
int barSize = getBarPosition().width;
pos += double(diff.x) * (end - start - page) / double(AbsolutePosition.width - barSize - 2 * AbsolutePosition.height);
}
if(pos < start)
pos = start;
else if(pos > end)
pos = end;
DragFrom = vec2i(clamp(mouse.x, AbsolutePosition.topLeft.x+size.height, AbsolutePosition.botRight.x-size.height),
clamp(mouse.y, AbsolutePosition.topLeft.y+size.width, AbsolutePosition.botRight.y-size.width));
emitChange();
return true;
}
} break;
case MET_Button_Up:
if(Focused) {
Dragging = false;
return true;
}
break;
case MET_Scrolled:
pos = min(max(end - page, start), max(start, pos + double(-event.y) * scroll));
emitChange();
return true;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
switch(event.type) {
case KET_Key_Down:
switch(event.key) {
case KEY_LEFT:
case KEY_UP:
pos = max(start, pos - scroll);
emitChange();
break;
case KEY_RIGHT:
case KEY_DOWN:
pos = min(end - page, pos + scroll);
emitChange();
break;
case KEY_PAGEUP:
pos = max(start, pos - page);
emitChange();
break;
case KEY_PAGEDOWN:
pos = min(end - page, pos + page);
emitChange();
break;
}
break;
}
return BaseGuiElement::onKeyEvent(event, source);
}
void updateAbsolutePosition() {
BaseGuiElement::updateAbsolutePosition();
orientation = Orientation;
}
double getOffsetPosition(vec2i offset) {
double range = end - start;
if(range < page || range <= 0.0)
return start;
if(Orientation == SO_Vertical) {
int buttonsize = AbsolutePosition.width;
int space = AbsolutePosition.height - buttonsize * 2;
int barsize = max(int(space * (bar / range)), 16);
return (double(offset.y - barsize - buttonsize) / double(space - barsize)) * range + start;
}
else {
int buttonsize = AbsolutePosition.height;
int space = AbsolutePosition.width - buttonsize * 2;
int barsize = max(int(space * (bar / range)), 16);
return (double(offset.x - barsize - buttonsize) / double(space - barsize)) * range + start;
}
}
recti getBarPosition() {
//Initial scrollbar space
double range = end - start;
if(range <= page || range <= 0.0)
return recti();
if(Orientation == SO_Vertical) {
int buttonsize = AbsolutePosition.width;
int space = AbsolutePosition.height - buttonsize * 2;
//Remove space for bar
int barsize = max(int(space * (bar / range)), 16);
space -= barsize;
//Draw bar at correct position
int barpos = ((pos - start) / (range - page)) * space;
vec2i barcoord = vec2i(0, barpos + buttonsize);
return recti(barcoord, barcoord + vec2i(AbsolutePosition.width, barsize));
}
else {
int buttonsize = AbsolutePosition.height;
int space = AbsolutePosition.width - buttonsize * 2;
//Remove space for bar
int barsize = max(int(space * (bar / range)), 16);
space -= barsize;
//Draw bar at correct position
int barpos = ((pos - start) / (range - page)) * space;
vec2i barcoord = vec2i(barpos + buttonsize, 0);
return recti(barcoord, barcoord + vec2i(barsize, AbsolutePosition.height));
}
}
void draw() {
recti barPos = getBarPosition() + AbsolutePosition.topLeft;
recti bgPos;
bool enabled = (end - start > page);
uint handleFlags = SF_Normal;
uint flags = SF_Normal;
if(!enabled)
flags |= SF_Disabled;
if(Dragging)
handleFlags |= SF_Active;
if(barPos.isWithin(mousePos))
handleFlags |= SF_Hovered;
if(Orientation == SO_Vertical) {
bgPos = recti(AbsolutePosition.topLeft + vec2i(0, AbsolutePosition.width),
AbsolutePosition.botRight - vec2i(0, AbsolutePosition.width));
}
else {
bgPos = recti(AbsolutePosition.topLeft + vec2i(AbsolutePosition.height, 0),
AbsolutePosition.botRight - vec2i(AbsolutePosition.height, 0));
}
skin.draw(style, flags, bgPos);
if(enabled)
skin.draw(handleStyle, handleFlags, barPos);
up.disabled = !enabled;
down.disabled = !enabled;
BaseGuiElement::draw();
}
};
+102
View File
@@ -0,0 +1,102 @@
import elements.BaseGuiElement;
export GuiShipStatus;
void drawShipStatus(const recti& pos, const Blueprint@ bp) {
drawShipStatus(pos, bp, bp.design);
}
void drawShipStatus(const recti& pos, const Design@ dsg) {
drawShipStatus(pos, null, dsg);
}
void drawShipStatus(const recti& pos, const Blueprint@ bp, const Design@ design) {
if(design is null)
return;
Color deadColor(0x000000cc);
const Hull@ hull = design.hull;
const Material@ hex = material::StatusHex;
vec2i hexSize = hex.size;
vec2i cellSize = hexSize + vec2i(0, 1);
vec2i bpSize(hull.gridSize.width * cellSize.x,
hull.gridSize.height * cellSize.y);
vec2i offset = pos.topLeft;
offset += (pos.size - bpSize) / 2;
for(int y = 0; y < hull.gridSize.height; ++y) {
for(int x = 0; x < hull.gridSize.width; ++x) {
if(!hull.active.get(x, y))
continue;
Color color(0xffffffff);
vec2i hexPos(x * cellSize.x, y * cellSize.y);
if(x % 2 != 0)
hexPos.y += cellSize.y / 2;
hexPos += offset;
const Subsystem@ sys;
if(design !is null)
@sys = design.subsystem(x, y);
if(sys !is null) {
//Color hex to represent damage
color = sys.type.color;
if(bp !is null) {
const HexStatus@ status = bp.getHexStatus(x, y);
const SysStatus@ ss = bp.getSysStatus(x, y);
float pct = float(status.hp) / 255.0;
color = deadColor.getInterpolated(color, pct);
}
}
else {
color = deadColor;
}
hex.draw(recti_area(hexPos, hexSize), color);
}
}
}
class GuiShipStatus : BaseGuiElement {
const Blueprint@ bp;
const Design@ design;
Object@ obj;
GuiShipStatus(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
}
GuiShipStatus(IGuiElement@ ParentElement, Alignment@ align) {
super(ParentElement, align);
}
bool onGuiEvent(const GuiEvent& event) {
return BaseGuiElement::onGuiEvent(event);
}
void display(Object@ Obj) {
if(!Obj.isShip)
return;
@obj = Obj;
Ship@ ship = cast<Ship@>(obj);
@bp = ship.blueprint;
}
void draw() {
if(bp is null)
return;
if(bp !is null) {
ObjectLock lock(obj, true);
drawShipStatus(AbsolutePosition, bp);
}
else if(design !is null)
drawShipStatus(AbsolutePosition, design);
BaseGuiElement::draw();
}
};
@@ -0,0 +1,48 @@
import elements.BaseGuiElement;
export GuiSkinElement;
class GuiSkinElement : BaseGuiElement {
SkinStyle style;
uint flags;
Color color;
bool clickThrough = false;
recti padding;
GuiSkinElement(IGuiElement@ ParentElement, const recti& Rectangle, int Style, uint Flags = 0) {
style = SkinStyle(Style);
flags = Flags;
super(ParentElement, Rectangle);
}
GuiSkinElement(IGuiElement@ ParentElement, Alignment@ alignment, int Style, uint Flags = 0) {
style = SkinStyle(Style);
flags = Flags;
super(ParentElement, alignment);
}
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;
}
if(!clickThrough && AbsoluteClipRect.isWithin(pos))
return this;
return null;
}
void draw() {
skin.draw(style, flags,
AbsolutePosition.padded(padding.topLeft.x,
padding.topLeft.y, padding.botRight.x, padding.botRight.y),
color);
BaseGuiElement::draw();
}
};
+152
View File
@@ -0,0 +1,152 @@
import elements.BaseGuiElement;
import elements.GuiTextbox;
import elements.GuiButton;
export GuiSpinbox;
class GuiSpinbox : BaseGuiElement {
GuiTextbox@ textbox;
GuiButton@ upButton;
GuiButton@ downButton;
double step;
int decimals;
double min;
double max;
GuiSpinbox(IGuiElement@ ParentElement, const recti& Rectangle, double num) {
super(ParentElement, Rectangle);
_GuiSpinbox(num);
}
GuiSpinbox(IGuiElement@ ParentElement, Alignment@ align, double num) {
super(ParentElement, align);
_GuiSpinbox(num);
}
GuiSpinbox(IGuiElement@ ParentElement, Alignment@ align, double num, double min, double max, double step, int decimals) {
super(ParentElement, align);
_GuiSpinbox(num);
this.min = min;
this.max = max;
this.step = step;
this.decimals = decimals;
}
GuiSpinbox(IGuiElement@ ParentElement, const recti& pos, double num, double min, double max, double step, int decimals) {
super(ParentElement, pos);
_GuiSpinbox(num);
this.min = min;
this.max = max;
this.step = step;
this.decimals = decimals;
}
GuiSpinbox(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
_GuiSpinbox(0.0);
}
GuiSpinbox(IGuiElement@ ParentElement, Alignment@ align) {
super(ParentElement, align);
_GuiSpinbox(0.0);
}
void set_disabled(bool value) {
upButton.visible = !value;
downButton.visible = !value;
textbox.disabled = value;
}
double get_value() {
return clamp(toDouble(textbox.text), min, max);
}
void set_value(double val) {
textbox.text = toString(clamp(val, min, max), decimals);
}
void set_maximum(double val) {
max = val;
if(!textbox.Focused)
value = value;
}
void set_minimum(double val) {
min = val;
if(!textbox.Focused)
value = value;
}
void set_font(FontType ft) {
textbox.font = ft;
}
void set_color(const Color& color) {
textbox.bgColor = color;
}
void _GuiSpinbox(double num) {
step = 1.0;
decimals = 0;
min = -INFINITY;
max = INFINITY;
@textbox = GuiTextbox(this, Alignment(Left, Top, Right-20, Bottom));
@upButton = GuiButton(this, Alignment(Right-20, Top, Right, Bottom-0.5f));
upButton.style = SS_SpinButton;
@downButton = GuiButton(this, Alignment(Right-20, Bottom-0.5f, Right, Bottom));
downButton.style = SS_SpinButton;
value = num;
updateAbsolutePosition();
}
void updateAbsolutePosition() {
int h = size.height / 2;
textbox.alignment.right.pixels = h;
upButton.alignment.left.pixels = h;
downButton.alignment.left.pixels = h;
BaseGuiElement::updateAbsolutePosition();
}
bool onGuiEvent(const GuiEvent& event) {
if(event.caller is upButton) {
if(event.type == GUI_Clicked) {
value = value + step;
emitClicked();
emitChanged();
return true;
}
}
else if(event.caller is downButton) {
if(event.type == GUI_Clicked) {
value = value - step;
emitClicked();
emitChanged();
return true;
}
}
else if(event.caller is textbox) {
GuiEvent evt = event;
@evt.caller = this;
return BaseGuiElement::onGuiEvent(evt);
}
return BaseGuiElement::onGuiEvent(event);
}
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
if(source is this || source.isChildOf(this)) {
switch(event.type) {
case MET_Scrolled:
value = value + event.y * step;
emitClicked();
emitChanged();
return true;
}
}
return BaseGuiElement::onMouseEvent(event, source);
}
};
+97
View File
@@ -0,0 +1,97 @@
import elements.BaseGuiElement;
export GuiSprite;
class GuiSprite : BaseGuiElement {
Sprite desc;
Color color;
const Shader@ shader;
bool keepAspect = true;
bool stretchOutside = false;
double horizAlign = 0.5;
double vertAlign = 0.5;
float saturation = 1.f;
GuiSprite(IGuiElement@ ParentElement, Alignment@ Align) {
super(ParentElement, Align);
updateAbsolutePosition();
}
GuiSprite(IGuiElement@ ParentElement, Alignment@ Align, const Sprite& sprt) {
desc = sprt;
super(ParentElement, Align);
updateAbsolutePosition();
}
GuiSprite(IGuiElement@ ParentElement, Alignment@ Align, const SpriteSheet@ Sheet, uint Sprite) {
@desc.sheet = Sheet;
desc.index = Sprite;
super(ParentElement, Align);
updateAbsolutePosition();
}
GuiSprite(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
updateAbsolutePosition();
}
GuiSprite(IGuiElement@ ParentElement, const recti& Rectangle, const Sprite& sprt) {
desc = sprt;
super(ParentElement, Rectangle);
updateAbsolutePosition();
}
GuiSprite(IGuiElement@ ParentElement, const recti& Rectangle, const SpriteSheet@ Sheet, uint Sprite) {
@desc.sheet = Sheet;
desc.index = Sprite;
super(ParentElement, Rectangle);
}
void set_sprite(uint index) {
desc.index = index;
}
void set_sheet(const SpriteSheet@ sheet) {
@desc.sheet = sheet;
@desc.mat = null;
}
void set_material(const Material@ mat) {
@desc.sheet = null;
@desc.mat = mat;
}
void draw() {
recti pos = AbsolutePosition;
if(keepAspect) {
vec2i size = desc.size;
if(size.y != 0) {
if(stretchOutside) {
double aspect = desc.aspect;
if(pos.width > pos.height) {
size.x = pos.width;
size.y = double(pos.width) * aspect;
}
else {
size.y = pos.height;
size.x = double(pos.height) / aspect;
}
pos = recti_area(pos.topLeft - vec2i((size.x-pos.width)/2, (size.y-pos.height)/2), size);
}
else {
pos = pos.aspectAligned(double(size.x) / double(size.y), horizAlign, vertAlign);
}
}
}
if(saturation != 1.f) {
shader::SATURATION_LEVEL = saturation;
desc.draw(pos, color, shader::Desaturate);
}
else
if(shader !is null)
desc.draw(pos, color, shader);
else
desc.draw(pos, color);
BaseGuiElement::draw();
}
};
+64
View File
@@ -0,0 +1,64 @@
#section game
import elements.BaseGuiElement;
import elements.MarkupTooltip;
import statuses;
export GuiStatusBox;
export updateStatusBoxes;
class GuiStatusBox : BaseGuiElement {
Status status;
Object@ fromObject;
GuiStatusBox(IGuiElement@ parent) {
super(parent, recti());
addLazyMarkupTooltip(this, width=350);
}
GuiStatusBox(IGuiElement@ parent, const recti& pos) {
super(parent, pos);
addLazyMarkupTooltip(this, width=350);
updateAbsolutePosition();
}
void update(const Status& other) {
status = other;
}
string get_tooltip() {
return status.getTooltip(valueObject=fromObject);
}
void draw() {
(spritesheet::ResourceIconsSmallMods+0).draw(AbsolutePosition, status.type.color);
status.type.icon.draw(AbsolutePosition.padded(2));
if(!status.type.unique && status.stacks > 1) {
skin.getFont(FT_Bold).draw(
pos=AbsolutePosition.padded(0,0,0,5),
horizAlign=1.0,
vertAlign=1.0,
text=format("x$1", toString(status.stacks)),
stroke=colors::Black,
color=status.type.color);
}
BaseGuiElement::draw();
}
};
void updateStatusBoxes(IGuiElement@ parent, array<Status>& statuses, array<GuiStatusBox@>& boxes, Object@ fromObject = null) {
uint prevCnt = boxes.length, cnt = statuses.length;
for(uint i = cnt; i < prevCnt; ++i)
boxes[i].remove();
boxes.length = cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ icon = boxes[i];
if(icon is null) {
@icon = GuiStatusBox(parent);
@boxes[i] = icon;
}
@icon.fromObject = fromObject;
icon.update(statuses[i]);
}
}
+155
View File
@@ -0,0 +1,155 @@
import elements.BaseGuiElement;
import elements.GuiButton;
import elements.GuiPanel;
export GuiTabPane;
class GuiTabPane : BaseGuiElement {
GuiButton@[] TabButtons;
GuiPanel@[] TabPanels;
SkinStyle TabStyle = SS_Tab;
uint TabHeight = 22;
uint Selected = 0;
bool Fill = true;
GuiTabPane(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
updateAbsolutePosition();
}
uint addTab(const string& name) {
uint num = TabButtons.length();
GuiButton@ btn = GuiButton(this, recti(), name);
btn.style = TabStyle;
btn.toggleButton = true;
GuiPanel@ pnl = GuiPanel(this, recti());
@pnl.alignment = Alignment(Left, Top+TabHeight, Right, Bottom);
pnl.visible = num == Selected;
btn.pressed = pnl.visible;
TabButtons.insertLast(btn);
TabPanels.insertLast(pnl);
alignTabs();
return num;
}
void setTabName(uint num, const string& name) {
if(num >= TabButtons.length())
throw("Tab index out of bounds.");
TabButtons[num].text = name;
}
void removeTab(uint num) {
if(num >= TabPanels.length())
throw("Tab index out of bounds.");
TabButtons[num].remove();
TabPanels[num].remove();
TabButtons.removeAt(num);
TabPanels.removeAt(num);
alignTabs();
if(num == Selected)
--num;
}
GuiPanel@ getTab(uint num) {
if(num >= TabPanels.length())
throw("Tab index out of bounds.");
return TabPanels[num];
}
string getTabName(uint num) {
if(num >= TabButtons.length())
throw("Tab index out of bounds.");
return TabButtons[num].text;
}
uint get_selected() {
return Selected;
}
void set_selected(uint num) {
if(Selected == num)
return;
if(Selected <= TabButtons.length()) {
TabButtons[Selected].pressed = false;
TabPanels[Selected].visible = false;
}
if(num <= TabButtons.length()) {
TabButtons[num].pressed = true;
TabPanels[num].visible = true;
}
Selected = num;
}
void set_tabHeight(int height) {
TabHeight = height;
alignTabs();
alignPanels();
}
void set_tabStyle(SkinStyle style) {
TabStyle = style;
for(uint i = 0, cnt = TabButtons.length(); i < cnt; ++i)
TabButtons[i].style = TabStyle;
}
void alignTabs() {
//Make sure the tab buttons are positioned right
uint w = 0;
uint tabCnt = TabButtons.length();
if(tabCnt == 0)
return;
uint size = absolutePosition.width / tabCnt;
for(uint i = 0, cnt = TabButtons.length(); i < cnt; ++i) {
GuiButton@ btn = TabButtons[i];
if(!Fill)
size = max(160, btn.textSize.width + 8);
btn.position = vec2i(w, 0);
btn.size = vec2i(size, TabHeight);
w += size;
}
}
void updateAbsolutePosition() {
vec2i prevSize = absolutePosition.size;
BaseGuiElement::updateAbsolutePosition();
if(absolutePosition.size.width != prevSize.width)
alignTabs();
}
void alignPanels() {
//Align the panels correctly to the parent
for(uint i = 0, cnt = TabPanels.length(); i < cnt; ++i) {
GuiPanel@ pnl = TabPanels[i];
pnl.alignment.set(
AS_Left, 0.f, 0,
AS_Top, 0.f, TabHeight,
AS_Right, 0.f, 0,
AS_Bottom, 0.f, 0);
}
}
bool onGuiEvent(const GuiEvent& event) {
if(event.type == GUI_Clicked) {
for(uint i = 0, cnt = TabButtons.length(); i < cnt; ++i) {
if(TabButtons[i] is cast<GuiButton@>(event.caller)) {
selected = i;
return true;
}
}
}
return BaseGuiElement::onGuiEvent(event);
}
void draw() {
BaseGuiElement::draw();
}
};
+166
View File
@@ -0,0 +1,166 @@
import elements.WordWrap;
import elements.BaseGuiElement;
export GuiText;
class GuiText : BaseGuiElement {
string Text;
WordWrap@ WordWrap;
double HorizAlign = 0.0;
double VertAlign = 0.5;
bool flex = false;
FontType TextFont = FT_Normal;
vec2i textOffset;
Color color;
Color stroke(0x00000000);
int strokeWidth = 1;
GuiText(IGuiElement@ ParentElement, const recti& Rectangle) {
super(ParentElement, Rectangle);
updateAbsolutePosition();
}
GuiText(IGuiElement@ ParentElement, const recti& Rectangle, const string &in Txt, FontType font = FT_Normal) {
super(ParentElement, Rectangle);
TextFont = font;
text = Txt;
}
GuiText(IGuiElement@ ParentElement, Alignment@ Align) {
super(ParentElement, Align);
updateAbsolutePosition();
}
GuiText(IGuiElement@ ParentElement, Alignment@ Align, const string& Txt, FontType font = FT_Normal) {
super(ParentElement, Align);
TextFont = font;
text = Txt;
}
void set_wordWrap(bool wrap) {
if(wrap) {
@WordWrap = WordWrap();
@WordWrap.font = skin.getFont(TextFont);
WordWrap.text = Text;
WordWrap.width = absolutePosition.width;
WordWrap.update();
}
else {
@WordWrap = null;
}
updateAbsolutePosition();
}
bool get_wordWrap() {
return WordWrap !is null;
}
void set_text(const string& txt) {
Text = txt;
if(WordWrap !is null) {
WordWrap.text = Text;
WordWrap.update();
}
updateAbsolutePosition();
}
string get_text() {
return Text;
}
void set_horizAlign(double align) {
if(HorizAlign == align)
return;
HorizAlign = align;
updateAbsolutePosition();
}
void set_vertAlign(double align) {
if(VertAlign == align)
return;
VertAlign = align;
updateAbsolutePosition();
}
void set_font(FontType type) {
if(TextFont == type)
return;
TextFont = type;
if(WordWrap !is null) {
@WordWrap.font = skin.getFont(TextFont);
WordWrap.update();
}
updateAbsolutePosition();
}
FontType get_font() {
return TextFont;
}
int get_lineCount() {
if(WordWrap !is null) {
return WordWrap.lines.length();
}
else {
return 1;
}
}
vec2i getTextDimension() {
const Font@ font = skin.getFont(TextFont);
if(WordWrap !is null) {
return vec2i(
WordWrap.maxWidth,
WordWrap.lines.length() * font.getLineHeight());
}
else {
return font.getDimension(Text);
}
}
void updateAbsolutePosition() {
BaseGuiElement::updateAbsolutePosition();
if(skin is null)
return;
const Font@ font = skin.getFont(TextFont);
vec2i textSize;
if(WordWrap !is null) {
WordWrap.width = absolutePosition.width;
WordWrap.update();
textSize.x = WordWrap.maxWidth;
textSize.y = WordWrap.lines.length() * font.getLineHeight();
}
else {
textSize = font.getDimension(Text);
}
if(flex) {
textOffset = vec2i();
textOffset.y += (font.getLineHeight() - font.getBaseline()) / 2;
textSize.y = font.getLineHeight() + textOffset.y;
size = textSize;
}
else {
textOffset = (Position.size - textSize);
textOffset.y += (font.getLineHeight() - font.getBaseline()) / 2;
textOffset.x *= HorizAlign;
textOffset.y *= VertAlign;
}
}
void draw() {
if(stroke.a != 0) {
//LORD HAVE MERCY ON MY SOUL
skin.draw(TextFont, AbsolutePosition.topLeft + textOffset + vec2i(strokeWidth, strokeWidth), Text, stroke);
skin.draw(TextFont, AbsolutePosition.topLeft + textOffset + vec2i(-strokeWidth, -strokeWidth), Text, stroke);
skin.draw(TextFont, AbsolutePosition.topLeft + textOffset + vec2i(strokeWidth, -strokeWidth), Text, stroke);
skin.draw(TextFont, AbsolutePosition.topLeft + textOffset + vec2i(-strokeWidth, strokeWidth), Text, stroke);
}
skin.draw(TextFont, AbsolutePosition.topLeft + textOffset, Text, color);
BaseGuiElement::draw();
}
};
File diff suppressed because it is too large Load Diff
+201
View File
@@ -0,0 +1,201 @@
final tidy class GuiEvent {
int type;
int value;
IGuiElement@ caller;
IGuiElement@ other;
};
enum GuiEventType {
//Triggered when the mouse hovers over an element
//"caller" is the hovered element
//"other" is the last element that was hovered (can be null)
//Returning true absorbs the hover event, preventing it from changing
GUI_Mouse_Entered,
//Triggered when the mouse leaves an element
//"caller" is the element that is left
//"other" is the element that is now hovered (can be null)
//Returning true absorbs the hover event, preventing it from changing
GUI_Mouse_Left,
//Triggered when an element is focused (e.g. by being clicked)
//"caller" is the focused element
//"other" is the old focus (can be null)
//Returning true absorbs the focus event, preventing it from changing
GUI_Focused,
//Triggered when an element is no longer focused
//"caller" is the element that lost focus
//"other" is the new focus (can be null)
//Returning true absorbs the focus event, preventing it from changing
GUI_Focus_Lost,
//Triggered when an element is clicked (not necessarily all elements)
//"caller" is the element that was clicked
GUI_Clicked,
//Triggered when an element is changed (not necessarily all elements)
//"caller" is the element that was changed
// example: dropdown box changing
GUI_Changed,
//Triggered when an element changes internal hover (not necessarily all elements)
//"caller" is the element that changed
GUI_Hover_Changed,
//Triggered when an element has an action confirmed (not necessarily all elements)
//"caller" is the element that was confirmed
// example: enter pressed in a textbox
GUI_Confirmed,
//Triggered when a keybind button is pressed down
//"value" is the keybind that was pressed
GUI_Keybind_Down,
//Triggered when a keybind button is released
//"value" is the keybind that was released
GUI_Keybind_Up,
//Triggered when an animation finishes.
//"value" is a custom identifier that was passed for animation.
GUI_Animation_Complete,
//Triggered when navigation (ie the controller focus) hits this element.
GUI_Navigation_Enter,
//Triggered when navigation (ie the controller focus) leaves this element.
GUI_Navigation_Leave,
//Triggered when a controller button is pressed on this element.
//"value" is the button code that was pressed for action.
GUI_Controller_Down,
//Triggered when a controller button is released on this element.
//"value" is the button code that was released for action.
GUI_Controller_Up,
};
final tidy class MouseEvent {
int type;
int button;
int x;
int y;
};
enum MouseEventType {
MET_Button_Down,
MET_Button_Up,
MET_Scrolled,
MET_Moved,
};
enum MouseButton {
MB_Left = 0,
MB_Right = 1,
MB_Middle = 2,
MB_DOUBLE = 0x80,
MB_DoubleLeft = MB_DOUBLE | MB_Left,
MB_DoubleRight = MB_DOUBLE | MB_Right,
MB_DoubleMiddle = MB_DOUBLE | MB_Middle,
};
final tidy class KeyboardEvent {
int type;
int key;
};
enum KeyboardEventType {
KET_Key_Down,
KET_Key_Up,
KET_Key_Typed,
};
enum NavigationMode {
NM_None,
NM_Action,
NM_Tooltip,
};
interface ITooltip {
float get_delay();
bool get_persist();
void update(const Skin@ skin, IGuiElement@ elem);
void show(const Skin@ skin, IGuiElement@ elem);
void draw(const Skin@ skin, IGuiElement@ elem);
void hide(const Skin@ skin, IGuiElement@ elem);
}
interface IGuiElement {
void draw();
bool onGuiEvent(const GuiEvent& event);
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source);
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source);
IGuiElement@ elementFromPosition(const vec2i& pos);
void move(const vec2i& moveBy);
void abs_move(const vec2i& moveBy);
void set_position(const vec2i& pos);
vec2i get_position() const;
void set_size(const vec2i& size);
vec2i get_size() const;
vec2i get_desiredSize() const;
void set_rect(const recti& rect);
recti get_rect() const;
int get_tabIndex() const;
void set_tabIndex(int ind);
void updateAbsolutePosition();
recti get_absolutePosition();
recti get_updatePosition();
recti get_absoluteClipRect();
string get_elementType() const;
int get_id() const;
bool get_isRoot() const;
bool get_visible() const;
bool get_actuallyVisible() const;
void set_visible(bool vis);
bool get_onScreen() const;
bool get_noClip() const;
void set_noClip(bool noclip);
IGuiElement@ get_parent() const;
void set_parent(IGuiElement@ NewParent);
void addChild(IGuiElement@ ele);
void removeChild(IGuiElement@ ele);
void remove();
IGuiElement@ getChild(uint index);
uint get_childCount() const;
bool isFront();
void bringToFront();
void bringToFront(IGuiElement@ childToReorder);
bool isBack();
void sendToBack();
void sendToBack(IGuiElement@ childToReorder);
bool isAncestorOf(const IGuiElement@ element) const;
bool isChildOf(const IGuiElement@ element) const;
string get_tooltip();
void set_tooltip(const string& ToolText);
void set_tooltipObject(ITooltip@ ToolTip);
ITooltip@ get_tooltipObject() const;
bool isNavigable(NavigationMode mode) const;
IGuiElement@ navigate(NavigationMode mode, const vec2d& line);
IGuiElement@ navigate(NavigationMode mode, const recti& box, const vec2d&in line);
void closestToLine(NavigationMode mode, const recti& box, const vec2d&in line, float& closestDist, IGuiElement@& closest, IGuiElement@ skip);
};
+170
View File
@@ -0,0 +1,170 @@
import elements.BaseGuiElement;
import elements.GuiMarkupText;
export MarkupTooltip, setMarkupTooltip;
export addLazyMarkupTooltip;
class MarkupTooltip : MarkupRenderer, ITooltip {
float Delay = 0.5f;
bool visible = false;
bool Persist = false;
bool FollowMouse = true;
bool Lazy = false;
bool LazyUpdate = true;
bool StaticPosition = false;
double lastUpdate = -INFINITY;
vec2i offset(12, 0);
int Width = 250;
int Padding = 10;
SkinStyle background = SS_Tooltip;
recti lastpos;
string lastText;
bool wrapAroundElement = true;
MarkupTooltip(const string& txt, int width = 250, float delay = 0.5f, bool persist = false, bool followMouse = true) {
super();
parseTree(txt);
visible = txt.length != 0;
lastText = txt;
Width = width;
Delay = delay;
Persist = persist;
FollowMouse = followMouse;
}
MarkupTooltip(int width = 250, float delay = 0.5f, bool persist = false, bool followMouse = true) {
super();
Width = width;
Delay = delay;
Persist = persist;
Lazy = true;
FollowMouse = followMouse;
}
void update(const Skin@ skin, IGuiElement@ elem) {
if(Lazy) {
update(elem);
lastUpdate = frameTime;
}
}
void set_text(const string& txt) {
visible = txt.length != 0;
if(txt == lastText)
return;
lastText = txt;
parseTree(txt);
}
void set_width(int w) {
Width = w;
}
float get_delay() {
return Delay;
}
bool get_persist() {
return Persist;
}
void show(const Skin@ skin, IGuiElement@ elem) {
if(Lazy)
update(elem);
visible = lastText.length != 0;
}
void hide(const Skin@ skin, IGuiElement@ elem) {
if(Lazy)
clear();
visible = false;
}
void update(IGuiElement@ elem) {
string tt;
while(elem !is null && tt.length == 0) {
tt = elem.tooltip;
@elem = elem.parent;
}
text = tt;
}
void clear() override {
lastText = "";
MarkupRenderer::clear();
}
void draw(const Skin@ skin, IGuiElement@ elem) {
if(Lazy && LazyUpdate) {
if(lastUpdate < frameTime - 0.2) {
update(elem);
lastUpdate = frameTime;
}
}
if(!visible)
return;
int prevHeight = height;
vec2i size(Width, height + Padding * 2);
vec2i pos;
if(StaticPosition) {
pos = offset;
}
else if(FollowMouse) {
pos = mousePos + offset;
if(wrapAroundElement) {
if(pos.x + size.x >= screenSize.x) {
pos = mousePos - offset;
pos.x -= size.x;
}
if(pos.y + size.y > screenSize.y)
pos.y = screenSize.y - size.y;
}
}
else {
recti elemPos = elem.absolutePosition;
pos = vec2i(elemPos.botRight.x, elemPos.topLeft.y) + offset;
if(pos.x + size.x >= screenSize.x) {
if(wrapAroundElement) {
pos = elemPos.topLeft - offset;
pos.x -= size.x;
}
else {
pos.x = screenSize.x - size.x;
}
}
if(pos.y + size.y > screenSize.y)
pos.y = screenSize.y - size.y;
}
//Draw the background
recti absPos = recti_area(pos, size);
skin.draw(background, SF_Normal, absPos);
//Draw the markup
draw(skin, absPos.padded(Padding));
if(height != prevHeight)
draw(skin, elem);
}
};
void setMarkupTooltip(BaseGuiElement@ ele, const string& tooltip, bool hoverStyle = true, int width = 250) {
MarkupTooltip@ obj = cast<MarkupTooltip>(ele.tooltipObject);
if(obj is null) {
@ele.tooltipObject = MarkupTooltip(tooltip, width, hoverStyle ? 0.f : 0.5f, hoverStyle);
}
else {
obj.width = width;
obj.Persist = hoverStyle;
obj.text = tooltip;
}
}
MarkupTooltip@ addLazyMarkupTooltip(BaseGuiElement@ ele, bool hoverStyle = true, int width = 250, bool update = false) {
MarkupTooltip tt(width, hoverStyle ? 0.f : 0.5f, hoverStyle);
tt.Lazy = true;
tt.LazyUpdate = update;
@ele.tooltipObject = tt;
return tt;
}
+141
View File
@@ -0,0 +1,141 @@
class WordWrap {
string[] lines;
int[] positions;
int[] ends;
int maxWidth;
bool changed;
const Font@ Font;
string Text;
int Width;
WordWrap() {
Width = 100;
maxWidth = 100;
changed = true;
}
void set_font(const Font@ fnt) {
if(fnt is Font)
return;
@Font = fnt;
changed = true;
}
void set_text(const string& txt) {
Text = txt;
changed = true;
}
void set_width(int wd) {
if(wd == Width)
return;
Width = wd;
changed = true;
}
int get_lineCount() {
return lines.length();
}
void update() {
if(!changed)
return;
uint len = Text.length();
uint line = 0;
uint start = 0;
int pos = 0;
int w = 0, ch = 0, prevCh = 0;
int word = -1, word_w = 0;
maxWidth = 0;
while(pos >= 0) {
int prevPos = pos;
u8next(Text, pos, ch);
if(ch == '\n') {
if(lines.length() <= line) {
lines.insertLast(Text.substr(start, prevPos - start));
positions.insertLast(start);
ends.insertLast(prevPos);
}
else {
lines[line] = Text.substr(start, prevPos - start);
positions[line] = start;
ends[line] = prevPos;
}
if(w > maxWidth)
maxWidth = w;
++line;
start = prevPos+1;
w = 0;
word = -1;
}
else {
int chW = Font.getDimension(ch, prevCh).x;
w += chW;
if(w > Width) {
if(word >= 0) {
prevPos = word;
w = w - word_w;
}
else {
w = chW;
}
if(lines.length() <= line) {
lines.insertLast(Text.substr(start, prevPos - start));
positions.insertLast(start);
ends.insertLast(prevPos);
}
else {
lines[line] = Text.substr(start, prevPos - start);
positions[line] = start;
ends[line] = prevPos;
}
++line;
start = prevPos;
word = -1;
maxWidth = Width;
}
else if(ch == ' ') {
word = pos;
word_w = w;
}
}
prevCh = ch;
}
if(start <= len) {
if(lines.length() <= line) {
lines.insertLast(Text.substr(start, len - start));
positions.insertLast(start);
ends.insertLast(len);
}
else {
lines[line] = Text.substr(start, len - start);
positions[line] = start;
ends[line] = len;
}
++line;
}
if(line != lines.length()) {
lines.resize(line);
positions.resize(line);
ends.resize(line);
}
if(w > maxWidth)
maxWidth = w;
changed = false;
}
};