Index: binaries/data/mods/mod/gui/gui.rnc
===================================================================
--- binaries/data/mods/mod/gui/gui.rnc
+++ binaries/data/mods/mod/gui/gui.rnc
@@ -116,7 +116,7 @@
##
# Objects #
##
-objects = element objects { (script | object)* }
+objects = element objects { (script | object | component)* }
script =
element script {
text &
@@ -139,6 +139,11 @@
base_settings,
ex_settings
}
+component =
+ element component {
+ class,
+ ((attribute * - (class))*)
+ }
action =
element action {
text,
Index: binaries/data/mods/mod/gui/gui.rng
===================================================================
--- binaries/data/mods/mod/gui/gui.rng
+++ binaries/data/mods/mod/gui/gui.rng
@@ -8,6 +8,7 @@
-->
+
@@ -461,6 +462,7 @@
+
@@ -483,6 +485,7 @@
+
@@ -501,6 +504,25 @@
+
+
+
+
+
+
+
+
+
+
+ class
+
+
+
+
+
+
+
+
Index: binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlay.js
===================================================================
--- binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlay.js
+++ binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlay.js
@@ -1,45 +1,48 @@
/**
- * This class stores the checkboxes that are part of the developer overlay.
- * These checkboxes may own their own helper class instances, such as the EntityState or TimeWarp feature.
+ * Developer overlay - Various useful debug commands.
*/
-class DeveloperOverlay
+class DeveloperOverlay extends Component
{
- constructor(playerViewControl, selection)
+ static initXML(parent, attrs)
{
- this.devCommandsOverlay = Engine.GetGUIObjectByName("devCommandsOverlay");
- this.devCommandsOverlay.onPress = this.toggle.bind(this);
+ return ``;
+ }
- this.checkBoxes = this.getCheckboxNames().map((name, i) =>
- new DeveloperOverlayCheckbox(
- new DeveloperOverlayCheckboxes.prototype[name](playerViewControl, selection),
- i));
+ init()
+ {
+ let top = 0;
+ for (let i in DeveloperOverlay.prototype.Controls)
+ {
+ top = this.appendChild(``).size.bottom;
+ }
+ this.onPress = () => this.toggle();
this.resize();
}
- /**
- * Mods may overwrite this to change the order.
- */
- getCheckboxNames()
- {
- return Object.keys(DeveloperOverlayCheckboxes.prototype);
- }
-
toggle()
{
if (g_IsNetworked && !g_GameAttributes.settings.CheatsEnabled)
return;
- this.devCommandsOverlay.hidden = !this.devCommandsOverlay.hidden;
+ this.hidden = !this.hidden;
this.sendNotification();
- this.checkBoxes.forEach(checkbox => {
- checkbox.setHidden(this.devCommandsOverlay.hidden);
- });
}
sendNotification()
{
- let message = this.devCommandsOverlay.hidden ? this.CloseNotification : this.OpenNotification;
+ let message = this.hidden ?
+ markForTranslation("The Developer Overlay was closed.") :
+ markForTranslation("The Developer Overlay was opened.");
// Only players can send the simulation chat command
if (Engine.GetPlayerID() == -1)
@@ -56,13 +59,8 @@
resize()
{
- let size = this.devCommandsOverlay.size;
- size.bottom =
- size.top +
- this.checkBoxes.reduce((height, checkbox) => height + checkbox.getHeight(), 0);
- this.devCommandsOverlay.size = size;
+ let size = this.size;
+ size.bottom = this.size.top + 5 + this.children[this.children.length - 1].size.bottom;
+ this.size = size;
}
}
-
-DeveloperOverlay.prototype.OpenNotification = markForTranslation("The Developer Overlay was opened.");
-DeveloperOverlay.prototype.CloseNotification = markForTranslation("The Developer Overlay was closed.");
Index: binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlay.xml
===================================================================
--- binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlay.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
Index: binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayButton.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayButton.js
@@ -0,0 +1,34 @@
+class DeveloperOverlayButton extends Component
+{
+ constructor(me, attrs)
+ {
+ super(me, attrs);
+
+ this.button = this.children[1];
+ this.definition = DeveloperOverlay.prototype.Controls[attrs.n];
+ this.label = Engine.GetGUIObjectByName(`dev_command_label_${attrs.n}`);
+ this.label.caption = this.definition.label;
+ if (this.definition.onPress)
+ this.button.onPress = () => this.definition.onPress(this);
+ }
+
+ static initXML(parent, attrs)
+ {
+ let n = +attrs.n;
+ return `
+
+ Text
+
+ `;
+ }
+}
Index: binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayCheckbox.js
===================================================================
--- binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayCheckbox.js
+++ binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayCheckbox.js
@@ -1,65 +1,42 @@
-/**
- * This class sets up a checkbox in the developer overlay and assigns its specific handler.
- */
-class DeveloperOverlayCheckbox
+class DeveloperOverlayCheckbox extends Component
{
- constructor(handler, i)
+ constructor(me, attrs)
{
- this.handler = handler;
- this.handler.update = () => this.update();
+ super(me, attrs);
- this.label = Engine.GetGUIObjectByName("dev_command_label[" + i + "]");
- this.label.caption = this.handler.label();
+ this.checkbox = this.children[1];
+ this.definition = DeveloperOverlay.prototype.Controls[attrs.n];
+ this.label = Engine.GetGUIObjectByName(`dev_command_label_${attrs.n}`);
+ this.label.caption = this.definition.label;
+ if (this.definition.onPress)
+ this.checkbox.onPress = () => this.definition.onPress(this.checkbox.checked) && this.updateControl();
- this.checkbox = Engine.GetGUIObjectByName("dev_command_checkbox[" + i + "]");
- this.checkbox.onPress = this.onPress.bind(this);
-
- this.body = Engine.GetGUIObjectByName("dev_command[" + i + "]");
- this.resize(i);
-
- this.updater = this.update.bind(this);
-
- if (this.handler.checked)
- registerPlayersInitHandler(this.updater);
- }
-
- onPress()
- {
- this.handler.onPress(this.checkbox.checked);
- this.update();
- }
-
- update()
- {
- if (this.handler.checked)
- this.checkbox.checked = this.handler.checked();
- if (this.handler.enabled)
- this.checkbox.enabled = this.handler.enabled();
- }
-
- setHidden(hidden)
- {
- if (!this.handler.checked)
- return;
-
- if (hidden)
- unregisterSimulationUpdateHandler(this.updater);
- else
- registerSimulationUpdateHandler(this.updater);
+ this.updateControl();
}
- getHeight()
+ updateControl()
{
- return this.body.size.bottom - this.body.size.top;
+ if (this.definition.checked)
+ this.checkbox.checked = this.definition.checked();
}
- resize(i)
+ static initXML(parent, attrs)
{
- let size = this.body.size;
- let height = size.bottom;
- size.top = height * i;
- size.bottom = height * (i + 1);
- this.body.size = size;
- this.body.hidden = false;
+ let n = +attrs.n;
+
+ let size = 20;
+ return `
+
+
+
+ `;
}
}
Index: binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayCheckboxes.js
===================================================================
--- binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayCheckboxes.js
+++ /dev/null
@@ -1,335 +0,0 @@
-/**
- * This class stores the handlers for the individual checkboxes available in the developer overlay.
- * Such a class must have label and onPress function.
- * If the class has a checked property, then that will be called every simulation update to
- * synchronize the state of the checkbox (only if the developer overaly is opened).
- */
-class DeveloperOverlayCheckboxes
-{
-}
-
-DeveloperOverlayCheckboxes.prototype.ControlAll = class
-{
- label()
- {
- return translate("Control all units");
- }
-
- onPress(checked)
- {
- Engine.PostNetworkCommand({
- "type": "control-all",
- "flag": checked
- });
- }
-
- checked()
- {
- let playerState = g_SimState.players[g_ViewedPlayer];
- return playerState ? playerState.controlsAll : false;
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.ChangePerspective = class
-{
- constructor(playerViewControl)
- {
- this.playerViewControl = playerViewControl;
- }
-
- label()
- {
- return translate("Change perspective");
- }
-
- onPress(checked)
- {
- this.playerViewControl.setChangePerspective(checked);
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.SelectionEntityState = class
-{
- constructor(playerViewControl, selection)
- {
- this.developerOverlayEntityState = new DeveloperOverlayEntityState(selection);
- }
-
- label()
- {
- return translate("Display selection state");
- }
-
- onPress(checked)
- {
- this.developerOverlayEntityState.setEnabled(checked);
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.PathfinderOverlay = class
-{
- label()
- {
- return translate("Pathfinder overlay");
- }
-
- onPress(checked)
- {
- Engine.GuiInterfaceCall("SetPathfinderDebugOverlay", checked);
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.HierarchicalPathfinderOverlay = class
-{
- label()
- {
- return translate("Hierarchical pathfinder overlay");
- }
-
- onPress(checked)
- {
- Engine.GuiInterfaceCall("SetPathfinderHierDebugOverlay", checked);
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.ObstructionOverlay = class
-{
- label()
- {
- return translate("Obstruction overlay");
- }
-
- onPress(checked)
- {
- Engine.GuiInterfaceCall("SetObstructionDebugOverlay", checked);
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.UnitMotionOverlay = class
-{
- label()
- {
- return translate("Unit motion overlay");
- }
-
- onPress(checked)
- {
- g_Selection.SetMotionDebugOverlay(checked);
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.RangeOverlay = class
-{
- label()
- {
- return translate("Range overlay");
- }
-
- onPress(checked)
- {
- Engine.GuiInterfaceCall("SetRangeDebugOverlay", checked);
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.BoundingBoxOverlay = class
-{
- label()
- {
- return translate("Bounding box overlay");
- }
-
- onPress(checked)
- {
- Engine.SetBoundingBoxDebugOverlay(checked);
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.RestrictCamera = class
-{
- label()
- {
- return translate("Restrict camera");
- }
-
- onPress(checked)
- {
- Engine.GameView_SetConstrainCameraEnabled(checked);
- }
-
- checked()
- {
- return Engine.GameView_GetConstrainCameraEnabled();
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.RevealMap = class
-{
- label()
- {
- return translate("Reveal map");
- }
-
- onPress(checked)
- {
- Engine.PostNetworkCommand({
- "type": "reveal-map",
- "enable": checked
- });
- }
-
- checked()
- {
- return Engine.GuiInterfaceCall("IsMapRevealed");
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.EnableTimeWarp = class
-{
- constructor()
- {
- this.timeWarp = new TimeWarp();
- }
-
- label()
- {
- return translate("Enable time warp");
- }
-
- onPress(checked)
- {
- this.timeWarp.setEnabled(checked);
- }
-};
-
-
-DeveloperOverlayCheckboxes.prototype.ActivateRejoinTest = class
-{
- constructor()
- {
- this.disabled = false;
- }
-
- label()
- {
- return translate("Activate Rejoin Test");
- }
-
- onPress(checked)
- {
- let box = new SessionMessageBox();
- box.Title = "Rejoin Test";
- box.Caption = "Warning: the rejoin test can't be de-activated and is quite slow. Its only purpose is to check for OOS.";
- let self = this;
- box.Buttons = [
- { "caption": "Cancel" }, { "caption": "OK", "onPress": () => {
- Engine.ActivateRejoinTest();
- this.disabled = true;
- this.update();
- } }
- ];
- box.display();
- }
-
- checked()
- {
- return this.disabled;
- }
-
- enabled()
- {
- return !this.disabled && g_GameAttributes.mapType != "random";
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.PromoteSelectedUnits = class
-{
- label()
- {
- return translate("Promote selected units");
- }
-
- onPress(checked)
- {
- Engine.PostNetworkCommand({
- "type": "promote",
- "entities": g_Selection.toList()
- });
- }
-
- checked()
- {
- return false;
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.EnableCulling = class
-{
- label()
- {
- return translate("Enable culling");
- }
-
- onPress(checked)
- {
- Engine.GameView_SetCullingEnabled(checked);
- }
-
- checked()
- {
- return Engine.GameView_GetCullingEnabled();
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.LockCullCamera = class
-{
- label()
- {
- return translate("Lock cull camera");
- }
-
- onPress(checked)
- {
- Engine.GameView_SetLockCullCameraEnabled(checked);
- }
-
- checked()
- {
- return Engine.GameView_GetLockCullCameraEnabled();
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.DisplayCameraFrustum = class
-{
- label()
- {
- return translate("Display camera frustum");
- }
-
- onPress(checked)
- {
- Engine.Renderer_SetDisplayFrustumEnabled(checked);
- }
-
- checked()
- {
- return Engine.Renderer_GetDisplayFrustumEnabled();
- }
-};
-
-DeveloperOverlayCheckboxes.prototype.DisplayShadowsFrustum = class
-{
- label()
- {
- return translate("Display shadows frustum");
- }
-
- onPress(checked)
- {
- Engine.Renderer_SetDisplayShadowsFrustumEnabled(checked);
- }
-
- checked()
- {
- return Engine.Renderer_GetDisplayShadowsFrustumEnabled();
- }
-};
Index: binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayControls.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayControls.js
@@ -0,0 +1,107 @@
+DeveloperOverlay.prototype.Controls = [
+ {
+ "label": translate("Control all units"),
+ "onPress": (checked) => Engine.PostNetworkCommand({
+ "type": "control-all",
+ "flag": checked
+ }),
+ "checked": () => {
+ let playerState = g_SimState.players[g_ViewedPlayer];
+ return playerState ? playerState.controlsAll : false;
+ }
+ },
+ {
+ "label": translate("Change perspective"),
+ "onPress": (checked) => g_PlayerViewControl.setChangePerspective(checked)
+ },
+ {
+ "label": translate("Display selection state"),
+ "class": "SelectionState",
+ },
+ {
+ "label": translate("Pathfinder overlay"),
+ "onPress": (checked) => Engine.GuiInterfaceCall("SetPathfinderDebugOverlay", checked)
+ },
+ {
+ "label": translate("Hierarchical pathfinder overlay"),
+ "onPress": (checked) => Engine.GuiInterfaceCall("SetPathfinderHierDebugOverlay", checked)
+ },
+ {
+ "label": translate("Obstruction overlay"),
+ "onPress": (checked) => Engine.GuiInterfaceCall("SetObstructionDebugOverlay", checked)
+ },
+ {
+ "label": translate("Unit motion overlay"),
+ "onPress": (checked) => g_Selection.SetMotionDebugOverlay(checked)
+ },
+ {
+ "label": translate("Range overlay"),
+ "onPress": (checked) => Engine.GuiInterfaceCall("SetRangeDebugOverlay", checked)
+ },
+ {
+ "label": translate("Bounding box overlay"),
+ "onPress": (checked) => Engine.SetBoundingBoxDebugOverlay(checked)
+ },
+ {
+ "label": translate("Restrict camera"),
+ "onPress": (checked) => Engine.GameView_SetConstrainCameraEnabled(checked),
+ "checked": () => Engine.GameView_GetConstrainCameraEnabled()
+ },
+ {
+ "label": translate("Reveal map"),
+ "onPress": (checked) => Engine.PostNetworkCommand({
+ "type": "reveal-map",
+ "enable": checked
+ }),
+ "checked": () => Engine.GuiInterfaceCall("IsMapRevealed")
+ },
+ {
+ "label": translate("Enable time warp"),
+ "class": "TimeWarp"
+ },
+ {
+ "label": translate("Activate Rejoin Test"),
+ "class": "DeveloperOverlayButton",
+ "onPress": (object) => {
+ let box = new SessionMessageBox();
+ box.Title = "Rejoin Test";
+ box.Caption = "Warning: the rejoin test can't be de-activated and is quite slow. Its only purpose is to check for OOS.";
+ box.Buttons = [
+ { "caption": "Cancel" },
+ { "caption": "OK", "onPress": () => {
+ Engine.ActivateRejoinTest();
+ object.button.enabled = false;
+ } }
+ ];
+ box.display();
+ }
+ },
+ {
+ "label": translate("Promote selected units"),
+ "class": "DeveloperOverlayButton",
+ "onPress": () => Engine.PostNetworkCommand({
+ "type": "promote",
+ "entities": g_Selection.toList()
+ })
+ },
+ {
+ "label": translate("Enable culling"),
+ "onPress": (checked) => Engine.GameView_SetCullingEnabled(checked),
+ "checked": () => Engine.GameView_GetCullingEnabled()
+ },
+ {
+ "label": translate("Lock cull camera"),
+ "onPress": (checked) => Engine.GameView_SetLockCullCameraEnabled(checked),
+ "checked": () => Engine.GameView_GetLockCullCameraEnabled()
+ },
+ {
+ "label": translate("Display camera frustum"),
+ "onPress": (checked) => Engine.Renderer_SetDisplayFrustumEnabled(checked),
+ "checked": () => Engine.Renderer_GetDisplayFrustumEnabled()
+ },
+ {
+ "label": translate("Display shadows frustum"),
+ "onPress": (checked) => Engine.Renderer_SetDisplayShadowsFrustumEnabled(checked),
+ "checked": () => Engine.Renderer_GetDisplayShadowsFrustumEnabled()
+ },
+];
Index: binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayEntityState.js
===================================================================
--- binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayEntityState.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * This class manages the developer overlay which displays the state of the first selected entity.
- */
-class DeveloperOverlayEntityState
-{
- constructor(selection)
- {
- this.developerOverlayEntityState = Engine.GetGUIObjectByName("developerOverlayEntityState");
- this.selection = selection;
- this.updater = this.update.bind(this);
- }
-
- setEnabled(enabled)
- {
- this.developerOverlayEntityState.hidden = !enabled;
-
- if (enabled)
- {
- registerSimulationUpdateHandler(this.updater);
- registerEntitySelectionChangeHandler(this.updater);
- }
- else
- {
- unregisterSimulationUpdateHandler(this.updater);
- unregisterEntitySelectionChangeHandler(this.updater);
- }
- }
-
- update()
- {
- let simState = clone(g_SimState);
- simState.players = "<<>>";
- let text = "simulation: " + uneval(simState);
-
- let selection = this.selection.toList();
- if (selection.length)
- {
- let entState = GetEntityState(selection[0]);
- if (entState)
- {
- let template = GetTemplateData(entState.template);
- text += "\n\nentity: {\n";
- for (let k in entState)
- text += " " + k + ":" + uneval(entState[k]) + "\n";
- text += "}\n\ntemplate: " + uneval(template);
- }
- }
-
- this.developerOverlayEntityState.caption = text.replace(/\[/g, "\\[");
- }
-}
Index: binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayEntityState.xml
===================================================================
--- binaries/data/mods/public/gui/session/developer_overlay/DeveloperOverlayEntityState.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
Index: binaries/data/mods/public/gui/session/developer_overlay/SelectionState.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/gui/session/developer_overlay/SelectionState.js
@@ -0,0 +1,59 @@
+class SelectionState extends DeveloperOverlayCheckbox
+{
+ constructor(me, attrs)
+ {
+ super(me, attrs);
+
+ this.textOverlay = this.appendChild(``);
+
+ this.checkbox.onPress = () => this.onPress(this.checkbox.checked);
+
+ this.update();
+ }
+
+ onPress(enabled)
+ {
+ this.textOverlay.hidden = !enabled;
+
+ if (enabled)
+ {
+ registerSimulationUpdateHandler(() => this.update());
+ registerEntitySelectionChangeHandler(() => this.update());
+ }
+ else
+ {
+ unregisterSimulationUpdateHandler(() => this.update());
+ unregisterEntitySelectionChangeHandler(() => this.update());
+ }
+ }
+ update()
+ {
+ let simState = clone(g_SimState);
+ simState.players = "<<>>";
+ let text = "simulation: " + uneval(simState);
+
+ let selection = g_Selection.toList();
+ if (selection.length)
+ {
+ let entState = GetEntityState(selection[0]);
+ if (entState)
+ {
+ let template = GetTemplateData(entState.template);
+ text += "\n\nentity: {\n";
+ for (let k in entState)
+ text += " " + k + ":" + uneval(entState[k]) + "\n";
+ text += "}\n\ntemplate: " + uneval(template);
+ }
+ }
+
+ this.textOverlay.caption = text.replace(/\[/g, "\\[");
+ }
+}
Index: binaries/data/mods/public/gui/session/developer_overlay/TimeWarp.js
===================================================================
--- binaries/data/mods/public/gui/session/developer_overlay/TimeWarp.js
+++ binaries/data/mods/public/gui/session/developer_overlay/TimeWarp.js
@@ -1,31 +1,48 @@
-/**
- * This class manages the timewarp hotkeys that allow jumping back in time and fast forwarding the game.
- */
-class TimeWarp
+class TimeWarp extends DeveloperOverlayCheckbox
{
- constructor()
+ constructor(me, attrs)
{
+ super(me, attrs);
this.enabled = false;
- this.timewarpRewind = Engine.GetGUIObjectByName("timewarpRewind");
- this.timewarpRewind.onPress = this.rewind.bind(this);
+ this.checkbox.onPress = () => this.setEnabled(this.checkbox.checked) && this.updateControl();
- this.timewarpFastForward = Engine.GetGUIObjectByName("timewarpFastForward");
- this.timewarpFastForward.onPress = this.fastForward.bind(this);
- this.timewarpFastForward.onRelease = this.resetSpeed.bind(this);
+ Engine.SetGlobalHotkey("session.timewarp.rewind", "Press", () => this.rewind());
+ Engine.SetGlobalHotkey("session.timewarp.fastforward", "Press", () => this.fastForward());
+ Engine.SetGlobalHotkey("session.timewarp.fastforward", "Release", () => this.resetSpeed());
+
+ // Number of turns between snapshots.
+ this.NumberTurns = 10;
+
+ // Gamespeed used while pressing the fast forward hotkey.
+ this.FastForwardSpeed = 20;
+ }
+
+ updateControl()
+ {
+ this.checkbox.checked = this.enabled;
}
setEnabled(enabled)
{
if (g_IsNetworked)
return;
-
this.enabled = enabled;
+ if (!enabled)
+ return;
- if (enabled)
- (new TimeWarpMessageBox()).display();
-
- Engine.EnableTimeWarpRecording(enabled ? this.NumberTurns : 0);
+ let box = new SessionMessageBox();
+ box.Width = 500;
+ box.Height = 250;
+ box.Title = translate("Time warp mode");
+ box.Caption = translate("Note: time warp mode is a developer option, and not intended for use over long periods of time. Using it incorrectly may cause the game to run out of memory or crash.");
+ box.Buttons = [
+ { "caption": "Cancel" },
+ { "caption": "OK", "onPress": () => {
+ Engine.EnableTimeWarpRecording(enabled ? this.NumberTurns : 0);
+ } }
+ ];
+ box.display();
}
rewind()
@@ -33,35 +50,14 @@
if (this.enabled)
Engine.RewindTimeWarp();
}
-
fastForward()
{
if (this.enabled)
Engine.SetSimRate(this.FastForwardSpeed);
}
-
resetSpeed()
{
if (this.enabled)
Engine.SetSimRate(1);
}
}
-
-/**
- * Number of turns between snapshots.
- */
-TimeWarp.prototype.NumberTurns = 10;
-
-/**
- * Gamespeed used while pressing the fast forward hotkey.
- */
-TimeWarp.prototype.FastForwardSpeed = 20;
-
-class TimeWarpMessageBox extends SessionMessageBox
-{
-}
-TimeWarpMessageBox.prototype.Width = 500;
-TimeWarpMessageBox.prototype.Height = 250;
-TimeWarpMessageBox.prototype.Title = translate("Time warp mode");
-TimeWarpMessageBox.prototype.Caption = translate(
- "Note: time warp mode is a developer option, and not intended for use over long periods of time. Using it incorrectly may cause the game to run out of memory or crash.");
Index: binaries/data/mods/public/gui/session/developer_overlay/TimeWarp.xml
===================================================================
--- binaries/data/mods/public/gui/session/developer_overlay/TimeWarp.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
Index: binaries/data/mods/public/gui/session/session.js
===================================================================
--- binaries/data/mods/public/gui/session/session.js
+++ binaries/data/mods/public/gui/session/session.js
@@ -12,7 +12,6 @@
var g_Ambient;
var g_Chat;
var g_Cheats;
-var g_DeveloperOverlay;
var g_DiplomacyColors;
var g_DiplomacyDialog;
var g_GameSpeedControl;
@@ -278,7 +277,8 @@
g_Ambient = new Ambient();
g_Chat = new Chat(g_PlayerViewControl, g_Cheats);
- g_DeveloperOverlay = new DeveloperOverlay(g_PlayerViewControl, g_Selection);
+ Engine.GetGUIObjectByName("session").appendChild(`
+ `).init();
g_DiplomacyDialog = new DiplomacyDialog(g_PlayerViewControl, g_DiplomacyColors);
g_GameSpeedControl = new GameSpeedControl(g_PlayerViewControl);
g_Menu = new Menu(g_PauseControl, g_PlayerViewControl, g_Chat);
Index: binaries/data/mods/public/gui/session/session.xml
===================================================================
--- binaries/data/mods/public/gui/session/session.xml
+++ binaries/data/mods/public/gui/session/session.xml
@@ -44,7 +44,6 @@
-
Index: source/gui/CGUI.h
===================================================================
--- source/gui/CGUI.h
+++ source/gui/CGUI.h
@@ -270,6 +270,13 @@
IGUIObject* ConstructObject(const CStr& str);
public:
+
+ /**
+ * Create components and assign them to the parent.
+ * @return the object or nullptr if the creation failed.
+ */
+ IGUIObject* AppendChildFromXML(IGUIObject* parent, const std::string& xmlData);
+
/**
* Get Focused Object.
*/
@@ -418,7 +425,15 @@
*
* @see LoadXmlFile()
*/
- void Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector >& NameSubst, std::unordered_set& Paths, u32 nesting_depth);
+ IGUIObject* Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector >& NameSubst, std::unordered_set& Paths, u32 nesting_depth);
+
+ /**
+ * Read in an and stores it as a child in the pParent.
+ * This will generate the XMl definiton using a JS object.
+ *
+ * @see Xeromyces_ReadObject.
+ */
+ IGUIObject* Xeromyces_ReadComponent(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector >& NameSubst, std::unordered_set& Paths, u32 nesting_depth);
/**
* Reads in the element \, which repeats its child \s
Index: source/gui/CGUI.cpp
===================================================================
--- source/gui/CGUI.cpp
+++ source/gui/CGUI.cpp
@@ -45,6 +45,8 @@
#include
#include
+#include "js/Wrapper.h"
+
extern int g_yres;
const double SELECT_DBLCLICK_RATE = 0.5;
@@ -349,6 +351,38 @@
return true;
}
+IGUIObject* CGUI::AppendChildFromXML(IGUIObject* parent, const std::string& xmlData)
+{
+ PROFILE2("AppendChildFromXML");
+
+ CXeromyces XeroFile;
+ if (XeroFile.LoadString(xmlData.c_str()) != PSRETURN_OK)
+ return nullptr;
+
+ XMBElement node = XeroFile.GetRoot();
+
+ std::vector > subst;
+ std::unordered_set paths;
+
+ IGUIObject* ret = nullptr;
+ if (XeroFile.GetElementString(node.GetNodeName()) == "object")
+ ret = Xeromyces_ReadObject(node, &XeroFile, parent, subst, paths, 0);
+ else if (XeroFile.GetElementString(node.GetNodeName()) == "component")
+ ret = Xeromyces_ReadComponent(node, &XeroFile, parent, subst, paths, 0);
+ else
+ LOGERROR("Unrecognised child");
+
+ if (!ret)
+ return nullptr;
+
+ parent->RecurseObject(nullptr, &IGUIObject::UpdateCachedSize);
+
+ SGUIMessage msg(GUIM_LOAD);
+ parent->RecurseObject(nullptr, &IGUIObject::HandleMessage, msg);
+
+ return ret;
+}
+
IGUIObject* CGUI::GetBaseObject()
{
return m_BaseObject.get();
@@ -522,6 +556,7 @@
void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, std::unordered_set& Paths)
{
int el_script = pFile->GetElementID("script");
+ int el_component = pFile->GetElementID("component");
std::vector > subst;
@@ -532,6 +567,8 @@
if (child.GetNodeName() == el_script)
// Execute the inline script
Xeromyces_ReadScript(child, pFile, Paths);
+ else if (child.GetNodeName() == el_component)
+ Xeromyces_ReadComponent(child, pFile, m_BaseObject.get(), subst, Paths, 0);
else
// Read in this whole object into the GUI
Xeromyces_ReadObject(child, pFile, m_BaseObject.get(), subst, Paths, 0);
@@ -569,7 +606,7 @@
}
}
-void CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector >& NameSubst, std::unordered_set& Paths, u32 nesting_depth)
+IGUIObject* CGUI::Xeromyces_ReadObject(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector >& NameSubst, std::unordered_set& Paths, u32 nesting_depth)
{
ENSURE(pParent);
@@ -587,12 +624,13 @@
if (!object)
{
LOGERROR("GUI: Unrecognized object type \"%s\"", type.c_str());
- return;
+ return nullptr;
}
// Cache some IDs for element attribute names, to avoid string comparisons
#define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
#define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
+ ELMT(component);
ELMT(object);
ELMT(action);
ELMT(script);
@@ -838,6 +876,8 @@
else
LOGERROR("GUI: 'include' XML element must have valid 'file' or 'directory' attribute found. (object %s)", object->GetPresentableName().c_str());
}
+ else if (element_name == elmt_component)
+ {}
else
{
// Try making the object read the tag.
@@ -848,6 +888,18 @@
object->AdditionalChildrenHandled();
+ if (!AddObject(*pParent, *object))
+ delete object;
+
+ for (XMBElement child : Element.GetChildNodes())
+ {
+ // Check what name the elements got
+ int element_name = child.GetNodeName();
+
+ if (element_name == elmt_component)
+ Xeromyces_ReadComponent(child, pFile, object, NameSubst, Paths, nesting_depth);
+ }
+
if (!ManuallySetZ)
{
// Set it automatically to 10 plus its parents
@@ -860,8 +912,128 @@
object->SetSetting("z", 10.f, false);
}
- if (!AddObject(*pParent, *object))
- delete object;
+ return object;
+}
+
+IGUIObject* CGUI::Xeromyces_ReadComponent(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector >& NameSubst, std::unordered_set& Paths, u32 nesting_depth)
+{
+ ENSURE(pParent);
+
+ XMBAttributeList attributes = Element.GetAttributes();
+
+ CStr JSClass(attributes.GetNamedItem(pFile->GetAttributeID("class")));
+ if (JSClass.empty())
+ {
+ // TODO: would probably be nice to have a better error here...
+ LOGERROR("GUI: Component has no class");
+ return nullptr;
+ }
+
+ ScriptRequest rq(m_ScriptInterface);
+ JS::RootedValue constructor(rq.cx);
+ if (!m_ScriptInterface->GetGlobalProperty(rq, JSClass, &constructor))
+ {
+ LOGERROR("GUI: Component of class %s : constructor not found", JSClass);
+ return nullptr;
+ }
+
+ JS::RootedValue JSAttributes(rq.cx);
+ if (!ScriptInterface::CreateObject(rq, &JSAttributes))
+ {
+ LOGERROR("GUI: Component of class %s : could not create object.", JSClass);
+ return nullptr;
+ }
+
+ int attr_name = pFile->GetAttributeID("name");
+
+ CStr name;
+ for (XMBAttribute attr : attributes)
+ {
+ if (attr.Name == attr_name)
+ name = attr.Value;
+ if (!m_ScriptInterface->SetProperty(JSAttributes, pFile->GetAttributeString(attr.Name).c_str(), attr.Value))
+ {
+ LOGERROR("GUI: Error calling initXML on Component %s, could not set attribute %s: %s", JSClass, pFile->GetAttributeString(attr.Name).c_str(), attr.Value);
+ return nullptr;
+ }
+ }
+
+ int attr_id = pFile->GetAttributeID("id");
+ int attr_context = pFile->GetAttributeID("context");
+ int elmt_translatableAttribute = pFile->GetElementID("translatableAttribute");
+ for (XMBElement child : Element.GetChildNodes())
+ {
+ if (child.GetNodeName() == elmt_translatableAttribute)
+ {
+ CStr attributeName(child.GetAttributes().GetNamedItem(attr_id)); // Read the attribute name.
+ if (attributeName.empty())
+ {
+ LOGERROR("GUI: ‘translatableAttribute’ XML component with empty ‘id’ XML attribute found. (class: %s)", JSClass);
+ continue;
+ }
+
+ CStr value(child.GetText());
+ if (value.empty())
+ continue;
+
+ CStr context(child.GetAttributes().GetNamedItem(attr_context)); // Read the context if any.
+
+ CStr translatedValue = context.empty() ?
+ g_L10n.Translate(value) :
+ g_L10n.TranslateWithContext(context, value);
+
+ if (!m_ScriptInterface->SetProperty(JSAttributes, attributeName.c_str(), translatedValue))
+ {
+ LOGERROR("GUI: Could not set ‘translatableAttribute’ %s value on component of class %s", attributeName, JSClass);
+ continue;
+ }
+ }
+ }
+
+ JS::RootedValue parent(rq.cx, JS::ObjectValue(*pParent->GetJSObject()));
+
+ std::string xmlData;
+ if (!m_ScriptInterface->CallFunction(constructor, "initXML", xmlData, parent, JSAttributes))
+ {
+ LOGERROR("GUI: Error calling initXML on Component %s", JSClass);
+ return nullptr;
+ }
+
+ CXeromyces XeroFile;
+ if (XeroFile.LoadString(xmlData.c_str()) != PSRETURN_OK)
+ {
+ LOGERROR("GUI: Error parsing XML returned from Component %s initXML()", JSClass);
+ return nullptr;
+ }
+
+ XMBElement node = XeroFile.GetRoot();
+
+ std::vector > subst;
+ std::unordered_set paths;
+
+ IGUIObject* guiObj = Xeromyces_ReadObject(node, &XeroFile, pParent, subst, paths, 0);
+ if (!guiObj)
+ {
+ LOGERROR("GUI: GUI Object for component %s (%s) could not be found", JSClass, name);
+ return nullptr;
+ }
+
+ JS::RootedValue component(rq.cx);
+ JS::RootedValueArray<2> args(rq.cx);
+ args[0].set(JS::ObjectValue(*guiObj->GetJSObject()));
+ args[1].set(JSAttributes);
+ m_ScriptInterface->CallConstructor(constructor, args, &component);
+
+ JS::RootedObject compObj(rq.cx, component.toObjectOrNull());
+ if (!compObj)
+ {
+ ScriptException::CatchPending(rq);
+ LOGERROR("GUI: Could not create JS component for %s (%s)", JSClass, name);
+ return nullptr;
+ }
+ guiObj->AssignJSComponent(compObj);
+
+ return guiObj;
}
void CGUI::Xeromyces_ReadRepeat(XMBElement Element, CXeromyces* pFile, IGUIObject* pParent, std::vector >& NameSubst, std::unordered_set& Paths, u32 nesting_depth)
Index: source/gui/ObjectBases/IGUIObject.h
===================================================================
--- source/gui/ObjectBases/IGUIObject.h
+++ source/gui/ObjectBases/IGUIObject.h
@@ -465,6 +465,11 @@
*/
virtual void CreateJSObject();
+ /**
+ * Assign a real JS object to this GUI Object, wrapping the proxy JS object.
+ */
+ void AssignJSComponent(JS::HandleObject obj);
+
/**
* Updates some internal data depending on the setting changed.
*/
@@ -539,6 +544,8 @@
// Cached JSObject representing this GUI object
JS::PersistentRootedObject m_JSObject;
+ // Objects created via a Component will return a real JS Object, proxying to m_JSObject.
+ JS::Heap m_JSComponent;
// Cache references to settings for performance
bool m_Enabled;
Index: source/gui/ObjectBases/IGUIObject.cpp
===================================================================
--- source/gui/ObjectBases/IGUIObject.cpp
+++ source/gui/ObjectBases/IGUIObject.cpp
@@ -77,7 +77,8 @@
for (const std::pair& p : m_Settings)
delete p.second;
- if (!m_ScriptHandlers.empty())
+
+ if (!m_ScriptHandlers.empty() || !!m_JSComponent)
JS_RemoveExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetGeneralJSContext(), Trace, this);
// m_Children is deleted along all other GUI Objects in the CGUI destructor
@@ -342,7 +343,7 @@
void IGUIObject::SetScriptHandler(const CStr& eventName, JS::HandleObject Function)
{
- if (m_ScriptHandlers.empty())
+ if (m_ScriptHandlers.empty() && !m_JSComponent)
JS_AddExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetGeneralJSContext(), Trace, this);
m_ScriptHandlers[eventName] = JS::Heap(Function);
@@ -466,6 +467,17 @@
ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton()), m_JSObject);
}
+void IGUIObject::AssignJSComponent(JS::HandleObject obj)
+{
+ if (!m_JSObject.initialized())
+ CreateJSObject();
+
+ if (m_ScriptHandlers.empty() && !m_JSComponent)
+ JS_AddExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetGeneralJSContext(), Trace, this);
+
+ m_JSComponent.set(obj);
+}
+
JSObject* IGUIObject::GetJSObject()
{
// Cache the object when somebody first asks for it, because otherwise
@@ -473,6 +485,9 @@
if (!m_JSObject.initialized())
CreateJSObject();
+ if (m_JSComponent)
+ return m_JSComponent.get();
+
return m_JSObject.get();
}
@@ -557,8 +572,10 @@
void IGUIObject::TraceMember(JSTracer* trc)
{
+ if (m_JSComponent)
+ JS::TraceEdge(trc, &m_JSComponent, "IGUIObject::m_JSComponent");
+
// Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced!
-
for (std::pair>& handler : m_ScriptHandlers)
JS::TraceEdge(trc, &handler.second, "IGUIObject::m_ScriptHandlers");
}
Index: source/gui/Scripting/JSInterface_GUIProxy.h
===================================================================
--- source/gui/Scripting/JSInterface_GUIProxy.h
+++ source/gui/Scripting/JSInterface_GUIProxy.h
@@ -106,6 +106,7 @@
protected:
// BaseProxyHandler interface below
+ virtual bool has(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, bool* bp) const override final;
// Handler for `object.x`
virtual bool get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue receiver, JS::HandleId id, JS::MutableHandleValue vp) const override final;
// Handler for `object.x = y;`
Index: source/gui/Scripting/JSInterface_GUIProxy_impl.h
===================================================================
--- source/gui/Scripting/JSInterface_GUIProxy_impl.h
+++ source/gui/Scripting/JSInterface_GUIProxy_impl.h
@@ -132,6 +132,33 @@
js::SetProxyReservedSlot(val, 0, data);
}
+template
+bool JSI_GUIProxy::has(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, bool* bp) const
+{
+ ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
+ ScriptRequest rq(*pScriptInterface);
+
+ T* e = static_cast(js::GetProxyPrivate(proxy.get()).toPrivate());
+ if (!e)
+ return false;
+
+ JS::RootedValue idval(rq.cx);
+ if (!JS_IdToValue(rq.cx, id, &idval))
+ return false;
+
+ std::string propName;
+ if (!ScriptInterface::FromJSVal(rq, idval, propName))
+ return false;
+
+ *bp = propName == "parent" || propName == "name" || propName == "children" || e->SettingExists(propName) || propName.substr(0, 2) == "on";
+ if (!*bp)
+ {
+ JS::RootedValue temp(rq.cx);
+ *bp = PropGetter(proxy, propName, &temp);
+ }
+ return true;
+}
+
template
bool JSI_GUIProxy::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue UNUSED(receiver), JS::HandleId id, JS::MutableHandleValue vp) const
{
Index: source/gui/Scripting/JSInterface_IGUIObject.cpp
===================================================================
--- source/gui/Scripting/JSInterface_IGUIObject.cpp
+++ source/gui/Scripting/JSInterface_IGUIObject.cpp
@@ -21,6 +21,26 @@
using GUIObjectType = IGUIObject;
+
+bool IGUIObject_AppendChildFromXML(JSContext* cx, uint argc, JS::Value* vp)
+{
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ IGUIObject* e = static_cast(js::GetProxyPrivate(args.thisv().toObjectOrNull()).toPrivate());
+ if (!e)
+ return false;
+
+ ScriptInterface& scriptInterface = *ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
+ ScriptRequest rq(scriptInterface);
+
+ std::string data;
+ ScriptInterface::FromJSVal(rq, args.get(0), data);
+ IGUIObject* child = e->GetGUI().AppendChildFromXML(e, data);
+ if (!child)
+ return false;
+ args.rval().set(JS::ObjectValue(*child->GetJSObject()));
+ return true;
+}
+
template<>
void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
{
@@ -30,6 +50,7 @@
cache->setFunction(rq, "blur", func(IGUIObject, blur), 0);
cache->setFunction(rq, "getComputedSize", func(IGUIObject, getComputedSize), 0);
#undef func
+ cache->setFunction(rq, "appendChild", IGUIObject_AppendChildFromXML, 1);
}
template class JSI_GUIProxy;