Open source Star Ruler 2 source code!
This commit is contained in:
@@ -0,0 +1,562 @@
|
||||
import tabs.Tab;
|
||||
import elements.GuiButton;
|
||||
import elements.GuiProgressbar;
|
||||
import elements.GuiPanel;
|
||||
import elements.GuiMarkupText;
|
||||
import elements.GuiEmpire;
|
||||
import elements.GuiOverlay;
|
||||
import elements.GuiSkinElement;
|
||||
import elements.MarkupTooltip;
|
||||
import dialogs.QuestionDialog;
|
||||
import attitudes;
|
||||
import abilities;
|
||||
|
||||
from overlays.InfoBar import AbilityAction;
|
||||
|
||||
import tabs.tabbar;
|
||||
|
||||
class LevelMarker : BaseGuiElement {
|
||||
const AttitudeLevel@ lvl;
|
||||
Color color;
|
||||
bool reached;
|
||||
bool hovered = false;
|
||||
|
||||
LevelMarker(IGuiElement@ parent) {
|
||||
super(parent, recti(0,0, 42,70));
|
||||
noClip = true;
|
||||
auto@ tt = addLazyMarkupTooltip(this, width=300);
|
||||
tt.FollowMouse = false;
|
||||
tt.offset = vec2i(0, 5);
|
||||
}
|
||||
|
||||
string get_tooltip() {
|
||||
string tt;
|
||||
tt += format("[font=Medium]$1 $2[/font]\n", locale::LEVEL, toString(lvl.level+1));
|
||||
tt += lvl.description;
|
||||
return tt;
|
||||
}
|
||||
|
||||
void update(Attitude& att) {
|
||||
double finalProgress = att.levels[att.maxLevel].threshold;
|
||||
double pct = clamp(lvl.threshold / finalProgress, 0.0, 1.0);
|
||||
|
||||
reached = att.level >= lvl.level+1;
|
||||
position = vec2i(parent.size.x * pct - size.width / 2, 0);
|
||||
|
||||
if(reached)
|
||||
color = Color(0x000000ff).interpolate(att.type.color, 0.4);
|
||||
else
|
||||
color = Color(0x666666ff);
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& evt) {
|
||||
switch(evt.type) {
|
||||
case GUI_Mouse_Entered:
|
||||
if(evt.caller is this)
|
||||
hovered = true;
|
||||
break;
|
||||
case GUI_Mouse_Left:
|
||||
if(evt.caller is this)
|
||||
hovered = false;
|
||||
break;
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(evt);
|
||||
}
|
||||
|
||||
void draw() override {
|
||||
if(hovered) {
|
||||
drawLine(AbsolutePosition.topLeft + vec2i(size.width/2, 2),
|
||||
AbsolutePosition.topLeft + vec2i(size.width/2, size.height-35),
|
||||
Color(0xffffff40), 12);
|
||||
}
|
||||
|
||||
drawLine(AbsolutePosition.topLeft + vec2i(size.width/2, 2),
|
||||
AbsolutePosition.topLeft + vec2i(size.width/2, size.height),
|
||||
Color(0x00000080), 5);
|
||||
|
||||
drawLine(AbsolutePosition.topLeft + vec2i(size.width/2, 2),
|
||||
AbsolutePosition.topLeft + vec2i(size.width/2, size.height),
|
||||
color, 3);
|
||||
|
||||
recti iconPos = recti_area(5,size.height-32, 32,32) + AbsolutePosition.topLeft;
|
||||
if(hovered)
|
||||
drawRectangle(iconPos.padded(-4), Color(0xffffff40));
|
||||
drawRectangle(iconPos.padded(-2), Color(0x00000080));
|
||||
drawRectangle(iconPos, color);
|
||||
|
||||
lvl.icon.draw(iconPos.aspectAligned(lvl.icon.aspect));
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
class DiscardConfirm : QuestionDialogCallback {
|
||||
const AttitudeType@ type;
|
||||
|
||||
DiscardConfirm(const AttitudeType@ type) {
|
||||
@this.type = type;
|
||||
}
|
||||
|
||||
void questionCallback(QuestionDialog@ dialog, int answer) {
|
||||
if(answer == QA_Yes)
|
||||
playerEmpire.discardAttitude(type.id);
|
||||
}
|
||||
};
|
||||
|
||||
class AttitudeBox : BaseGuiElement {
|
||||
Attitude@ att;
|
||||
|
||||
GuiMarkupText@ title;
|
||||
GuiMarkupText@ progressText;
|
||||
GuiProgressbar@ bar;
|
||||
|
||||
array<LevelMarker@> markers;
|
||||
array<AbilityAction@> abilities;
|
||||
|
||||
GuiButton@ discardButton;
|
||||
GuiMarkupText@ discardText;
|
||||
|
||||
AttitudeBox(IGuiElement@ parent) {
|
||||
super(parent, recti());
|
||||
|
||||
@title = GuiMarkupText(this, Alignment(Left+12, Top+8, Right-12, Top+40));
|
||||
title.defaultFont = FT_Medium;
|
||||
title.defaultStroke = colors::Black;
|
||||
|
||||
@progressText = GuiMarkupText(this, Alignment(Left+20, Top+36, Right-12, Top+65));
|
||||
progressText.defaultColor = Color(0x888888ff);
|
||||
progressText.defaultStroke = colors::Black;
|
||||
|
||||
@bar = GuiProgressbar(this, Alignment(Left+12, Top+65, Right-220, Top+110));
|
||||
|
||||
@discardButton = GuiButton(this, Alignment(Right-140, Top+3, Right-4, Top+36));
|
||||
discardButton.color = colors::Red;
|
||||
@discardText = GuiMarkupText(discardButton, Alignment(Left, Top+6, Right, Bottom));
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) override {
|
||||
if(event.type == GUI_Clicked) {
|
||||
if(event.caller is discardButton) {
|
||||
auto@ diag = question(format(locale::ATT_CONFIRM_DISCARD, att.type.name),
|
||||
locale::DISCARD, locale::CANCEL, DiscardConfirm(att.type));
|
||||
diag.titleBox.color = Color(0xff0000ff);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void update() {
|
||||
if(att is null || att.type is null)
|
||||
return;
|
||||
updateAbsolutePosition();
|
||||
|
||||
double curProgress = att.progress;
|
||||
double nextProgress = att.levels[att.nextLevel].threshold;
|
||||
double finalProgress = att.levels[att.maxLevel].threshold;
|
||||
|
||||
//Progress data
|
||||
if(nextProgress > curProgress) {
|
||||
progressText.text = format("[color=#aaa][b]$1 $2:[/b][/color] $3",
|
||||
locale::LEVEL, toString(att.nextLevel),
|
||||
format(att.type.progress, toString(nextProgress-curProgress, 0)));
|
||||
}
|
||||
else {
|
||||
progressText.text = locale::ATT_MAX_LEVEL;
|
||||
}
|
||||
|
||||
title.text = att.type.name;
|
||||
bar.frontColor = att.type.color;
|
||||
bar.progress = curProgress / finalProgress;
|
||||
|
||||
//Level markers
|
||||
uint prevCnt = markers.length;
|
||||
uint newCnt = att.type.levels.length;
|
||||
for(uint i = newCnt; i < prevCnt; ++i)
|
||||
markers[i].remove();
|
||||
markers.length = newCnt;
|
||||
for(uint i = prevCnt; i < newCnt; ++i)
|
||||
@markers[i] = LevelMarker(bar);
|
||||
|
||||
for(uint i = 0; i < newCnt; ++i) {
|
||||
@markers[i].lvl = att.type.levels[i];
|
||||
markers[i].update(att);
|
||||
}
|
||||
|
||||
//Discarding
|
||||
int discardCost = att.getDiscardCost(playerEmpire);
|
||||
discardText.text = "[center]"+format(locale::ATT_DISCARD, toString(discardCost))+"[/center]";
|
||||
discardButton.disabled = playerEmpire.Influence < discardCost;
|
||||
|
||||
//Abilities
|
||||
prevCnt = abilities.length;
|
||||
newCnt = 0;
|
||||
|
||||
for(uint i = 0, cnt = att.allHookCount; i < cnt; ++i) {
|
||||
Ability@ abl;
|
||||
if(newCnt < prevCnt)
|
||||
@abl = abilities[newCnt].abl;
|
||||
@abl = att.allHooks[i].showAbility(att, playerEmpire, abl);
|
||||
if(abl !is null) {
|
||||
if(newCnt >= prevCnt) {
|
||||
AbilityAction act(abl, abl.type.name);
|
||||
act.independent = true;
|
||||
@act.parent = this;
|
||||
abilities.insertLast(act);
|
||||
}
|
||||
newCnt += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for(uint i = newCnt; i < prevCnt; ++i)
|
||||
abilities[i].remove();
|
||||
|
||||
int y = 62;
|
||||
if(newCnt > 1)
|
||||
y = 40;
|
||||
abilities.length = newCnt;
|
||||
for(uint i = 0, cnt = abilities.length; i < cnt; ++i) {
|
||||
auto@ btn = abilities[i];
|
||||
btn.init();
|
||||
btn.position = vec2i(size.width-184, y);
|
||||
btn.size = vec2i(172, 50);
|
||||
y += 50;
|
||||
}
|
||||
}
|
||||
|
||||
void addAbility(Ability@ abl, uint& newCnt) {
|
||||
}
|
||||
|
||||
void draw() override {
|
||||
if(att is null || att.type is null)
|
||||
return;
|
||||
skin.draw(SS_EmpireBox, SF_Normal, AbsolutePosition, att.type.color);
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
class AttitudesTab : Tab {
|
||||
array<AttitudeBox@> boxes;
|
||||
array<Attitude> attitudes;
|
||||
|
||||
GuiButton@ takeButton;
|
||||
GuiMarkupText@ takeText;
|
||||
|
||||
GuiSkinElement@ header;
|
||||
GuiEmpire@ portrait;
|
||||
GuiMarkupText@ headerText;
|
||||
|
||||
GuiPanel@ panel;
|
||||
|
||||
AttitudesTab() {
|
||||
super();
|
||||
title = locale::ATTITUDES_TAB;
|
||||
|
||||
@panel = GuiPanel(this, Alignment());
|
||||
@takeButton = GuiButton(panel, recti_area(0,0, 280,60));
|
||||
@takeText = GuiMarkupText(takeButton, Alignment(Left, Top+15, Right, Bottom));
|
||||
takeText.defaultFont = FT_Subtitle;
|
||||
|
||||
@header = GuiSkinElement(panel, Alignment(Left+0.5f-400, Top+16, Left+0.5f+400, Top+200), SS_Panel);
|
||||
@portrait = GuiEmpire(header, Alignment(Left+4, Top+4, Left+180, Bottom-4));
|
||||
|
||||
@headerText = GuiMarkupText(header, Alignment(Left+188, Top+8, Right-8, Bottom-8));
|
||||
headerText.text = locale::ATT_HEADER_DESC;
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
Color get_activeColor() {
|
||||
return Color(0x63ebdbff);
|
||||
}
|
||||
|
||||
Color get_inactiveColor() {
|
||||
return Color(0x6dd6caff);
|
||||
}
|
||||
|
||||
Color get_seperatorColor() {
|
||||
return Color(0x37837aff);
|
||||
}
|
||||
|
||||
TabCategory get_category() {
|
||||
return TC_Attitudes;
|
||||
}
|
||||
|
||||
Sprite get_icon() {
|
||||
return Sprite(material::TabAttitude);
|
||||
}
|
||||
|
||||
void tick(double time) override {
|
||||
if(!visible)
|
||||
return;
|
||||
if(playerEmpire is null || !playerEmpire.valid) {
|
||||
attitudes.length = 0;
|
||||
}
|
||||
else {
|
||||
attitudes.syncFrom(playerEmpire.getAttitudes());
|
||||
@portrait.empire = playerEmpire;
|
||||
}
|
||||
|
||||
uint prevCnt = boxes.length;
|
||||
uint newCnt = attitudes.length;
|
||||
for(uint i = newCnt; i < prevCnt; ++i)
|
||||
boxes[i].remove();
|
||||
boxes.length = newCnt;
|
||||
for(uint i = prevCnt; i < newCnt; ++i)
|
||||
@boxes[i] = AttitudeBox(panel);
|
||||
|
||||
int y = 216, h = 150;
|
||||
for(uint i = 0; i < newCnt; ++i) {
|
||||
@boxes[i].att = attitudes[i];
|
||||
@boxes[i].alignment = Alignment(Left+0.1f, Top+y, Right-0.1f, Top+y+h);
|
||||
boxes[i].update();
|
||||
y += h+8;
|
||||
}
|
||||
|
||||
bool haveTakeable = false;
|
||||
for(uint i = 0, cnt = getAttitudeTypeCount(); i < cnt; ++i) {
|
||||
auto@ att = getAttitudeType(i);
|
||||
if(att.canTake(playerEmpire)) {
|
||||
haveTakeable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(haveTakeable) {
|
||||
takeButton.visible = true;
|
||||
|
||||
int extraCost = playerEmpire.getNextAttitudeCost();
|
||||
takeButton.position = vec2i((size.width-takeButton.size.width)/2, y+20);
|
||||
takeText.text = "[center]"+format(locale::ATT_TAKE_COST, toString(extraCost))+"[/center]";
|
||||
}
|
||||
else {
|
||||
takeButton.visible = false;
|
||||
}
|
||||
|
||||
if(prevCnt != newCnt)
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.type == GUI_Clicked) {
|
||||
if(event.caller is takeButton) {
|
||||
TakeAttitudeOverlay(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_DesignOverviewBG, SF_Normal, AbsolutePosition, Color(0x6dd6caff));
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
class GuiFlatButton : GuiButton {
|
||||
bool taken = false;
|
||||
bool available = false;
|
||||
|
||||
GuiFlatButton(IGuiElement@ ParentElement, const recti& Rectangle) {
|
||||
super(ParentElement, Rectangle);
|
||||
updateAbsolutePosition();
|
||||
navigable = true;
|
||||
style = SS_NULL;
|
||||
}
|
||||
|
||||
void draw() {
|
||||
if(!available) {
|
||||
if(flags & SF_Active != 0)
|
||||
drawRectangle(AbsolutePosition, color.interpolate(Color(0x00000080), 0.6));
|
||||
else
|
||||
drawRectangle(AbsolutePosition, Color(0x00000040));
|
||||
}
|
||||
else {
|
||||
if(flags & SF_Active != 0)
|
||||
drawRectangle(AbsolutePosition, color);
|
||||
else
|
||||
drawRectangle(AbsolutePosition, Color(0x00000060));
|
||||
}
|
||||
|
||||
if(flags & SF_Hovered != 0)
|
||||
drawRectangle(AbsolutePosition, Color(0xffffff20));
|
||||
|
||||
if(!available) {
|
||||
drawLine(AbsolutePosition.topLeft+vec2i(size.width/5, size.height/2+3),
|
||||
AbsolutePosition.botRight-vec2i(size.width/5, size.height/2-3),
|
||||
Color(0x444444ff), 3);
|
||||
}
|
||||
|
||||
if(taken) {
|
||||
vec2i tl = AbsolutePosition.topLeft + vec2i(2,2);
|
||||
vec2i br = AbsolutePosition.botRight - vec2i(2,2);
|
||||
|
||||
drawLine(vec2i(tl.x,tl.y), vec2i(br.x,tl.y), color.interpolate(colors::White, 0.2), 3);
|
||||
drawLine(vec2i(tl.x,tl.y), vec2i(tl.x,br.y), color.interpolate(colors::White, 0.2), 3);
|
||||
|
||||
drawLine(vec2i(br.x,tl.y), vec2i(br.x,br.y), color.interpolate(colors::White, 0.2), 3);
|
||||
drawLine(vec2i(tl.x,br.y), vec2i(br.x,br.y), color.interpolate(colors::White, 0.2), 3);
|
||||
|
||||
|
||||
Color acolor = color;
|
||||
acolor.a = 0x50;
|
||||
drawRectangle(AbsolutePosition, acolor);
|
||||
}
|
||||
GuiButton::draw();
|
||||
}
|
||||
}
|
||||
|
||||
class TakeAttitudeOverlay : GuiOverlay {
|
||||
GuiSkinElement@ bg;
|
||||
GuiPanel@ leftPanel;
|
||||
GuiPanel@ rightPanel;
|
||||
GuiMarkupText@ text;
|
||||
|
||||
array<const AttitudeType@> attitudes;
|
||||
array<GuiButton@> buttons;
|
||||
const AttitudeType@ selected;
|
||||
GuiButton@ takeButton;
|
||||
GuiMarkupText@ takeText;
|
||||
GuiMarkupText@ takeCaption;
|
||||
|
||||
TakeAttitudeOverlay(IGuiElement@ parent) {
|
||||
super(parent);
|
||||
closeSelf = false;
|
||||
|
||||
for(uint i = 0, cnt = getAttitudeTypeCount(); i < cnt; ++i) {
|
||||
auto@ att = getAttitudeType(i);
|
||||
/*if(att.canTake(playerEmpire))*/
|
||||
attitudes.insertLast(att);
|
||||
}
|
||||
attitudes.sortAsc();
|
||||
|
||||
int h = 16 + clamp(attitudes.length/2 * 50, 370, 600) + 50;
|
||||
|
||||
@bg = GuiSkinElement(this, Alignment(Left+0.5f-425, Top+0.5f-(h/2), Left+0.5f+425, Top+0.5f+(h/2)), SS_Panel);
|
||||
@leftPanel = GuiPanel(bg, Alignment(Left, Top, Left+300, Bottom-50));
|
||||
@rightPanel = GuiPanel(bg, Alignment(Left+300, Top, Right, Bottom-50));
|
||||
@text = GuiMarkupText(rightPanel, recti_area(8,8, 550-16, 100));
|
||||
text.flexHeight = true;
|
||||
@takeButton = GuiButton(bg, Alignment(Left+0.5f-140, Bottom-50, Left+0.5f+140, Bottom-8));
|
||||
@takeText = GuiMarkupText(takeButton, Alignment(Left, Top+10, Right, Bottom));
|
||||
@takeCaption = GuiMarkupText(bg, Alignment(Left+0.5f-140, Bottom-40, Left+0.5f+140, Bottom-8));
|
||||
takeCaption.defaultFont = FT_Bold;
|
||||
takeCaption.defaultColor = Color(0xaa8080ff);
|
||||
takeCaption.visible = false;
|
||||
|
||||
int y = 8;
|
||||
uint sel = uint(-1);
|
||||
for(uint i = 0, cnt = attitudes.length; i < cnt; ++i) {
|
||||
int x = 8;
|
||||
if(i%2 != 0)
|
||||
x = 150;
|
||||
|
||||
auto@ att = attitudes[i];
|
||||
|
||||
GuiFlatButton btn(leftPanel, recti_area(x,y+2, 140, 46));
|
||||
btn.toggleButton = true;
|
||||
btn.pressed = (i == 0);
|
||||
btn.text = att.name;
|
||||
btn.color = att.color.interpolate(colors::Black, 0.75);
|
||||
|
||||
if(playerEmpire.hasAttitude(att.id)) {
|
||||
btn.taken = true;
|
||||
btn.available = true;
|
||||
btn.textColor = colors::White;
|
||||
}
|
||||
else if(!att.canTake(playerEmpire)) {
|
||||
btn.taken = false;
|
||||
btn.available = false;
|
||||
btn.textColor = Color(0x666666ff);
|
||||
}
|
||||
else {
|
||||
btn.taken = false;
|
||||
btn.available = true;
|
||||
btn.textColor = colors::White;
|
||||
|
||||
if(sel == uint(-1))
|
||||
sel = i;
|
||||
}
|
||||
|
||||
if(i%2 != 0)
|
||||
y += 50;
|
||||
|
||||
buttons.insertLast(btn);
|
||||
}
|
||||
|
||||
if(sel == uint(-1))
|
||||
sel = 0;
|
||||
if(attitudes.length != 0)
|
||||
select(attitudes[sel]);
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
void select(const AttitudeType@ type) {
|
||||
for(uint j = 0, cnt = buttons.length; j < cnt; ++j)
|
||||
buttons[j].pressed = (type is attitudes[j]);
|
||||
@selected = type;
|
||||
|
||||
string desc = format("[font=Medium][color=$1][stroke=#000]$2[/stroke][/color][/font]\n[vspace=6/]",
|
||||
toString(type.color.interpolate(colors::White, 0.1)), type.name);
|
||||
|
||||
for(uint i = 0, cnt = type.levels.length; i < cnt; ++i) {
|
||||
auto@ lvl = type.levels[i];
|
||||
desc += format("[img=$4;32][color=#aaa][b]$1 $2:[/b][/color] [color=#888]$3[/color]\n",
|
||||
locale::LEVEL, toString(i+1),
|
||||
format(type.progress, toString(lvl.threshold, 0)),
|
||||
getSpriteDesc(lvl.icon));
|
||||
desc += "[offset=20]"+lvl.description+"[/offset][/img]";
|
||||
desc += "\n[vspace=2/]";
|
||||
}
|
||||
|
||||
takeButton.color = type.color;
|
||||
|
||||
int extraCost = playerEmpire.getNextAttitudeCost();
|
||||
takeText.text = "[center]"+format(locale::ATT_TAKE_ATT, type.name, toString(extraCost))+"[/center]";
|
||||
takeButton.disabled = playerEmpire.Influence < extraCost;
|
||||
|
||||
takeButton.visible = selected.canTake(playerEmpire);
|
||||
takeCaption.visible = !takeButton.visible;
|
||||
|
||||
if(takeCaption.visible) {
|
||||
if(playerEmpire.hasAttitude(selected.id))
|
||||
takeCaption.text = "[center]"+locale::ATT_HAVE_ATTITUDE+"[/center]";
|
||||
else
|
||||
takeCaption.text = "[center]"+locale::ATT_CANNOT_ATTITUDE+"[/center]";
|
||||
}
|
||||
|
||||
text.text = desc;
|
||||
rightPanel.updateAbsolutePosition();
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) override {
|
||||
if(event.type == GUI_Clicked) {
|
||||
for(uint i = 0, cnt = buttons.length; i < cnt; ++i) {
|
||||
if(buttons[i] is event.caller) {
|
||||
select(attitudes[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(event.caller is takeButton) {
|
||||
playerEmpire.takeAttitude(selected.id);
|
||||
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return GuiOverlay::onGuiEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
Tab@ createAttitudesTab() {
|
||||
return AttitudesTab();
|
||||
}
|
||||
|
||||
void resetTabs() {
|
||||
for(uint i = 0, cnt = tabs.length; i < cnt; ++i) {
|
||||
if(tabs[i].category == TC_Attitudes)
|
||||
browseTab(tabs[i], createAttitudesTab());
|
||||
}
|
||||
}
|
||||
|
||||
void postReload(Message& msg) {
|
||||
resetTabs();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,688 @@
|
||||
import tabs.Tab;
|
||||
import elements.GuiPanel;
|
||||
import elements.GuiText;
|
||||
import elements.GuiTextbox;
|
||||
import elements.GuiBlueprint;
|
||||
import elements.MarkupTooltip;
|
||||
import elements.GuiSprite;
|
||||
import dialogs.InputDialog;
|
||||
from dialogs.DesignImportDialog import DesignImportDialog, addDialog;
|
||||
import resources;
|
||||
import tile_resources;
|
||||
import icons;
|
||||
import heralds_icons;
|
||||
#include "dialogs/include/UniqueDialogs.as"
|
||||
|
||||
from tabs.tabbar import newTab, browseTab, switchToTab;
|
||||
from tabs.DesignEditorTab import createDesignEditorTab, loadDesignEditor;
|
||||
|
||||
const uint D_EL_WIDTH = 325;
|
||||
const uint D_EL_HEIGHT = 200;
|
||||
const uint D_EL_SPACING = 8;
|
||||
const uint D_ICON_WIDTH = 38;
|
||||
const uint D_ICON_HEIGHT = 28;
|
||||
const uint D_ICON_SPACING = 4;
|
||||
|
||||
const Color[] CLASS_COLORS = {
|
||||
Color(0x00c3ffff),
|
||||
Color(0xcf4cffff),
|
||||
Color(0xbaff4cff),
|
||||
Color(0xff6c00ff),
|
||||
Color(0xebc0ffff),
|
||||
Color(0xc7af99ff),
|
||||
Color(0xf49bcfff),
|
||||
Color(0x00ff9cff),
|
||||
Color(0x00deffff)
|
||||
};
|
||||
|
||||
enum Dialogs {
|
||||
D_CreateClass,
|
||||
};
|
||||
|
||||
class CreateClass : InputDialogCallback {
|
||||
DesignOverview@ overview;
|
||||
|
||||
CreateClass(DesignOverview@ ovw) {
|
||||
@overview = ovw;
|
||||
}
|
||||
|
||||
void inputCallback(InputDialog@ dialog, bool accepted) {
|
||||
if(accepted) {
|
||||
string name = dialog.getTextInput(0);
|
||||
if(name.length != 0)
|
||||
playerEmpire.getDesignClass(name);
|
||||
|
||||
overview.refresh(playerEmpire);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DesignImporter : DesignImportDialog {
|
||||
DesignImporter(IGuiElement@ bind) {
|
||||
super(bind);
|
||||
}
|
||||
|
||||
bool showDesign(const Design@ dsg) {
|
||||
cast<DesignOverview>(elem).showDesignEditor(dsg);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class DesignOverview : Tab {
|
||||
GuiPanel@ clsPanel;
|
||||
DesignClassElement@[] classes;
|
||||
DesignElement@ selected;
|
||||
|
||||
GuiButton@ createClassButton;
|
||||
GuiButton@ importButton;
|
||||
GuiButton@ showObsoleteButton;
|
||||
|
||||
uint prevDesignCount = 0;
|
||||
|
||||
DesignOverview() {
|
||||
super();
|
||||
|
||||
@clsPanel = GuiPanel(this, recti());
|
||||
@clsPanel.alignment = Alignment(Left+8, Top, Right-8, Bottom);
|
||||
|
||||
@createClassButton = GuiButton(clsPanel, recti(0, 0, 195, 47),
|
||||
locale::CREATE_DESIGN_CLASS);
|
||||
createClassButton.font = FT_Medium;
|
||||
createClassButton.buttonIcon = icons::Create.colorized(Color(0x8888ffff));
|
||||
|
||||
@importButton = GuiButton(clsPanel, recti(0, 0, 195, 47),
|
||||
locale::IMPORT_DESIGNS);
|
||||
importButton.font = FT_Medium;
|
||||
importButton.buttonIcon = icons::Import;
|
||||
|
||||
@showObsoleteButton = GuiButton(clsPanel, recti(0, 0, 195, 47),
|
||||
locale::SHOW_OBSOLETE);
|
||||
showObsoleteButton.tooltip = locale::TT_SHOW_OBSOLETE;
|
||||
showObsoleteButton.font = FT_Medium;
|
||||
showObsoleteButton.buttonIcon = icons::Obsolete;
|
||||
showObsoleteButton.toggleButton = true;
|
||||
|
||||
title = locale::DESIGNS;
|
||||
refresh(playerEmpire);
|
||||
}
|
||||
|
||||
Color get_activeColor() {
|
||||
return Color(0x83cfffff);
|
||||
}
|
||||
|
||||
Color get_inactiveColor() {
|
||||
return Color(0x009cffff);
|
||||
}
|
||||
|
||||
Color get_seperatorColor() {
|
||||
return Color(0x49738dff);
|
||||
}
|
||||
|
||||
TabCategory get_category() {
|
||||
return TC_Designs;
|
||||
}
|
||||
|
||||
Sprite get_icon() {
|
||||
return Sprite(material::TabDesigns);
|
||||
}
|
||||
|
||||
void showDesignEditor(const Design@ dsg) {
|
||||
//Keep a design editor tab in previous so we
|
||||
//don't need to create it all the time
|
||||
if(previous is null) {
|
||||
@previous = createDesignEditorTab();
|
||||
previous.locked = locked;
|
||||
}
|
||||
loadDesignEditor(previous, dsg.mostUpdated());
|
||||
browseTab(this, previous, true);
|
||||
}
|
||||
|
||||
void showDesignEditor(const Hull@ hull, const DesignClass@ cls, const string& name, uint size) {
|
||||
//Keep a design editor tab in previous so we
|
||||
//don't need to create it all the time
|
||||
if(previous is null) {
|
||||
@previous = createDesignEditorTab();
|
||||
previous.locked = locked;
|
||||
}
|
||||
loadDesignEditor(previous, hull, cls, name, size);
|
||||
browseTab(this, previous, true);
|
||||
}
|
||||
|
||||
void refresh(Empire@ emp) {
|
||||
//Keep designs locked while we're doing this
|
||||
ReadLock lock(emp.designMutex);
|
||||
prevDesignCount = emp.designCount;
|
||||
|
||||
const Design@ sel;
|
||||
if(selected !is null) {
|
||||
@sel = selected.dsg;
|
||||
selected.selected = false;
|
||||
@selected = null;
|
||||
}
|
||||
|
||||
//Create panel with list of designs in classes
|
||||
uint designCount = emp.designClassCount;
|
||||
for(uint i = designCount; i < classes.length; ++i)
|
||||
classes[i].remove();
|
||||
|
||||
classes.length = designCount;
|
||||
uint y = 6;
|
||||
for(uint i = 0; i < designCount; ++i) {
|
||||
const DesignClass@ cls = emp.getDesignClass(i);
|
||||
|
||||
//Make a new design class or repurpose one
|
||||
DesignClassElement@ el;
|
||||
if(classes[i] !is null) {
|
||||
@el = classes[i];
|
||||
}
|
||||
else {
|
||||
@el = DesignClassElement(clsPanel, this);
|
||||
@classes[i] = el;
|
||||
}
|
||||
|
||||
//Update the design class list
|
||||
el.refresh(cls);
|
||||
if(el.visible) {
|
||||
el.position = vec2i(0, y);
|
||||
y += el.size.height;
|
||||
}
|
||||
}
|
||||
|
||||
if(sel !is null) {
|
||||
for(uint i = 0, cnt = classes.length; i < cnt; ++i) {
|
||||
for(uint n = 0, ncnt = classes[i].designs.length; n < ncnt; ++n) {
|
||||
if(classes[i].designs[n].dsg is sel) {
|
||||
@selected = classes[i].designs[n];
|
||||
selected.selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createClassButton.position = vec2i(0, y + 4);
|
||||
importButton.position = vec2i(205, y + 4);
|
||||
if(!importButton.visible)
|
||||
showObsoleteButton.position = vec2i(205, y + 4);
|
||||
else
|
||||
showObsoleteButton.position = vec2i(410, y + 4);
|
||||
clsPanel.updateAbsolutePosition();
|
||||
gui_root.updateHover();
|
||||
}
|
||||
|
||||
void show() {
|
||||
visible = true;
|
||||
refresh(playerEmpire);
|
||||
}
|
||||
|
||||
void hide() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
double timer = 0.0;
|
||||
void tick(double time) {
|
||||
if(visible) {
|
||||
timer += time;
|
||||
if(playerEmpire.designCount != prevDesignCount || timer >= 1.5) {
|
||||
refresh(playerEmpire);
|
||||
timer = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateAbsolutePosition() {
|
||||
refresh(playerEmpire);
|
||||
BaseGuiElement::updateAbsolutePosition();
|
||||
}
|
||||
|
||||
void draw() {
|
||||
//Draw the global background
|
||||
skin.draw(SS_DesignOverviewBG, SF_Normal, AbsolutePosition);
|
||||
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
|
||||
void selectDesign(const Design@ dsg) {
|
||||
uint clsCnt = classes.length;
|
||||
for(uint i = 0; i < clsCnt; ++i) {
|
||||
DesignClassElement@ clsEl = classes[i];
|
||||
|
||||
uint dsgCnt = clsEl.designs.length;
|
||||
for(uint j = 0; j < dsgCnt; ++j) {
|
||||
DesignElement@ dsgEl = clsEl.designs[j];
|
||||
if(dsgEl.dsg !is dsg) {
|
||||
dsgEl.deselect();
|
||||
}
|
||||
else {
|
||||
dsgEl.select();
|
||||
@selected = dsgEl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clickDesign(const Design@ dsg, int button = 0) {
|
||||
if(button == 2 || (button == 0 && ctrlKey)) {
|
||||
Tab@ tab = createDesignEditorTab(dsg);
|
||||
tab.locked = locked;
|
||||
newTab(this, tab);
|
||||
|
||||
if(shiftKey)
|
||||
switchToTab(tab);
|
||||
}
|
||||
else if(button == 0) {
|
||||
showDesignEditor(dsg);
|
||||
}
|
||||
}
|
||||
|
||||
void scrollToClass(const DesignClass@ cls) {
|
||||
uint clsCnt = classes.length;
|
||||
for(uint i = 0; i < clsCnt; ++i) {
|
||||
DesignClassElement@ clsEl = classes[i];
|
||||
|
||||
if(clsEl.cls !is cls)
|
||||
continue;
|
||||
|
||||
clsPanel.vertPosition = clsEl.position.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void promptCreateClass() {
|
||||
if(focusDialog(D_CreateClass))
|
||||
return;
|
||||
|
||||
InputDialog@ dialog = InputDialog(CreateClass(this), this);
|
||||
dialog.addTitle(locale::CREATE_DESIGN_CLASS);
|
||||
dialog.accept.text = locale::CREATE;
|
||||
dialog.addTextInput(locale::NAME, "");
|
||||
|
||||
addDialog(D_CreateClass, dialog);
|
||||
dialog.focusInput();
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.caller is this) {
|
||||
}
|
||||
else if(event.type == GUI_Clicked) {
|
||||
if(event.caller is createClassButton) {
|
||||
promptCreateClass();
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is importButton) {
|
||||
DesignImporter diag(this);
|
||||
addDialog(diag);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is showObsoleteButton) {
|
||||
refresh(playerEmpire);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
uint clsCnt = classes.length;
|
||||
for(uint i = 0; i < clsCnt; ++i) {
|
||||
DesignClassElement@ clsEl = classes[i];
|
||||
|
||||
uint dsgCnt = clsEl.designs.length;
|
||||
for(uint j = 0; j < dsgCnt; ++j) {
|
||||
DesignElement@ dsgEl = clsEl.designs[j];
|
||||
|
||||
if(event.caller is dsgEl) {
|
||||
clickDesign(dsgEl.dsg, event.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
class DesignClassElement : BaseGuiElement {
|
||||
DesignOverview@ overview;
|
||||
const DesignClass@ cls;
|
||||
DesignElement@[] designs;
|
||||
GuiButton@ createButton;
|
||||
|
||||
DesignClassElement(BaseGuiElement@ pnl, DesignOverview@ ovw) {
|
||||
@overview = ovw;
|
||||
super(pnl, recti());
|
||||
|
||||
@createButton = GuiButton(this,
|
||||
recti(0, 0, int(D_EL_WIDTH * 0.6),
|
||||
int(D_EL_HEIGHT * 0.3)),
|
||||
locale::CREATE_DESIGN);
|
||||
createButton.buttonIcon = icons::Create;
|
||||
createButton.font = FT_Medium;
|
||||
}
|
||||
|
||||
array<DesignSorter> sorter;
|
||||
void refresh(const DesignClass@ Cls) {
|
||||
@cls = Cls;
|
||||
|
||||
int width = parent.size.width - 20;
|
||||
int perRow = (width - 16) / (D_EL_WIDTH + D_EL_SPACING);
|
||||
|
||||
if(perRow == 0)
|
||||
return;
|
||||
|
||||
uint cnt = cls.designCount;
|
||||
for(uint i = cnt; i < designs.length; ++i)
|
||||
designs[i].remove();
|
||||
|
||||
//Sort designs
|
||||
sorter.length = cnt;
|
||||
for(uint i = 0; i < cnt; ++i)
|
||||
@sorter[i].dsg = cls.designs[i];
|
||||
sorter.sortDesc();
|
||||
|
||||
designs.length = cnt;
|
||||
uint index = 0;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
const Design@ dsg = sorter[i].dsg;
|
||||
if(!overview.showObsoleteButton.pressed && dsg.obsolete)
|
||||
continue;
|
||||
|
||||
//Make a new design class or repurpose one
|
||||
DesignElement@ el;
|
||||
if(designs[index] !is null) {
|
||||
@el = designs[index];
|
||||
}
|
||||
else {
|
||||
@el = DesignElement(this, overview);
|
||||
@designs[index] = el;
|
||||
}
|
||||
|
||||
//Update the design summary
|
||||
el.refresh(dsg);
|
||||
++index;
|
||||
}
|
||||
for(uint i = index; i < cnt; ++i) {
|
||||
if(designs[i] !is null)
|
||||
designs[i].remove();
|
||||
}
|
||||
|
||||
designs.length = index;
|
||||
cnt = index;
|
||||
uint itemCount = cnt + 1;
|
||||
int height = 40 + (itemCount / perRow) * (D_EL_HEIGHT + D_EL_SPACING);
|
||||
if(itemCount % perRow != 0 || cls.designCount == 0)
|
||||
height += D_EL_HEIGHT + D_EL_SPACING;
|
||||
|
||||
size = vec2i(width, height);
|
||||
|
||||
//Position them
|
||||
int x = 8, y = 37;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
DesignElement@ el = designs[i];
|
||||
el.position = vec2i(x, y);
|
||||
|
||||
x += D_EL_WIDTH + D_EL_SPACING;
|
||||
if(x + D_EL_WIDTH + D_EL_SPACING > width) {
|
||||
x = 8;
|
||||
y += D_EL_HEIGHT + D_EL_SPACING;
|
||||
}
|
||||
}
|
||||
|
||||
createButton.position = vec2i(
|
||||
x + D_EL_WIDTH * 0.2,
|
||||
y + D_EL_HEIGHT * 0.4);
|
||||
visible = designs.length != 0 || cls.designCount == 0;
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.caller is createButton && event.type == GUI_Clicked) {
|
||||
overview.refresh(playerEmpire);
|
||||
overview.showDesignEditor(null, cls, "", 1.0);
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
if(cls is null)
|
||||
return;
|
||||
|
||||
skin.draw(SS_DesignClass, SF_Normal,
|
||||
recti(AbsolutePosition.topLeft,
|
||||
AbsolutePosition.botRight - vec2i(6, 6)));
|
||||
|
||||
skin.draw(SS_DesignClassHeader, SF_Normal,
|
||||
recti_area(AbsolutePosition.topLeft + vec2i(1, 1),
|
||||
vec2i(AbsolutePosition.size.width-9, 30)),
|
||||
CLASS_COLORS[cls.id % CLASS_COLORS.length]);
|
||||
skin.draw(FT_Medium, AbsolutePosition.topLeft + vec2i(12, 4), cls.name);
|
||||
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
class DesignSorter {
|
||||
const Design@ dsg;
|
||||
|
||||
DesignSorter(const Design@ Dsg) {
|
||||
@dsg = Dsg;
|
||||
}
|
||||
|
||||
DesignSorter() {
|
||||
}
|
||||
|
||||
int opCmp(const DesignSorter@ other) const {
|
||||
if(dsg.size > other.dsg.size)
|
||||
return 1;
|
||||
if(dsg.size < other.dsg.size)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const string str_Support = "Support";
|
||||
class DesignElement : BaseGuiElement {
|
||||
DesignOverview@ overview;
|
||||
GuiBlueprint@ bpview;
|
||||
const Design@ dsg;
|
||||
GuiText@ title;
|
||||
GuiText@ sizeBox;
|
||||
GuiText@ moneyBox;
|
||||
GuiText@ laborBox;
|
||||
GuiSprite@ supportIcon;
|
||||
GuiSprite@ satelliteIcon;
|
||||
GuiSprite@ flagshipIcon;
|
||||
GuiSprite@ stationIcon;
|
||||
|
||||
GuiButton@ editButton;
|
||||
GuiButton@ obsoleteButton;
|
||||
|
||||
bool hovered;
|
||||
bool selected;
|
||||
bool pressed;
|
||||
|
||||
DesignElement(BaseGuiElement@ pnl, DesignOverview@ ovw) {
|
||||
super(pnl, recti(0, 0, D_EL_WIDTH, D_EL_HEIGHT));
|
||||
|
||||
@overview = ovw;
|
||||
|
||||
hovered = false;
|
||||
selected = false;
|
||||
pressed = false;
|
||||
|
||||
@bpview = GuiBlueprint(this, recti());
|
||||
@bpview.alignment = Alignment(Left+2, Top+28, Left+1.0f-2, Bottom-28);
|
||||
bpview.displayHovered = false;
|
||||
|
||||
@editButton = GuiButton(this, Alignment(Right-40, Top+38, Width=34, Height=34));
|
||||
editButton.style = SS_IconButton;
|
||||
editButton.setIcon(icons::Edit);
|
||||
setMarkupTooltip(editButton, locale::TT_EDIT_DESIGN);
|
||||
|
||||
@obsoleteButton = GuiButton(this, Alignment(Right-40, Top+76, Width=34, Height=34));
|
||||
obsoleteButton.style = SS_IconButton;
|
||||
obsoleteButton.setIcon(icons::Obsolete);
|
||||
setMarkupTooltip(obsoleteButton, locale::TT_OBSOLETE_DESIGN);
|
||||
|
||||
@title = GuiText(this, Alignment(Left+8, Top+3, Right-100, Top+30));
|
||||
title.font = FT_Subtitle;
|
||||
title.stroke = colors::Black;
|
||||
|
||||
@sizeBox = GuiText(this, Alignment(Right-150, Top+3, Right-10, Top+30));
|
||||
sizeBox.horizAlign = 1.0;
|
||||
sizeBox.font = FT_Subtitle;
|
||||
sizeBox.stroke = colors::Black;
|
||||
|
||||
@moneyBox = GuiText(this, Alignment(Left+30, Bottom-31, Right, Bottom-4));
|
||||
moneyBox.stroke = colors::Black;
|
||||
|
||||
@laborBox = GuiText(this, Alignment(Left+0.5f, Bottom-31, Right-30, Bottom-4));
|
||||
laborBox.horizAlign = 1.0;
|
||||
laborBox.stroke = colors::Black;
|
||||
|
||||
@supportIcon = GuiSprite(this, Alignment(Left+8, Top+38, Width=30, Height=30));
|
||||
supportIcon.desc = icons::ManageSupports;
|
||||
setMarkupTooltip(supportIcon, locale::TT_SUPPORT_DESIGN);
|
||||
supportIcon.visible = false;
|
||||
|
||||
@satelliteIcon = GuiSprite(this, Alignment(Left+10, Top+38, Width=36, Height=36));
|
||||
satelliteIcon.desc = icons::Satellite;
|
||||
setMarkupTooltip(satelliteIcon, locale::TT_SATELLITE_DESIGN);
|
||||
satelliteIcon.visible = false;
|
||||
|
||||
@flagshipIcon = GuiSprite(this, Alignment(Left+8, Top+38, Width=30, Height=30));
|
||||
flagshipIcon.desc = Sprite(spritesheet::AttributeIcons, 1);
|
||||
flagshipIcon.color = Color(0x00e5f7ff);
|
||||
setMarkupTooltip(flagshipIcon, locale::TT_FLAGSHIP_DESIGN);
|
||||
flagshipIcon.visible = false;
|
||||
|
||||
@stationIcon = GuiSprite(this, Alignment(Left+8, Top+38, Width=30, Height=30));
|
||||
stationIcon.desc = Sprite(spritesheet::GuiOrbitalIcons, 0);
|
||||
stationIcon.color = Color(0x00e5f7ff);
|
||||
setMarkupTooltip(stationIcon, locale::TT_STATION_DESIGN);
|
||||
stationIcon.visible = false;
|
||||
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
void select() {
|
||||
selected = true;
|
||||
}
|
||||
|
||||
void deselect() {
|
||||
selected = false;
|
||||
}
|
||||
|
||||
void refresh(const Design@ Dsg) {
|
||||
@dsg = Dsg;
|
||||
@bpview.hull = dsg.hull;
|
||||
@bpview.design = dsg;
|
||||
|
||||
title.text = dsg.name;
|
||||
|
||||
sizeBox.text = format(locale::DESIGN_SIZE, toString(dsg.size, 0));
|
||||
|
||||
const Font@ fnt = skin.getFont(FT_Subtitle);
|
||||
@fnt = skin.getFont(FT_Normal);
|
||||
|
||||
int build = 0, maintain = 0;
|
||||
double labor = 0;
|
||||
getBuildCost(dsg, build, maintain, labor);
|
||||
|
||||
moneyBox.text = formatMoney(build, maintain);
|
||||
laborBox.text = standardize(labor);
|
||||
|
||||
supportIcon.visible = dsg.hasTag(ST_SupportShip);
|
||||
satelliteIcon.visible = dsg.hasTag(ST_Satellite);
|
||||
stationIcon.visible = !supportIcon.visible && dsg.hasTag(ST_Station);
|
||||
flagshipIcon.visible = !supportIcon.visible && !stationIcon.visible && !satelliteIcon.visible;
|
||||
|
||||
if(dsg.obsolete) {
|
||||
obsoleteButton.setIcon(icons::Unobsolete);
|
||||
setMarkupTooltip(obsoleteButton, locale::TT_UNOBSOLETE_DESIGN);
|
||||
}
|
||||
else {
|
||||
obsoleteButton.setIcon(icons::Obsolete);
|
||||
setMarkupTooltip(obsoleteButton, locale::TT_OBSOLETE_DESIGN);
|
||||
}
|
||||
}
|
||||
|
||||
void draw() {
|
||||
if(dsg is null)
|
||||
return;
|
||||
|
||||
uint flags = SF_Normal;
|
||||
if(selected)
|
||||
flags |= SF_Active;
|
||||
if(hovered)
|
||||
flags |= SF_Hovered;
|
||||
|
||||
skin.draw(SS_DesignSummary, flags, AbsolutePosition, dsg.color);
|
||||
skin.draw(SS_FullTitle, flags, recti_area(AbsolutePosition.topLeft+vec2i(1,0),
|
||||
vec2i(AbsolutePosition.size.width-3,33)), dsg.color);
|
||||
if(hovered)
|
||||
skin.draw(SS_SubtleGlow, SF_Normal, AbsolutePosition, dsg.color);
|
||||
|
||||
BaseGuiElement::draw();
|
||||
|
||||
//Draw cost
|
||||
const Font@ normal = skin.getFont(FT_Normal);
|
||||
recti pos = recti_area(vec2i(AbsolutePosition.topLeft.x+4,
|
||||
AbsolutePosition.botRight.y-31), vec2i(24, 24));
|
||||
getTileResourceSprite(TR_Money).draw(pos);
|
||||
|
||||
//Draw labor
|
||||
pos = recti_area(vec2i(AbsolutePosition.botRight.x-32,
|
||||
AbsolutePosition.botRight.y-31), vec2i(24, 24));
|
||||
getTileResourceSprite(TR_Labor).draw(pos + vec2i(4, 0));
|
||||
}
|
||||
|
||||
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) override {
|
||||
if(cast<GuiButton>(source) is null) {
|
||||
switch(event.type) {
|
||||
case MET_Button_Down:
|
||||
pressed = true;
|
||||
return true;
|
||||
case MET_Button_Up:
|
||||
if(hovered && pressed) {
|
||||
GuiEvent evt;
|
||||
evt.type = GUI_Clicked;
|
||||
evt.value = event.button;
|
||||
@evt.caller = this;
|
||||
onGuiEvent(evt);
|
||||
pressed = false;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onMouseEvent(event, source);
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) override {
|
||||
if(event.type == GUI_Clicked) {
|
||||
if(event.caller is editButton) {
|
||||
emitClicked(event.value);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is obsoleteButton) {
|
||||
bool value = !dsg.obsolete;
|
||||
markDesignObsolete(dsg, value);
|
||||
dsg.setObsolete(value);
|
||||
overview.refresh(playerEmpire);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(event.caller is this) {
|
||||
switch(event.type) {
|
||||
case GUI_Mouse_Entered:
|
||||
hovered = true;
|
||||
break;
|
||||
case GUI_Mouse_Left:
|
||||
hovered = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
Tab@ createDesignOverviewTab() {
|
||||
return DesignOverview();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,624 @@
|
||||
import elements.BaseGuiElement;
|
||||
import elements.GuiText;
|
||||
import elements.GuiTextbox;
|
||||
import elements.GuiButton;
|
||||
import elements.GuiSprite;
|
||||
import elements.GuiMarkupText;
|
||||
import elements.GuiContextMenu;
|
||||
import elements.GuiProgressbar;
|
||||
import elements.MarkupTooltip;
|
||||
import targeting.ObjectTarget;
|
||||
import resources;
|
||||
import research;
|
||||
import icons;
|
||||
#include "include/resource_constants.as"
|
||||
|
||||
import tabs.tabbar;
|
||||
from tabs.ResearchTab import createResearchTab;
|
||||
from tabs.DiplomacyTab import createDiplomacyTab;
|
||||
|
||||
const double UPDATE_INTERVAL = 0.05;
|
||||
|
||||
class ResourceDisplay : BaseGuiElement {
|
||||
Color color;
|
||||
GuiSprite@ icon;
|
||||
BaseGuiElement@ value;
|
||||
int padding = 4;
|
||||
MarkupTooltip@ ttip;
|
||||
|
||||
GuiMarkupText@ upperText;
|
||||
GuiMarkupText@ lowerText;
|
||||
|
||||
ResourceDisplay(IGuiElement@ parent, Alignment@ align) {
|
||||
super(parent, align);
|
||||
updateAbsolutePosition();
|
||||
@value = BaseGuiElement(this, recti());
|
||||
|
||||
@ttip = MarkupTooltip("", 320, 0.5f, true, false);
|
||||
ttip.StaticPosition = true;
|
||||
ttip.Lazy = true;
|
||||
ttip.LazyUpdate = true;
|
||||
@tooltipObject = ttip;
|
||||
}
|
||||
|
||||
void addIcon(const Sprite& sprt) {
|
||||
@icon = GuiSprite(this, recti_area(vec2i(), sprt.size));
|
||||
icon.desc = sprt;
|
||||
}
|
||||
|
||||
void addTexts() {
|
||||
@upperText = GuiMarkupText(value, recti());
|
||||
upperText.defaultFont = FT_Medium;
|
||||
upperText.memo = true;
|
||||
@lowerText = GuiMarkupText(value, recti());
|
||||
lowerText.memo = true;
|
||||
lowerText.defaultColor = Color(0xaaaaaaff);
|
||||
}
|
||||
|
||||
int get_baseValueWidth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void update() {
|
||||
//Center the elements
|
||||
int width = 0;
|
||||
if(icon !is null)
|
||||
width += icon.size.width + padding;
|
||||
|
||||
if(value !is null) {
|
||||
int valueWidth = this.baseValueWidth;
|
||||
if(upperText !is null) {
|
||||
valueWidth = max(valueWidth, upperText.textWidth);
|
||||
upperText.position = vec2i(0, 1);
|
||||
upperText.size = vec2i(300, size.height/2);
|
||||
}
|
||||
if(lowerText !is null) {
|
||||
valueWidth = max(valueWidth, lowerText.textWidth);
|
||||
lowerText.position = vec2i(2, size.height/2-1);
|
||||
lowerText.size = vec2i(300, size.height/2);
|
||||
}
|
||||
width += valueWidth;
|
||||
value.size = vec2i(valueWidth+padding, size.height);
|
||||
}
|
||||
|
||||
int pos = (size.width - width) / 2;
|
||||
if(icon !is null) {
|
||||
icon.position = vec2i(pos, (size.height - icon.size.height) / 2 - 2);
|
||||
pos += icon.size.width + padding;
|
||||
}
|
||||
if(value !is null) {
|
||||
value.size = vec2i(value.size.width, size.height);
|
||||
value.position = vec2i(pos, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void updateAbsolutePosition() {
|
||||
BaseGuiElement::updateAbsolutePosition();
|
||||
if(value !is null)
|
||||
update();
|
||||
if(ttip !is null) {
|
||||
ttip.width = max(size.width, 250);
|
||||
ttip.offset = absolutePosition.topLeft + vec2i(min(size.width-250,0), size.height);
|
||||
}
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_PlainBox, SF_Normal, AbsolutePosition.padded(0,-2,0,1));
|
||||
|
||||
Color topColor = color;
|
||||
topColor.a = 0x30;
|
||||
|
||||
Color botColor = color;
|
||||
botColor.a = 0x10;
|
||||
|
||||
drawRectangle(AbsolutePosition, topColor, topColor, botColor, botColor);
|
||||
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
class InfluenceResource : ResourceDisplay {
|
||||
InfluenceResource(IGuiElement@ parent, Alignment@ align) {
|
||||
super(parent, align);
|
||||
|
||||
color = colors::Influence;
|
||||
addIcon(icons::Influence);
|
||||
addTexts();
|
||||
}
|
||||
|
||||
string get_tooltip() {
|
||||
return format(locale::GTT_INFLUENCE,
|
||||
toString(playerEmpire.Influence),
|
||||
toString(playerEmpire.InfluenceCap, 0),
|
||||
formatIncomeRate(playerEmpire.InfluenceIncome, perMinute=true),
|
||||
toString(playerEmpire.InfluencePercentage*100.0, 0)+"%",
|
||||
toString(playerEmpire.getInfluenceStock(), 0));
|
||||
}
|
||||
|
||||
void update() {
|
||||
int influence = playerEmpire.Influence;
|
||||
double income = playerEmpire.InfluenceIncome;
|
||||
double percentage = playerEmpire.InfluencePercentage;
|
||||
double efficiency = playerEmpire.InfluenceEfficiency;
|
||||
int cap = playerEmpire.InfluenceCap;
|
||||
|
||||
Color storedColor = colors::White;
|
||||
if(efficiency < 1.0 - 0.01)
|
||||
storedColor = Color(0xff0000ff).interpolate(storedColor, efficiency);
|
||||
|
||||
upperText.text = format(
|
||||
"[color=$3]$1[/color][color=#aaa][vspace=6][font=Normal]/$2[/font][/vspace][/color]",
|
||||
toString(influence), toString(cap), toString(storedColor));
|
||||
|
||||
lowerText.text = format(
|
||||
"$1 ($2%)",
|
||||
formatIncomeRate(income, perMinute=true), toString(percentage * 100.f, 0));
|
||||
ResourceDisplay::update();
|
||||
}
|
||||
};
|
||||
|
||||
class EnergyResource : ResourceDisplay {
|
||||
EnergyResource(IGuiElement@ parent, Alignment@ align) {
|
||||
super(parent, align);
|
||||
|
||||
color = colors::Energy;
|
||||
addIcon(icons::Energy);
|
||||
addTexts();
|
||||
}
|
||||
|
||||
string get_tooltip() {
|
||||
double income = playerEmpire.EnergyIncome - playerEmpire.EnergyUse;
|
||||
double factor = playerEmpire.EnergyEfficiency;
|
||||
if(income > 0)
|
||||
income *= factor;
|
||||
|
||||
return format(locale::GTT_ENERGY,
|
||||
toString(playerEmpire.EnergyStored, 0),
|
||||
formatIncomeRate(income),
|
||||
toString(playerEmpire.FreeEnergyStorage, 0),
|
||||
"-"+toString((1.0-factor)*100.0, 0)+"%");
|
||||
}
|
||||
|
||||
void update() {
|
||||
double stored = playerEmpire.EnergyStored;
|
||||
double income = playerEmpire.EnergyIncome - playerEmpire.EnergyUse;
|
||||
double factor = playerEmpire.EnergyEfficiency;
|
||||
if(income > 0)
|
||||
income *= factor;
|
||||
|
||||
upperText.text = toString(stored, 0);
|
||||
lowerText.text = formatIncomeRate(income);
|
||||
ResourceDisplay::update();
|
||||
}
|
||||
};
|
||||
|
||||
class FTLResource : ResourceDisplay {
|
||||
FTLResource(IGuiElement@ parent, Alignment@ align) {
|
||||
super(parent, align);
|
||||
|
||||
color = colors::FTLResource;
|
||||
addIcon(icons::FTL);
|
||||
addTexts();
|
||||
}
|
||||
|
||||
string get_tooltip() {
|
||||
return format(locale::GTT_FTL,
|
||||
toString(playerEmpire.FTLStored, 0),
|
||||
toString(playerEmpire.FTLCapacity, 0),
|
||||
formatIncomeRate(playerEmpire.FTLIncome - playerEmpire.FTLUse));
|
||||
}
|
||||
|
||||
void update() {
|
||||
double stored = playerEmpire.FTLStored;
|
||||
double income = playerEmpire.FTLIncome - playerEmpire.FTLUse;
|
||||
double capacity = playerEmpire.FTLCapacity;
|
||||
|
||||
upperText.text = format(
|
||||
"$1[color=#aaa][vspace=6][font=Normal]/$2[/font][/vspace][/color]",
|
||||
toString(stored, 0), toString(capacity, 0));
|
||||
|
||||
lowerText.text = formatIncomeRate(income);
|
||||
ResourceDisplay::update();
|
||||
}
|
||||
};
|
||||
|
||||
class ResearchResource : ResourceDisplay {
|
||||
array<TechnologyNode> researching;
|
||||
|
||||
GuiProgressbar@ techBar;
|
||||
GuiSprite@ techIcon;
|
||||
|
||||
ResearchResource(IGuiElement@ parent, Alignment@ align) {
|
||||
super(parent, align);
|
||||
|
||||
color = colors::Research;
|
||||
addIcon(icons::Research);
|
||||
addTexts();
|
||||
|
||||
@techBar = GuiProgressbar(value, Alignment(Left, Bottom-0.5f+3, Left+100, Bottom-4));
|
||||
techBar.strokeColor = colors::Black;
|
||||
techBar.textHorizAlign = 0.9;
|
||||
@techIcon = GuiSprite(techBar, Alignment(Left+2, Top+0.5f-12, Left+2+24, Top+0.5f+12));
|
||||
techIcon.noClip = true;
|
||||
}
|
||||
|
||||
int get_baseValueWidth() {
|
||||
if(techBar is null || !techBar.visible)
|
||||
return 0;
|
||||
return 100;
|
||||
}
|
||||
|
||||
string get_tooltip() {
|
||||
string tt = format(locale::GTT_RESEARCH,
|
||||
toString(playerEmpire.ResearchPoints, 0),
|
||||
formatIncomeRate(playerEmpire.ResearchRate));
|
||||
for(uint i = 0, cnt = researching.length; i < cnt; ++i) {
|
||||
tt += "\n"+format(locale::GTT_RESEARCH_TECH,
|
||||
researching[i].type.name, formatTime(researching[i].timer),
|
||||
toString(researching[i].type.color));
|
||||
}
|
||||
return tt;
|
||||
}
|
||||
|
||||
void update() {
|
||||
double stored = playerEmpire.ResearchPoints;
|
||||
double income = playerEmpire.ResearchRate;
|
||||
|
||||
upperText.text = toString(stored, 0);
|
||||
lowerText.text = formatIncomeRate(income);
|
||||
|
||||
researching.syncFrom(playerEmpire.getResearchingNodes());
|
||||
if(researching.length != 0) {
|
||||
researching.sortAsc();
|
||||
|
||||
techBar.visible = true;
|
||||
lowerText.visible = false;
|
||||
|
||||
auto@ activeTech = researching[0];
|
||||
techBar.text = formatTime(activeTech.timer);
|
||||
techIcon.desc = activeTech.type.icon;
|
||||
techBar.frontColor = activeTech.type.color;
|
||||
techBar.textColor = activeTech.type.color.interpolate(colors::White, 0.75f);
|
||||
techBar.progress = 1.0 - (activeTech.timer / activeTech.getTimeCost(playerEmpire));
|
||||
}
|
||||
else {
|
||||
techBar.visible = false;
|
||||
lowerText.visible = true;
|
||||
}
|
||||
|
||||
ResourceDisplay::update();
|
||||
}
|
||||
};
|
||||
|
||||
class ChangeWelfare : GuiContextOption {
|
||||
ChangeWelfare(const string& text, uint index) {
|
||||
value = int(index);
|
||||
this.text = text;
|
||||
icon = Sprite(spritesheet::ConvertIcon, index);
|
||||
}
|
||||
|
||||
void call(GuiContextMenu@ menu) override {
|
||||
playerEmpire.WelfareMode = uint(value);
|
||||
}
|
||||
};
|
||||
|
||||
class BudgetResource : ResourceDisplay {
|
||||
array<TechnologyNode> researching;
|
||||
|
||||
GuiProgressbar@ cycleBar;
|
||||
|
||||
GuiButton@ welfareButton;
|
||||
GuiSprite@ welfareIcon;
|
||||
|
||||
GuiText@ nextBudget;
|
||||
|
||||
BudgetResource(IGuiElement@ parent, Alignment@ align) {
|
||||
super(parent, align);
|
||||
|
||||
color = colors::Money;
|
||||
addIcon(icons::Money);
|
||||
addTexts();
|
||||
|
||||
@cycleBar = GuiProgressbar(value, Alignment(Left, Bottom-0.5f+3, Left+120, Bottom-4));
|
||||
cycleBar.font = FT_Small;
|
||||
|
||||
@nextBudget = GuiText(value, Alignment(Left+120, Bottom-0.5f+1, Left+200, Bottom-1));
|
||||
nextBudget.horizAlign = 0.5;
|
||||
|
||||
@welfareButton = GuiButton(value, Alignment(Right-84+40-25, Top, Width=50, Height=24));
|
||||
@welfareIcon = GuiSprite(welfareButton, Alignment(Left+8, Top-5, Right-8, Bottom+5),
|
||||
Sprite(spritesheet::ConvertIcon, 0));
|
||||
|
||||
setMarkupTooltip(welfareButton, locale::WELFARE_TT, hoverStyle=false);
|
||||
}
|
||||
|
||||
int get_baseValueWidth() {
|
||||
return 200;
|
||||
}
|
||||
|
||||
string get_tooltip() {
|
||||
string tt = format(locale::GTT_MONEY,
|
||||
formatMoneyChange(playerEmpire.RemainingBudget, colored=true),
|
||||
formatMoneyChange(playerEmpire.EstNextBudget, colored=true),
|
||||
formatTime(playerEmpire.BudgetCycle - playerEmpire.BudgetTimer),
|
||||
getSpriteDesc(welfareIcon.desc));
|
||||
tt += format("\n[font=Medium]$1[/font]\n", locale::RESOURCE_BUDGET);
|
||||
for(int i = MoT_COUNT - 1; i >= 0; --i) {
|
||||
int money = playerEmpire.getMoneyFromType(i);
|
||||
if(money != 0) {
|
||||
tt += format("$1: [right]$2[/right]",
|
||||
localize("MONEY_TYPE_"+i), formatMoneyChange(money, true));
|
||||
}
|
||||
}
|
||||
|
||||
int bonusMoney = playerEmpire.BonusBudget;
|
||||
if(bonusMoney != 0)
|
||||
tt += "\n\n"+format(locale::GTT_BONUS_MONEY, formatMoney(bonusMoney));
|
||||
|
||||
float debtFactor = playerEmpire.DebtFactor;
|
||||
if(debtFactor > 1.f) {
|
||||
float effFactor = pow(0.5f, debtFactor-1.f);
|
||||
tt += "\n\n"+format(locale::GTT_FLEET_PENALTY, "-"+toString((1.f - effFactor)*100.f, 0)+"%");
|
||||
}
|
||||
if(debtFactor > 0.f) {
|
||||
float growthFactor = 1.f;
|
||||
for(; debtFactor > 0; debtFactor -= 1.f)
|
||||
growthFactor *= 0.33f + 0.67f * (1.f - min(debtFactor, 1.f));
|
||||
tt += "\n\n"+format(locale::GTT_DEBT_PENALTY, "-"+toString((1.f - growthFactor)*100.f, 0)+"%");
|
||||
}
|
||||
return tt;
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& evt) override {
|
||||
if(evt.caller is welfareButton && evt.type == GUI_Clicked) {
|
||||
GuiContextMenu menu(mousePos);
|
||||
menu.itemHeight = 54;
|
||||
string money = formatMoney(350.0 / playerEmpire.WelfareEfficiency);
|
||||
menu.addOption(ChangeWelfare(format(locale::WELFARE_INFLUENCE, money), 0));
|
||||
menu.addOption(ChangeWelfare(format(locale::WELFARE_ENERGY, money), 1));
|
||||
menu.addOption(ChangeWelfare(format(locale::WELFARE_RESEARCH, money), 2));
|
||||
menu.addOption(ChangeWelfare(format(locale::WELFARE_LABOR, money), 3));
|
||||
menu.addOption(ChangeWelfare(format(locale::WELFARE_DEFENSE, money), 4));
|
||||
menu.updateAbsolutePosition();
|
||||
return true;
|
||||
}
|
||||
return ResourceDisplay::onGuiEvent(evt);
|
||||
}
|
||||
|
||||
void update() {
|
||||
//NOTE: Maybe related to spectating?
|
||||
if(playerEmpire is null)
|
||||
return;
|
||||
|
||||
//Current budget
|
||||
int curBudget = playerEmpire.RemainingBudget;
|
||||
int bonusBudget = playerEmpire.BonusBudget;
|
||||
|
||||
Color color(0xffffffff);
|
||||
if(curBudget < 0)
|
||||
color = Color(0xff0000ff);
|
||||
else if(curBudget - bonusBudget < 0)
|
||||
color = Color(0xff8000ff);
|
||||
else
|
||||
color = Color(0xffffffff);
|
||||
|
||||
upperText.defaultColor = color;
|
||||
upperText.text = formatMoney(curBudget, roundUp=false);
|
||||
|
||||
welfareIcon.desc = Sprite(spritesheet::ConvertIcon, playerEmpire.WelfareMode);
|
||||
|
||||
//Cycle timer
|
||||
double cycle = playerEmpire.BudgetCycle;
|
||||
double timer = playerEmpire.BudgetTimer;
|
||||
|
||||
cycleBar.text = formatTime(cycle - timer);
|
||||
if(cycle == 0)
|
||||
cycleBar.progress = 0.f;
|
||||
else
|
||||
cycleBar.progress = timer / cycle;
|
||||
|
||||
if(cycleBar.progress < (1.0 / 3.0))
|
||||
cycleBar.frontColor = colors::Money;
|
||||
else if(cycleBar.progress < (2.0 / 3.0))
|
||||
cycleBar.frontColor = colors::Money.interpolate(colors::Red, 0.3);
|
||||
else
|
||||
cycleBar.frontColor = colors::Money.interpolate(colors::Red, 0.6);
|
||||
|
||||
//Next budget
|
||||
int upcoming = playerEmpire.EstNextBudget;
|
||||
if(upcoming < 0)
|
||||
nextBudget.color = Color(0xbb0000ff);
|
||||
else
|
||||
nextBudget.color = Color(0xbbbbbbff);
|
||||
nextBudget.text = formatMoney(upcoming);
|
||||
|
||||
ResourceDisplay::update();
|
||||
}
|
||||
};
|
||||
|
||||
class DeployTarget : ObjectTargeting {
|
||||
DeployTarget() {
|
||||
icon = icons::Defense;
|
||||
}
|
||||
|
||||
void call(Object@ target) {
|
||||
playerEmpire.deployDefense(target);
|
||||
}
|
||||
|
||||
string message(Object@ obj, bool valid) {
|
||||
return locale::TT_DEPLOY;
|
||||
}
|
||||
|
||||
bool valid(Object@ obj) {
|
||||
if(!obj.isPlanet)
|
||||
return false;
|
||||
return obj.owner !is null && obj.owner.valid;
|
||||
}
|
||||
};
|
||||
|
||||
class DefenseResource : ResourceDisplay {
|
||||
GuiProgressbar@ bar;
|
||||
GuiButton@ button;
|
||||
|
||||
DefenseResource(IGuiElement@ parent, Alignment@ align) {
|
||||
super(parent, align);
|
||||
|
||||
color = colors::Defense;
|
||||
addIcon(icons::Defense);
|
||||
addTexts();
|
||||
|
||||
@bar = GuiProgressbar(value, Alignment(Left, Bottom-0.5f+3, Left+100, Bottom-4));
|
||||
bar.frontColor = colors::Defense;
|
||||
bar.font = FT_Small;
|
||||
bar.visible = false;
|
||||
|
||||
@button = GuiButton(value, Alignment(Left+70, Top+2, Left+100, Top+28));
|
||||
GuiSprite(button, Alignment().padded(-2), icons::Strength);
|
||||
button.visible = false;
|
||||
setMarkupTooltip(button, locale::TT_DEPLOY);
|
||||
}
|
||||
|
||||
string get_tooltip() {
|
||||
return format(locale::GTT_DEFENSE,
|
||||
standardize(playerEmpire.globalDefenseRate * 60.0 / DEFENSE_LABOR_PM, true)+locale::PER_MINUTE,
|
||||
standardize(playerEmpire.globalDefenseStorage, true),
|
||||
standardize(playerEmpire.globalDefenseStored, true));
|
||||
}
|
||||
|
||||
int get_baseValueWidth() {
|
||||
return bar.visible ? 100 : 0;
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& evt) override {
|
||||
if(evt.caller is button && evt.type == GUI_Clicked) {
|
||||
targetObject(DeployTarget());
|
||||
return true;
|
||||
}
|
||||
return ResourceDisplay::onGuiEvent(evt);
|
||||
}
|
||||
|
||||
void update() {
|
||||
double income = playerEmpire.globalDefenseRate / DEFENSE_LABOR_PM;
|
||||
upperText.text = format(
|
||||
"$1[color=#aaa][vspace=6][font=Normal]$2[/font][/vspace][/color]",
|
||||
toString(income * 60.0, 0), locale::PER_MINUTE);
|
||||
ResourceDisplay::update();
|
||||
|
||||
double storage = playerEmpire.globalDefenseStorage;
|
||||
double stored = playerEmpire.globalDefenseStored;
|
||||
|
||||
if(storage == 0) {
|
||||
upperText.position = vec2i(0, 8);
|
||||
bar.visible = false;
|
||||
button.visible = false;
|
||||
}
|
||||
else {
|
||||
upperText.position = vec2i(0, 1);
|
||||
bar.text = standardize(floor(stored), true)+" / "+standardize(storage, true);
|
||||
bar.progress= stored / storage;
|
||||
bar.visible = true;
|
||||
button.visible = stored >= storage*0.9999;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class GlobalBar : BaseGuiElement {
|
||||
BaseGuiElement@ container;
|
||||
double updateTimer = -INFINITY;
|
||||
|
||||
array<ResourceDisplay@> sections;
|
||||
ResourceDisplay@ budget;
|
||||
ResourceDisplay@ energy;
|
||||
ResourceDisplay@ ftl;
|
||||
ResourceDisplay@ influence;
|
||||
ResourceDisplay@ research;
|
||||
ResourceDisplay@ defense;
|
||||
|
||||
GlobalBar() {
|
||||
super(null, recti());
|
||||
|
||||
@container = BaseGuiElement(this, Alignment_Fill());
|
||||
container.StrictBounds = true;
|
||||
|
||||
@budget = BudgetResource(container, Alignment());
|
||||
sections.insertLast(budget);
|
||||
|
||||
@influence = InfluenceResource(container, Alignment());
|
||||
sections.insertLast(influence);
|
||||
|
||||
@energy = EnergyResource(container, Alignment());
|
||||
sections.insertLast(energy);
|
||||
|
||||
@ftl = FTLResource(container, Alignment());
|
||||
sections.insertLast(ftl);
|
||||
|
||||
@research = ResearchResource(container, Alignment());
|
||||
sections.insertLast(research);
|
||||
|
||||
@defense = DefenseResource(container, Alignment());
|
||||
sections.insertLast(defense);
|
||||
|
||||
updateSections();
|
||||
}
|
||||
|
||||
void update() {
|
||||
for(uint i = 0, cnt = sections.length; i < cnt; ++i)
|
||||
sections[i].update();
|
||||
}
|
||||
|
||||
void updateSections(){
|
||||
float x = 0.f;
|
||||
for(uint i = 0, cnt = sections.length; i < cnt; ++i) {
|
||||
float w;
|
||||
if(size.width >= 1600)
|
||||
w = 1.f / 6.f;
|
||||
else if(i == 0)
|
||||
w = 1.f / 5.f;
|
||||
else
|
||||
w = (1.f - (1.f / 5.f)) / 5.f;
|
||||
|
||||
sections[i].alignment = Alignment(Left+x, Top, Left+x+w, Bottom);
|
||||
x += w;
|
||||
}
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
void updateAbsolutePosition() {
|
||||
int width = AbsolutePosition.width;
|
||||
BaseGuiElement::updateAbsolutePosition();
|
||||
if(width != AbsolutePosition.width)
|
||||
updateSections();
|
||||
}
|
||||
|
||||
void draw() {
|
||||
if(frameTime - UPDATE_INTERVAL >= updateTimer) {
|
||||
update();
|
||||
updateTimer = frameTime;
|
||||
}
|
||||
|
||||
skin.draw(SS_GlobalBar, SF_Normal, AbsolutePosition);
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
}
|
||||
|
||||
BaseGuiElement@ createGlobalBar() {
|
||||
return GlobalBar();
|
||||
}
|
||||
|
||||
void preReload(Message& msg) {
|
||||
globalBar.remove();
|
||||
}
|
||||
|
||||
void postReload(Message& msg) {
|
||||
@globalBar = GlobalBar();
|
||||
@globalBar.alignment = Alignment(Left, Top+TAB_HEIGHT + 2, Right, Top+TAB_HEIGHT + 2 + GLOBAL_BAR_HEIGHT);
|
||||
}
|
||||
|
||||
void deploy_defense(bool pressed) {
|
||||
if(pressed)
|
||||
targetObject(DeployTarget());
|
||||
}
|
||||
|
||||
void init() {
|
||||
keybinds::Global.addBind(KB_DEPLOY_DEFENSE, "deploy_defense");
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import tabs.Tab;
|
||||
from tabs.tabbar import newTab, switchToTab;
|
||||
|
||||
Tab@ createGraphicsEditorTab() {
|
||||
return GraphicsEditor();
|
||||
}
|
||||
|
||||
class GraphicsEditor : Tab {
|
||||
string& get_title() {
|
||||
return locale::GRAPHICS_EDITOR;
|
||||
}
|
||||
|
||||
Color get_activeColor() {
|
||||
return Color(0xaaaaaaff);
|
||||
}
|
||||
|
||||
Color get_inactiveColor() {
|
||||
return Color(0x777777ff);
|
||||
}
|
||||
|
||||
TabCategory get_category() {
|
||||
return TC_Graphics;
|
||||
}
|
||||
};
|
||||
|
||||
interface Anchor {
|
||||
vec3d get_position() const;
|
||||
|
||||
vec3d get_normal() const;
|
||||
};
|
||||
|
||||
class PointAnchor : Anchor {
|
||||
vec3d point;
|
||||
vec3d direction;
|
||||
|
||||
vec3d get_position() const {
|
||||
return point;
|
||||
}
|
||||
|
||||
vec3d get_normal() const {
|
||||
return direction;
|
||||
}
|
||||
}
|
||||
|
||||
class CurveAnchor : Anchor {
|
||||
const Anchor@ from;
|
||||
const Anchor@ to;
|
||||
float percent;
|
||||
|
||||
vec3d get_position() const {
|
||||
vec3d p0 = from.position;
|
||||
vec3d p1 = p0 + from.normal;
|
||||
vec3d p3 = to.position;
|
||||
vec3d p2 = p3 + to.normal;
|
||||
|
||||
double t = percent, nt = 1.0 - percent;
|
||||
|
||||
return p0 * (nt*nt*nt) + p1 * (3.0*t*nt*nt) + p2 * (3.0*t*t*nt) + p3 * (t*t*t);
|
||||
}
|
||||
|
||||
vec3d get_normal() const {
|
||||
vec3d p0 = from.position;
|
||||
vec3d p1 = p0 + from.normal;
|
||||
vec3d p3 = to.position;
|
||||
vec3d p2 = p3 + to.normal;
|
||||
|
||||
double t = percent, nt = 1.0 - percent;
|
||||
|
||||
return (p0*(t*t) + p1 * (3.0*t*t - 4.0*t + 1.0) + p2*(-3.0*t*t + 2.0*t) + p3 * ((t - 1.0)*(t-1.0))).normalize();
|
||||
}
|
||||
}
|
||||
|
||||
class GraphicsEditorCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
Tab@ editor = createGraphicsEditorTab();
|
||||
newTab(editor);
|
||||
switchToTab(editor);
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
addConsoleCommand("graphics_editor", GraphicsEditorCommand());
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import tabs.Tab;
|
||||
import elements.GuiButton;
|
||||
|
||||
from tabs.tabbar import popTab, browseTab;
|
||||
from tabs.DiplomacyTab import createDiplomacyTab;
|
||||
from tabs.GalaxyTab import createGalaxyTab;
|
||||
from tabs.DesignOverviewTab import createDesignOverviewTab;
|
||||
from tabs.WikiTab import createWikiTab;
|
||||
from tabs.ResearchTab import createResearchTab;
|
||||
from tabs.PlanetsTab import createPlanetsTab;
|
||||
from tabs.AttitudesTab import createAttitudesTab;
|
||||
from community.Home import createCommunityHome;
|
||||
|
||||
class HomeTab : Tab {
|
||||
GuiButton@ galaxyButton;
|
||||
GuiButton@ researchButton;
|
||||
GuiButton@ designsButton;
|
||||
GuiButton@ diplomacyButton;
|
||||
GuiButton@ planetsButton;
|
||||
GuiButton@ wikiButton;
|
||||
GuiButton@ menuButton;
|
||||
GuiButton@ attButton;
|
||||
|
||||
HomeTab() {
|
||||
super();
|
||||
title = "Home";
|
||||
|
||||
int w = 300, hw = w/2;
|
||||
@galaxyButton = GuiButton(this, Alignment(Left+0.5f-w-hw, Top+20, Width=w-10, Height=80), locale::GALAXY);
|
||||
galaxyButton.font = FT_Medium;
|
||||
galaxyButton.buttonIcon = Sprite(material::SystemUnderAttack);
|
||||
galaxyButton.color = Color(0xff9600ff);
|
||||
@researchButton = GuiButton(this, Alignment(Left+0.5f-hw, Top+20, Width=w-10, Height=80), locale::RESEARCH);
|
||||
researchButton.font = FT_Medium;
|
||||
researchButton.buttonIcon = Sprite(material::TabResearch);
|
||||
researchButton.color = Color(0xd482ffff);
|
||||
@designsButton = GuiButton(this, Alignment(Left+0.5f+hw, Top+20, Width=w-10, Height=80), locale::DESIGNS);
|
||||
designsButton.font = FT_Medium;
|
||||
designsButton.buttonIcon = Sprite(material::TabDesigns);
|
||||
designsButton.color = Color(0x009cffff);
|
||||
@diplomacyButton = GuiButton(this, Alignment(Left+0.5f-w-hw, Top+110, Width=w-10, Height=80), locale::DIPLOMACY);
|
||||
diplomacyButton.font = FT_Medium;
|
||||
diplomacyButton.buttonIcon = Sprite(material::TabDiplomacy);
|
||||
diplomacyButton.color = Color(0x37ff00ff);
|
||||
@planetsButton = GuiButton(this, Alignment(Left+0.5f-hw, Top+110, Width=w-10, Height=80), locale::PLANETS_TAB);
|
||||
planetsButton.font = FT_Medium;
|
||||
planetsButton.buttonIcon = Sprite(material::TabPlanets);
|
||||
planetsButton.color = Color(0xccff00ff);
|
||||
@wikiButton = GuiButton(this, Alignment(Left+0.5f+hw, Top+110, Width=w-10, Height=80), locale::COMMUNITY_HOME_TITLE);
|
||||
wikiButton.font = FT_Medium;
|
||||
wikiButton.buttonIcon = Sprite(spritesheet::MenuIcons, 3);
|
||||
wikiButton.color = Color(0xff0077ff);
|
||||
|
||||
if(hasDLC("Heralds")) {
|
||||
@attButton = GuiButton(this, Alignment(Left+0.5f-w-hw, Top+200, Width=w-10, Height=80), locale::ATTITUDES_TAB);
|
||||
attButton.font = FT_Medium;
|
||||
attButton.buttonIcon = Sprite(material::TabGalaxy);
|
||||
attButton.color = Color(0x63ebdbff);
|
||||
}
|
||||
|
||||
@menuButton = GuiButton(this, Alignment(Left+0.5f-hw, Bottom-90, Width=w-10, Height=80), locale::MAIN_MENU);
|
||||
menuButton.font = FT_Medium;
|
||||
menuButton.color = Color(0xaaaaaaff);
|
||||
}
|
||||
|
||||
void hide() {
|
||||
if(previous !is null)
|
||||
popTab(this);
|
||||
Tab::hide();
|
||||
}
|
||||
|
||||
Color get_seperatorColor() {
|
||||
return Color(0x8e8e8eff);
|
||||
}
|
||||
|
||||
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
|
||||
if(source is this) {
|
||||
switch(event.type) {
|
||||
case KET_Key_Up:
|
||||
if(event.key == KEY_ESC) {
|
||||
if(previous !is null)
|
||||
popTab(this);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onKeyEvent(event, source);
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.type == GUI_Clicked) {
|
||||
if(event.caller is galaxyButton) {
|
||||
browseTab(this, createGalaxyTab(), false);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is researchButton) {
|
||||
browseTab(this, createResearchTab(), false);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is designsButton) {
|
||||
browseTab(this, createDesignOverviewTab(), false);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is diplomacyButton) {
|
||||
browseTab(this, createDiplomacyTab(), false);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is planetsButton) {
|
||||
browseTab(this, createPlanetsTab(), false);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is wikiButton) {
|
||||
browseTab(this, createCommunityHome(), false);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is attButton) {
|
||||
browseTab(this, createAttitudesTab(), false);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is menuButton) {
|
||||
switchToMenu();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_DesignOverviewBG, SF_Normal, AbsolutePosition);
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
Tab@ createHomeTab() {
|
||||
return HomeTab();
|
||||
}
|
||||
|
||||
bool isHomeTab(Tab@ tab) {
|
||||
return cast<HomeTab@>(tab) !is null;
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import tabs.Tab;
|
||||
import elements.GuiButton;
|
||||
import elements.GuiText;
|
||||
import elements.GuiMarkupText;
|
||||
import elements.GuiSkinElement;
|
||||
import elements.GuiBackgroundPanel;
|
||||
import influence;
|
||||
import util.formatting;
|
||||
|
||||
from tabs.tabbar import popTab, browseTab, get_ActiveTab;
|
||||
from tabs.InfluenceVoteTab import createInfluenceVoteTab;
|
||||
|
||||
const double UPDATE_TIMER = 1.0;
|
||||
|
||||
class InfluenceHistoryTab : Tab {
|
||||
uint limit = 10;
|
||||
int beforeId = -1;
|
||||
bool reverse = true;
|
||||
|
||||
GuiBackgroundPanel@ bg;
|
||||
|
||||
GuiButton@ backButton;
|
||||
GuiButton@ olderButton;
|
||||
GuiButton@ newerButton;
|
||||
|
||||
InfluenceVote[] votes;
|
||||
VoteBox@[] boxes;
|
||||
|
||||
double updateTime = 0.0;
|
||||
|
||||
InfluenceHistoryTab() {
|
||||
super();
|
||||
title = locale::VOTE_HISTORY;
|
||||
|
||||
@bg = GuiBackgroundPanel(this, Alignment(Left+8, Top+8, Right-8, Bottom-8));
|
||||
bg.title = locale::VOTE_HISTORY;
|
||||
bg.titleColor = Color(0x00bffeff);
|
||||
bg.picture = Sprite(material::Propositions);
|
||||
|
||||
@backButton = GuiButton(this, recti(14, 40, 212, 70), locale::DIPLOMACY_BACK);
|
||||
|
||||
@newerButton = GuiButton(this, Alignment(Left+0.5f-222, Top+40, Left+0.5f-2, Top+70), locale::NEWER);
|
||||
|
||||
@olderButton = GuiButton(this, Alignment(Left+0.5f+2, Top+40, Left+0.5f+222, Top+70), locale::OLDER);
|
||||
|
||||
update();
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
updateTime -= time;
|
||||
if(updateTime <= 0) {
|
||||
update();
|
||||
updateTime += UPDATE_TIMER;
|
||||
}
|
||||
}
|
||||
|
||||
void update() {
|
||||
if(reverse) {
|
||||
votes.syncFrom(getInfluenceVoteHistory(limit+1, beforeId, true));
|
||||
}
|
||||
else {
|
||||
votes.syncFrom(getInfluenceVoteHistory(limit+1, beforeId, false));
|
||||
if(votes.length <= limit) {
|
||||
beforeId = -1;
|
||||
}
|
||||
else {
|
||||
beforeId = votes[limit].id;
|
||||
votes.removeAt(limit);
|
||||
}
|
||||
votes.reverse();
|
||||
}
|
||||
|
||||
uint oldCnt = boxes.length;
|
||||
uint newCnt = min(votes.length, limit);
|
||||
for(uint i = newCnt; i < oldCnt; ++i)
|
||||
boxes[i].remove();
|
||||
boxes.length = newCnt;
|
||||
|
||||
for(uint i = 0; i < newCnt; ++i) {
|
||||
if(boxes[i] is null)
|
||||
@boxes[i] = VoteBox(this);
|
||||
boxes[i].set(votes[i]);
|
||||
boxes[i].position = vec2i(16, 78 + i * 32);
|
||||
}
|
||||
|
||||
olderButton.disabled = reverse && votes.length <= limit;
|
||||
newerButton.disabled = votes.length == 0 || beforeId == -1;
|
||||
reverse = true;
|
||||
}
|
||||
|
||||
void hide() {
|
||||
Tab::hide();
|
||||
}
|
||||
|
||||
void updateAbsolutePosition() {
|
||||
//Update box list size
|
||||
uint show = max((size.height - 80) / 32, 2);
|
||||
if(show != limit) {
|
||||
limit = show;
|
||||
update();
|
||||
}
|
||||
|
||||
Tab::updateAbsolutePosition();
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.type == GUI_Clicked) {
|
||||
if(event.caller is backButton) {
|
||||
popTab(this);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is olderButton) {
|
||||
if(votes.length < limit)
|
||||
return true;
|
||||
beforeId = votes[limit-1].id;
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is newerButton) {
|
||||
if(votes.length == 0)
|
||||
return true;
|
||||
reverse = false;
|
||||
beforeId = votes[0].id;
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
Color get_activeColor() {
|
||||
return Color(0x74fc4eff);
|
||||
}
|
||||
|
||||
Color get_inactiveColor() {
|
||||
return Color(0x37ff00ff);
|
||||
}
|
||||
|
||||
Color get_seperatorColor() {
|
||||
return Color(0x408c2bff);
|
||||
}
|
||||
|
||||
TabCategory get_category() {
|
||||
return TC_Diplomacy;
|
||||
}
|
||||
|
||||
Sprite get_icon() {
|
||||
return Sprite(material::TabDiplomacy);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_DiplomacyBG, SF_Normal, AbsolutePosition);
|
||||
Tab::draw();
|
||||
}
|
||||
};
|
||||
|
||||
class VoteBox : BaseGuiElement {
|
||||
uint prevId = uint(-1);
|
||||
InfluenceVote@ vote;
|
||||
|
||||
GuiText@ timeBox;
|
||||
GuiMarkupText@ titleBox;
|
||||
GuiText@ statusBox;
|
||||
|
||||
GuiButton@ detailsButton;
|
||||
|
||||
VoteBox(IGuiElement@ parent) {
|
||||
super(parent, recti(0, 0, parent.size.width - 48, 32));
|
||||
|
||||
@timeBox = GuiText(this, recti(4, 6, 120, 26));
|
||||
timeBox.font = FT_Small;
|
||||
timeBox.color = Color(0x888888ff);
|
||||
timeBox.horizAlign = 0.5;
|
||||
|
||||
@titleBox = GuiMarkupText(this, Alignment(Left+124, Top+6, Right-208, Top+26));
|
||||
|
||||
@statusBox = GuiText(this, Alignment(Right-204, Top+6, Right-4, Top+26));
|
||||
|
||||
@detailsButton = GuiButton(this, Alignment(Right-84, Top+4, Right-4, Top+28), locale::VIEW);
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
void set(InfluenceVote@ newVote) {
|
||||
//Only update data when set to a different vote
|
||||
if(vote !is null && newVote.id == prevId) {
|
||||
@vote = newVote;
|
||||
return;
|
||||
}
|
||||
|
||||
@vote = newVote;
|
||||
prevId = vote.id;
|
||||
|
||||
//Set the data fields
|
||||
titleBox.text = formatEmpireName(vote.startedBy)+": "+vote.formatTitle();
|
||||
timeBox.text = formatGameTime(vote.startedAt)+" - "+formatGameTime(vote.endedAt);
|
||||
|
||||
if(vote.succeeded) {
|
||||
statusBox.text = locale::PASSED;
|
||||
statusBox.color = Color(0x00ff00ff);
|
||||
}
|
||||
else {
|
||||
statusBox.text = locale::FAILED;
|
||||
statusBox.color = Color(0xff0000ff);
|
||||
}
|
||||
}
|
||||
|
||||
void remove() {
|
||||
@vote = null;
|
||||
BaseGuiElement::remove();
|
||||
}
|
||||
|
||||
void updateAbsolutePosition() {
|
||||
BaseGuiElement::updateAbsolutePosition();
|
||||
|
||||
int w = parent.size.width - 24;
|
||||
if(size.width != w)
|
||||
size = vec2i(w, size.height);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_InfluenceVoteBox, SF_Normal, AbsolutePosition);
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& evt) {
|
||||
switch(evt.type) {
|
||||
case GUI_Clicked:
|
||||
if(evt.caller is detailsButton) {
|
||||
browseTab(ActiveTab, createInfluenceVoteTab(vote.id), true);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(evt);
|
||||
}
|
||||
};
|
||||
|
||||
Tab@ createInfluenceHistoryTab() {
|
||||
return InfluenceHistoryTab();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
import tabs.Tab;
|
||||
import elements.GuiText;
|
||||
import elements.GuiTextbox;
|
||||
import elements.GuiButton;
|
||||
|
||||
from tabs.tabbar import newTab, switchToTab;
|
||||
|
||||
Tab@ createNameGeneratorTab() {
|
||||
return NameGeneratorTab();
|
||||
}
|
||||
|
||||
class NameGeneratorCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
Tab@ editor = createNameGeneratorTab();
|
||||
newTab(editor);
|
||||
switchToTab(editor);
|
||||
}
|
||||
};
|
||||
|
||||
void init() {
|
||||
addConsoleCommand("name_generator", NameGeneratorCommand());
|
||||
}
|
||||
|
||||
class NameGeneratorTab : Tab {
|
||||
NameGenerator gen;
|
||||
|
||||
GuiTextbox@ filename;
|
||||
GuiButton@ saveButton;
|
||||
GuiButton@ loadButton;
|
||||
|
||||
GuiText@ mutationHeader;
|
||||
GuiTextbox@ mutationChance;
|
||||
GuiText@ nameCount;
|
||||
|
||||
GuiTextbox@ nameBox;
|
||||
GuiButton@ generateButton;
|
||||
GuiButton@ addButton;
|
||||
|
||||
NameGeneratorTab() {
|
||||
super();
|
||||
title = "Name Generator";
|
||||
gen.read("data/system_names.txt");
|
||||
|
||||
@filename = GuiTextbox(this, recti(4, 4, 404, 30), "data/system_names.txt");
|
||||
@loadButton = GuiButton(this, recti(410, 4, 510, 30), locale::LOAD);
|
||||
@saveButton = GuiButton(this, recti(516, 4, 616, 30), locale::SAVE);
|
||||
|
||||
@mutationHeader = GuiText(this, recti(4, 34, 204, 60), locale::MUTATION_CHANCE);
|
||||
@mutationChance = GuiTextbox(this, recti(210, 34, 310, 60), "0.00");
|
||||
@nameCount = GuiText(this, recti(316, 34, 610, 60));
|
||||
nameCount.horizAlign = 1.0;
|
||||
nameCount.text = format(locale::NAME_COUNT, toString(gen.nameCount));
|
||||
|
||||
@nameBox = GuiTextbox(this, recti(4, 80, 404, 106));
|
||||
@generateButton = GuiButton(this, recti(410, 80, 510, 106), locale::GENERATE);
|
||||
@addButton = GuiButton(this, recti(516, 80, 616, 106), locale::ADD);
|
||||
|
||||
nameBox.text = gen.generate();
|
||||
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
switch(event.type) {
|
||||
case GUI_Clicked:
|
||||
if(event.caller is loadButton) {
|
||||
gen.clear();
|
||||
gen.read(filename.text);
|
||||
nameCount.text = format(locale::NAME_COUNT, toString(gen.nameCount));
|
||||
nameBox.text = gen.generate();
|
||||
mutationChance.text = toString(gen.mutationChance, 2);
|
||||
}
|
||||
else if(event.caller is saveButton) {
|
||||
gen.write(filename.text);
|
||||
}
|
||||
else if(event.caller is generateButton) {
|
||||
nameBox.text = gen.generate();
|
||||
}
|
||||
else if(event.caller is addButton) {
|
||||
if(!gen.hasName(nameBox.text))
|
||||
gen.addName(nameBox.text);
|
||||
nameBox.text = gen.generate();
|
||||
nameCount.text = format(locale::NAME_COUNT, toString(gen.nameCount));
|
||||
}
|
||||
break;
|
||||
case GUI_Changed:
|
||||
if(event.caller is mutationChance) {
|
||||
gen.mutationChance = toFloat(mutationChance.text);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_DesignOverviewBG, SF_Normal, AbsolutePosition);
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
import tabs.Tab;
|
||||
import elements.GuiText;
|
||||
import elements.GuiPanel;
|
||||
import elements.GuiTextbox;
|
||||
import elements.GuiListbox;
|
||||
import elements.GuiCheckbox;
|
||||
import elements.GuiButton;
|
||||
import dialogs.MaterialChooser;
|
||||
import dialogs.ColorDialog;
|
||||
import dialogs.InputDialog;
|
||||
from tabs.tabbar import newTab, switchToTab;
|
||||
|
||||
Tab@ createParticlesTab() {
|
||||
return ParticlesTab();
|
||||
}
|
||||
|
||||
class ParticlesTabCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
Tab@ editor = createParticlesTab();
|
||||
newTab(editor);
|
||||
switchToTab(editor);
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
addConsoleCommand("particle_editor", ParticlesTabCommand());
|
||||
}
|
||||
|
||||
class FlowEntry : GuiListText {
|
||||
ParticlesTab@ tab;
|
||||
ParticleFlow@ flow;
|
||||
uint index;
|
||||
|
||||
FlowEntry(ParticlesTab@ Tab, ParticleFlow@ Flow, uint Index) {
|
||||
@tab = Tab;
|
||||
@flow = Flow;
|
||||
index = Index;
|
||||
|
||||
string matName = Flow.materials[0];
|
||||
if(matName.length == 0)
|
||||
super("Empty Flow");
|
||||
else
|
||||
super(matName);
|
||||
}
|
||||
|
||||
void onSelect() override {
|
||||
tab.switchToFlow(flow, index);
|
||||
}
|
||||
};
|
||||
|
||||
class SizeChangeEvent : InputDialogCallback {
|
||||
ParticlesTab@ tab;
|
||||
uint index;
|
||||
|
||||
SizeChangeEvent(ParticlesTab@ Tab, uint Index) {
|
||||
index = Index;
|
||||
@tab = Tab;
|
||||
}
|
||||
|
||||
void inputCallback(InputDialog@ dialog, bool accepted) {
|
||||
tab.changeSize(index, dialog.getSpinboxInput(0));
|
||||
}
|
||||
|
||||
void changeCallback(InputDialog@ dialog) {
|
||||
tab.changeSize(index, dialog.getSpinboxInput(0));
|
||||
}
|
||||
};
|
||||
|
||||
class ColorChangeEvent : ColorDialogCallback {
|
||||
ParticlesTab@ tab;
|
||||
uint index;
|
||||
|
||||
ColorChangeEvent(ParticlesTab@ Tab, uint Index) {
|
||||
index = Index;
|
||||
@tab = Tab;
|
||||
}
|
||||
|
||||
void colorChosen(Color Col) {
|
||||
tab.changeColor(index, Col);
|
||||
}
|
||||
};
|
||||
|
||||
class ColorEntry : GuiListElement {
|
||||
ParticlesTab@ tab;
|
||||
Color col;
|
||||
|
||||
ColorEntry(Color Col, ParticlesTab@ Tab) {
|
||||
col = Col;
|
||||
@tab = Tab;
|
||||
}
|
||||
|
||||
void draw(GuiListbox@ ele, uint flags, const recti& absPos) override {
|
||||
Color solid = col;
|
||||
solid.a = 255;
|
||||
drawRectangle(absPos, solid, col, col, solid);
|
||||
}
|
||||
};
|
||||
|
||||
class onMatPicked : MaterialChoiceCallback {
|
||||
ParticlesTab@ tab;
|
||||
int editIndex;
|
||||
|
||||
onMatPicked(ParticlesTab@ editor, int EditIndex = -1) {
|
||||
editIndex = EditIndex;
|
||||
@tab = editor;
|
||||
}
|
||||
|
||||
void onMaterialChosen(const Material@ material, const string& id) {
|
||||
if(editIndex < 0)
|
||||
tab.addFlowMaterial(id);
|
||||
else
|
||||
tab.setFlowMaterial(editIndex, id);
|
||||
}
|
||||
|
||||
void onSpriteSheetChosen(const SpriteSheet@ spritebank, uint spriteIndex, const string& id) {}
|
||||
};
|
||||
|
||||
dictionary changedSystems;
|
||||
|
||||
class ParticlesTab : Tab {
|
||||
vec2i prevSize;
|
||||
RenderTarget@ rt;
|
||||
ParticleSystem@ ps;
|
||||
ParticleFlow@ flow;
|
||||
uint flowIndex = 0;
|
||||
int delFlowIndex = -1;
|
||||
Node@ node;
|
||||
|
||||
GuiTextbox@ psName;
|
||||
GuiButton@ savePS, loadPS;
|
||||
GuiListbox@ flows;
|
||||
GuiButton@ addFlow, delFlow, copyFlow;
|
||||
|
||||
GuiPanel@ flowData;
|
||||
|
||||
GuiListbox@ flowMats;
|
||||
GuiButton@ addMat, delMat, editMat;
|
||||
GuiTextbox@ flowScaleMin, flowScaleMax, flowLifeMin, flowLifeMax, flowRate;
|
||||
GuiTextbox@ flowStart, flowEnd, flowConeMin, flowConeMax, flowSpeedMin, flowSpeedMax, flowDistMin, flowDistMax;
|
||||
GuiCheckbox@ flowFlat;
|
||||
GuiTextbox@ flowStartSound;
|
||||
GuiListbox@ flowCols, flowSizes;
|
||||
GuiButton@ addCol, delCol, addSize, delSize;
|
||||
|
||||
int nextTabIndex = 0;
|
||||
void prepTab(IGuiElement@ ele) {
|
||||
ele.tabIndex = nextTabIndex++;
|
||||
}
|
||||
|
||||
ParticlesTab() {
|
||||
super();
|
||||
title = "Particle Editor";
|
||||
@psName = GuiTextbox(this, recti(4, 4, 220, 28));
|
||||
@savePS = GuiButton(this, recti(224, 4, 300, 28), "Save");
|
||||
@loadPS = GuiButton(this, recti(304, 4, 380, 28), "Load");
|
||||
|
||||
@flows = GuiListbox(this, Alignment(Left+4, Top+32, Left+220, Bottom-304));
|
||||
@addFlow = GuiButton(this, Alignment(Left+4, Bottom-260, Left+108, Bottom-220), "New Flow");
|
||||
@delFlow = GuiButton(this, Alignment(Left+112, Bottom-260, Left+220, Bottom-220), "Remove");
|
||||
@copyFlow = GuiButton(this, Alignment(Left+4, Bottom-300, Left+220, Bottom-264), "Duplicate");
|
||||
|
||||
@flowData = GuiPanel(this, Alignment(Left+4, Bottom-216, Right+0, Bottom+0));
|
||||
|
||||
@flowMats = GuiListbox(flowData, Alignment(Left+0, Top+0, Left+220, Bottom-60));
|
||||
@delMat = GuiButton(flowData, Alignment(Left+0, Bottom-56, Left+108, Bottom-32), "Remove");
|
||||
@editMat = GuiButton(flowData, Alignment(Left+112, Bottom-56, Left+220, Bottom-32), "Change");
|
||||
@addMat = GuiButton(flowData, Alignment(Left+0, Bottom-28, Left+220, Bottom-4), "Add Material");
|
||||
|
||||
GuiText(flowData, Alignment(Left+224, Top+0, Left+300, Top+24), "Scale:");
|
||||
@flowScaleMin = GuiTextbox(flowData, Alignment(Left+270, Top+0, Left+370, Top+24));
|
||||
prepTab(flowScaleMin);
|
||||
@flowScaleMax = GuiTextbox(flowData, Alignment(Left+374, Top+0, Left+474, Top+24));
|
||||
prepTab(flowScaleMax);
|
||||
|
||||
GuiText(flowData, Alignment(Left+224, Top+26, Left+300, Top+52), "Life:");
|
||||
@flowLifeMin = GuiTextbox(flowData, Alignment(Left+270, Top+26, Left+370, Top+52));
|
||||
prepTab(flowLifeMin);
|
||||
@flowLifeMax = GuiTextbox(flowData, Alignment(Left+374, Top+26, Left+474, Top+52));
|
||||
prepTab(flowLifeMax);
|
||||
|
||||
GuiText(flowData, Alignment(Left+224, Top+54, Left+300, Top+80), "Play:");
|
||||
@flowStart = GuiTextbox(flowData, Alignment(Left+270, Top+54, Left+370, Top+80));
|
||||
flowStart.tooltip = "Time offset (seconds) to begin playing this flow";
|
||||
prepTab(flowStart);
|
||||
@flowEnd = GuiTextbox(flowData, Alignment(Left+374, Top+54, Left+474, Top+80));
|
||||
flowEnd.tooltip = "Time offset (seconds) to stop playing this flow";
|
||||
prepTab(flowEnd);
|
||||
|
||||
GuiText(flowData, Alignment(Left+224, Top+82, Left+300, Top+108), "Rate:");
|
||||
@flowRate = GuiTextbox(flowData, Alignment(Left+270, Top+82, Left+370, Top+108));
|
||||
prepTab(flowRate);
|
||||
|
||||
GuiText(flowData, Alignment(Left+224, Top+110, Left+300, Top+136), "Cone:");
|
||||
@flowConeMin = GuiTextbox(flowData, Alignment(Left+270, Top+110, Left+370, Top+136));
|
||||
prepTab(flowConeMin);
|
||||
@flowConeMax = GuiTextbox(flowData, Alignment(Left+374, Top+110, Left+474, Top+136));
|
||||
prepTab(flowConeMax);
|
||||
|
||||
GuiText(flowData, Alignment(Left+224, Top+138, Left+300, Top+164), "Speed:");
|
||||
@flowSpeedMin = GuiTextbox(flowData, Alignment(Left+270, Top+138, Left+370, Top+164));
|
||||
prepTab(flowSpeedMin);
|
||||
@flowSpeedMax = GuiTextbox(flowData, Alignment(Left+374, Top+138, Left+474, Top+164));
|
||||
prepTab(flowSpeedMax);
|
||||
|
||||
GuiText(flowData, Alignment(Left+224, Top+166, Left+300, Top+192), "Dist:");
|
||||
@flowDistMin = GuiTextbox(flowData, Alignment(Left+270, Top+166, Left+370, Top+192));
|
||||
flowDistMin.tooltip = "Minimum distance to spawn particles away from the center.";
|
||||
prepTab(flowDistMin);
|
||||
@flowDistMax = GuiTextbox(flowData, Alignment(Left+374, Top+166, Left+474, Top+192));
|
||||
prepTab(flowDistMax);
|
||||
|
||||
@flowFlat = GuiCheckbox(flowData, Alignment(Left+244, Top+194, Left+474, Top+220), "Flat");
|
||||
prepTab(flowFlat);
|
||||
|
||||
@flowCols = GuiListbox(flowData, Alignment(Left+478, Top, Left+550, Bottom-36));
|
||||
flowCols.DblClickConfirm = true;
|
||||
flowCols.Required = true;
|
||||
@flowSizes = GuiListbox(flowData, Alignment(Left+554, Top, Left+654, Bottom-36));
|
||||
flowSizes.DblClickConfirm = true;
|
||||
flowSizes.Required = true;
|
||||
|
||||
@addCol = GuiButton(flowData, Alignment(Left+478, Bottom-32, Left+512, Bottom-4), "Add");
|
||||
@delCol = GuiButton(flowData, Alignment(Left+516, Bottom-32, Left+550, Bottom-4), "Del");
|
||||
|
||||
@addSize = GuiButton(flowData, Alignment(Left+554, Bottom-32, Left+602, Bottom-4), "Add");
|
||||
@delSize = GuiButton(flowData, Alignment(Left+606, Bottom-32, Left+654, Bottom-4), "Del");
|
||||
|
||||
GuiText(flowData, Alignment(Left+660,Top,Left+720,Top+26), "Sound");
|
||||
@flowStartSound = GuiTextbox(flowData, Alignment(Left+724,Top,Left+824,Top+26));
|
||||
|
||||
loadParticleSystem("ImpactFlare");
|
||||
}
|
||||
|
||||
void switchToFlow(ParticleFlow@ Flow, uint Index) {
|
||||
@flow = Flow;
|
||||
flowIndex = Index;
|
||||
if(flow is null) {
|
||||
flowData.visible = false;
|
||||
}
|
||||
else {
|
||||
flows.selected = Index;
|
||||
flowData.visible = true;
|
||||
flowMats.clearItems();
|
||||
for(uint i = 0, cnt = flow.materialCount; i < cnt; ++i)
|
||||
flowMats.addItem(flow.materials[i]);
|
||||
flowCols.clearItems();
|
||||
for(uint i = 0, cnt = max(flow.colorCount,1); i < cnt; ++i)
|
||||
flowCols.addItem(ColorEntry(flow.colors[i], this));
|
||||
flowSizes.clearItems();
|
||||
for(uint i = 0, cnt = max(flow.sizeCount,1); i < cnt; ++i)
|
||||
flowSizes.addItem(toString(flow.sizes[i], 2));
|
||||
flowScaleMin.text = toString(flow.scale.min, 2);
|
||||
flowScaleMax.text = toString(flow.scale.max, 2);
|
||||
flowLifeMin.text = toString(flow.life.min, 2);
|
||||
flowLifeMax.text = toString(flow.life.max, 2);
|
||||
flowStart.text = toString(flow.start, 2);
|
||||
flowEnd.text = toString(flow.end, 2);
|
||||
flowRate.text = toString(flow.rate, 2);
|
||||
flowConeMin.text = toString(flow.cone.min, 2);
|
||||
flowConeMax.text = toString(flow.cone.max, 2);
|
||||
flowSpeedMin.text = toString(flow.speed.min, 2);
|
||||
flowSpeedMax.text = toString(flow.speed.max, 2);
|
||||
flowDistMin.text = toString(flow.spawnDist.min, 2);
|
||||
flowDistMax.text = toString(flow.spawnDist.max, 2);
|
||||
flowStartSound.text = flow.soundStart;
|
||||
flowFlat.checked = flow.flat;
|
||||
}
|
||||
}
|
||||
|
||||
void parseFlowData() {
|
||||
flow.scale.min = toDouble(flowScaleMin.text);
|
||||
flow.scale.max = toDouble(flowScaleMax.text);
|
||||
flow.life.min = toDouble(flowLifeMin.text);
|
||||
flow.life.max = toDouble(flowLifeMax.text);
|
||||
flow.start = toDouble(flowStart.text);
|
||||
flow.end = toDouble(flowEnd.text);
|
||||
flow.rate = toDouble(flowRate.text);
|
||||
flow.cone.min = toDouble(flowConeMin.text);
|
||||
flow.cone.max = toDouble(flowConeMax.text);
|
||||
flow.speed.min = toDouble(flowSpeedMin.text);
|
||||
flow.speed.max = toDouble(flowSpeedMax.text);
|
||||
flow.spawnDist.min = toDouble(flowDistMin.text);
|
||||
flow.spawnDist.max = toDouble(flowDistMax.text);
|
||||
flow.soundStart = flowStartSound.text;
|
||||
flow.flat = flowFlat.checked;
|
||||
postEdit();
|
||||
}
|
||||
|
||||
void createFlow() {
|
||||
if(ps is null)
|
||||
return;
|
||||
ParticleFlow@ Flow = ps.createFlow();
|
||||
flows.addItem(FlowEntry(this, Flow, ps.flowCount-1));
|
||||
switchToFlow(Flow, ps.flowCount-1);
|
||||
}
|
||||
|
||||
void duplicateFlow(uint index) {
|
||||
if(ps is null)
|
||||
return;
|
||||
ParticleFlow@ Flow = ps.duplicateFlow(ps.flows[index]);
|
||||
flows.addItem(FlowEntry(this, Flow, ps.flowCount-1));
|
||||
switchToFlow(Flow, ps.flowCount-1);
|
||||
}
|
||||
|
||||
void changeColor(uint index, Color col) {
|
||||
if(flow is null)
|
||||
return;
|
||||
flow.colors[index] = col;
|
||||
flowCols.setItem(index, ColorEntry(col, this));
|
||||
postEdit();
|
||||
}
|
||||
|
||||
void changeSize(uint index, double size) {
|
||||
if(flow is null)
|
||||
return;
|
||||
flow.sizes[index] = size;
|
||||
flowSizes.setItem(index, toString(size, 2));
|
||||
postEdit();
|
||||
}
|
||||
|
||||
void addFlowMaterial(const string &in matName) {
|
||||
if(flow is null)
|
||||
return;
|
||||
flow.addMaterial(matName);
|
||||
flowMats.addItem(matName);
|
||||
flows.setItem(flowIndex, FlowEntry(this, flow, flowIndex));
|
||||
postEdit();
|
||||
}
|
||||
|
||||
void setFlowMaterial(uint index, const string &in matName) {
|
||||
if(flow is null)
|
||||
return;
|
||||
flow.materials[index] = matName;
|
||||
flowMats.setItem(index, matName);
|
||||
flows.setItem(flowIndex, FlowEntry(this, flow, flowIndex));
|
||||
postEdit();
|
||||
}
|
||||
|
||||
void loadParticleSystem(const string &in name) {
|
||||
ParticleSystem@ psys;
|
||||
if(!changedSystems.get(name, @psys))
|
||||
@ps = getParticleSystem(name).duplicate();
|
||||
else
|
||||
@ps = psys;
|
||||
flows.clearItems();
|
||||
flowData.visible = false;
|
||||
|
||||
if(ps !is null) {
|
||||
psName.text = name;
|
||||
|
||||
for(uint i = 0, cnt = ps.flowCount; i < cnt; ++i) {
|
||||
flows.addItem(FlowEntry(this, ps.flows[i], i));
|
||||
}
|
||||
}
|
||||
else {
|
||||
psName.text = "";
|
||||
}
|
||||
|
||||
postEdit();
|
||||
}
|
||||
|
||||
void postEdit() {
|
||||
if(node !is null)
|
||||
node.markForDeletion();
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.type == GUI_Clicked) {
|
||||
if(event.caller is addFlow) {
|
||||
createFlow();
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is delFlow) {
|
||||
if(flows.selected >= 0 && uint(flows.selected) < flows.itemCount && flows.itemCount > 1) {
|
||||
delFlowIndex = flows.selected;
|
||||
postEdit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is copyFlow) {
|
||||
if(flows.selected >= 0 && uint(flows.selected) < flows.itemCount) {
|
||||
duplicateFlow(uint(flows.selected));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is addMat) {
|
||||
if(flow !is null)
|
||||
openMaterialChooser(onMatPicked(this), MCM_Materials);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is delMat) {
|
||||
if(flow !is null) {
|
||||
if(flowMats.selected >= 0 && uint(flowMats.selected) < flowMats.itemCount) {
|
||||
flow.removeMaterial(flowMats.selected);
|
||||
flowMats.removeItem(flowMats.selected);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is addCol) {
|
||||
if(flow !is null) {
|
||||
int index = flow.colorCount;
|
||||
flow.addColor(index, flow.colors[index-1]);
|
||||
if(index == 0)
|
||||
flow.addColor(index, flow.colors[index-1]);
|
||||
flowCols.addItem(ColorEntry(flow.colors[index], this));
|
||||
postEdit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is delCol) {
|
||||
if(flow !is null && flowCols.selected >= 0 && uint(flowCols.selected) < flowCols.itemCount) {
|
||||
flow.removeColor(flowCols.selected);
|
||||
flowCols.removeItem(flowCols.selected);
|
||||
postEdit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is addSize) {
|
||||
if(flow !is null) {
|
||||
int index = flow.sizeCount;
|
||||
flow.addSize(index, flow.sizes[index-1]);
|
||||
if(index == 0)
|
||||
flow.addSize(index, flow.sizes[index-1]);
|
||||
flowSizes.addItem(toString(flow.sizes[index], 2));
|
||||
postEdit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is delSize) {
|
||||
if(flow !is null && flowSizes.selected >= 0 && uint(flowSizes.selected) < flowSizes.itemCount) {
|
||||
flow.removeSize(flowSizes.selected);
|
||||
flowSizes.removeItem(flowSizes.selected);
|
||||
postEdit();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is editMat) {
|
||||
if(flow !is null && flowMats.selected >= 0)
|
||||
openMaterialChooser(onMatPicked(this, flowMats.selected), MCM_Materials);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is savePS) {
|
||||
string sysName = psName.text;
|
||||
if(sysName.length > 0) {
|
||||
if(ps !is null)
|
||||
ps.save("data/particles/" + sysName + ".ps");
|
||||
changedSystems.set(sysName, @ps);
|
||||
}
|
||||
else {
|
||||
sound::error.play(priority=true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is loadPS) {
|
||||
loadParticleSystem(psName.text);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(event.type == GUI_Confirmed) {
|
||||
if(event.caller is flowCols) {
|
||||
ColorDialog(ColorChangeEvent(this, event.value), null, flow.colors[event.value]);
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is flowSizes) {
|
||||
InputDialog@ dialog = InputDialog(SizeChangeEvent(this, event.value), null);
|
||||
dialog.addSpinboxInput("Size", flow.sizes[event.value], 0.1, -1000.0, 1000.0, 3);
|
||||
addDialog(dialog);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(event.type == GUI_Changed) {
|
||||
if(event.caller.isChildOf(flowData)) {
|
||||
parseFlowData();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
vec2i size = AbsolutePosition.size;
|
||||
if(size != prevSize) {
|
||||
prevSize = size;
|
||||
if(rt !is null) {
|
||||
rt.size = size;
|
||||
}
|
||||
else {
|
||||
@rt = RenderTarget(size);
|
||||
rt.camera.yaw(pi * 0.45, true);
|
||||
rt.camera.pitch(pi * 0.1, true);
|
||||
}
|
||||
}
|
||||
|
||||
if((node is null || node.parent is null) && rt !is null) {
|
||||
if(delFlowIndex >= 0) {
|
||||
ps.removeFlow(delFlowIndex);
|
||||
flows.removeItem(delFlowIndex);
|
||||
switchToFlow(ps.flows[delFlowIndex], delFlowIndex);
|
||||
delFlowIndex = -1;
|
||||
}
|
||||
@node = playParticleSystem(ps, vec3d(), 20.f, rt.node);
|
||||
}
|
||||
|
||||
if(rt !is null) {
|
||||
rt.animate(frameLength);
|
||||
rt.draw(AbsolutePosition);
|
||||
}
|
||||
|
||||
Tab::draw();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,131 @@
|
||||
import tabs.Tab;
|
||||
import elements.GuiSkinElement;
|
||||
import elements.GuiListbox;
|
||||
import elements.GuiText;
|
||||
import elements.GuiPanel;
|
||||
|
||||
from tabs.tabbar import newTab, switchToTab;
|
||||
|
||||
Tab@ createSkinViewerTab() {
|
||||
return SkinViewer();
|
||||
}
|
||||
|
||||
class SkinViewerCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
Tab@ editor = createSkinViewerTab();
|
||||
newTab(editor);
|
||||
switchToTab(editor);
|
||||
}
|
||||
};
|
||||
|
||||
void init() {
|
||||
addConsoleCommand("skin_viewer", SkinViewerCommand());
|
||||
}
|
||||
|
||||
class SkinViewer : Tab {
|
||||
dictionary skinStyles;
|
||||
GuiListbox@ styles;
|
||||
GuiPanel@ leftPanel;
|
||||
|
||||
SkinStyle selStyle;
|
||||
uint eleCnt;
|
||||
array<GuiText@> skinHeaders;
|
||||
array<GuiSkinElement@> skinExamples;
|
||||
|
||||
void draw() {
|
||||
//Draw the background
|
||||
skin.draw(SS_DesignOverviewBG, SF_Normal, AbsolutePosition);
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
|
||||
SkinViewer() {
|
||||
super();
|
||||
title = "Skin Viewer";
|
||||
|
||||
@leftPanel = GuiPanel(this, Alignment(Left, Top, Right-0.25f, Bottom));
|
||||
|
||||
@styles = GuiListbox(this, Alignment(Right-0.25f, Top+0+4, Right-0-4, Bottom-0-4));
|
||||
styles.required = true;
|
||||
|
||||
updateStyles();
|
||||
updateAbsolutePosition();
|
||||
showSelectedStyle();
|
||||
}
|
||||
|
||||
void updateStyles() {
|
||||
getSkinStyles(skinStyles);
|
||||
styles.clearItems();
|
||||
dictionary_iterator style = skinStyles.iterator();
|
||||
string name; int64 value;
|
||||
while(style.iterate(name, value))
|
||||
styles.addItem(name);
|
||||
}
|
||||
|
||||
void addSkinExample(SkinStyle style, uint flags, Alignment@ alignment) {
|
||||
GuiSkinElement@ example = GuiSkinElement(leftPanel, recti(), 0);
|
||||
@example.alignment = alignment;
|
||||
example.style = style;
|
||||
example.flags = flags;
|
||||
skinExamples.insertLast(example);
|
||||
}
|
||||
|
||||
void showStyle(SkinStyle style) {
|
||||
//Remove old ones
|
||||
for(uint i = 0; i < skinExamples.length; ++i)
|
||||
skinExamples[i].remove();
|
||||
for(uint i = 0; i < skinHeaders.length; ++i)
|
||||
skinHeaders[i].remove();
|
||||
skinExamples.length = 0;
|
||||
skinHeaders.length = 0;
|
||||
selStyle = style;
|
||||
|
||||
//Add new ones
|
||||
eleCnt = skin.getStyleElementCount(style);
|
||||
int y = 0;
|
||||
for(uint i = 0; i < eleCnt; ++i) {
|
||||
uint flags = skin.getStyleElementFlags(style, i);
|
||||
|
||||
//Add header
|
||||
GuiText@ txt = GuiText(leftPanel, recti(), getElementFlagName(flags));
|
||||
@txt.alignment = Alignment(Left+4, Top+4 + y, Right-0.5f-4, Top+24 + y);
|
||||
skinHeaders.insertLast(txt);
|
||||
y += 32;
|
||||
|
||||
//Add examples
|
||||
addSkinExample(style, flags, Alignment(Left+4, Top+4 + y, Right-0.5f-4, Top+250 + y));
|
||||
|
||||
addSkinExample(style, flags, Alignment(Left+4, Top+254 + y, Left+0.0f+20, Top+270 + y));
|
||||
|
||||
addSkinExample(style, flags, Alignment(Left+24, Top+254 + y, Left+0.0f+88, Top+318 + y));
|
||||
|
||||
addSkinExample(style, flags, Alignment(Left+92, Top+254 + y, Left+0.0f+292, Top+276 + y));
|
||||
|
||||
y += 326;
|
||||
}
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
if(skinStyles.getSize() != getSkinStyleCount())
|
||||
updateStyles();
|
||||
if(skin.getStyleElementCount(selStyle) != eleCnt)
|
||||
showStyle(selStyle);
|
||||
}
|
||||
|
||||
void showSelectedStyle() {
|
||||
if(styles.selected >= 0) {
|
||||
int64 styleID;
|
||||
skinStyles.get(styles.getItem(styles.selected), styleID);
|
||||
showStyle(SkinStyle(styleID));
|
||||
}
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.type == GUI_Changed) {
|
||||
if(event.caller is styles) {
|
||||
showSelectedStyle();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
import tabs.Tab;
|
||||
from tabs.tabbar import newTab, switchToTab;
|
||||
|
||||
Tab@ createStatsTab() {
|
||||
return StatsTab();
|
||||
}
|
||||
|
||||
class StatsTabCommand : ConsoleCommand {
|
||||
void execute(const string& args) {
|
||||
Tab@ editor = createStatsTab();
|
||||
newTab(editor);
|
||||
switchToTab(editor);
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
addConsoleCommand("stats_tab", StatsTabCommand());
|
||||
}
|
||||
|
||||
array<int> valuePoints;
|
||||
int findNearest(int start, int bound) {
|
||||
int above = start;
|
||||
bool validAbove = false;
|
||||
|
||||
while(!validAbove && above > 0) {
|
||||
validAbove = true;
|
||||
|
||||
for(uint i = 0, cnt = valuePoints.length; i < cnt; ++i) {
|
||||
int pt = valuePoints[i];
|
||||
if(above > pt - 24 && above < pt + 24) {
|
||||
validAbove = false;
|
||||
above = pt - 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int below = start;
|
||||
bool validBelow = false;
|
||||
|
||||
while(!validBelow && below < bound) {
|
||||
validBelow = true;
|
||||
|
||||
for(uint i = 0, cnt = valuePoints.length; i < cnt; ++i) {
|
||||
int pt = valuePoints[i];
|
||||
if(below > pt - 24 && below < pt + 24) {
|
||||
validBelow = false;
|
||||
below = pt + 24;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(validAbove) {
|
||||
if(!validBelow) {
|
||||
start = above;
|
||||
}
|
||||
else {
|
||||
int aDiff = abs(above - start);
|
||||
int bDiff = abs(below - start);
|
||||
if(aDiff < bDiff)
|
||||
start = above;
|
||||
else
|
||||
start = below;
|
||||
}
|
||||
}
|
||||
else if(validBelow) {
|
||||
start = below;
|
||||
}
|
||||
|
||||
valuePoints.insertLast(start);
|
||||
return start;
|
||||
}
|
||||
|
||||
interface StatTracker {
|
||||
void update();
|
||||
void draw(const Skin@ skin, recti bound);
|
||||
};
|
||||
|
||||
class StatsTab : Tab {
|
||||
uint endTime = 0;
|
||||
array<StatTracker@> stats;
|
||||
|
||||
double nextUpdate = 0;
|
||||
|
||||
StatsTab() {
|
||||
super();
|
||||
title = locale::STATS_TAB;
|
||||
|
||||
stats.insertLast(IntegerStat(stat::Planets, Color(0xff9955ff), 15, " Planets"));
|
||||
stats.insertLast(IntegerStat(stat::Ships, Color(0x4422ffff), 15, " Ships"));
|
||||
stats.insertLast(IntegerStat(stat::ShipsDestroyed, Color(0xff2222ff), 15, " Killed"));
|
||||
stats.insertLast(FloatStat(stat::Budget, Color(0x22ff22ff), 60, " $"));
|
||||
stats.insertLast(FloatStat(stat::Energy, Color(0xffff22ff), 60, " Energy"));
|
||||
stats.insertLast(FloatStat(stat::FTL, Color(0xff22ffff), 60, " FTL"));
|
||||
stats.insertLast(IntegerStat(stat::Influence, Color(0xffffffff), 60, " Influence"));
|
||||
}
|
||||
|
||||
void updateStats() {
|
||||
for(uint i = 0, end = stats.length; i < end; ++i)
|
||||
stats[i].update();
|
||||
}
|
||||
|
||||
void draw() {
|
||||
if(gameTime > nextUpdate) {
|
||||
nextUpdate = gameTime + 1.0;
|
||||
updateStats();
|
||||
}
|
||||
|
||||
endTime = uint(gameTime);
|
||||
|
||||
valuePoints.length = 0;
|
||||
|
||||
skin.draw(SS_DiplomacyBG, SF_Normal, AbsolutePosition);
|
||||
|
||||
recti statBound = recti_centered(AbsolutePosition, AbsolutePosition.size - vec2i(64,16)) - vec2i(60,0);
|
||||
skin.draw(SS_Panel, SF_Normal, statBound);
|
||||
|
||||
statBound = recti_centered(statBound, statBound.size - vec2i(1,1));
|
||||
|
||||
for(uint i = 0, end = stats.length; i < end; ++i)
|
||||
stats[i].draw(skin, statBound);
|
||||
|
||||
Tab::draw();
|
||||
}
|
||||
}
|
||||
|
||||
final class IntegerStat : StatTracker {
|
||||
int minVal = 0, maxVal = 100;
|
||||
stat::EmpireStat stat;
|
||||
Color color;
|
||||
int filterDuration = 1;
|
||||
array<vec2i> data;
|
||||
string suffix;
|
||||
|
||||
IntegerStat(stat::EmpireStat Stat, const Color& col, int FilterDuration, string Suffix) {
|
||||
stat = Stat;
|
||||
color = col;
|
||||
filterDuration = FilterDuration;
|
||||
suffix = Suffix;
|
||||
}
|
||||
|
||||
void update() {
|
||||
minVal = 0;
|
||||
maxVal = 100;
|
||||
data.length = 0;
|
||||
|
||||
StatHistory history(playerEmpire, stat);
|
||||
|
||||
while(history.advance(1)) {
|
||||
int val = history.intVal;
|
||||
|
||||
if(val > maxVal)
|
||||
maxVal = val;
|
||||
if(val < minVal)
|
||||
minVal = val;
|
||||
|
||||
data.insertLast(vec2i(int(history.time), val));
|
||||
}
|
||||
}
|
||||
|
||||
void draw(const Skin@ skin, recti bound) {
|
||||
if(data.length == 0) {
|
||||
drawLine(bound.topLeft + vec2i(0, bound.height), bound.botRight, color);
|
||||
return;
|
||||
}
|
||||
|
||||
vec2i size = bound.size;
|
||||
|
||||
float endTime = float(gameTime);
|
||||
vec2f factor(1.f / endTime, -1.f / float(maxVal));
|
||||
|
||||
int secondStep = int(max(float(size.x) / endTime, 1.f));
|
||||
int timeStep = int(max(float(size.x * filterDuration) / endTime, float(filterDuration)));
|
||||
vec2i corner = bound.topLeft + vec2i(0,size.y);
|
||||
vec2i prev = corner;
|
||||
|
||||
//Draw segments
|
||||
for(uint i = 0, cnt = data.length; i < cnt; ++i) {
|
||||
vec2f ptf = vec2f(data[i]);
|
||||
vec2i pt = corner + vec2i(float(size.x) * ptf.x * factor.x, float(size.y) * ptf.y * factor.y);
|
||||
|
||||
if(i != 0 && prev.x >= pt.x - timeStep) {
|
||||
drawLine(prev, pt, color);
|
||||
}
|
||||
else {
|
||||
vec2i med = vec2i(pt.x - secondStep, prev.y);
|
||||
drawLine(prev, med, color);
|
||||
drawLine(med, pt, color);
|
||||
}
|
||||
prev = pt;
|
||||
}
|
||||
|
||||
//Draw final segment until current moment if nothing has changed
|
||||
if(data[data.length - 1].x < int(endTime)) {
|
||||
vec2i end = vec2i(corner.x + size.x, prev.y);
|
||||
drawLine(prev, end, color);
|
||||
prev = end;
|
||||
}
|
||||
|
||||
vec2i around = prev + vec2i(6, 0);
|
||||
around.y = findNearest(around.y - bound.topLeft.y, size.height) + bound.topLeft.y - 8;
|
||||
skin.draw(FT_Normal, around, string(data[data.length - 1].y) + suffix, color);
|
||||
}
|
||||
};
|
||||
|
||||
final class FloatStat : StatTracker {
|
||||
float minVal = 0, maxVal = 100.f;
|
||||
stat::EmpireStat stat;
|
||||
Color color;
|
||||
int filterDuration = 1;
|
||||
array<vec2f> data;
|
||||
string suffix;
|
||||
|
||||
FloatStat(stat::EmpireStat Stat, const Color& col, int FilterDuration, string Suffix) {
|
||||
stat = Stat;
|
||||
color = col;
|
||||
filterDuration = FilterDuration;
|
||||
suffix = Suffix;
|
||||
}
|
||||
|
||||
void update() {
|
||||
minVal = 0;
|
||||
maxVal = 100;
|
||||
data.length = 0;
|
||||
|
||||
StatHistory history(playerEmpire, stat);
|
||||
|
||||
while(history.advance(1)) {
|
||||
float val = history.floatVal;
|
||||
|
||||
if(val > maxVal)
|
||||
maxVal = val;
|
||||
if(val < minVal)
|
||||
minVal = val;
|
||||
|
||||
data.insertLast(vec2f(history.time, val));
|
||||
}
|
||||
}
|
||||
|
||||
void draw(const Skin@ skin, recti bound) {
|
||||
if(data.length == 0) {
|
||||
drawLine(bound.topLeft + vec2i(0, bound.height), bound.botRight, color);
|
||||
return;
|
||||
}
|
||||
|
||||
vec2i size = bound.size;
|
||||
|
||||
float endTime = float(gameTime);
|
||||
vec2f factor(1.f / endTime, -1.f / float(maxVal));
|
||||
|
||||
int secondStep = int(max(float(size.x) / endTime, 1.f));
|
||||
int timeStep = int(max(float(size.x * filterDuration) / endTime, float(filterDuration)));
|
||||
vec2i corner = bound.topLeft + vec2i(0,size.y);
|
||||
vec2i prev = corner;
|
||||
|
||||
//Draw segments
|
||||
for(uint i = 0, cnt = data.length; i < cnt; ++i) {
|
||||
vec2f ptf = data[i];
|
||||
vec2i pt = corner + vec2i(float(size.x) * ptf.x * factor.x, float(size.y) * ptf.y * factor.y);
|
||||
|
||||
if(i != 0 && prev.x >= pt.x - timeStep) {
|
||||
drawLine(prev, pt, color);
|
||||
}
|
||||
else {
|
||||
vec2i med = vec2i(pt.x - secondStep, prev.y);
|
||||
drawLine(prev, med, color);
|
||||
drawLine(med, pt, color);
|
||||
}
|
||||
prev = pt;
|
||||
}
|
||||
|
||||
//Draw final segment until current moment if nothing has changed
|
||||
if(data[data.length - 1].x < int(endTime)) {
|
||||
vec2i end = vec2i(corner.x + size.x, prev.y);
|
||||
drawLine(prev, end, color);
|
||||
prev = end;
|
||||
}
|
||||
|
||||
|
||||
vec2i around = prev + vec2i(6, 0);
|
||||
around.y = findNearest(around.y - bound.topLeft.y, size.height) + bound.topLeft.y - 8;
|
||||
skin.draw(FT_Normal, around, standardize(data[data.length - 1].y) + suffix, color);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,371 @@
|
||||
import tabs.Tab;
|
||||
import elements.GuiAccordion;
|
||||
import elements.GuiPanel;
|
||||
import elements.GuiButton;
|
||||
import elements.GuiText;
|
||||
import elements.GuiSprite;
|
||||
import elements.GuiMarkupText;
|
||||
from elements.Gui3DObject import Gui3DObject, ObjectAction;
|
||||
from elements.GuiResources import GuiResources;
|
||||
import planet_types;
|
||||
import planet_levels;
|
||||
import resources;
|
||||
import systems;
|
||||
import util.formatting;
|
||||
from obj_selection import selectObject;
|
||||
import overlays.Popup;
|
||||
from overlays.PlanetPopup import PlanetPopup;
|
||||
|
||||
from overlays.ContextMenu import openContextMenu;
|
||||
from tabs.tabbar import get_ActiveTab, browseTab, popTab;
|
||||
import void zoomTabTo(Object@ obj) from "tabs.GalaxyTab";
|
||||
import void openOverlay(Object@ obj) from "tabs.GalaxyTab";
|
||||
|
||||
const vec2i SIZE_PER_PX(5.0, 8.0);
|
||||
|
||||
final class SystemBox {
|
||||
SystemDesc@ desc;
|
||||
recti pos;
|
||||
};
|
||||
|
||||
final class LineBox {
|
||||
SystemBox@ from;
|
||||
SystemBox@ to;
|
||||
vec2i fromPos;
|
||||
vec2i toPos;
|
||||
recti area;
|
||||
};
|
||||
|
||||
array<SystemBox> systemBoxes;
|
||||
array<LineBox@> systemLines;
|
||||
void createSystemBoxes() {
|
||||
uint sysCnt = systemCount;
|
||||
systemBoxes.length = sysCnt;
|
||||
for(uint i = 0,cnt = sysCnt; i < cnt; ++i) {
|
||||
SystemDesc@ desc = getSystem(i);
|
||||
vec2i pos = vec2i(desc.position.x / SIZE_PER_PX.x, desc.position.z / SIZE_PER_PX.y);
|
||||
vec2i size = vec2i(desc.object.planetCount*160+40, 220);
|
||||
|
||||
SystemBox@ box = systemBoxes[i];
|
||||
@box.desc = desc;
|
||||
box.pos = recti_area(pos-(size/2), size);
|
||||
|
||||
for(uint j = 0, jcnt = desc.adjacent.length; j < jcnt; ++j) {
|
||||
uint adj = desc.adjacent[j];
|
||||
if(adj < i) {
|
||||
LineBox line;
|
||||
@line.from = box;
|
||||
@line.to = systemBoxes[adj];
|
||||
line.fromPos = line.from.pos.center;
|
||||
line.toPos = line.to.pos.center;
|
||||
|
||||
line.area.topLeft.x = min(line.fromPos.x - 5, line.toPos.x - 5);
|
||||
line.area.topLeft.y = min(line.fromPos.y - 5, line.toPos.y - 5);
|
||||
line.area.botRight.x = max(line.fromPos.x + 5, line.toPos.x + 5);
|
||||
line.area.botRight.y = max(line.fromPos.y + 5, line.toPos.y + 5);
|
||||
|
||||
systemLines.insertLast(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SystemTab : Tab {
|
||||
Region@ sys;
|
||||
const SystemDesc@ desc;
|
||||
|
||||
array<PlanetPopup@> popups;
|
||||
|
||||
bool heldLeft = false;
|
||||
bool dragging = false;
|
||||
bool moved = false;
|
||||
vec2d scroll;
|
||||
vec2i dragStart;
|
||||
vec2i prevScroll;
|
||||
|
||||
set_int instantiated;
|
||||
|
||||
SystemTab() {
|
||||
super();
|
||||
title = locale::SYSTEM_TAB;
|
||||
if(systemBoxes.length == 0)
|
||||
createSystemBoxes();
|
||||
}
|
||||
|
||||
void display(Object@ system) {
|
||||
for(uint i = 0, cnt = systemBoxes.length; i < cnt; ++i) {
|
||||
SystemBox@ box = systemBoxes[i];
|
||||
if(system is box.desc.object) {
|
||||
scroll = vec2d(box.pos.center);
|
||||
prevScroll = vec2i(scroll);
|
||||
moved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateAbsolutePosition() {
|
||||
Tab::updateAbsolutePosition();
|
||||
}
|
||||
|
||||
Color get_activeColor() {
|
||||
return Color(0xfcb44eff);
|
||||
}
|
||||
|
||||
Color get_inactiveColor() {
|
||||
return Color(0xff9600ff);
|
||||
}
|
||||
|
||||
Color get_seperatorColor() {
|
||||
return Color(0x8c642bff);
|
||||
}
|
||||
|
||||
TabCategory get_category() {
|
||||
return TC_Galaxy;
|
||||
}
|
||||
|
||||
void instantiate(const recti& relArea, SystemBox@ box) {
|
||||
instantiated.insert(box.desc.index);
|
||||
|
||||
vec2i startPos = box.pos.topLeft - relArea.topLeft + vec2i(20, 36);
|
||||
uint plCnt = box.desc.object.planetCount;
|
||||
|
||||
for(uint i = 0; i < plCnt; ++i) {
|
||||
Planet@ pl = box.desc.object.planets[i];
|
||||
PlanetPopup pop(this);
|
||||
pop.set(pl);
|
||||
pop.mouseLinked = false;
|
||||
pop.isSelectable = true;
|
||||
pop.update();
|
||||
|
||||
pop.visible = pl.visible;
|
||||
pop.position = startPos + vec2i(160*i, 0);
|
||||
popups.insertLast(pop);
|
||||
}
|
||||
}
|
||||
|
||||
void deinstantiate(SystemBox@ box) {
|
||||
instantiated.erase(box.desc.index);
|
||||
for(int i = popups.length - 1; i >= 0; --i) {
|
||||
if(popups[i].pl.region is box.desc.object) {
|
||||
popups[i].remove();
|
||||
popups.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void movePopups() {
|
||||
vec2i roundedScroll(scroll);
|
||||
if(prevScroll == roundedScroll)
|
||||
return;
|
||||
|
||||
vec2i moveBy = prevScroll - roundedScroll;
|
||||
prevScroll = roundedScroll;
|
||||
for(uint i = 0, cnt = popups.length; i < cnt; ++i) {
|
||||
PlanetPopup@ pop = popups[i];
|
||||
pop.move(moveBy);
|
||||
}
|
||||
}
|
||||
|
||||
void updatePopups() {
|
||||
for(uint i = 0, cnt = popups.length; i < cnt; ++i) {
|
||||
popups[i].visible = popups[i].pl.visible;
|
||||
if(popups[i].visible)
|
||||
popups[i].update();
|
||||
}
|
||||
}
|
||||
|
||||
IGuiElement@ elementFromPosition(const vec2i& pos) {
|
||||
if(dragging) {
|
||||
//Cannot access inner elements while dragging,
|
||||
if(AbsoluteClipRect.isWithin(pos))
|
||||
return this;
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return Tab::elementFromPosition(pos);
|
||||
}
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& evt) {
|
||||
switch(evt.type) {
|
||||
case GUI_Clicked: {
|
||||
PlanetPopup@ pop = cast<PlanetPopup>(evt.caller);
|
||||
if(pop !is null && pop.parent is this) {
|
||||
switch(evt.value) {
|
||||
case PA_Select:
|
||||
selectObject(pop.pl, shiftKey);
|
||||
return true;
|
||||
case PA_Manage:
|
||||
popTab(this);
|
||||
zoomTabTo(pop.pl);
|
||||
openOverlay(pop.pl);
|
||||
return true;
|
||||
case PA_Zoom:
|
||||
popTab(this);
|
||||
zoomTabTo(pop.pl);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return Tab::onGuiEvent(evt);
|
||||
}
|
||||
|
||||
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) {
|
||||
if(previous !is null) {
|
||||
popTab(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return Tab::onKeyEvent(event, source);
|
||||
}
|
||||
|
||||
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
|
||||
if(event.type == MET_Button_Up) {
|
||||
if(event.button == 1) {
|
||||
if(previous !is null) {
|
||||
popTab(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(source is this) {
|
||||
switch(event.type) {
|
||||
case MET_Button_Down:
|
||||
if(event.button == 0) {
|
||||
heldLeft = true;
|
||||
dragStart = vec2i(event.x, event.y);
|
||||
}
|
||||
break;
|
||||
case MET_Moved: {
|
||||
vec2i mouse = vec2i(event.x, event.y);
|
||||
vec2i d = mouse - dragStart;
|
||||
|
||||
if(dragging) {
|
||||
scroll -= vec2d(double(d.x), double(d.y));
|
||||
dragStart = mouse;
|
||||
moved = true;
|
||||
}
|
||||
else if(heldLeft) {
|
||||
if(abs(d.x) >= 5 || abs(d.y) >= 5) {
|
||||
dragging = true;
|
||||
moved = true;
|
||||
scroll -= vec2d(double(d.x), double(d.y));
|
||||
dragStart = mouse;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case MET_Button_Up:
|
||||
if(event.button == 0) {
|
||||
if(!dragging) {
|
||||
//Do stuff
|
||||
}
|
||||
heldLeft = false;
|
||||
dragging = false;
|
||||
dragStart = vec2i();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Tab::onMouseEvent(event, source);
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
if(!visible)
|
||||
return;
|
||||
|
||||
if(moved) {
|
||||
movePopups();
|
||||
moved = false;
|
||||
|
||||
recti relArea = recti_area(vec2i(scroll) - (size/2), size);
|
||||
for(uint i = 0, cnt = systemBoxes.length; i < cnt; ++i) {
|
||||
SystemBox@ box = systemBoxes[i];
|
||||
bool inst = instantiated.contains(i);
|
||||
|
||||
if(box.pos.overlaps(relArea)) {
|
||||
if(!inst)
|
||||
instantiate(relArea, box);
|
||||
}
|
||||
else {
|
||||
if(inst)
|
||||
deinstantiate(box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePopups();
|
||||
}
|
||||
|
||||
void drawSystemLine(const recti& relArea, LineBox@ box) {
|
||||
Color col(0xffffff30);
|
||||
vec2i from = box.fromPos - relArea.topLeft + AbsolutePosition.topLeft;
|
||||
vec2i to = box.toPos - relArea.topLeft + AbsolutePosition.topLeft;
|
||||
|
||||
bool canTrade = (box.to.desc.object.TradeMask & playerEmpire.mask) != 0
|
||||
|| (box.from.desc.object.TradeMask & playerEmpire.mask) != 0;
|
||||
if(canTrade) {
|
||||
col = playerEmpire.color;
|
||||
col.a = 0x60;
|
||||
}
|
||||
|
||||
drawLine(from, to, col, 10);
|
||||
}
|
||||
|
||||
void drawSystemBox(const recti& relArea, SystemBox@ box) {
|
||||
recti bpos = box.pos - relArea.topLeft + AbsolutePosition.topLeft;
|
||||
const Font@ ft = skin.getFont(FT_Medium);
|
||||
Color col;
|
||||
|
||||
Empire@ prim = box.desc.object.visiblePrimaryEmpire;
|
||||
if(prim !is null)
|
||||
col = prim.color;
|
||||
|
||||
skin.draw(SS_SystemPanel, SF_Normal, bpos, col);
|
||||
ft.draw(bpos.topLeft+vec2i(8, 5), box.desc.name);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_SystemListBG, SF_Normal, AbsolutePosition);
|
||||
|
||||
recti relArea = recti_area(vec2i(scroll) - (size/2), size);
|
||||
|
||||
//Draw the lines
|
||||
for(uint i = 0, cnt = systemLines.length; i < cnt; ++i) {
|
||||
LineBox@ box = systemLines[i];
|
||||
if(box.area.overlaps(relArea))
|
||||
drawSystemLine(relArea, box);
|
||||
}
|
||||
|
||||
//Draw the boxes
|
||||
for(uint i = 0, cnt = systemBoxes.length; i < cnt; ++i) {
|
||||
SystemBox@ box = systemBoxes[i];
|
||||
if(box.pos.overlaps(relArea))
|
||||
drawSystemBox(relArea, box);
|
||||
}
|
||||
|
||||
Tab::draw();
|
||||
}
|
||||
};
|
||||
|
||||
Tab@ createSystemTab() {
|
||||
return SystemTab();
|
||||
}
|
||||
|
||||
Tab@ createSystemTab(Object@ system) {
|
||||
SystemTab tab;
|
||||
tab.display(system);
|
||||
|
||||
return tab;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import elements.BaseGuiElement;
|
||||
|
||||
enum TabCategory {
|
||||
TC_Invalid,
|
||||
TC_Other,
|
||||
TC_Galaxy,
|
||||
TC_Designs,
|
||||
TC_Home,
|
||||
TC_Research,
|
||||
TC_Graphics,
|
||||
TC_Diplomacy,
|
||||
TC_Planets,
|
||||
TC_Wiki,
|
||||
TC_Attitudes,
|
||||
};
|
||||
|
||||
class Tab : BaseGuiElement {
|
||||
string title;
|
||||
Tab@ previous;
|
||||
IGuiElement@ prevFocus = this;
|
||||
bool initialized = false;
|
||||
bool flashing = false;
|
||||
bool locked = false;
|
||||
|
||||
Tab() {
|
||||
super(null, recti());
|
||||
visible = false;
|
||||
}
|
||||
|
||||
//Called when the tab is first created and positioned
|
||||
void init() {
|
||||
}
|
||||
|
||||
//Called when the tab is asked to close
|
||||
// hide() will also be called first when closing a visible tab
|
||||
void close() {
|
||||
@prevFocus = null;
|
||||
}
|
||||
|
||||
//Called when a closed tab needs to be reopened
|
||||
// show() will also be called afterwards
|
||||
void reopen() {
|
||||
}
|
||||
|
||||
//Call to flash the tab in the tabbar until focused
|
||||
void flash() {
|
||||
if(!visible)
|
||||
flashing = true;
|
||||
}
|
||||
|
||||
//Get the title for the tab
|
||||
string& get_title() {
|
||||
return title;
|
||||
}
|
||||
|
||||
void set_title(const string& newTitle) {
|
||||
title = newTitle;
|
||||
}
|
||||
|
||||
//Get the icon for the tab
|
||||
Sprite get_icon() {
|
||||
return Sprite();
|
||||
}
|
||||
|
||||
//Get the colors for the tab
|
||||
Color get_activeColor() {
|
||||
return Color(0xffffffff);
|
||||
}
|
||||
|
||||
Color get_inactiveColor() {
|
||||
return Color(0xffffffff);
|
||||
}
|
||||
|
||||
Color get_seperatorColor() {
|
||||
return Color(0xffffffff);
|
||||
}
|
||||
|
||||
//Get the tab category
|
||||
TabCategory get_category() {
|
||||
return TC_Other;
|
||||
}
|
||||
|
||||
//Show the tab and do any necessary syncing
|
||||
void show() {
|
||||
updateAbsolutePosition();
|
||||
visible = true;
|
||||
flashing = false;
|
||||
setGuiFocus(prevFocus);
|
||||
}
|
||||
|
||||
//Hide the tab and do any necessary cleanup
|
||||
void hide() {
|
||||
@prevFocus = getGuiFocus();
|
||||
visible = false;
|
||||
}
|
||||
|
||||
//Tick the tab. This is called regardless
|
||||
//of whether the tab is visible, so the tab itself
|
||||
//needs to figure what it needs to update
|
||||
void tick(double time) {
|
||||
}
|
||||
|
||||
//Called before any ticks, rendering or node animation
|
||||
//Should update camera position for the next frame when necessary
|
||||
void preRender(double time) {
|
||||
}
|
||||
|
||||
//Called when the 3D scene should be rendered,
|
||||
//if any exists in this tab
|
||||
void render(double time) {
|
||||
}
|
||||
|
||||
//Called whenever an object is clicked
|
||||
//Return true to prevent whatever other behaviors would normally occur
|
||||
bool objectInteraction(Object& object, uint mouseButton, bool doubleClick) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Called for utilising the joystick. Return true if the joystick is grabbed.
|
||||
bool joystick(Joystick& joystick) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void save(SaveFile& file) {
|
||||
}
|
||||
|
||||
void load(SaveFile& file) {
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,619 @@
|
||||
#priority init -200
|
||||
import tabs.Tab;
|
||||
import tabs.tabbar;
|
||||
import elements.GuiButton;
|
||||
import elements.GuiText;
|
||||
import elements.GuiMarkupText;
|
||||
import elements.GuiEmpire;
|
||||
import elements.GuiSkinElement;
|
||||
import elements.GuiListbox;
|
||||
import elements.GuiPanel;
|
||||
import elements.MarkupTooltip;
|
||||
import util.formatting;
|
||||
import icons;
|
||||
from multiplayer import wantSpectator;
|
||||
|
||||
class VictoryTab : Tab {
|
||||
GuiText@ heading;
|
||||
|
||||
GuiSkinElement@ victorPanel;
|
||||
GuiMarkupText@ description;
|
||||
GuiEmpire@ victor;
|
||||
|
||||
GuiSkinElement@ buttonBar;
|
||||
GuiPanel@ buttonPanel;
|
||||
|
||||
GuiButton@ rankingsButton;
|
||||
GuiListbox@ rankings;
|
||||
|
||||
array<StatPage@> stats;
|
||||
array<GuiButton@> statButtons;
|
||||
Alignment@ statAlign;
|
||||
|
||||
Sprite typeIcon;
|
||||
|
||||
VictoryTab() {
|
||||
super();
|
||||
|
||||
@heading = GuiText(this, Alignment(Left, Top+30, Right, Top+90));
|
||||
heading.horizAlign = 0.5;
|
||||
heading.font = FT_Big;
|
||||
heading.color = colors::Green;
|
||||
heading.stroke = colors::Black;
|
||||
|
||||
@victorPanel = GuiSkinElement(this, Alignment(Left+0.1f, Top+90, Right-0.1f, Top+242), SS_Panel);
|
||||
|
||||
@victor = GuiEmpire(victorPanel, Alignment(Left+6, Top+6, Width=140, Height=140));
|
||||
|
||||
@description = GuiMarkupText(victorPanel, Alignment(Left+220, Top+20, Right-6, Bottom-6));
|
||||
description.defaultFont = FT_Subtitle;
|
||||
|
||||
@buttonBar = GuiSkinElement(this, Alignment(Left+0.1f, Top+242+20, Right-0.1f, Height=42), SS_PlainBox);
|
||||
@buttonPanel = GuiPanel(this, Alignment(Left+0.1f, Top+242, Right-0.1f, Height=62));
|
||||
buttonPanel.vertType = ST_Never;
|
||||
|
||||
@statAlign = Alignment(Left+0.1f, Top+242+62, Right-0.1f, Bottom-30);
|
||||
|
||||
if(!playerEmpire.valid) {
|
||||
victorPanel.visible = false;
|
||||
heading.visible = false;
|
||||
buttonBar.alignment.top.pixels = 32;
|
||||
buttonPanel.alignment.top.pixels = 12;
|
||||
statAlign.top.pixels = 74;
|
||||
}
|
||||
|
||||
@rankingsButton = GuiButton(buttonPanel, Alignment(Left, Bottom-40, Width=135, Height=40), locale::V_RANKINGS);
|
||||
rankingsButton.buttonIcon = Sprite(material::PointsIcon);
|
||||
rankingsButton.toggleButton = true;
|
||||
rankingsButton.pressed = true;
|
||||
rankingsButton.font = FT_Bold;
|
||||
@rankings = GuiListbox(this, statAlign);
|
||||
rankings.itemHeight = 40;
|
||||
rankings.disabled = true;
|
||||
rankings.vertPadding = 8;
|
||||
rankings.style = SS_Panel;
|
||||
|
||||
addStat(ST_Int, stat::Points, 60, locale::STAT_POINTS, Sprite(material::PointsIcon), 2000);
|
||||
addStat(ST_Float, stat::Military, 60, locale::STAT_STRENGTH, icons::Strength, 5000);
|
||||
addStat(ST_Int, stat::Planets, 15, locale::STAT_PLANETS, icons::Planet, 10);
|
||||
addStat(ST_Int, stat::Ships, 15, locale::STAT_SHIPS, icons::Defense, 10);
|
||||
addStat(ST_Float, stat::Budget, 60, locale::STAT_BUDGET, icons::Money, 2000);
|
||||
addStat(ST_Float, stat::NetBudget, 60, locale::STAT_NET_BUDGET, icons::Money, 2000);
|
||||
addStat(ST_Float, stat::EnergyIncome, 60, locale::STAT_ENERGY_INCOME, icons::Energy, 2);
|
||||
addStat(ST_Float, stat::InfluenceIncome, 60, locale::STAT_INFLUENCE_INCOME, icons::Influence, 3/60.0);
|
||||
addStat(ST_Float, stat::ResearchIncome, 60, locale::STAT_RESEARCH_INCOME, icons::Research, 2);
|
||||
addStat(ST_Float, stat::ResearchTotal, 60, locale::STAT_RESEARCH_TOTAL, icons::Research, 1000);
|
||||
|
||||
updateAbsolutePosition();
|
||||
update();
|
||||
|
||||
locked = true;
|
||||
}
|
||||
|
||||
void addStat(StatType type, stat::EmpireStat Stat, int FilterDuration, const string& name, const Sprite& icon, double baseMax = 1) {
|
||||
StatPage page(this, statAlign, type, Stat, FilterDuration, name, baseMax);
|
||||
page.visible = false;
|
||||
stats.insertLast(page);
|
||||
|
||||
int index = stats.length;
|
||||
GuiButton btn(buttonPanel, Alignment(Left+135*index, Bottom-40, Width=135, Height=40), name);
|
||||
btn.toggleButton = true;
|
||||
btn.pressed = false;
|
||||
btn.buttonIcon = icon;
|
||||
btn.font = FT_Bold;
|
||||
statButtons.insertLast(btn);
|
||||
}
|
||||
|
||||
Color get_activeColor() {
|
||||
return Color(0xffc283ff);
|
||||
}
|
||||
|
||||
Color get_inactiveColor() {
|
||||
return Color(0xffb900ff);
|
||||
}
|
||||
|
||||
Color get_seperatorColor() {
|
||||
return Color(0x8d7949ff);
|
||||
}
|
||||
|
||||
Sprite get_icon() {
|
||||
return typeIcon;
|
||||
}
|
||||
|
||||
void switchPage(uint page) {
|
||||
rankings.visible = page == 0;
|
||||
rankingsButton.pressed = page == 0;
|
||||
|
||||
for(uint i = 0, cnt = stats.length; i < cnt; ++i) {
|
||||
stats[i].visible = i+1 == page;
|
||||
statButtons[i].pressed = i+1 == page;
|
||||
}
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
update();
|
||||
}
|
||||
|
||||
int lastUpdate = systemTime - 1000;
|
||||
bool fullUpdate = true;
|
||||
uint nextUpdateIndex = 0;
|
||||
|
||||
void update() {
|
||||
if(systemTime - lastUpdate < 1000)
|
||||
return;
|
||||
lastUpdate = systemTime;
|
||||
|
||||
Empire@ winner = playerEmpire;
|
||||
int myVictory = playerEmpire.Victory;
|
||||
|
||||
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
||||
if(getEmpire(i).Victory == 1) {
|
||||
@winner = getEmpire(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Show the appropriate victory message
|
||||
if(myVictory == 1) {
|
||||
heading.text = locale::V_WON_TITLE;
|
||||
heading.color = colors::Green;
|
||||
description.text = format(locale::V_WON_TEXT, formatEmpireName(winner), formatEmpireName(playerEmpire));
|
||||
typeIcon = Sprite(material::PointsIcon);
|
||||
}
|
||||
else if(myVictory == -2 && playerEmpire.SubjugatedBy.Victory == 1) {
|
||||
heading.text = locale::V_LESSER_TITLE;
|
||||
heading.color = Color(0xfff500ff);
|
||||
description.text = format(locale::V_LESSER_TEXT, formatEmpireName(winner), formatEmpireName(playerEmpire));
|
||||
typeIcon = Sprite(material::PointsIcon);
|
||||
}
|
||||
else {
|
||||
heading.text = locale::V_LOST_TITLE;
|
||||
heading.color = colors::Red;
|
||||
description.text = format(locale::V_LOST_TEXT, formatEmpireName(winner), formatEmpireName(playerEmpire));
|
||||
typeIcon = Sprite(material::SystemUnderAttack);
|
||||
}
|
||||
|
||||
if(victorPanel.visible)
|
||||
title = heading.text;
|
||||
else
|
||||
title = locale::V_SCORES;
|
||||
|
||||
//Update rankings
|
||||
array<EmpSorter> sorted;
|
||||
for(uint i = 0, cnt = getEmpireCount(); i < cnt; ++i) {
|
||||
Empire@ other = getEmpire(i);
|
||||
if(other.major) {
|
||||
EmpSorter sort;
|
||||
@sort.emp = other;
|
||||
sorted.insertLast(sort);
|
||||
}
|
||||
}
|
||||
sorted.sortAsc();
|
||||
|
||||
rankings.clearItems();
|
||||
for(uint i = 0, cnt = sorted.length; i < cnt; ++i) {
|
||||
Empire@ emp = sorted[i].emp;
|
||||
string text = "";
|
||||
|
||||
text += format(" #$1", toString(i+1));
|
||||
text += format("[offset=100][img=$2;28;$3/] $1[/offset]", formatEmpireName(emp),
|
||||
getSpriteDesc(Sprite(emp.flag)), toString(emp.color));
|
||||
|
||||
if(hasGameEnded()) {
|
||||
string victory = "";
|
||||
if(emp.SubjugatedBy !is null)
|
||||
victory = format("[color=#aaa]$1[/color]", format(locale::V_SUBJUGATED, formatEmpireName(emp.SubjugatedBy)));
|
||||
else if(emp.Victory == 1)
|
||||
victory = format("[color=#0f0]$1[/color]", locale::V_WON_TITLE);
|
||||
else
|
||||
victory = format("[color=#f00]$1[/color]", locale::V_LOST_TITLE);
|
||||
text += format("[offset=450]$1[/offset]", victory);
|
||||
}
|
||||
else {
|
||||
string victory;
|
||||
if(emp.SubjugatedBy !is null)
|
||||
victory = format("[color=#aaa]$1[/color]", format(locale::V_SUBJUGATED, formatEmpireName(emp.SubjugatedBy)));
|
||||
text += format("[offset=450]$1[/offset]", victory);
|
||||
}
|
||||
|
||||
text += format("[offset=880]$1[/offset]", format(locale::EMPIRE_POINTS, toString(emp.points.value)));
|
||||
rankings.addItem(GuiMarkupListText(text, FT_Subtitle));
|
||||
}
|
||||
|
||||
//Update stats data
|
||||
if(fullUpdate) {
|
||||
fullUpdate = false;
|
||||
for(uint i = 0, cnt = stats.length; i < cnt; ++i)
|
||||
stats[i].update();
|
||||
}
|
||||
else {
|
||||
//TODO: Even this probably isn't necessary, make sure
|
||||
stats[(nextUpdateIndex++) % stats.length].update();
|
||||
}
|
||||
|
||||
victorPanel.color = winner.color;
|
||||
@victor.empire = winner;
|
||||
|
||||
if(!victorPanel.visible && playerEmpire.valid) {
|
||||
locked = false;
|
||||
shownVictory = false;
|
||||
closeTab(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
|
||||
return BaseGuiElement::onKeyEvent(event, source);
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.type == GUI_Clicked) {
|
||||
if(event.caller is rankingsButton) {
|
||||
switchPage(0);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
for(uint i = 0, cnt = statButtons.length; i < cnt; ++i) {
|
||||
if(event.caller is statButtons[i]) {
|
||||
switchPage(i+1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_DesignOverviewBG, SF_Normal, AbsolutePosition);
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
class EmpSorter {
|
||||
Empire@ emp;
|
||||
|
||||
int opCmp(const EmpSorter& other) const {
|
||||
if(other.emp.Victory != emp.Victory) {
|
||||
if(emp.Victory == 1)
|
||||
return -1;
|
||||
if(emp.Victory == -1)
|
||||
return 1;
|
||||
if(other.emp.Victory == 1)
|
||||
return 1;
|
||||
if(other.emp.Victory == -1)
|
||||
return -1;
|
||||
if(emp.Victory == -2) {
|
||||
if(other.emp.Victory == 0)
|
||||
return 1;
|
||||
if(emp.SubjugatedBy.Victory == 1)
|
||||
return -1;
|
||||
}
|
||||
if(other.emp.Victory == -2) {
|
||||
if(emp.Victory == 0)
|
||||
return -1;
|
||||
if(other.emp.SubjugatedBy.Victory == 1)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if(emp.points.value > other.emp.points.value)
|
||||
return -1;
|
||||
if(emp.points.value < other.emp.points.value)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
class StatPage : BaseGuiElement {
|
||||
stat::EmpireStat stat;
|
||||
StatType type;
|
||||
array<StatTracker@> trackers;
|
||||
double endTime = 0;
|
||||
double minVal = 0;
|
||||
double maxVal = 0;
|
||||
double baseMin = 0;
|
||||
double baseMax = 1;
|
||||
bool sqrScale = false;
|
||||
|
||||
StatPage(IGuiElement@ parent, Alignment@ align, StatType type, stat::EmpireStat Stat, int FilterDuration, const string& name, double baseMax = 1) {
|
||||
super(parent, align);
|
||||
this.stat = Stat;
|
||||
this.type = type;
|
||||
this.baseMax = baseMax;
|
||||
if(stat == stat::Military) {
|
||||
this.sqrScale = true;
|
||||
this.baseMin = 2000;
|
||||
}
|
||||
for(uint i = 0, cnt = getEmpireCount(); i <cnt; ++i) {
|
||||
Empire@ emp = getEmpire(i);
|
||||
if(emp.major)
|
||||
trackers.insertLast(StatTracker(type, Stat, emp.color, FilterDuration, emp, sqrScale));
|
||||
}
|
||||
|
||||
addLazyMarkupTooltip(this, width=300, update=true);
|
||||
}
|
||||
|
||||
double getOffsetTime(int offset) {
|
||||
return double(offset-52) / double(size.x-64) * endTime;
|
||||
}
|
||||
|
||||
string formatValue(double amt, bool shortForm = false) {
|
||||
if(stat == stat::Budget || stat == stat::NetBudget)
|
||||
return formatMoney(int(amt));
|
||||
if(stat == stat::EnergyIncome || stat == stat::ResearchIncome)
|
||||
return standardize(amt, true)+locale::PER_SECOND;
|
||||
if(stat == stat::InfluenceIncome)
|
||||
return standardize(amt * 60.0, true)+locale::PER_MIN;
|
||||
if(type == ST_Int) {
|
||||
if(shortForm && amt >= 1000)
|
||||
return standardize(amt, true);
|
||||
else
|
||||
return toString(floor(amt), 0);
|
||||
}
|
||||
return standardize(amt, true);
|
||||
}
|
||||
|
||||
string get_tooltip() override {
|
||||
int mx = mousePos.x;
|
||||
if(mx < AbsolutePosition.topLeft.x+52 || mx > AbsolutePosition.botRight.x-12)
|
||||
return "";
|
||||
string text;
|
||||
double time = getOffsetTime(mx - absolutePosition.topLeft.x);
|
||||
text += format("[center][font=Subtitle]$1[/font][/center]", formatGameTime(time));
|
||||
for(uint i = 0, cnt = trackers.length; i < cnt; ++i) {
|
||||
double amt = trackers[i].getClosest(time).y;
|
||||
text += format("[b]$1[offset=150]$2[/offset][/b]\n",
|
||||
formatEmpireName(trackers[i].emp),
|
||||
formatValue(amt));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void update() {
|
||||
endTime = gameTime;
|
||||
minVal = baseMin; maxVal = baseMax;
|
||||
for(uint i = 0, cnt = trackers.length; i < cnt; ++i) {
|
||||
trackers[i].update();
|
||||
minVal = min(minVal, trackers[i].minVal);
|
||||
maxVal = max(maxVal, trackers[i].maxVal);
|
||||
}
|
||||
for(uint i = 0, cnt = trackers.length; i < cnt; ++i) {
|
||||
trackers[i].minVal = minVal;
|
||||
trackers[i].maxVal = maxVal;
|
||||
}
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_Panel, SF_Normal, AbsolutePosition);
|
||||
|
||||
const Font@ ft = skin.getFont(FT_Normal);
|
||||
|
||||
{
|
||||
int interval = 100;
|
||||
vec2i pos(154, size.height-15);
|
||||
while(pos.x < size.x) {
|
||||
double time = double(pos.x-52) / double(size.x-64) * endTime;
|
||||
ft.draw(pos=recti_centered(pos+AbsolutePosition.topLeft, vec2i(200,30)),
|
||||
horizAlign=0.5, text=formatTime(time), color=Color(0xaaaaaaff));
|
||||
drawLine(vec2i(pos.x, 12)+AbsolutePosition.topLeft,
|
||||
vec2i(pos.x, size.height-40)+AbsolutePosition.topLeft,
|
||||
Color(0xaaaaaa20));
|
||||
pos.x += interval;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int interval = 40;
|
||||
vec2i pos(26, size.height-40);
|
||||
string prevVal;
|
||||
if(type == ST_Int) {
|
||||
double mult = ceil(double(size.height-52) / ceil(maxVal-minVal));
|
||||
interval = ceil(ceil(double(interval) / mult) * mult);
|
||||
}
|
||||
while(pos.y > 14) {
|
||||
double val = 0;
|
||||
if(sqrScale)
|
||||
val = minVal + (maxVal - minVal) * sqr(1.0 - (double(pos.y-12) / double(size.y-52)));
|
||||
else
|
||||
val = minVal + (maxVal - minVal) * (1.0 - (double(pos.y-12) / double(size.y-52)));
|
||||
string valDisp = formatValue(val, true);
|
||||
if(valDisp != prevVal) {
|
||||
ft.draw(pos=recti_centered(pos+AbsolutePosition.topLeft, vec2i(200,30)),
|
||||
horizAlign=0.5, text=valDisp, color=Color(0xaaaaaaff));
|
||||
prevVal = valDisp;
|
||||
}
|
||||
drawLine(vec2i(42, pos.y)+AbsolutePosition.topLeft,
|
||||
vec2i(size.width-12, pos.y)+AbsolutePosition.topLeft,
|
||||
Color(0xaaaaaa20));
|
||||
pos.y -= interval;
|
||||
}
|
||||
|
||||
if(minVal < 0 && maxVal > 0 && !sqrScale) {
|
||||
int y = size.height-40 - size.y * (-minVal) / (maxVal - minVal);
|
||||
drawLine(vec2i(42, y)+AbsolutePosition.topLeft,
|
||||
vec2i(size.width-12, y)+AbsolutePosition.topLeft,
|
||||
Color(0xaa000040), size=3);
|
||||
}
|
||||
}
|
||||
|
||||
for(uint i = 0, cnt = trackers.length; i < cnt; ++i)
|
||||
trackers[i].draw(skin, AbsolutePosition.padded(42,12,12,40));
|
||||
|
||||
if(AbsolutePosition.isWithin(mousePos)) {
|
||||
int mx = mousePos.x;
|
||||
if(mx >= AbsolutePosition.topLeft.x+52 && mx <= AbsolutePosition.botRight.x-12) {
|
||||
drawLine(vec2i(mx, AbsolutePosition.topLeft.y+12),
|
||||
vec2i(mx, AbsolutePosition.botRight.y-40),
|
||||
Color(0xaaaaaaff));
|
||||
}
|
||||
}
|
||||
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
enum StatType {
|
||||
ST_Int,
|
||||
ST_Float,
|
||||
}
|
||||
|
||||
final class StatTracker {
|
||||
double minVal = 0, maxVal = 1;
|
||||
stat::EmpireStat stat;
|
||||
Color color;
|
||||
int filterDuration = 1;
|
||||
array<vec2d> data;
|
||||
Empire@ emp;
|
||||
StatType type;
|
||||
double endTime;
|
||||
bool sqrScale = false;
|
||||
|
||||
array<vec2i> points;
|
||||
vec2i lastSize;
|
||||
|
||||
StatTracker(StatType type, stat::EmpireStat Stat, const Color& col, int FilterDuration, Empire@ emp = playerEmpire, bool sqrScale = false) {
|
||||
stat = Stat;
|
||||
color = col;
|
||||
filterDuration = FilterDuration;
|
||||
this.type = type;
|
||||
@this.emp = emp;
|
||||
this.sqrScale = sqrScale;
|
||||
}
|
||||
|
||||
vec2d getClosest(double time) {
|
||||
vec2d closest(0, 0);
|
||||
double dist = INFINITY;
|
||||
//Don't make me do a binary search you monster
|
||||
for(uint i = 0, cnt = data.length; i < cnt; ++i) {
|
||||
double d = abs(data[i].x - time);
|
||||
if(d < dist) {
|
||||
dist = d;
|
||||
closest = data[i];
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
void rebuildPoints(vec2i size) {
|
||||
array<vec3d> pts(1);
|
||||
|
||||
vec2d factor(double(size.x) / endTime, -double(size.y) / (maxVal - minVal));
|
||||
vec2d offset(0.0, -minVal);
|
||||
|
||||
uint lastX = 0;
|
||||
for(uint i = 0, cnt = data.length; i < cnt; ++i) {
|
||||
vec2d pt = data[i] + offset;
|
||||
pt.x *= factor.x;
|
||||
|
||||
if(!sqrScale) {
|
||||
pt.y *= factor.y;
|
||||
}
|
||||
else {
|
||||
if(pt.y != 0)
|
||||
pt.y = -double(size.y) * sqrt(pt.y / (maxVal - minVal));
|
||||
}
|
||||
|
||||
uint x = int(pt.x);
|
||||
if(i == 0 && x != 0)
|
||||
pts[0] = vec3d(0.0, pt.y, 1.0);
|
||||
|
||||
if(x != lastX)
|
||||
pts.resize(pts.length + 1);
|
||||
pts[pts.length-1] += vec3d(pt.x, pt.y, 1.0);
|
||||
}
|
||||
|
||||
int maxTimeDelta = max(int(ceil(double(size.x * filterDuration) / endTime)), int(1));
|
||||
|
||||
points.length = 0;
|
||||
points.reserve(pts.length + 1);
|
||||
for(uint i = 0, cnt = pts.length; i < cnt; ++i) {
|
||||
vec3d pt = pts[i];
|
||||
vec2i px = vec2i(pt.x / pt.z, pt.y / pt.z + 0.5);
|
||||
if(i > 0) {
|
||||
vec2i prev = points.last;
|
||||
if(prev.x < px.x - maxTimeDelta)
|
||||
points.insertLast(vec2i(px.x - 1, prev.y));
|
||||
}
|
||||
points.insertLast(px);
|
||||
}
|
||||
|
||||
if(points.last.x < size.x - 1)
|
||||
points.insertLast(vec2i(size.x - 1, points.last.y));
|
||||
|
||||
lastSize = size;
|
||||
}
|
||||
|
||||
void update() {
|
||||
minVal = INFINITY;
|
||||
maxVal = -INFINITY;
|
||||
data.length = 0;
|
||||
|
||||
StatHistory history(emp, stat);
|
||||
endTime = gameTime;
|
||||
|
||||
while(history.advance(1)) {
|
||||
double val = 0;
|
||||
if(type == ST_Int)
|
||||
val = history.intVal;
|
||||
else
|
||||
val = history.floatVal;
|
||||
|
||||
if(val > maxVal)
|
||||
maxVal = val;
|
||||
if(val < minVal)
|
||||
minVal = val;
|
||||
|
||||
data.insertLast(vec2d(history.time, val));
|
||||
}
|
||||
|
||||
lastSize = vec2i();
|
||||
}
|
||||
|
||||
void draw(const Skin@ skin, recti bound) {
|
||||
vec2i size = bound.size;
|
||||
if(size != lastSize)
|
||||
rebuildPoints(size);
|
||||
|
||||
if(points.length == 0) {
|
||||
drawLine(bound.topLeft + vec2i(0, bound.height), bound.botRight, color);
|
||||
return;
|
||||
}
|
||||
|
||||
vec2i corner = bound.topLeft + vec2i(0,size.y);
|
||||
vec2i prev = points[0] + corner;
|
||||
|
||||
uint lines = points.length - 1;
|
||||
uint index = 0;
|
||||
while(index < lines) {
|
||||
drawPolygonStart(PT_LineStrip, min(lines - index, 250));
|
||||
drawPolygonPoint(prev, color);
|
||||
for(uint i = 0; i < 250 && index + i < lines; ++i) {
|
||||
vec2i pt = points[index + i + 1] + corner;
|
||||
drawPolygonPoint(pt);
|
||||
prev = pt;
|
||||
}
|
||||
drawPolygonEnd();
|
||||
index += 250;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Tab@ createVictoryTab() {
|
||||
return VictoryTab();
|
||||
}
|
||||
|
||||
void init() {
|
||||
/*auto@ tab = createVictoryTab();*/
|
||||
/*newTab(tab);*/
|
||||
/*switchToTab(tab);*/
|
||||
}
|
||||
|
||||
bool shownVictory = false;
|
||||
void tick(double time) {
|
||||
if(!shownVictory && (hasGameEnded() || (wantSpectator && !playerEmpire.valid))) {
|
||||
auto@ tab = createVictoryTab();
|
||||
newTab(tab);
|
||||
if(hasGameEnded())
|
||||
switchToTab(tab);
|
||||
shownVictory = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
import tabs.Tab;
|
||||
import elements.GuiPanel;
|
||||
import elements.GuiMarkupText;
|
||||
import elements.MarkupTooltip;
|
||||
import elements.GuiBackgroundPanel;
|
||||
import elements.GuiButton;
|
||||
import elements.GuiTextbox;
|
||||
import icons;
|
||||
|
||||
from tabs.tabbar import popTab, newTab, switchToTab, findTab, ActiveTab, browseTab;
|
||||
import Tab@ createCommunityDesignPage(int id) from "community.DesignPage";
|
||||
|
||||
const string URI_HINT = "://";
|
||||
void openGeneralLink(const string& link, int button = 0) {
|
||||
if(link.findFirst(URI_HINT) != -1) {
|
||||
if(button == 0)
|
||||
openBrowser(link);
|
||||
}
|
||||
else if(link.startswith_nocase("design:")) {
|
||||
int id = toInt(link.substr(7));
|
||||
if(button == 2)
|
||||
newTab(createCommunityDesignPage(id));
|
||||
else
|
||||
browseTab(ActiveTab, createCommunityDesignPage(id), true);
|
||||
}
|
||||
else {
|
||||
openWikiLink(link, button);
|
||||
}
|
||||
}
|
||||
|
||||
void openWikiLink(const string& link, int button = 0) {
|
||||
if(button == 0) {
|
||||
auto@ t = cast<WikiTab>(ActiveTab);
|
||||
if(t !is null)
|
||||
t.load(link);
|
||||
else
|
||||
browseTab(ActiveTab, createWikiTab(link), true);
|
||||
}
|
||||
else if(button == 2) {
|
||||
Tab@ newtb = createWikiTab(link, true);
|
||||
newTab(ActiveTab, newtb);
|
||||
|
||||
if(shiftKey)
|
||||
switchToTab(newtb);
|
||||
}
|
||||
}
|
||||
|
||||
class LinkableMarkupText : GuiMarkupText {
|
||||
LinkableMarkupText(IGuiElement@ parent, const recti& pos, bool paragraphize = false) {
|
||||
super(parent, pos);
|
||||
this.paragraphize = paragraphize;
|
||||
@tooltipObject = MarkupTooltip(300, 0.f, true, true);
|
||||
}
|
||||
|
||||
LinkableMarkupText(IGuiElement@ parent, Alignment@ align, bool paragraphize = false) {
|
||||
super(parent, align);
|
||||
this.paragraphize = paragraphize;
|
||||
@tooltipObject = MarkupTooltip(300, 0.f, true, true);
|
||||
}
|
||||
|
||||
void onLinkClicked(const string& link, int button) override {
|
||||
openGeneralLink(link, button);
|
||||
}
|
||||
};
|
||||
|
||||
class WikiTab : Tab {
|
||||
WebData wdata;
|
||||
bool loading = false;
|
||||
string loadPage;
|
||||
string anchor;
|
||||
string[] history;
|
||||
|
||||
GuiBackgroundPanel@ bg;
|
||||
GuiPanel@ panel;
|
||||
LinkableMarkupText@ text;
|
||||
|
||||
GuiButton@ backButton;
|
||||
GuiButton@ goButton;
|
||||
GuiButton@ homeButton;
|
||||
GuiTextbox@ navBox;
|
||||
|
||||
WikiTab() {
|
||||
super();
|
||||
@backButton = GuiButton(this, Alignment(Left+8, Top+4, Left+108, Top+36), locale::WIKI_BACK);
|
||||
backButton.buttonIcon = icons::Back;
|
||||
|
||||
@homeButton = GuiButton(this, Alignment(Left+112, Top+4, Left+212, Top+36), locale::WIKI_HOME);
|
||||
@navBox = GuiTextbox(this, Alignment(Left+216, Top+4, Right-186, Top+36));
|
||||
|
||||
@goButton = GuiButton(this, Alignment(Right-182, Top+4, Right-8, Top+36), locale::WIKI_BROWSER);
|
||||
goButton.buttonIcon = icons::Go;
|
||||
|
||||
@bg = GuiBackgroundPanel(this, Alignment(Left+8, Top+40, Right-8, Bottom-8));
|
||||
bg.titleColor = Color(0xff83bcff);
|
||||
bg.title = title;
|
||||
|
||||
@panel = GuiPanel(bg, Alignment(Left+8, Top+30, Right-4, Bottom-4));
|
||||
panel.horizType = ST_Never;
|
||||
@text = LinkableMarkupText(panel, recti(0, 0, 100, 100), true);
|
||||
text.defaultColor = Color(0xccccccff);
|
||||
load("Main Page");
|
||||
}
|
||||
|
||||
Color get_activeColor() {
|
||||
return Color(0xff83bcff);
|
||||
}
|
||||
|
||||
Color get_inactiveColor() {
|
||||
return Color(0xff0077ff);
|
||||
}
|
||||
|
||||
Color get_seperatorColor() {
|
||||
return Color(0x8d4969ff);
|
||||
}
|
||||
|
||||
TabCategory get_category() {
|
||||
return TC_Wiki;
|
||||
}
|
||||
|
||||
Sprite get_icon() {
|
||||
return Sprite(material::TabWiki);
|
||||
}
|
||||
|
||||
void updateAbsolutePosition() {
|
||||
Tab::updateAbsolutePosition();
|
||||
if(text.size.width != panel.size.width-20) {
|
||||
text.size = vec2i(panel.size.width-20, text.size.height);
|
||||
text.updateAbsolutePosition();
|
||||
}
|
||||
}
|
||||
|
||||
void show() {
|
||||
Tab::show();
|
||||
if(loadPage.length != 0) {
|
||||
load(loadPage);
|
||||
loadPage = "";
|
||||
}
|
||||
}
|
||||
|
||||
void load(const string& page, bool record = true, bool loadNow = false) {
|
||||
string ptitle = page;
|
||||
ptitle[0] = uppercase(ptitle[0]);
|
||||
|
||||
title = format(locale::WIKI_TITLE, ptitle);
|
||||
bg.title = ptitle;
|
||||
navBox.text = ptitle;
|
||||
|
||||
if((!visible && !loadNow) || loading) {
|
||||
loadPage = page;
|
||||
return;
|
||||
}
|
||||
|
||||
text.text = locale::WIKI_LOADING;
|
||||
panel.updateAbsolutePosition();
|
||||
|
||||
if(record) {
|
||||
if(history.length == 0 || history[history.length-1] != page)
|
||||
history.insertLast(page);
|
||||
}
|
||||
|
||||
string link = page;
|
||||
int aPos = page.findFirst("#");
|
||||
if(aPos != -1 && aPos < int(page.length)-1) {
|
||||
anchor = page.substr(aPos+1);
|
||||
link = page.substr(0, aPos);
|
||||
}
|
||||
else {
|
||||
anchor = "";
|
||||
}
|
||||
|
||||
loading = true;
|
||||
getWikiPage(link, wdata);
|
||||
}
|
||||
|
||||
void back() {
|
||||
if(history.length <= 1) {
|
||||
if(previous !is null)
|
||||
popTab(this);
|
||||
return;
|
||||
}
|
||||
|
||||
load(history[history.length - 2], false);
|
||||
history.removeAt(history.length - 1);
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
if(!visible)
|
||||
return;
|
||||
|
||||
backButton.disabled = history.length <= 1 && previous is null;
|
||||
if(loading) {
|
||||
if(wdata.completed) {
|
||||
if(wdata.error)
|
||||
text.text = locale::WIKI_404;
|
||||
else
|
||||
text.text = wdata.result;
|
||||
panel.updateAbsolutePosition();
|
||||
loading = false;
|
||||
|
||||
if(anchor.length != 0) {
|
||||
int pos = text.getAnchor(anchor);
|
||||
if(pos != -1)
|
||||
panel.scrollToVert(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(loadPage.length != 0) {
|
||||
load(loadPage);
|
||||
loadPage = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool onKeyEvent(const KeyboardEvent& event, IGuiElement@ source) {
|
||||
if(source is this) {
|
||||
switch(event.type) {
|
||||
case KET_Key_Up:
|
||||
if(event.key == KEY_ESC) {
|
||||
if(previous !is null)
|
||||
popTab(this);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onKeyEvent(event, source);
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
switch(event.type) {
|
||||
case GUI_Confirmed:
|
||||
if(event.caller is navBox) {
|
||||
load(navBox.text);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case GUI_Clicked:
|
||||
if(event.caller is homeButton) {
|
||||
if(previous !is null && previous.category == TC_Wiki)
|
||||
popTab(this);
|
||||
else
|
||||
load("Main Page");
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is backButton) {
|
||||
back();
|
||||
return true;
|
||||
}
|
||||
else if(event.caller is goButton) {
|
||||
openBrowser("http://wiki.starruler2.com/"+navBox.text);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
skin.draw(SS_WikiBG, SF_Normal, AbsolutePosition);
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
};
|
||||
|
||||
Tab@ createWikiTab() {
|
||||
return WikiTab();
|
||||
}
|
||||
|
||||
Tab@ createWikiTab(const string& page, bool loadNow = false) {
|
||||
WikiTab@ tab = WikiTab();
|
||||
tab.load(page, true, loadNow);
|
||||
return tab;
|
||||
}
|
||||
|
||||
void showWikiPage(const string& page, bool background = false) {
|
||||
if(background) {
|
||||
newTab(createWikiTab(page, true));
|
||||
}
|
||||
else {
|
||||
WikiTab@ tb = cast<WikiTab>(findTab(TC_Wiki));
|
||||
if(tb !is null) {
|
||||
tb.load(page);
|
||||
switchToTab(tb);
|
||||
}
|
||||
else {
|
||||
switchToTab(newTab(createWikiTab(page, true)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,953 @@
|
||||
#priority render 100
|
||||
#priority init 5
|
||||
import tabs.Tab;
|
||||
import elements.GuiButton;
|
||||
import version;
|
||||
from input import lastCamera;
|
||||
|
||||
import BaseGuiElement@ createGlobalBar() from "tabs.GlobalBar";
|
||||
|
||||
import Tab@ createHomeTab() from "tabs.HomeTab";
|
||||
import bool isHomeTab(Tab@) from "tabs.HomeTab";
|
||||
import Tab@ createDiplomacyTab() from "tabs.DiplomacyTab";
|
||||
import Tab@ createResearchTab() from "tabs.ResearchTab";
|
||||
import Tab@ createGalaxyTab() from "tabs.GalaxyTab";
|
||||
import Tab@ createPlanetsTab() from "tabs.PlanetsTab";
|
||||
import Tab@ createWikiTab() from "tabs.WikiTab";
|
||||
import Tab@ createCommunityHome() from "community.Home";
|
||||
import Tab@ createGalaxyTab(Empire@) from "tabs.GalaxyTab";
|
||||
import Tab@ createDesignOverviewTab() from "tabs.DesignOverviewTab";
|
||||
import Tab@ createAttitudesTab() from "tabs.AttitudesTab";
|
||||
import IGuiElement@ getGoDialog() from "navigation.go";
|
||||
import void toggleGoDialog() from "navigation.go";
|
||||
|
||||
const int TAB_HOME_OFFSET = 84;
|
||||
const int TAB_MAX_WIDTH = 186;
|
||||
const int BUTTON_NEW_WIDTH = 12;
|
||||
const int TAB_MIN_WIDTH = 45;
|
||||
const int TAB_HEIGHT = 26;
|
||||
const int TAB_SPACING = 8;
|
||||
const int TAB_SCRUNCH = -18;
|
||||
const int GLOBAL_BAR_HEIGHT = 50;
|
||||
const vec2i TAB_ICON_OFFSET(16, 2);
|
||||
const vec2i TAB_ICON_SIZE(24, 24);
|
||||
const vec2i TAB_CLOSE_OFFSET(14, 7);
|
||||
const vec2i TAB_TEXT_OFFSET(44, 5);
|
||||
const vec2i TAB_PADDING(4, 4);
|
||||
const string ellipsis = "...";
|
||||
const uint MAX_STORED_TABS = 8;
|
||||
const bool ALWAYS_SCRUNCH_TABS = true;
|
||||
const Color TAB_FLASH_COLOR(0xffffffff);
|
||||
const double TAB_FLASH_PERIOD = 1.0;
|
||||
|
||||
GuiTabBar@ tabBar;
|
||||
BaseGuiElement@ globalBar;
|
||||
Tab@[] tabs;
|
||||
Tab@[] closedTabs;
|
||||
Tab@ activeTab;
|
||||
|
||||
//Tab management public interface
|
||||
Tab@ newTab() {
|
||||
Tab@ tab = createHomeTab();
|
||||
newTab(tab);
|
||||
return tab;
|
||||
}
|
||||
|
||||
Tab@ get_ActiveTab() {
|
||||
return activeTab;
|
||||
}
|
||||
|
||||
uint get_tabCount() {
|
||||
return tabs.length;
|
||||
}
|
||||
|
||||
Tab@ get_Tabs(uint i) {
|
||||
return tabs[i];
|
||||
}
|
||||
|
||||
Tab@ findTab(int cat) {
|
||||
int cnt = tabs.length;
|
||||
int index = tabs.find(activeTab);
|
||||
int find = max(index, cnt - index);
|
||||
|
||||
for(int i = 0; i <= find; ++i) {
|
||||
if(index + i < cnt) {
|
||||
if(tabs[index + i].category == cat)
|
||||
return tabs[index + i];
|
||||
}
|
||||
if(index - i >= 0) {
|
||||
if(tabs[index - i].category == cat)
|
||||
return tabs[index - i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Tab@ findTab(IGuiElement@ elem) {
|
||||
while(elem !is null) {
|
||||
if(cast<Tab>(elem) !is null)
|
||||
return cast<Tab>(elem);
|
||||
@elem = elem.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void prepareTab(Tab@ tab) {
|
||||
if(tab.initialized)
|
||||
return;
|
||||
@tab.alignment = Alignment(Left, Top+TAB_HEIGHT + 2 + GLOBAL_BAR_HEIGHT, Right, Bottom);
|
||||
tab.updateAbsolutePosition();
|
||||
tab.init();
|
||||
tab.sendToBack();
|
||||
tab.initialized = true;
|
||||
}
|
||||
|
||||
Tab@ newTab(Tab@ tab) {
|
||||
prepareTab(tab);
|
||||
tabs.insertLast(tab);
|
||||
tabBar.refresh();
|
||||
return tab;
|
||||
}
|
||||
|
||||
Tab@ newTab(Tab@ from, Tab@ tab) {
|
||||
prepareTab(tab);
|
||||
int index = tabs.find(from);
|
||||
if(index >= 0)
|
||||
tabs.insertAt(index+1, tab);
|
||||
else
|
||||
tabs.insertLast(tab);
|
||||
tabBar.refresh();
|
||||
return tab;
|
||||
}
|
||||
|
||||
void switchToTab(Tab@ tab) {
|
||||
if(tab is null)
|
||||
return;
|
||||
if(tab is activeTab)
|
||||
return;
|
||||
|
||||
Tab@ prev = activeTab;
|
||||
@activeTab = tab;
|
||||
|
||||
if(prev !is null)
|
||||
prev.hide();
|
||||
tab.show();
|
||||
}
|
||||
|
||||
void switchToTab(int pos) {
|
||||
int index = tabs.find(activeTab);
|
||||
index = (index + pos) % tabs.length;
|
||||
|
||||
while(index < 0)
|
||||
index += tabs.length;
|
||||
|
||||
switchToTab(tabs[index]);
|
||||
}
|
||||
|
||||
bool switchToTab(TabCategory cat) {
|
||||
Tab@ tab = findTab(cat);
|
||||
if(tab !is null) {
|
||||
switchToTab(tab);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void closeTab() {
|
||||
closeTab(activeTab);
|
||||
}
|
||||
|
||||
Tab@ reopenTab() {
|
||||
//Switch back to the previous tab from the final home tab
|
||||
if(tabs.length == 1 && isHomeTab(tabs[0]) && tabs[0].previous !is null) {
|
||||
browseTab(tabs[0], tabs[0].previous, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
//Check if there are any tabs left to reopen
|
||||
if(closedTabs.length == 0)
|
||||
return null;
|
||||
|
||||
//Set the tabs to reopen
|
||||
Tab@ tab = closedTabs[0];
|
||||
if(tab !is null)
|
||||
tab.reopen();
|
||||
|
||||
//Create the new tab
|
||||
@tab = closedTabs[0];
|
||||
newTab(tab);
|
||||
closedTabs.removeAt(0);
|
||||
return tab;
|
||||
}
|
||||
|
||||
void closeTab(Tab@ tab) {
|
||||
//Don't close locked tabs
|
||||
if(tab.locked)
|
||||
return;
|
||||
|
||||
//Check if it is in the list at all
|
||||
int index = tabs.find(tab);
|
||||
if(index >= 0) {
|
||||
//Never close the last tab
|
||||
if(tabs.length == 1) {
|
||||
browseTab(activeTab, createHomeTab(), false);
|
||||
return;
|
||||
}
|
||||
|
||||
//Remove from the list
|
||||
tabs.removeAt(index);
|
||||
|
||||
//Hide it if it is active
|
||||
if(activeTab is tab)
|
||||
switchToTab(tabs[max(0, index - 1)]);
|
||||
}
|
||||
|
||||
//Store the tab for reopening
|
||||
if(tab !is null && (!isHomeTab(tab) || tab.previous !is null)) {
|
||||
closedTabs.insertAt(0, tab);
|
||||
if(closedTabs.length >= MAX_STORED_TABS) {
|
||||
for(uint i = MAX_STORED_TABS; i < closedTabs.length; ++i)
|
||||
closedTabs[i].remove();
|
||||
closedTabs.length = MAX_STORED_TABS;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove it and all previous tabs
|
||||
Tab@ first = tab;
|
||||
while(tab !is null) {
|
||||
//Close the tab
|
||||
tab.close();
|
||||
|
||||
//Go to the previous tab
|
||||
@tab = tab.previous;
|
||||
if(tab is first)
|
||||
break;
|
||||
}
|
||||
|
||||
//Refresh stuff
|
||||
tabBar.refresh();
|
||||
}
|
||||
|
||||
void browseTab(Tab@ to, bool remember = false) {
|
||||
browseTab(activeTab, to, remember);
|
||||
}
|
||||
|
||||
void browseTab(Tab@ inside, Tab@ to, bool remember = false) {
|
||||
//Make sure our new tab is initialized
|
||||
prepareTab(to);
|
||||
|
||||
if(inside.locked && !to.locked)
|
||||
to.locked = true;
|
||||
|
||||
//Set it in the list
|
||||
int index = tabs.find(inside);
|
||||
if(index >= 0)
|
||||
@tabs[index] = to;
|
||||
|
||||
//Do showing and hiding
|
||||
if(inside is activeTab)
|
||||
switchToTab(to);
|
||||
|
||||
//New tab knows where it came from
|
||||
if(remember) {
|
||||
@to.previous = inside;
|
||||
}
|
||||
else {
|
||||
Tab@ first = inside;
|
||||
while(inside !is null) {
|
||||
inside.close();
|
||||
inside.remove();
|
||||
@inside = inside.previous;
|
||||
if(inside !is null && inside.parent is null)
|
||||
@inside = null;
|
||||
if(inside is first)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Refresh stuff
|
||||
tabBar.refresh();
|
||||
}
|
||||
|
||||
void browseTab(TabCategory inside, Tab@ to, bool remember = false, bool createNew = true) {
|
||||
Tab@ tab = findTab(inside);
|
||||
if(tab is null) {
|
||||
if(createNew) {
|
||||
newTab(to);
|
||||
switchToTab(to);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@tab = activeTab;
|
||||
}
|
||||
}
|
||||
|
||||
browseTab(tab, to, remember);
|
||||
switchToTab(to);
|
||||
}
|
||||
|
||||
void popTab(Tab@ top) {
|
||||
if(top.previous is null) {
|
||||
closeTab(top);
|
||||
return;
|
||||
}
|
||||
|
||||
//Set it in the list
|
||||
int index = tabs.find(top);
|
||||
if(index >= 0)
|
||||
@tabs[index] = top.previous;
|
||||
|
||||
//Do showing and hiding
|
||||
if(top is activeTab)
|
||||
switchToTab(top.previous);
|
||||
|
||||
//New tab knows where it came from
|
||||
top.close();
|
||||
top.remove();
|
||||
|
||||
//Refresh stuff
|
||||
tabBar.refresh();
|
||||
}
|
||||
|
||||
//Tab selection gui elements
|
||||
class GuiTabBar : BaseGuiElement {
|
||||
GuiTab@[] tabs;
|
||||
GuiButton@ homeButton;
|
||||
GuiButton@ goButton;
|
||||
GuiButton@ newButton;
|
||||
bool leftGo = false;
|
||||
|
||||
GuiTabBar() {
|
||||
super(null, Alignment(Left, Top, Right, Top+TAB_HEIGHT + 3));
|
||||
|
||||
@homeButton = GuiButton(this, recti(0, 0, 30, 19));
|
||||
homeButton.navigable = false;
|
||||
homeButton.style = SS_HomeIcon;
|
||||
homeButton.position = vec2i(3, 4);
|
||||
|
||||
@goButton = GuiButton(this, recti(0, 0, 30, 19));
|
||||
goButton.navigable = false;
|
||||
goButton.style = SS_GoIcon;
|
||||
goButton.position = vec2i(35, 4);
|
||||
|
||||
@newButton = GuiButton(this, recti(0, 0, 30 + BUTTON_NEW_WIDTH, 19));
|
||||
newButton.navigable = false;
|
||||
newButton.style = SS_GameTabNew;
|
||||
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
void updateAbsolutePosition() {
|
||||
int w = size.width;
|
||||
BaseGuiElement::updateAbsolutePosition();
|
||||
if(size.width != w)
|
||||
refresh();
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
uint cnt = ::tabs.length;
|
||||
uint oldcnt = tabs.length;
|
||||
|
||||
//Remove old tabs
|
||||
for(uint i = cnt; i < oldcnt; ++i)
|
||||
tabs[i].remove();
|
||||
tabs.length = cnt;
|
||||
|
||||
//Create new tabs
|
||||
for(uint i = oldcnt; i < cnt; ++i) {
|
||||
@tabs[i] = GuiTab(this);
|
||||
tabs[i].sendToBack();
|
||||
}
|
||||
|
||||
//Refresh tabs
|
||||
int x = TAB_HOME_OFFSET;
|
||||
int cat = TC_Invalid;
|
||||
|
||||
//Calculate the total used spacing
|
||||
int totalSpacing = 0;
|
||||
cat = TC_Invalid;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
//Scrunch tabs in the same category
|
||||
int newCat = ::tabs[i].category;
|
||||
if(newCat == cat || ALWAYS_SCRUNCH_TABS)
|
||||
totalSpacing += TAB_SCRUNCH;
|
||||
else
|
||||
totalSpacing += TAB_SPACING;
|
||||
cat = newCat;
|
||||
}
|
||||
|
||||
//Fit the tab width
|
||||
int w = TAB_MAX_WIDTH;
|
||||
bool overflow = false;
|
||||
int avail = size.width - TAB_HOME_OFFSET - newButton.size.width - TAB_SPACING - 4;
|
||||
if(cnt * w + totalSpacing > avail) {
|
||||
w = max(TAB_MIN_WIDTH, (avail - totalSpacing) / cnt);
|
||||
overflow = true;
|
||||
}
|
||||
|
||||
//Position the actual tabs
|
||||
cat = TC_Invalid;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
//Scrunch tabs in the same category
|
||||
int newCat = ::tabs[i].category;
|
||||
if(newCat == cat || ALWAYS_SCRUNCH_TABS)
|
||||
x += TAB_SCRUNCH;
|
||||
else
|
||||
x += TAB_SPACING;
|
||||
cat = newCat;
|
||||
|
||||
//Position the tab
|
||||
tabs[i].setTab(::tabs[i]);
|
||||
tabs[i].position = vec2i(x, 0);
|
||||
tabs[i].size = vec2i(w, TAB_HEIGHT);
|
||||
x += w;
|
||||
}
|
||||
|
||||
//Set new button
|
||||
if(overflow)
|
||||
newButton.position = vec2i(size.width - newButton.size.width - 4, 4);
|
||||
else
|
||||
newButton.position = vec2i(x + TAB_SPACING, 4);
|
||||
}
|
||||
|
||||
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
|
||||
if(source is this) {
|
||||
if(event.type == MET_Button_Up) {
|
||||
if(event.button == 2) {
|
||||
switchToTab(newTab());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onMouseEvent(event, source);
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.caller is newButton) {
|
||||
if(event.type == GUI_Clicked) {
|
||||
switchToTab(newTab());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(event.caller is homeButton) {
|
||||
if(event.type == GUI_Clicked) {
|
||||
bool isHome = isHomeTab(activeTab);
|
||||
if(!isHome || activeTab.previous is null) {
|
||||
browseTab(activeTab, createHomeTab(), true);
|
||||
}
|
||||
else {
|
||||
popTab(activeTab);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(event.caller is goButton) {
|
||||
if(event.type == GUI_Clicked) {
|
||||
if(leftGo)
|
||||
leftGo = false;
|
||||
else
|
||||
toggleGoDialog();
|
||||
return true;
|
||||
}
|
||||
else if(event.type == GUI_Focused) {
|
||||
if(event.other !is null && event.other.isChildOf(getGoDialog())) {
|
||||
leftGo = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(event.type == GUI_Focus_Lost) {
|
||||
leftGo = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
//Draw the tab bar background
|
||||
skin.draw(SS_GameTabBar, SF_Normal, AbsolutePosition);
|
||||
|
||||
//Draw build version
|
||||
//skin.getFont(FT_Normal).draw(pos=AbsolutePosition.padded(8, 0, 8, 3),
|
||||
// text=SCRIPT_VERSION, horizAlign=1.0, color=Color(0xaaaaaaff));
|
||||
|
||||
//Find the active tab to draw on top
|
||||
GuiTab@ active = null;
|
||||
GuiTab@ dragging = null;
|
||||
uint cnt = tabs.length;
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
if(tabs[i].Dragging) {
|
||||
@dragging = tabs[i];
|
||||
tabs[i].Visible = false;
|
||||
}
|
||||
else if(tabs[i].tab is activeTab) {
|
||||
@active = tabs[i];
|
||||
tabs[i].Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
BaseGuiElement::draw();
|
||||
|
||||
//Draw the separator lines
|
||||
drawRectangle(recti_area(
|
||||
AbsolutePosition.topLeft + vec2i(0,24),
|
||||
vec2i(size.width, 1)), Color(0x202020ff));
|
||||
|
||||
drawRectangle(recti_area(
|
||||
AbsolutePosition.topLeft + vec2i(0, 25),
|
||||
vec2i(size.width, 2)), activeTab.seperatorColor);
|
||||
|
||||
drawRectangle(recti_area(
|
||||
AbsolutePosition.topLeft + vec2i(0,27),
|
||||
vec2i(size.width, 1)), Color(0x202020ff));
|
||||
|
||||
//Draw the active tab
|
||||
if(active !is null) {
|
||||
active.Visible = true;
|
||||
active.draw();
|
||||
}
|
||||
|
||||
//Draw the dragging tab
|
||||
if(dragging !is null) {
|
||||
dragging.Visible = true;
|
||||
dragging.draw();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class GuiTab : BaseGuiElement {
|
||||
GuiTabBar@ bar;
|
||||
GuiButton@ closeButton;
|
||||
Tab@ tab;
|
||||
bool Hovered;
|
||||
bool Focused;
|
||||
bool Pressed;
|
||||
bool LeftHeld = false;
|
||||
bool Dragging;
|
||||
vec2i dragStart;
|
||||
|
||||
GuiTab(GuiTabBar@ Bar) {
|
||||
@bar = Bar;
|
||||
Hovered = false;
|
||||
Focused = false;
|
||||
Pressed = false;
|
||||
Dragging = false;
|
||||
|
||||
super(bar, recti_area(0, 0, TAB_MAX_WIDTH, TAB_HEIGHT));
|
||||
|
||||
vec2i size = skin.getSize(SS_GameTabClose, SF_Normal);
|
||||
@closeButton = GuiButton(this, recti());
|
||||
closeButton.navigable = false;
|
||||
closeButton.style = SS_GameTabClose;
|
||||
@closeButton.alignment = Alignment(Right-TAB_CLOSE_OFFSET.x-size.x, Top+TAB_CLOSE_OFFSET.y, Right-TAB_CLOSE_OFFSET.x, Top+TAB_CLOSE_OFFSET.y+size.y);
|
||||
|
||||
updateAbsolutePosition();
|
||||
}
|
||||
|
||||
void setTab(Tab@ forTab) {
|
||||
if(tab is forTab)
|
||||
return;
|
||||
@tab = forTab;
|
||||
}
|
||||
|
||||
bool onGuiEvent(const GuiEvent& event) {
|
||||
if(event.caller is closeButton) {
|
||||
if(event.type == GUI_Clicked) {
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(event.caller is this) {
|
||||
switch(event.type) {
|
||||
case GUI_Mouse_Entered:
|
||||
Hovered = true;
|
||||
break;
|
||||
case GUI_Mouse_Left:
|
||||
Hovered = false;
|
||||
Pressed = false;
|
||||
LeftHeld = false;
|
||||
break;
|
||||
case GUI_Focused:
|
||||
Focused = true;
|
||||
return false;
|
||||
case GUI_Focus_Lost:
|
||||
Focused = false;
|
||||
Pressed = false;
|
||||
LeftHeld = false;
|
||||
Dragging = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onGuiEvent(event);
|
||||
}
|
||||
|
||||
void click() {
|
||||
switchToTab(tab);
|
||||
}
|
||||
|
||||
void close() {
|
||||
closeTab(tab);
|
||||
}
|
||||
|
||||
bool onMouseEvent(const MouseEvent& event, IGuiElement@ source) {
|
||||
if(source is this) {
|
||||
switch(event.type) {
|
||||
case MET_Moved: {
|
||||
vec2i mouse(event.x, event.y);
|
||||
if(Dragging) {
|
||||
if(!mouseLeft) {
|
||||
Dragging = false;
|
||||
LeftHeld = false;
|
||||
return true;
|
||||
}
|
||||
if(mouse.x < AbsolutePosition.topLeft.x - 5) {
|
||||
int index = tabs.find(tab);
|
||||
if(index > 0) {
|
||||
@tabs[index] = tabs[index - 1];
|
||||
@tabs[index - 1] = tab;
|
||||
@bar.tabs[index] = bar.tabs[index - 1];
|
||||
@bar.tabs[index - 1] = this;
|
||||
swap(bar.tabs[index]);
|
||||
bar.refresh();
|
||||
}
|
||||
}
|
||||
else if(mouse.x > AbsolutePosition.botRight.x + 5) {
|
||||
int index = tabs.find(tab);
|
||||
if(index < int(tabs.length - 1)) {
|
||||
@tabs[index] = tabs[index + 1];
|
||||
@tabs[index + 1] = tab;
|
||||
@bar.tabs[index] = bar.tabs[index + 1];
|
||||
@bar.tabs[index + 1] = this;
|
||||
swap(bar.tabs[index]);
|
||||
bar.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(Pressed && LeftHeld) {
|
||||
if(abs(dragStart.x - mouse.x) > 5 || abs(dragStart.y - mouse.y) > 5) {
|
||||
Pressed = false;
|
||||
Dragging = true;
|
||||
closeButton.visible = false;
|
||||
dragStart = dragStart - AbsolutePosition.topLeft;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case MET_Button_Down:
|
||||
Pressed = true;
|
||||
if(event.button == 0) {
|
||||
dragStart = mousePos;
|
||||
LeftHeld = true;
|
||||
}
|
||||
return true;
|
||||
case MET_Button_Up:
|
||||
if(Dragging) {
|
||||
if(event.button == 0) {
|
||||
Dragging = false;
|
||||
LeftHeld = false;
|
||||
closeButton.visible = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(Pressed) {
|
||||
if(Hovered) {
|
||||
if(event.button == 2) {
|
||||
close();
|
||||
}
|
||||
else if(event.button == 0) {
|
||||
if(Hovered)
|
||||
click();
|
||||
}
|
||||
}
|
||||
Pressed = false;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return BaseGuiElement::onMouseEvent(event, source);
|
||||
}
|
||||
|
||||
void draw(const recti& pos) {
|
||||
Color color(0xffffffff);
|
||||
|
||||
uint flags = SF_Normal;
|
||||
if(Pressed)
|
||||
flags |= SF_Pressed;
|
||||
if(Hovered)
|
||||
flags |= SF_Hovered;
|
||||
if(Focused)
|
||||
flags |= SF_Focused;
|
||||
|
||||
if(tab is activeTab) {
|
||||
flags |= SF_Active;
|
||||
color = tab.activeColor;
|
||||
}
|
||||
else {
|
||||
color = tab.inactiveColor;
|
||||
}
|
||||
|
||||
const Font@ fnt = skin.getFont(FT_Normal);
|
||||
|
||||
//Do flashing
|
||||
if(tab.flashing) {
|
||||
double cycleTime = frameTime % TAB_FLASH_PERIOD;
|
||||
float pct = 0.f;
|
||||
|
||||
if(cycleTime < TAB_FLASH_PERIOD * 0.5)
|
||||
pct = (cycleTime / (0.5 * TAB_FLASH_PERIOD));
|
||||
else
|
||||
pct = 1.0 - ((cycleTime - (0.5 * TAB_FLASH_PERIOD)) * 2.0);
|
||||
|
||||
color = color.interpolate(TAB_FLASH_COLOR, pct);
|
||||
}
|
||||
|
||||
//Tab background
|
||||
skin.draw(SS_GameTab, flags, pos, color);
|
||||
|
||||
//Category icon
|
||||
tab.icon.draw(recti_area(
|
||||
pos.topLeft + TAB_ICON_OFFSET,
|
||||
TAB_ICON_SIZE));
|
||||
|
||||
//Title text
|
||||
fnt.draw(recti_area(
|
||||
pos.topLeft + TAB_TEXT_OFFSET,
|
||||
vec2i(size.width - TAB_CLOSE_OFFSET.x - TAB_PADDING.x - TAB_TEXT_OFFSET.x,
|
||||
size.height - TAB_PADDING.y - TAB_TEXT_OFFSET.y)), tab.title, ellipsis);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
closeButton.visible = !tab.locked;
|
||||
if(Dragging) {
|
||||
clearClip();
|
||||
int maxPos = bar.tabs[bar.tabs.length - 1].AbsolutePosition.topLeft.x;
|
||||
int pos = clamp(mousePos.x - dragStart.x, TAB_HOME_OFFSET, maxPos);
|
||||
|
||||
draw(recti_area( vec2i(pos, AbsolutePosition.topLeft.y), size));
|
||||
}
|
||||
else {
|
||||
draw(AbsolutePosition);
|
||||
BaseGuiElement::draw();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//Tab internal management
|
||||
void tab_next(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(1);
|
||||
}
|
||||
|
||||
void tab_previous(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(-1);
|
||||
}
|
||||
|
||||
void tab_new(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(newTab());
|
||||
}
|
||||
|
||||
void tab_close(bool pressed) {
|
||||
if(pressed)
|
||||
closeTab();
|
||||
}
|
||||
|
||||
void tab_reopen(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(reopenTab());
|
||||
}
|
||||
|
||||
void tab_1(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(tabs[0]);
|
||||
}
|
||||
|
||||
void tab_2(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(tabs[1 % tabs.length]);
|
||||
}
|
||||
|
||||
void tab_3(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(tabs[2 % tabs.length]);
|
||||
}
|
||||
|
||||
void tab_4(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(tabs[3 % tabs.length]);
|
||||
}
|
||||
|
||||
void tab_5(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(tabs[4 % tabs.length]);
|
||||
}
|
||||
|
||||
void tab_6(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(tabs[5 % tabs.length]);
|
||||
}
|
||||
|
||||
void tab_7(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(tabs[6 % tabs.length]);
|
||||
}
|
||||
|
||||
void tab_8(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(tabs[7 % tabs.length]);
|
||||
}
|
||||
|
||||
void tab_9(bool pressed) {
|
||||
if(pressed)
|
||||
switchToTab(tabs[8 % tabs.length]);
|
||||
}
|
||||
|
||||
bool tabEscape() {
|
||||
if(!settings::bEscapeGalaxyTab)
|
||||
return false;
|
||||
if(ActiveTab.category == TC_Galaxy)
|
||||
return false;
|
||||
auto@ otherTab = findTab(TC_Galaxy);
|
||||
if(otherTab is null)
|
||||
return false;
|
||||
switchToTab(otherTab);
|
||||
return true;
|
||||
}
|
||||
|
||||
void init() {
|
||||
//Create the tabbar
|
||||
@tabBar = GuiTabBar();
|
||||
|
||||
//Create the global bar
|
||||
@globalBar = createGlobalBar();
|
||||
@globalBar.alignment = Alignment(Left, Top+TAB_HEIGHT + 2, Right, Top+TAB_HEIGHT + 2 + GLOBAL_BAR_HEIGHT);
|
||||
|
||||
//Create initial galaxy view
|
||||
auto defaultTab = createGalaxyTab(playerEmpire);
|
||||
newTab(defaultTab);
|
||||
switchToTab(defaultTab);
|
||||
|
||||
newTab(createDiplomacyTab());
|
||||
if(hasDLC("Heralds"))
|
||||
newTab(createAttitudesTab());
|
||||
newTab(createResearchTab());
|
||||
newTab(createDesignOverviewTab());
|
||||
newTab(createPlanetsTab());
|
||||
newTab(createCommunityHome());
|
||||
|
||||
//Bind keybinds
|
||||
keybinds::Global.addBind(KB_TAB_NEW, "tab_new");
|
||||
keybinds::Global.addBind(KB_TAB_CLOSE, "tab_close");
|
||||
keybinds::Global.addBind(KB_TAB_REOPEN, "tab_reopen");
|
||||
keybinds::Global.addBind(KB_TAB_NEXT, "tab_next");
|
||||
keybinds::Global.addBind(KB_TAB_PREVIOUS, "tab_previous");
|
||||
|
||||
keybinds::Global.addBind(KB_TAB_1, "tab_1");
|
||||
keybinds::Global.addBind(KB_TAB_2, "tab_2");
|
||||
keybinds::Global.addBind(KB_TAB_3, "tab_3");
|
||||
keybinds::Global.addBind(KB_TAB_4, "tab_4");
|
||||
keybinds::Global.addBind(KB_TAB_5, "tab_5");
|
||||
keybinds::Global.addBind(KB_TAB_6, "tab_6");
|
||||
keybinds::Global.addBind(KB_TAB_7, "tab_7");
|
||||
keybinds::Global.addBind(KB_TAB_8, "tab_8");
|
||||
keybinds::Global.addBind(KB_TAB_9, "tab_9");
|
||||
}
|
||||
|
||||
void preReload(Message& msg) {
|
||||
tabBar.remove();
|
||||
globalBar.remove();
|
||||
}
|
||||
|
||||
void postReload(Message& msg) {
|
||||
init();
|
||||
}
|
||||
|
||||
void tick(double time) {
|
||||
for(uint i = 0, cnt = tabs.length; i < cnt; ++i)
|
||||
tabs[i].tick(time);
|
||||
}
|
||||
|
||||
const Shader@ getFullscreenShader() {
|
||||
if(settings::bBloom || settings::bChromaticAberration || settings::bFilmGrain || settings::bGodRays || settings::bVignette)
|
||||
return shader::FullscreenPostProcess;
|
||||
return null;
|
||||
}
|
||||
|
||||
//Render last view for menu if requested
|
||||
void preRender(double time) {
|
||||
if(game_state == GS_Menu) {
|
||||
@fullscreenShader = shader::MenuRender;
|
||||
|
||||
lastCamera.camera.animate(time);
|
||||
updateRenderCamera(lastCamera.camera);
|
||||
}
|
||||
else {
|
||||
@fullscreenShader = getFullscreenShader();
|
||||
activeTab.preRender(time);
|
||||
}
|
||||
}
|
||||
|
||||
void render(double time) {
|
||||
if(game_state == GS_Menu) {
|
||||
prepareRender(lastCamera.camera);
|
||||
renderWorld();
|
||||
}
|
||||
else {
|
||||
activeTab.render(time);
|
||||
}
|
||||
}
|
||||
|
||||
void deinit() {
|
||||
@fullscreenShader = null;
|
||||
}
|
||||
|
||||
void save(SaveFile& file) {
|
||||
uint cnt = tabs.length;
|
||||
uint mainTab = 0;
|
||||
file << cnt;
|
||||
for(uint i = 0, cnt = tabs.length; i < cnt; ++i) {
|
||||
uint cat = tabs[i].category;
|
||||
file << cat;
|
||||
tabs[i].save(file);
|
||||
if(tabs[i] is activeTab)
|
||||
mainTab = i;
|
||||
}
|
||||
file << mainTab;
|
||||
}
|
||||
|
||||
void load(SaveFile& file) {
|
||||
uint cnt = 0;
|
||||
file >> cnt;
|
||||
|
||||
for(uint i = 0, cnt = tabs.length; i < cnt; ++i)
|
||||
tabs[i].close();
|
||||
tabs.length = 0;
|
||||
|
||||
for(uint i = 0; i < cnt; ++i) {
|
||||
Tab@ t;
|
||||
uint cat = 0;
|
||||
file >> cat;
|
||||
|
||||
switch(cat) {
|
||||
case TC_Galaxy: @t = createGalaxyTab(); break;
|
||||
case TC_Designs: @t = createDesignOverviewTab(); break;
|
||||
case TC_Home: @t = createHomeTab(); break;
|
||||
case TC_Research: @t = createResearchTab(); break;
|
||||
case TC_Planets: @t = createPlanetsTab(); break;
|
||||
case TC_Diplomacy: @t = createDiplomacyTab(); break;
|
||||
case TC_Wiki: @t = createCommunityHome(); break;
|
||||
case TC_Attitudes: @t = createAttitudesTab(); break;
|
||||
}
|
||||
|
||||
if(t !is null) {
|
||||
t.load(file);
|
||||
newTab(t);
|
||||
}
|
||||
}
|
||||
|
||||
uint mainTab = 0;
|
||||
file >> mainTab;
|
||||
switchToTab(tabs[mainTab]);
|
||||
}
|
||||
Reference in New Issue
Block a user