Open source Star Ruler 2 source code!

This commit is contained in:
Lucas de Vries
2018-07-17 14:15:37 +02:00
commit cc307720ff
4342 changed files with 2365070 additions and 0 deletions
+562
View File
@@ -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
+688
View File
@@ -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
+624
View File
@@ -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");
}
+83
View File
@@ -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());
}
+141
View File
@@ -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;
}
+242
View File
@@ -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
+99
View File
@@ -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();
}
}
+512
View File
@@ -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
+131
View File
@@ -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);
}
}
+283
View File
@@ -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);
}
};
+371
View File
@@ -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;
}
+129
View File
@@ -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) {
}
};
+619
View 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;
}
}
+289
View File
@@ -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)));
}
}
}
+953
View File
@@ -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]);
}