Files
starruler-linux/scripts/gui/tabs/tabbar.as
T
2018-07-17 14:15:37 +02:00

954 lines
20 KiB
ActionScript

#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]);
}