1228 lines
30 KiB
ActionScript
1228 lines
30 KiB
ActionScript
import tabs.Tab;
|
|
import elements.GuiButton;
|
|
import elements.GuiResources;
|
|
import elements.GuiPanel;
|
|
import elements.GuiSprite;
|
|
import elements.GuiSkinElement;
|
|
import elements.GuiTextbox;
|
|
import elements.BaseGuiElement;
|
|
import elements.MarkupTooltip;
|
|
import resources;
|
|
import tile_resources;
|
|
import util.icon_view;
|
|
import tabs.tabbar;
|
|
import planet_levels;
|
|
import icons;
|
|
import statuses;
|
|
from overlays.PlanetPopup import PlanetPopup;
|
|
from overlays.ContextMenu import openContextMenu;
|
|
from obj_selection import selectObject, selectedObject, selectedObjects;
|
|
import void zoomTo(Object@ obj) from "tabs.GalaxyTab";
|
|
import void openOverlay(Object@ obj) from "tabs.GalaxyTab";
|
|
|
|
const int ICON_SIZE = 46;
|
|
|
|
Color COLOR_DROP(0xaaffaa80);
|
|
Color COLOR_CLEAR(0xaaaaff60);
|
|
Color COLOR_HOVER(0xffffff20);
|
|
bool SHOW_PLANETS = true;
|
|
|
|
enum PlanetModes {
|
|
PM_NORMAL,
|
|
PM_MONEY,
|
|
PM_PRODUCTION,
|
|
PM_REQUIREMENTS,
|
|
PM_TILES,
|
|
PM_PRESSURE
|
|
};
|
|
|
|
class PlanetElement {
|
|
Object@ parentObject;
|
|
Object@ obj;
|
|
|
|
int width = 0;
|
|
int height = 0;
|
|
|
|
PlanetElement@ parent;
|
|
array<PlanetElement@> children;
|
|
array<Status> statuses;
|
|
|
|
PlanetModes mode = PM_NORMAL;
|
|
int income = 0;
|
|
int level = 0;
|
|
|
|
const ResourceRequirements@ reqs;
|
|
array<int>@ remaining = null;
|
|
array<double>@ production = null;
|
|
|
|
int numUsed = -1;
|
|
int numTotal = -1;
|
|
|
|
string name;
|
|
string moneyText;
|
|
const ResourceType@ resource;
|
|
Resource@ primary;
|
|
bool isPrimary = true;
|
|
bool excess = false;
|
|
bool locked = false;
|
|
bool focused = false;
|
|
bool decaying = false;
|
|
Resources available;
|
|
|
|
int minWidth = 0;
|
|
int coffset = 0;
|
|
|
|
bool hovered = false;
|
|
bool showChildren = true;
|
|
Color hoverColor;
|
|
|
|
void clear() {
|
|
children.length = 0;
|
|
@parent = null;
|
|
}
|
|
|
|
PlanetElement@ get_root() {
|
|
PlanetElement@ par = this;
|
|
while(par.parent !is null)
|
|
@par = par.parent;
|
|
return par;
|
|
}
|
|
|
|
string getTooltip() {
|
|
string ttip = getResourceTooltip(primary.type, primary, obj);
|
|
for(uint i = 0, cnt = statuses.length; i < cnt; ++i) {
|
|
auto@ status = statuses[i];
|
|
string title = status.type.name;
|
|
if(status.stacks > 1 && !status.type.unique)
|
|
title += format(" ($1x)", toString(status.stacks));
|
|
ttip += "\n\n";
|
|
ttip += status.getTooltip(obj);
|
|
}
|
|
return ttip;
|
|
}
|
|
|
|
void cache(Resource@ res, bool isPrimary) {
|
|
if(res !is null) {
|
|
if(primary is null)
|
|
@primary = Resource();
|
|
primary = res;
|
|
@parentObject = isPrimary ? primary.exportedTo : res.origin;
|
|
@resource = primary.type;
|
|
}
|
|
else {
|
|
@primary = null;
|
|
@parentObject = null;
|
|
@resource = null;
|
|
}
|
|
this.isPrimary = isPrimary;
|
|
|
|
bool hasSurface = obj.hasSurfaceComponent;
|
|
name = obj.name;
|
|
level = hasSurface ? obj.level : 0;
|
|
|
|
//Load primary resource
|
|
excess = false;
|
|
if(primary !is null && primary.type !is null) {
|
|
excess = primary.exportedTo is null && primary.origin !is null && primary.usable
|
|
&& (!hasSurface || !primary.type.isMaterial(level));
|
|
locked = primary.locked || !primary.type.exportable || !isPrimary;
|
|
}
|
|
|
|
//Check resource requirements
|
|
if(isPrimary) {
|
|
if(mode == PM_MONEY && hasSurface) {
|
|
income = obj.income;
|
|
moneyText = formatMoney(income);
|
|
}
|
|
else {
|
|
income = 0;
|
|
}
|
|
|
|
decaying = hasSurface && obj.decayTime > 0;
|
|
@reqs = null;
|
|
if(hasSurface) {
|
|
if(decaying && mode == PM_NORMAL) {
|
|
@reqs = getPlanetLevel(obj, level).reqs;
|
|
}
|
|
else if(mode == PM_REQUIREMENTS) {
|
|
if(level < int(getMaxPlanetLevel(obj))) {
|
|
if(primary is null || primary.exportedTo is null || level < int(primary.type.level))
|
|
@reqs = getPlanetLevel(obj, level+1).reqs;
|
|
}
|
|
}
|
|
|
|
if(reqs !is null) {
|
|
if(remaining is null)
|
|
@remaining = array<int>();
|
|
available.clear();
|
|
receive(obj.getResourceAmounts(), available);
|
|
reqs.satisfiedBy(available, null, true, remaining);
|
|
}
|
|
else {
|
|
@remaining = null;
|
|
}
|
|
}
|
|
|
|
//Check resource production
|
|
if(mode == PM_PRODUCTION && hasSurface) {
|
|
if(production is null)
|
|
@production = array<double>(TR_COUNT);
|
|
for(uint i = 0; i < TR_COUNT; ++i) {
|
|
if(i == TR_Labor)
|
|
production[i] = obj.laborIncome * 60.0;
|
|
else
|
|
production[i] = obj.getResourceProduction(i);
|
|
}
|
|
}
|
|
else {
|
|
@production = null;
|
|
}
|
|
|
|
//Check statuses
|
|
if(obj.hasStatuses)
|
|
statuses.syncFrom(obj.getStatusEffects());
|
|
else
|
|
statuses.length = 0;
|
|
|
|
//Check surface size
|
|
if(mode == PM_TILES && hasSurface) {
|
|
numTotal = obj.totalSurfaceTiles;
|
|
numUsed = obj.usedSurfaceTiles;
|
|
}
|
|
else if(mode == PM_PRESSURE && hasSurface) {
|
|
numTotal = obj.pressureCap;
|
|
numUsed = obj.totalPressure;
|
|
}
|
|
else {
|
|
numTotal = -1;
|
|
numUsed = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void export(PlanetElement@ to) {
|
|
if(to is null)
|
|
obj.exportResource(0, null);
|
|
else
|
|
obj.exportResource(0, to.obj);
|
|
}
|
|
|
|
void calc() {
|
|
height = 0;
|
|
width = 0;
|
|
if(showChildren) {
|
|
for(uint i = 0, cnt = children.length; i < cnt; ++i) {
|
|
auto@ child = children[i];
|
|
child.calc();
|
|
if(child.height > height)
|
|
height = child.height;
|
|
width += child.width;
|
|
}
|
|
}
|
|
if(width < minWidth) {
|
|
coffset = (minWidth - width) / 2;
|
|
width = minWidth;
|
|
}
|
|
if(width == 0)
|
|
width = 50;
|
|
height += 50;
|
|
}
|
|
|
|
void sort() {
|
|
children.sortDesc();
|
|
for(uint i = 0, cnt = children.length; i < cnt; ++i)
|
|
children[i].sort();
|
|
}
|
|
|
|
void dump() {
|
|
dump("");
|
|
}
|
|
|
|
void click(int button) {
|
|
if(button == 0) {
|
|
if(obj.selected) {
|
|
if(!focused) {
|
|
zoomTo(obj);
|
|
openOverlay(obj);
|
|
}
|
|
}
|
|
else
|
|
selectObject(obj);
|
|
}
|
|
else if(button == 1) {
|
|
if(selectedObject is null)
|
|
openContextMenu(obj, obj);
|
|
else
|
|
openContextMenu(obj);
|
|
}
|
|
else if(button == 2) {
|
|
zoomTo(obj);
|
|
}
|
|
}
|
|
|
|
void renderLine(const recti& fromPos, const recti& toPos, const Color& lineColor, bool sourceLine = false) {
|
|
vec2i from = fromPos.center;
|
|
vec2i to = vec2i(from.x, toPos.topLeft.y + 22);
|
|
from += (to - from).normalized(18);
|
|
|
|
drawLine(from, to, lineColor, 4);
|
|
if(sourceLine) {
|
|
if(from.x < toPos.topLeft.x)
|
|
drawLine(to - vec2i(2,1), vec2i(toPos.topLeft.x, to.y-1), lineColor, 4);
|
|
else if(from.x > toPos.botRight.x)
|
|
drawLine(vec2i(toPos.botRight.x, to.y-1), to + vec2i(2, -1), lineColor, 4);
|
|
}
|
|
}
|
|
|
|
PlanetElement@ planetFromPosition(const vec2i& startPos, const vec2i& offset, bool strict = true) {
|
|
if(!getFullPos(startPos).isWithin(offset))
|
|
return null;
|
|
if(getIconPos(startPos).isWithin(offset))
|
|
return this;
|
|
vec2i pos(startPos.x, startPos.y + 50);
|
|
pos.x += coffset;
|
|
for(uint i = 0, cnt = children.length; i < cnt; ++i) {
|
|
PlanetElement@ elem = children[i].planetFromPosition(pos, offset);
|
|
if(elem !is null)
|
|
return elem;
|
|
pos.x += children[i].width;
|
|
}
|
|
return strict ? null : this;
|
|
}
|
|
|
|
recti getIconPos(const vec2i& startPos) {
|
|
return recti_area(startPos.x + ((width - ICON_SIZE) / 2), startPos.y+2, ICON_SIZE,ICON_SIZE);
|
|
}
|
|
|
|
recti getFullPos(const vec2i& startPos) {
|
|
return recti_area(startPos, vec2i(width, height));
|
|
}
|
|
|
|
void drawTree(const vec2i& startPos) {
|
|
recti iconPos = getIconPos(startPos);
|
|
vec2i pos(startPos.x, startPos.y + 50);
|
|
|
|
//Draw the resource lines
|
|
if(children.length != 0 && showChildren) {
|
|
Color lineColor = playerEmpire.color;
|
|
lineColor.a = 0x80;
|
|
if(children.length > 1) {
|
|
drawLine(vec2i(startPos.x + coffset + children[0].width/2 - 3, startPos.y + 23),
|
|
vec2i(startPos.x + width/2 - 22, startPos.y + 23),
|
|
lineColor, 4);
|
|
drawLine(vec2i(startPos.x + width/2 + 22, startPos.y + 23),
|
|
vec2i(startPos.x + width - children.last.width/2 + 2 - coffset, startPos.y + 23),
|
|
lineColor, 4);
|
|
}
|
|
pos.x += coffset;
|
|
for(uint i = 0, cnt = children.length; i < cnt; ++i) {
|
|
auto@ child = children[i];
|
|
|
|
Color color = lineColor;
|
|
bool sourceLine = false;
|
|
if(child.primary !is null && (!child.primary.usable || child.decaying)) {
|
|
float pct = abs((frameTime % 1.0) - 0.5f) * 2.f;
|
|
color = colors::Red.interpolate(colors::Orange, pct);
|
|
sourceLine = true;
|
|
}
|
|
|
|
recti childPos = child.getIconPos(pos);
|
|
renderLine(childPos, iconPos, color, sourceLine);
|
|
pos.x += child.width;
|
|
}
|
|
}
|
|
|
|
//Draw children
|
|
if(showChildren) {
|
|
vec2i pos(startPos.x, startPos.y + 50);
|
|
pos.x += coffset;
|
|
for(uint i = 0, cnt = children.length; i < cnt; ++i) {
|
|
children[i].drawTree(pos);
|
|
pos.x += children[i].width;
|
|
}
|
|
}
|
|
|
|
//Draw self
|
|
if(decaying)
|
|
drawRectangle(iconPos, Color(0xff000080));
|
|
if(hovered)
|
|
drawRectangle(iconPos, hoverColor);
|
|
if(focused && isPrimary)
|
|
material::SelectionCircle.draw(iconPos.padded(-2), Color(0xffc0ff80));
|
|
else if(obj.selected && isPrimary)
|
|
material::SelectionCircle.draw(iconPos);
|
|
drawPlanet(iconPos);
|
|
|
|
//Draw data
|
|
if(isPrimary) {
|
|
if(statuses.length != 0) {
|
|
vec2i pos(iconPos.botRight.x-12, iconPos.topLeft.y+4);
|
|
int count = int(statuses.length);
|
|
int spacing = min(iconPos.height / count, 22);
|
|
const Font@ ft = font::DroidSans_11_Bold;
|
|
for(uint i = 0, cnt = statuses.length; i < cnt; ++i) {
|
|
auto@ status = statuses[i];
|
|
spritesheet::ResourceIconsMods.draw(0, recti_area(pos-vec2i(3), vec2i(26)), status.type.color);
|
|
status.type.icon.draw(recti_area(pos, vec2i(20)));
|
|
if(!status.type.unique && status.stacks > 1)
|
|
ft.draw(
|
|
pos=recti_area(pos-vec2i(3), vec2i(26)),
|
|
horizAlign=1.0, vertAlign=1.0,
|
|
stroke=colors::Black,
|
|
color=status.type.color,
|
|
text=toString(status.stacks)+"x");
|
|
pos.y += spacing;
|
|
}
|
|
}
|
|
if(reqs !is null) {
|
|
uint rCnt = reqs.reqs.length;
|
|
int tot = 0;
|
|
for(uint i = 0; i < rCnt; ++i)
|
|
tot += remaining[i];
|
|
if(tot > 0) {
|
|
int tw = min(width, tot * 20);
|
|
int step = tw / tot;
|
|
|
|
vec2i pos(iconPos.center.x - tw/2, iconPos.topLeft.y - 10);
|
|
for(uint i = 0; i < rCnt; ++i) {
|
|
Sprite sprt = getRequirementIcon(reqs.reqs[i]);
|
|
for(uint n = 0, ncnt = remaining[i]; n < ncnt; ++n) {
|
|
sprt.draw(recti_area(pos, vec2i(20)));
|
|
pos.x += step;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(mode == PM_MONEY && income != 0) {
|
|
const Font@ ft = font::DroidSans_11_Bold;
|
|
if(income == -100)
|
|
@ft = font::DroidSans_10;
|
|
recti area = recti_area(vec2i(startPos.x, iconPos.topLeft.y - 8), vec2i(width, 20));
|
|
Color color = colors::Red;
|
|
if(income > 0)
|
|
color = colors::Green;
|
|
else if(income == 0)
|
|
color = Color(0xaaaaaaff);
|
|
ft.draw(pos=area+vec2i(-1,-1), text=moneyText, color=colors::Black, horizAlign=0.5);
|
|
ft.draw(pos=area+vec2i(1,-1), text=moneyText, color=colors::Black, horizAlign=0.5);
|
|
ft.draw(pos=area+vec2i(-1,1), text=moneyText, color=colors::Black, horizAlign=0.5);
|
|
ft.draw(pos=area+vec2i(1,1), text=moneyText, color=colors::Black, horizAlign=0.5);
|
|
ft.draw(pos=area, text=moneyText, color=color, horizAlign=0.5);
|
|
}
|
|
if(production !is null) {
|
|
int tot = 0;
|
|
for(uint i = 0; i < TR_COUNT; ++i) {
|
|
if(production[i] > 0.0)
|
|
tot += 1;
|
|
}
|
|
if(tot > 0) {
|
|
int tw = min(width, tot * 40);
|
|
int step = tw / tot;
|
|
|
|
vec2i pos(iconPos.center.x - tw/2, iconPos.topLeft.y - 16);
|
|
for(uint i = 0; i < TR_COUNT; ++i) {
|
|
double amt = production[i];
|
|
if(amt > 0) {
|
|
Sprite sprt = getTileResourceSprite(i);
|
|
sprt.draw(recti_area(pos, vec2i(20)));
|
|
string text = standardize(amt, true);
|
|
const Font@ ft = font::DroidSans_11_Bold;
|
|
recti area = recti_area(pos+vec2i(18,-1), vec2i(24));
|
|
Color color = getTileResourceColor(i);
|
|
ft.draw(pos=area+vec2i(-1,-1), text=text, color=colors::Black, horizAlign=0.5);
|
|
ft.draw(pos=area+vec2i(1,-1), text=text, color=colors::Black, horizAlign=0.5);
|
|
ft.draw(pos=area+vec2i(-1,1), text=text, color=colors::Black, horizAlign=0.5);
|
|
ft.draw(pos=area+vec2i(1,1), text=text, color=colors::Black, horizAlign=0.5);
|
|
ft.draw(pos=area, text=text, color=color, horizAlign=0.5);
|
|
|
|
pos.x += step;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(numTotal != -1) {
|
|
recti area = recti_area(vec2i(startPos.x - 20, iconPos.topLeft.y - 8), vec2i(width+40, 20));
|
|
string txt = format("$1/$2", toString(numUsed), toString(numTotal));
|
|
Color baseColor(0xaaaaaaff);
|
|
const Font@ ft = font::DroidSans_11_Bold;
|
|
if(mode == PM_TILES) {
|
|
if(numTotal < 50)
|
|
@ft = font::DroidSans_8;
|
|
else if(numTotal < 90)
|
|
@ft = font::DroidSans_10;
|
|
else if(numTotal < 150)
|
|
@ft = font::DroidSans_11;
|
|
else
|
|
baseColor = colors::Green;
|
|
}
|
|
else {
|
|
if(numUsed < numTotal)
|
|
baseColor = colors::Green;
|
|
else
|
|
baseColor = colors::Red;
|
|
}
|
|
Color color;
|
|
if(numTotal != 0)
|
|
color = baseColor.interpolate(colors::Orange, float(numUsed) / float(numTotal));
|
|
ft.draw(pos=area, text=txt, color=color, horizAlign=0.5, stroke=colors::Black);
|
|
}
|
|
}
|
|
}
|
|
|
|
void drawPlanet(const recti& pos) {
|
|
if(isPrimary) {
|
|
if(primary !is null) {
|
|
if(obj.isPlanet)
|
|
drawPlanetIcon(cast<Planet>(obj), pos, primary, showType = SHOW_PLANETS);
|
|
else
|
|
drawObjectIcon(obj, pos, primary);
|
|
}
|
|
else
|
|
drawObjectIcon(obj, pos);
|
|
}
|
|
else if(primary !is null) {
|
|
drawSmallResource(primary.type, primary,
|
|
pos.padded(0.3 * pos.width, 0.3 * pos.height),
|
|
obj, onPlanet=true);
|
|
}
|
|
}
|
|
|
|
void dump(string prefix) {
|
|
print(prefix+"-- "+obj.name);
|
|
for(uint i = 0, cnt = children.length; i < cnt; ++i)
|
|
children[i].dump(prefix+" ");
|
|
}
|
|
|
|
int opCmp(const PlanetElement@ other) const {
|
|
if(focused && !other.focused)
|
|
return 1;
|
|
if(other.focused && !focused)
|
|
return -1;
|
|
//Sort no resources last
|
|
if(other.resource is null) {
|
|
if(resource is null)
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
else {
|
|
if(resource is null)
|
|
return -1;
|
|
}
|
|
//Sort by level
|
|
if(level < other.level)
|
|
return -1;
|
|
else if(level > other.level)
|
|
return 1;
|
|
if(resource.level < other.resource.level)
|
|
return -1;
|
|
else if(resource.level > other.resource.level)
|
|
return 1;
|
|
if(resource.id < other.resource.id)
|
|
return -1;
|
|
else if(resource.id > other.resource.id)
|
|
return 1;
|
|
if(obj.id < other.obj.id)
|
|
return -1;
|
|
else if(obj.id > other.obj.id)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
class PlanetTree : BaseGuiElement {
|
|
MarkupTooltip@ tip;
|
|
array<PlanetElement@> planets;
|
|
|
|
Object@ focusObject;
|
|
bool showExcess = true;
|
|
|
|
PlanetPopup@ popup;
|
|
PlanetModes mode = PM_NORMAL;
|
|
PlanetElement@ focusEle;
|
|
PlanetElement@ hovered;
|
|
PlanetElement@ covered;
|
|
PlanetElement@ dragging;
|
|
recti dragElemStart;
|
|
vec2i dragMouseStart;
|
|
|
|
bool heldLeft = false;
|
|
bool isDragging = false;
|
|
bool moved = false;
|
|
vec2d scroll;
|
|
vec2i dragStart;
|
|
vec2i prevScroll;
|
|
|
|
int totalWidth = 0;
|
|
int totalHeight = 0;
|
|
|
|
PlanetTree(IGuiElement@ parent, Alignment@ align) {
|
|
super(parent, align);
|
|
_PlanetTree();
|
|
}
|
|
|
|
PlanetTree(IGuiElement@ parent, const recti& pos) {
|
|
super(parent, pos);
|
|
_PlanetTree();
|
|
}
|
|
|
|
~PlanetTree() {
|
|
if(popup !is null) {
|
|
popup.visible = false;
|
|
popup.remove();
|
|
@popup = null;
|
|
}
|
|
}
|
|
|
|
void _PlanetTree() {
|
|
@popup = PlanetPopup(null);
|
|
popup.mouseLinked = true;
|
|
popup.visible = false;
|
|
|
|
@tip = MarkupTooltip("", width=400, delay=0.f);
|
|
tip.wrapAroundElement = false;
|
|
}
|
|
|
|
void remove() {
|
|
if(popup !is null) {
|
|
popup.visible = false;
|
|
popup.remove();
|
|
@popup = null;
|
|
}
|
|
for(uint i = 0, cnt = planets.length; i < cnt; ++i)
|
|
planets[i].clear();
|
|
planets.length = 0;
|
|
BaseGuiElement::remove();
|
|
}
|
|
|
|
bool shouldShow(PlanetElement@ elem){
|
|
if(focusObject !is null && elem.obj !is focusObject) {
|
|
if(showExcess && elem.excess)
|
|
return true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void updateAbsolutePosition() {
|
|
BaseGuiElement::updateAbsolutePosition();
|
|
if(focusEle !is null) {
|
|
focusEle.minWidth = size.width - 20;
|
|
focusEle.calc();
|
|
}
|
|
}
|
|
|
|
vec2i prevMousePos = vec2i();
|
|
bool prevVisible = false;
|
|
double timer = 1.0;
|
|
void tick(double time) {
|
|
if(!actuallyVisible) {
|
|
if(popup !is null && popup.visible)
|
|
popup.visible = false;
|
|
prevVisible = false;
|
|
return;
|
|
}
|
|
timer += time;
|
|
if(timer >= 1.0) {
|
|
if(dragging is null)
|
|
update();
|
|
timer = 0.0;
|
|
}
|
|
if(prevMousePos != mousePos) {
|
|
updateHover();
|
|
prevMousePos = mousePos;
|
|
}
|
|
if(!prevVisible) {
|
|
while(progressing)
|
|
progressUpdate();
|
|
}
|
|
else {
|
|
progressUpdate();
|
|
}
|
|
popup.update();
|
|
prevVisible = true;
|
|
}
|
|
|
|
map elemLookup;
|
|
array<Object@> list;
|
|
array<PlanetElement@> allElems;
|
|
bool progressing = false;
|
|
uint progressIndex = 0;
|
|
bool forceProgress = false;
|
|
array<Resource> resources;
|
|
|
|
void progressUpdate(bool force = false) {
|
|
if(!progressing)
|
|
return;
|
|
if(forceProgress)
|
|
force = true;
|
|
timer = 0.0;
|
|
if(progressIndex < list.length) {
|
|
//First pass: generate planet elements
|
|
uint count = min(list.length/10, 10);
|
|
if(force)
|
|
count = list.length;
|
|
count = max(count, 1);
|
|
for(uint n = 0; n < count; ++n) {
|
|
uint i = progressIndex++;
|
|
if(i >= list.length)
|
|
break;
|
|
Object@ pl = list[i];
|
|
if(pl is null)
|
|
continue;
|
|
|
|
resources.syncFrom(pl.getNativeResources());
|
|
for(uint i = 0, cnt = resources.length; i < cnt; ++i) {
|
|
if(i != 0 && resources[i].exportedTo !is null)
|
|
continue;
|
|
PlanetElement ele;
|
|
@ele.obj = pl;
|
|
ele.mode = mode;
|
|
|
|
if(ele.obj is focusObject && i == 0) {
|
|
@focusEle = ele;
|
|
ele.focused = true;
|
|
ele.minWidth = size.width - 20;
|
|
}
|
|
ele.showChildren = focusObject is null || ele.root.obj is focusObject;
|
|
|
|
ele.cache(resources[i], i == 0);
|
|
|
|
allElems.insertLast(ele);
|
|
if(i == 0)
|
|
elemLookup.set(pl.id, @ele);
|
|
}
|
|
if(resources.length == 0) {
|
|
PlanetElement ele;
|
|
@ele.obj = pl;
|
|
ele.mode = mode;
|
|
|
|
if(ele.obj is focusObject && i == 0) {
|
|
@focusEle = ele;
|
|
ele.focused = true;
|
|
ele.minWidth = size.width - 20;
|
|
}
|
|
ele.showChildren = focusObject is null || ele.root.obj is focusObject;
|
|
|
|
ele.cache(null, true);
|
|
|
|
allElems.insertLast(ele);
|
|
elemLookup.set(pl.id, @ele);
|
|
}
|
|
}
|
|
if(!force)
|
|
return;
|
|
}
|
|
|
|
progressing = false;
|
|
forceProgress = false;
|
|
planets.length = 0;
|
|
planets.reserve(allElems.length);
|
|
|
|
//Second pass: create proper child relations
|
|
for(uint i = 0, cnt = allElems.length; i < cnt; ++i) {
|
|
PlanetElement@ ele = allElems[i];
|
|
if(ele.parentObject !is null) {
|
|
PlanetElement@ other;
|
|
elemLookup.get(ele.parentObject.id, @other);
|
|
|
|
//Break cycles
|
|
auto@ check = other;
|
|
while(check !is null) {
|
|
if(check is ele) {
|
|
@other = null;
|
|
break;
|
|
}
|
|
@check = check.parent;
|
|
}
|
|
|
|
//Add in the right spot
|
|
if(other !is null) {
|
|
@ele.parent = other;
|
|
other.children.insertLast(ele);
|
|
}
|
|
else {
|
|
@ele.parentObject = null;
|
|
planets.insertLast(ele);
|
|
}
|
|
}
|
|
else {
|
|
planets.insertLast(ele);
|
|
}
|
|
}
|
|
|
|
if(focusEle !is null && !shouldShow(focusEle.root))
|
|
planets.insertLast(focusEle);
|
|
|
|
//Third pass: sort
|
|
planets.sortDesc();
|
|
for(uint i = 0, cnt = planets.length; i < cnt; ++i) {
|
|
planets[i].sort();
|
|
planets[i].calc();
|
|
}
|
|
updateHover();
|
|
}
|
|
|
|
void update(bool force = false) {
|
|
if(progressing && !force)
|
|
return;
|
|
|
|
if(force)
|
|
forceProgress = true;
|
|
|
|
progressing = true;
|
|
list.length = 0;
|
|
elemLookup.deleteAll();
|
|
allElems.length = 0;
|
|
|
|
{
|
|
Object@ obj;
|
|
|
|
DataList@ objs = playerEmpire.getPlanets();
|
|
while(receive(objs, obj))
|
|
list.insertLast(obj);
|
|
|
|
DataList@ roids = playerEmpire.getAsteroids();
|
|
while(receive(roids, obj)) {
|
|
if(obj.nativeResourceCount != 0)
|
|
list.insertLast(obj);
|
|
}
|
|
}
|
|
|
|
allElems.reserve(list.length);
|
|
progressIndex = 0;
|
|
}
|
|
|
|
void updateHover() {
|
|
auto@ newHover = planetFromPosition(mousePos - AbsolutePosition.topLeft);
|
|
if(newHover !is hovered) {
|
|
if(hovered !is null)
|
|
hovered.hovered = false;
|
|
if(newHover !is null)
|
|
newHover.hovered = true;
|
|
@hovered = newHover;
|
|
}
|
|
if(hovered !is null) {
|
|
if(dragging !is null && dragging !is hovered)
|
|
hovered.hoverColor = COLOR_DROP;
|
|
else
|
|
hovered.hoverColor = COLOR_HOVER;
|
|
}
|
|
@covered = planetFromPosition(mousePos - AbsolutePosition.topLeft, strict=false);
|
|
if(popup !is null) {
|
|
popup.visible = hovered !is null && dragging is null && hovered.obj !is null && hovered.obj.isPlanet;
|
|
if(popup.visible) {
|
|
popup.set(hovered.obj);
|
|
popup.updatePosition(hovered.obj);
|
|
tip.offset.y = 140;
|
|
}
|
|
else {
|
|
tip.offset.y = 0;
|
|
}
|
|
}
|
|
else {
|
|
tip.offset.y = 0;
|
|
}
|
|
}
|
|
|
|
void center() {
|
|
scroll.x = double(totalWidth - size.width) / 2.0;
|
|
scroll.y = double(totalHeight - size.height + 40) / 2.0;
|
|
scroll.x = clamp(scroll.x, 0.0, max(double(totalWidth - size.width), 0.0));
|
|
scroll.y = clamp(scroll.y, 0.0, max(double(totalHeight - size.height + 60.0), 0.0));
|
|
}
|
|
|
|
void scrollPane(double dx, double dy) {
|
|
scroll -= vec2d(dx, dy);
|
|
scroll.x = clamp(scroll.x, 0.0, max(double(totalWidth - size.width), 0.0));
|
|
scroll.y = clamp(scroll.y, 0.0, max(double(totalHeight - size.height + 60.0), 0.0));
|
|
}
|
|
|
|
PlanetElement@ planetFromPosition(const vec2i& offset, bool strict = true) {
|
|
if(offset.y > size.height)
|
|
return null;
|
|
vec2i pos = vec2i(12,16) - vec2i(scroll);
|
|
vec2i origPos = pos;
|
|
int lineHeight = 0;
|
|
for(uint i = 0, cnt = planets.length; i < cnt; ++i) {
|
|
auto@ pl = planets[i];
|
|
if(!shouldShow(pl))
|
|
continue;
|
|
if(pl.width + pos.x - origPos.x > size.width - 24) {
|
|
pos = vec2i(origPos.x, pos.y + lineHeight + 20);
|
|
lineHeight = 0;
|
|
}
|
|
PlanetElement@ elem = planets[i].planetFromPosition(pos, offset, strict);
|
|
if(elem !is null)
|
|
return elem;
|
|
if(pl.height > lineHeight)
|
|
lineHeight = pl.height;
|
|
pos += vec2i(pl.width+20, 0);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bool onGuiEvent(const GuiEvent& event) {
|
|
if(event.type == GUI_Focus_Lost) {
|
|
if(!isAncestorOf(event.caller)) {
|
|
heldLeft = false;
|
|
isDragging = false;
|
|
@dragging = null;
|
|
}
|
|
}
|
|
return BaseGuiElement::onGuiEvent(event);
|
|
}
|
|
|
|
void stopDragging() {
|
|
heldLeft = false;
|
|
isDragging = false;
|
|
@dragging = null;
|
|
}
|
|
|
|
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
|
|
switch(event.type) {
|
|
case MET_Button_Down:
|
|
if(event.button == 0 && source is this) {
|
|
if(hovered !is null) {
|
|
if(!hovered.locked) {
|
|
@dragging = hovered;
|
|
dragMouseStart = mousePos;
|
|
}
|
|
else {
|
|
@dragging = null;
|
|
}
|
|
}
|
|
else {
|
|
heldLeft = true;
|
|
dragStart = vec2i(event.x, event.y);
|
|
}
|
|
return true;
|
|
}
|
|
break;
|
|
case MET_Moved: {
|
|
vec2i mouse = vec2i(event.x, event.y);
|
|
vec2i d = mouse - dragStart;
|
|
updateHover();
|
|
if(isDragging || dragging !is null) {
|
|
if(!mouseLeft)
|
|
stopDragging();
|
|
}
|
|
if(isDragging) {
|
|
scrollPane(d.x, d.y);
|
|
dragStart = mouse;
|
|
moved = true;
|
|
}
|
|
else if(heldLeft) {
|
|
if(abs(d.x) >= 5 || abs(d.y) >= 5) {
|
|
isDragging = true;
|
|
moved = true;
|
|
scrollPane(d.x, d.y);
|
|
dragStart = mouse;
|
|
}
|
|
}
|
|
} break;
|
|
case MET_Button_Up:
|
|
if(event.button == 0) {
|
|
bool wasDragging = isDragging || dragging !is null;
|
|
if(dragging !is null) {
|
|
if(dragMouseStart.distanceTo(mousePos) <= 6) {
|
|
if(hovered !is null)
|
|
hovered.click(0);
|
|
}
|
|
else if(hovered !is null && hovered !is dragging) {
|
|
if(selectedObjects.length > 1 && dragging.obj.selected) {
|
|
for(uint i = 0, cnt = selectedObjects.length; i < cnt; ++i) {
|
|
if(selectedObjects[i].hasResources)
|
|
selectedObjects[i].exportResource(0, hovered.obj);
|
|
}
|
|
}
|
|
else
|
|
dragging.export(hovered);
|
|
}
|
|
else if(dragElemStart.distanceTo(mousePos) > 50
|
|
&& covered is null) {
|
|
if(selectedObjects.length > 1 && dragging.obj.selected) {
|
|
for(uint i = 0, cnt = selectedObjects.length; i < cnt; ++i) {
|
|
if(selectedObjects[i].hasResources)
|
|
selectedObjects[i].exportResource(0, null);
|
|
}
|
|
}
|
|
else
|
|
dragging.export(null);
|
|
}
|
|
@dragging = null;
|
|
update(force=true);
|
|
timer = 0.08;
|
|
return true;
|
|
}
|
|
else {
|
|
if(!isDragging) {
|
|
if(hovered !is null) {
|
|
hovered.click(0);
|
|
}
|
|
else
|
|
selectObject(null);
|
|
}
|
|
heldLeft = false;
|
|
isDragging = false;
|
|
dragStart = vec2i();
|
|
}
|
|
if(source is this || wasDragging)
|
|
return true;
|
|
}
|
|
else {
|
|
if(hovered !is null) {
|
|
hovered.click(event.button);
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
case MET_Scrolled:
|
|
if(source is this) {
|
|
if(totalHeight > size.height)
|
|
scrollPane(0, 20*event.y);
|
|
else if(totalWidth > size.width)
|
|
scrollPane(20*event.y, 0);
|
|
}
|
|
break;
|
|
}
|
|
return BaseGuiElement::onMouseEvent(event, source);
|
|
}
|
|
|
|
void draw() {
|
|
if(dragging !is null && hovered is null
|
|
&& dragElemStart.distanceTo(mousePos) > 50
|
|
&& covered is null) {
|
|
drawRectangle(recti_area(mousePos - vec2i(50), vec2i(100)), COLOR_CLEAR);
|
|
}
|
|
|
|
vec2i pos = AbsolutePosition.topLeft + vec2i(12,16) - vec2i(scroll);
|
|
vec2i origPos = pos;
|
|
int lineHeight = 0;
|
|
totalWidth = 0;
|
|
for(uint i = 0, cnt = planets.length; i < cnt; ++i) {
|
|
auto@ pl = planets[i];
|
|
if(!shouldShow(pl))
|
|
continue;
|
|
if(pl.width + pos.x - origPos.x > size.width - 24) {
|
|
totalWidth = max(pos.x - origPos.x, totalWidth);
|
|
pos = vec2i(origPos.x, pos.y + lineHeight + 20);
|
|
lineHeight = 0;
|
|
}
|
|
if(pl is dragging)
|
|
dragElemStart = pl.getFullPos(pos);
|
|
pl.drawTree(pos);
|
|
if(pl.height > lineHeight)
|
|
lineHeight = pl.height;
|
|
pos += vec2i(pl.width+20, 0);
|
|
}
|
|
totalWidth = max(pos.x - origPos.x, totalWidth);
|
|
totalHeight = pos.y + lineHeight - origPos.y;
|
|
clearClip();
|
|
if(dragging !is null) {
|
|
vec2i pos = vec2i(mousePos.x - dragging.width/2, mousePos.y-ICON_SIZE/2);
|
|
if(dragMouseStart.distanceTo(mousePos) > 6)
|
|
dragging.drawTree(pos);
|
|
}
|
|
if(hovered !is null && hovered.primary !is null) {
|
|
tip.text = hovered.getTooltip();
|
|
|
|
vec2i mpos = mousePos;
|
|
vec2i offset(10, 0);
|
|
if(mpos.x + 300 > screenSize.x)
|
|
offset.x -= 350;
|
|
int h = popup.visible ? popup.size.height+4 : 0;
|
|
h += tip.height + 20;
|
|
if(mpos.y + h > screenSize.y)
|
|
offset.y -= (h - (screenSize.y - mpos.y));
|
|
if(popup.visible) {
|
|
popup.mouseOffset = offset;
|
|
tip.offset = offset + vec2i(16, popup.size.height+4);
|
|
}
|
|
else {
|
|
tip.offset = offset + vec2i(16, 0);
|
|
}
|
|
|
|
tip.draw(skin, this);
|
|
}
|
|
BaseGuiElement::draw();
|
|
}
|
|
};
|
|
|
|
class PlanetsTab : Tab {
|
|
PlanetTree@ tree;
|
|
|
|
GuiButton@ normalMode;
|
|
GuiButton@ moneyMode;
|
|
GuiButton@ productionMode;
|
|
GuiButton@ reqsMode;
|
|
GuiButton@ tilesMode;
|
|
GuiButton@ pressureMode;
|
|
|
|
GuiButton@ bgButton;
|
|
|
|
GuiTextbox@ searchBar;
|
|
|
|
GuiSkinElement@ bar;
|
|
|
|
PlanetsTab() {
|
|
super();
|
|
title = locale::PLANETS_TAB;
|
|
|
|
@tree = PlanetTree(this, Alignment().padded(0,0,0,50));
|
|
@bar = GuiSkinElement(this, Alignment(Left-4, Bottom-42, Right+4, Bottom+4), SS_PlanetBar);
|
|
|
|
int w = 160;
|
|
int c = 6;
|
|
int off = (c * w) / 2;
|
|
@normalMode = GuiButton(bar, Alignment(Left+0.5f-off, Top+4, Width=w, Height=34), locale::PLANETS_MODE_NORMAL);
|
|
normalMode.buttonIcon = Sprite(spritesheet::MenuIcons, 0);
|
|
normalMode.toggleButton = true;
|
|
normalMode.pressed = true;
|
|
off -= w;
|
|
|
|
@moneyMode = GuiButton(bar, Alignment(Left+0.5f-off, Top+4, Width=w, Height=34), locale::PLANETS_MODE_MONEY);
|
|
moneyMode.buttonIcon = icons::Money;
|
|
moneyMode.toggleButton = true;
|
|
moneyMode.pressed = false;
|
|
off -= w;
|
|
|
|
@productionMode = GuiButton(bar, Alignment(Left+0.5f-off, Top+4, Width=w, Height=34), locale::PLANETS_MODE_PRODUCTION);
|
|
productionMode.buttonIcon = icons::Add;
|
|
productionMode.toggleButton = true;
|
|
productionMode.pressed = false;
|
|
off -= w;
|
|
|
|
@reqsMode = GuiButton(bar, Alignment(Left+0.5f-off, Top+4, Width=w, Height=34), locale::PLANETS_MODE_REQS);
|
|
reqsMode.buttonIcon = Sprite(spritesheet::ResourceClassIcons, 0);
|
|
reqsMode.toggleButton = true;
|
|
reqsMode.pressed = false;
|
|
off -= w;
|
|
|
|
@pressureMode = GuiButton(bar, Alignment(Left+0.5f-off, Top+4, Width=w, Height=34), locale::PLANETS_MODE_PRESSURE);
|
|
pressureMode.buttonIcon = icons::Pressure;
|
|
pressureMode.toggleButton = true;
|
|
pressureMode.pressed = false;
|
|
pressureMode.disabled = playerEmpire.HasPopulation == 0;
|
|
off -= w;
|
|
|
|
@tilesMode = GuiButton(bar, Alignment(Left+0.5f-off, Top+4, Width=w, Height=34), locale::PLANETS_MODE_TILES);
|
|
tilesMode.buttonIcon = Sprite(spritesheet::PlanetType, 0);
|
|
tilesMode.toggleButton = true;
|
|
tilesMode.pressed = false;
|
|
off -= w;
|
|
|
|
@bgButton = GuiButton(bar, Alignment(Right-38, Top+4, Width=34, Height=34));
|
|
GuiSprite sprt(bgButton, Alignment().padded(4), Sprite(spritesheet::PlanetType, 2));
|
|
bgButton.toggleButton = true;
|
|
bgButton.pressed = true;
|
|
}
|
|
|
|
void show() {
|
|
tree.update();
|
|
Tab::show();
|
|
}
|
|
|
|
void hide() {
|
|
if(previous !is null)
|
|
popTab(this);
|
|
Tab::hide();
|
|
}
|
|
|
|
Color get_activeColor() {
|
|
return Color(0xdafc4eff);
|
|
}
|
|
|
|
Color get_inactiveColor() {
|
|
return Color(0xccff00ff);
|
|
}
|
|
|
|
Color get_seperatorColor() {
|
|
return Color(0x798c2bff);
|
|
}
|
|
|
|
Sprite get_icon() {
|
|
return Sprite(material::TabPlanets);
|
|
}
|
|
|
|
TabCategory get_category() {
|
|
return TC_Planets;
|
|
}
|
|
|
|
void tick(double time) override {
|
|
tree.tick(time);
|
|
if(!visible)
|
|
return;
|
|
if(!tree.isDragging && settings::bEdgePan && windowFocused && mouseOverWindow) {
|
|
if(mousePos.x <= 2) {
|
|
tree.scrollPane(+1000.0 * time, 0);
|
|
}
|
|
else if(mousePos.x >= screenSize.width - 2) {
|
|
tree.scrollPane(-1000.0 * time, 0);
|
|
}
|
|
if(mousePos.y <= 2) {
|
|
tree.scrollPane(0, +1000.0 * time);
|
|
}
|
|
else if(mousePos.y >= screenSize.height - 2) {
|
|
tree.scrollPane(0, -1000.0 * time);
|
|
}
|
|
}
|
|
}
|
|
|
|
void switchMode(PlanetModes mode) {
|
|
normalMode.pressed = mode == PM_NORMAL;
|
|
moneyMode.pressed = mode == PM_MONEY;
|
|
productionMode.pressed = mode == PM_PRODUCTION;
|
|
reqsMode.pressed = mode == PM_REQUIREMENTS;
|
|
tilesMode.pressed = mode == PM_TILES;
|
|
pressureMode.pressed = mode == PM_PRESSURE;
|
|
if(tree.mode != mode) {
|
|
tree.mode = mode;
|
|
tree.update();
|
|
}
|
|
}
|
|
|
|
bool onGuiEvent(const GuiEvent& event) {
|
|
if(event.type == GUI_Clicked) {
|
|
if(event.caller is normalMode) {
|
|
switchMode(PM_NORMAL);
|
|
return true;
|
|
}
|
|
if(event.caller is moneyMode) {
|
|
switchMode(PM_MONEY);
|
|
return true;
|
|
}
|
|
if(event.caller is productionMode) {
|
|
switchMode(PM_PRODUCTION);
|
|
return true;
|
|
}
|
|
if(event.caller is reqsMode) {
|
|
switchMode(PM_REQUIREMENTS);
|
|
return true;
|
|
}
|
|
if(event.caller is tilesMode) {
|
|
switchMode(PM_TILES);
|
|
return true;
|
|
}
|
|
if(event.caller is pressureMode) {
|
|
switchMode(PM_PRESSURE);
|
|
return true;
|
|
}
|
|
if(event.caller is bgButton) {
|
|
SHOW_PLANETS = bgButton.pressed;
|
|
return true;
|
|
}
|
|
}
|
|
return BaseGuiElement::onGuiEvent(event);
|
|
}
|
|
|
|
void draw() {
|
|
skin.draw(SS_DesignOverviewBG, SF_Normal, AbsolutePosition);
|
|
BaseGuiElement::draw();
|
|
}
|
|
};
|
|
|
|
Tab@ createPlanetsTab() {
|
|
return PlanetsTab();
|
|
}
|