Open source Star Ruler 2 source code!
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user