Index: ps/trunk/binaries/data/config/default.cfg
===================================================================
--- ps/trunk/binaries/data/config/default.cfg
+++ ps/trunk/binaries/data/config/default.cfg
@@ -288,6 +288,9 @@
8 = 8, Num8
9 = 9, Num9
+[hotkey.gamesetup]
+mapbrowser.open = "M"
+
[hotkey.session]
kill = Delete, Backspace ; Destroy selected units
stop = "H" ; Stop the current action
Index: ps/trunk/binaries/data/mods/public/gui/common/MapCache.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/MapCache.js
+++ ps/trunk/binaries/data/mods/public/gui/common/MapCache.js
@@ -1,92 +0,0 @@
-/**
- * This class obtains, caches, and provides the game settings from map XML and JSON files.
- */
-class MapCache
-{
- constructor()
- {
- this.cache = {};
- }
-
- getMapData(mapType, mapPath)
- {
- if (!mapPath || mapPath == "random")
- return undefined;
-
- if (!this.cache[mapPath])
- {
- let mapData = g_Settings.MapTypes.find(type => type.Name == mapType).GetData(mapPath);
-
- // Remove gaia, TODO: Maps should be consistent
- if (mapData &&
- mapData.settings &&
- mapData.settings.PlayerData &&
- mapData.settings.PlayerData.length &&
- !mapData.settings.PlayerData[0])
- {
- mapData.settings.PlayerData.shift();
- }
-
- this.cache[mapPath] = mapData;
- }
-
- return this.cache[mapPath];
- }
-
- /**
- * Doesn't translate, so that lobby page viewers can do that locally.
- * The result is to be used with translateMapName.
- */
- getTranslatableMapName(mapType, mapPath)
- {
- if (mapPath == "random")
- return "random";
-
- let mapData = this.getMapData(mapType, mapPath);
- return mapData && mapData.settings && mapData.settings.Name || undefined;
- }
-
- translateMapName(mapName)
- {
- return mapName == "random" ?
- translateWithContext("map selection", "Random") :
- mapName ? translate(mapName) : "";
- }
-
- getTranslatedMapDescription(mapType, mapPath)
- {
- if (mapPath == "random")
- return translate("A randomly selected map.");
-
- let mapData = this.getMapData(mapType, mapPath);
- return mapData && mapData.settings && translate(mapData.settings.Description) || "";
- }
-
- getMapPreview(mapType, mapPath, gameAttributes = undefined)
- {
- let filename = gameAttributes && gameAttributes.settings && gameAttributes.settings.Preview || undefined;
-
- if (!filename)
- {
- let mapData = this.getMapData(mapType, mapPath);
- filename = mapData && mapData.settings && mapData.settings.Preview || this.DefaultPreview;
- }
-
- return "cropped:" + this.PreviewWidth + "," + this.PreviewHeight + ":" + this.PreviewsPath + filename;
- }
-}
-
-MapCache.prototype.TexturesPath =
- "art/textures/ui/";
-
-MapCache.prototype.PreviewsPath =
- "session/icons/mappreview/";
-
-MapCache.prototype.DefaultPreview =
- "nopreview.png";
-
-MapCache.prototype.PreviewWidth =
- 400 / 512;
-
-MapCache.prototype.PreviewHeight =
- 300 / 512;
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/MapFilters.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/MapFilters.js
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Controls/MapFilters.js
@@ -1,119 +0,0 @@
-class MapFilters
-{
- constructor(mapCache)
- {
- this.mapCache = mapCache;
- }
-
- /**
- * Some map filters may reject every map of a particular mapType.
- * This function allows identifying which map filters have any matches for that maptype.
- */
- getAvailableMapFilters(mapTypeName)
- {
- return this.Filters.filter(filter =>
- this.getFilteredMaps(mapTypeName, filter.Name, true));
- }
-
- /**
- * This function identifies all maps matching the given mapType and mapFilter.
- * If existence is true, it will only test if there is at least one file for that mapType and mapFilter.
- * Otherwise it returns an array with filename, translated map title and map description.
- */
- getFilteredMaps(mapTypeName, filterName, existence)
- {
- let index = g_MapTypes.Name.findIndex(name => name == mapTypeName);
- if (index == -1)
- {
- error("Can't get filtered maps for invalid maptype: " + mapTypeName);
- return undefined;
- }
-
- let mapFilter = this.Filters.find(filter => filter.Name == filterName);
- if (!mapFilter)
- {
- error("Invalid mapfilter name: " + filterName);
- return undefined;
- }
-
- Engine.ProfileStart("getFilteredMaps");
-
- let maps = [];
- let mapTypePath = g_MapTypes.Path[index];
- for (let filename of listFiles(mapTypePath, g_MapTypes.Suffix[index], false))
- {
- if (filename.startsWith(this.HiddenFilesPrefix))
- continue;
-
- let mapPath = mapTypePath + filename;
- let mapData = this.mapCache.getMapData(mapTypeName, mapPath);
-
- // Map files may come with custom json files
- if (!mapData || !mapData.settings)
- continue;
-
- if (MatchesClassList(mapData.settings.Keywords || [], mapFilter.Match))
- {
- if (existence)
- {
- Engine.ProfileStop();
- return true;
- }
-
- maps.push({
- "file": mapPath,
- "name": translate(mapData.settings.Name),
- "description": translate(mapData.settings.Description)
- });
- }
- }
-
- Engine.ProfileStop();
- return existence ? false : maps;
- }
-}
-
-/**
- * When maps start with this prefix, they will not appear in the maplist.
- * Used for the Atlas _default.xml for instance.
- */
-MapFilters.prototype.HiddenFilesPrefix = "_";
-
-MapFilters.prototype.Filters = [
- {
- "Name": "default",
- "Title": translate("Default"),
- "Description": translate("All maps except naval and demo maps."),
- "Match": ["!naval !demo !hidden"]
- },
- {
- "Name": "naval",
- "Title": translate("Naval Maps"),
- "Description": translate("Maps where ships are needed to reach the enemy."),
- "Match": ["naval"]
- },
- {
- "Name": "demo",
- "Title": translate("Demo Maps"),
- "Description": translate("These maps are not playable but for demonstration purposes only."),
- "Match": ["demo"]
- },
- {
- "Name": "new",
- "Title": translate("New Maps"),
- "Description": translate("Maps that are brand new in this release of the game."),
- "Match": ["new"]
- },
- {
- "Name": "trigger",
- "Title": translate("Trigger Maps"),
- "Description": translate("Maps that come with scripted events and potentially spawn enemy units."),
- "Match": ["trigger"]
- },
- {
- "Name": "all",
- "Title": translate("All Maps"),
- "Description": translate("Every map of the chosen maptype."),
- "Match": "!"
- }
-];
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControlButton.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControlButton.js
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControlButton.js
@@ -0,0 +1,25 @@
+/**
+ * This class is implemented by gamesettings that are controlled by a button.
+ */
+class GameSettingControlButton extends GameSettingControl
+{
+ setControl(gameSettingControlManager)
+ {
+ let row = gameSettingControlManager.getNextRow("buttonSettingFrame");
+ this.frame = Engine.GetGUIObjectByName("buttonSettingFrame[" + row + "]");
+ this.button = Engine.GetGUIObjectByName("buttonSettingControl[" + row + "]");
+ this.button.onPress = this.onPress.bind(this);
+ if (this.Caption)
+ this.button.caption = this.Caption;
+ }
+
+ setControlTooltip(tooltip)
+ {
+ this.button.tooltip = tooltip;
+ }
+
+ setControlHidden(hidden)
+ {
+ this.button.hidden = hidden;
+ }
+}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControlButton.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControlButton.xml
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControlButton.xml
@@ -0,0 +1,13 @@
+
+
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout.js
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingsLayout.js
@@ -9,6 +9,7 @@
"MapType",
"MapFilter",
"MapSelection",
+ "MapBrowser",
"MapSize",
"TeamPlacement",
"Landscape",
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Buttons/MapBrowser.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Buttons/MapBrowser.js
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Buttons/MapBrowser.js
@@ -0,0 +1,29 @@
+GameSettingControls.MapBrowser = class extends GameSettingControlButton
+{
+ constructor(...args)
+ {
+ super(...args);
+
+ this.button.tooltip = colorizeHotkey(this.HotkeyTooltip, this.HotkeyConfig);
+ Engine.SetGlobalHotkey(this.HotkeyConfig, "Press", this.onPress.bind(this));
+ }
+
+ setControlHidden()
+ {
+ this.button.hidden = false;
+ }
+
+ onPress()
+ {
+ this.setupWindow.pages.MapBrowserPage.openPage();
+ }
+};
+
+GameSettingControls.MapBrowser.prototype.HotkeyConfig =
+ "gamesetup.mapbrowser.open";
+
+GameSettingControls.MapBrowser.prototype.Caption =
+ translate("Browse Maps");
+
+GameSettingControls.MapBrowser.prototype.HotkeyTooltip =
+ translate("Press %(hotkey)s to view the list of available maps.");
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSetupPage.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSetupPage.xml
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/GameSetupPage.xml
@@ -6,6 +6,7 @@
+
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/GameSettingsPanel.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/GameSettingsPanel.xml
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/GameSettingsPanel.xml
@@ -5,6 +5,13 @@
+
+
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/MapPreview.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/MapPreview.js
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/MapPreview.js
@@ -2,16 +2,24 @@
{
constructor(setupWindow)
{
+ this.setupWindow = setupWindow;
this.gameSettingsControl = setupWindow.controls.gameSettingsControl;
this.mapCache = setupWindow.controls.mapCache;
this.mapInfoName = Engine.GetGUIObjectByName("mapInfoName");
this.mapPreview = Engine.GetGUIObjectByName("mapPreview");
+ this.mapPreview.onMouseLeftPress = this.onPress.bind(this); // TODO: Why does onPress not work? CGUI.cpp seems to support it
+ this.mapPreview.tooltip = this.Tooltip;
this.gameSettingsControl.registerMapChangeHandler(this.onMapChange.bind(this));
this.gameSettingsControl.registerGameAttributesBatchChangeHandler(this.onGameAttributesBatchChange.bind(this));
}
+ onPress()
+ {
+ this.setupWindow.pages.MapBrowserPage.openPage();
+ }
+
onMapChange(mapData)
{
let preview = mapData && mapData.settings && mapData.settings.Preview;
@@ -34,3 +42,6 @@
this.mapCache.getMapPreview(g_GameAttributes.mapType, g_GameAttributes.map, g_GameAttributes);
}
}
+
+MapPreview.prototype.Tooltip =
+ translate("Click to view the list of available maps.");
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/MapPreview.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/MapPreview.xml
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/GameSetupPage/Panels/MapPreview.xml
@@ -1,7 +1,7 @@
-
+
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/MapBrowserPage/MapBrowserPage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/MapBrowserPage/MapBrowserPage.js
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/MapBrowserPage/MapBrowserPage.js
@@ -0,0 +1,22 @@
+SetupWindowPages.MapBrowserPage = class extends MapBrowser
+{
+ constructor(setupWindow)
+ {
+ super(setupWindow.controls.mapCache, setupWindow.controls.mapFilters, setupWindow);
+ this.mapBrowserPage.hidden = true;
+ }
+
+ openPage()
+ {
+ super.openPage();
+
+ this.mapBrowserPage.hidden = false;
+ }
+
+ closePage()
+ {
+ super.closePage();
+
+ this.mapBrowserPage.hidden = true;
+ }
+};
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/MapBrowserPage/MapBrowserPage.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/MapBrowserPage/MapBrowserPage.xml
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/Pages/MapBrowserPage/MapBrowserPage.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
@@ -2,6 +2,7 @@
+
@@ -15,6 +16,7 @@
+
Index: ps/trunk/binaries/data/mods/public/gui/loadgame/load.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/loadgame/load.xml
+++ ps/trunk/binaries/data/mods/public/gui/loadgame/load.xml
@@ -3,6 +3,7 @@
+
Index: ps/trunk/binaries/data/mods/public/gui/lobby/lobby.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/lobby.xml
+++ ps/trunk/binaries/data/mods/public/gui/lobby/lobby.xml
@@ -3,6 +3,7 @@
+
Index: ps/trunk/binaries/data/mods/public/gui/maps/MapCache.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/MapCache.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/MapCache.js
@@ -0,0 +1,92 @@
+/**
+ * This class obtains, caches, and provides the game settings from map XML and JSON files.
+ */
+class MapCache
+{
+ constructor()
+ {
+ this.cache = {};
+ }
+
+ getMapData(mapType, mapPath)
+ {
+ if (!mapPath || mapPath == "random")
+ return undefined;
+
+ if (!this.cache[mapPath])
+ {
+ let mapData = g_Settings.MapTypes.find(type => type.Name == mapType).GetData(mapPath);
+
+ // Remove gaia, TODO: Maps should be consistent
+ if (mapData &&
+ mapData.settings &&
+ mapData.settings.PlayerData &&
+ mapData.settings.PlayerData.length &&
+ !mapData.settings.PlayerData[0])
+ {
+ mapData.settings.PlayerData.shift();
+ }
+
+ this.cache[mapPath] = mapData;
+ }
+
+ return this.cache[mapPath];
+ }
+
+ /**
+ * Doesn't translate, so that lobby page viewers can do that locally.
+ * The result is to be used with translateMapName.
+ */
+ getTranslatableMapName(mapType, mapPath)
+ {
+ if (mapPath == "random")
+ return "random";
+
+ let mapData = this.getMapData(mapType, mapPath);
+ return mapData && mapData.settings && mapData.settings.Name || undefined;
+ }
+
+ translateMapName(mapName)
+ {
+ return mapName == "random" ?
+ translateWithContext("map selection", "Random") :
+ mapName ? translate(mapName) : "";
+ }
+
+ getTranslatedMapDescription(mapType, mapPath)
+ {
+ if (mapPath == "random")
+ return translate("A randomly selected map.");
+
+ let mapData = this.getMapData(mapType, mapPath);
+ return mapData && mapData.settings && translate(mapData.settings.Description) || "";
+ }
+
+ getMapPreview(mapType, mapPath, gameAttributes = undefined)
+ {
+ let filename = gameAttributes && gameAttributes.settings && gameAttributes.settings.Preview || undefined;
+
+ if (!filename)
+ {
+ let mapData = this.getMapData(mapType, mapPath);
+ filename = mapData && mapData.settings && mapData.settings.Preview || this.DefaultPreview;
+ }
+
+ return "cropped:" + this.PreviewWidth + "," + this.PreviewHeight + ":" + this.PreviewsPath + filename;
+ }
+}
+
+MapCache.prototype.TexturesPath =
+ "art/textures/ui/";
+
+MapCache.prototype.PreviewsPath =
+ "session/icons/mappreview/";
+
+MapCache.prototype.DefaultPreview =
+ "nopreview.png";
+
+MapCache.prototype.PreviewWidth =
+ 400 / 512;
+
+MapCache.prototype.PreviewHeight =
+ 300 / 512;
Index: ps/trunk/binaries/data/mods/public/gui/maps/MapFilters.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/MapFilters.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/MapFilters.js
@@ -0,0 +1,119 @@
+class MapFilters
+{
+ constructor(mapCache)
+ {
+ this.mapCache = mapCache;
+ }
+
+ /**
+ * Some map filters may reject every map of a particular mapType.
+ * This function allows identifying which map filters have any matches for that maptype.
+ */
+ getAvailableMapFilters(mapTypeName)
+ {
+ return this.Filters.filter(filter =>
+ this.getFilteredMaps(mapTypeName, filter.Name, true));
+ }
+
+ /**
+ * This function identifies all maps matching the given mapType and mapFilter.
+ * If existence is true, it will only test if there is at least one file for that mapType and mapFilter.
+ * Otherwise it returns an array with filename, translated map title and map description.
+ */
+ getFilteredMaps(mapTypeName, filterName, existence)
+ {
+ let index = g_MapTypes.Name.findIndex(name => name == mapTypeName);
+ if (index == -1)
+ {
+ error("Can't get filtered maps for invalid maptype: " + mapTypeName);
+ return undefined;
+ }
+
+ let mapFilter = this.Filters.find(filter => filter.Name == filterName);
+ if (!mapFilter)
+ {
+ error("Invalid mapfilter name: " + filterName);
+ return undefined;
+ }
+
+ Engine.ProfileStart("getFilteredMaps");
+
+ let maps = [];
+ let mapTypePath = g_MapTypes.Path[index];
+ for (let filename of listFiles(mapTypePath, g_MapTypes.Suffix[index], false))
+ {
+ if (filename.startsWith(this.HiddenFilesPrefix))
+ continue;
+
+ let mapPath = mapTypePath + filename;
+ let mapData = this.mapCache.getMapData(mapTypeName, mapPath);
+
+ // Map files may come with custom json files
+ if (!mapData || !mapData.settings)
+ continue;
+
+ if (MatchesClassList(mapData.settings.Keywords || [], mapFilter.Match))
+ {
+ if (existence)
+ {
+ Engine.ProfileStop();
+ return true;
+ }
+
+ maps.push({
+ "file": mapPath,
+ "name": translate(mapData.settings.Name),
+ "description": translate(mapData.settings.Description)
+ });
+ }
+ }
+
+ Engine.ProfileStop();
+ return existence ? false : maps.sort((a, b) => a.name.localeCompare(b.name));
+ }
+}
+
+/**
+ * When maps start with this prefix, they will not appear in the maplist.
+ * Used for the Atlas _default.xml for instance.
+ */
+MapFilters.prototype.HiddenFilesPrefix = "_";
+
+MapFilters.prototype.Filters = [
+ {
+ "Name": "default",
+ "Title": translate("Default"),
+ "Description": translate("All maps except naval and demo maps."),
+ "Match": ["!naval !demo !hidden"]
+ },
+ {
+ "Name": "naval",
+ "Title": translate("Naval Maps"),
+ "Description": translate("Maps where ships are needed to reach the enemy."),
+ "Match": ["naval"]
+ },
+ {
+ "Name": "demo",
+ "Title": translate("Demo Maps"),
+ "Description": translate("These maps are not playable but for demonstration purposes only."),
+ "Match": ["demo"]
+ },
+ {
+ "Name": "new",
+ "Title": translate("New Maps"),
+ "Description": translate("Maps that are brand new in this release of the game."),
+ "Match": ["new"]
+ },
+ {
+ "Name": "trigger",
+ "Title": translate("Trigger Maps"),
+ "Description": translate("Maps that come with scripted events and potentially spawn enemy units."),
+ "Match": ["trigger"]
+ },
+ {
+ "Name": "all",
+ "Title": translate("All Maps"),
+ "Description": translate("Every map of the chosen maptype."),
+ "Match": "!"
+ }
+];
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowser.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowser.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowser.js
@@ -0,0 +1,48 @@
+class MapBrowser
+{
+ constructor(mapCache, mapFilters, setupWindow = undefined)
+ {
+ this.openPageHandlers = new Set();
+ this.closePageHandlers = new Set();
+
+ this.mapCache = mapCache;
+ this.mapFilters = mapFilters;
+
+ this.mapBrowserPage = Engine.GetGUIObjectByName("mapBrowserPage");
+ this.mapBrowserPageDialog = Engine.GetGUIObjectByName("mapBrowserPageDialog");
+
+ this.gridBrowser = new MapGridBrowser(this, setupWindow);
+ this.controls = new MapBrowserPageControls(this, this.gridBrowser);
+
+ this.open = false;
+ }
+
+ // TODO: this is mostly gamesetup specific stuff.
+ registerOpenPageHandler(handler)
+ {
+ this.openPageHandlers.add(handler);
+ }
+
+ registerClosePageHandler(handler)
+ {
+ this.closePageHandlers.add(handler);
+ }
+
+ openPage()
+ {
+ if (this.open)
+ return;
+ for (let handler of this.openPageHandlers)
+ handler();
+ this.open = true;
+ }
+
+ closePage()
+ {
+ if (!this.open)
+ return;
+ for (let handler of this.closePageHandlers)
+ handler();
+ this.open = false;
+ }
+}
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowser.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowser.xml
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowser.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Map Browser
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowserPage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowserPage.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowserPage.js
@@ -0,0 +1,20 @@
+/**
+ * TODO: better global state handling in the GUI.
+ * In particular a bunch of those shadow gamesetup stuff.
+ */
+const g_IsController = false;
+const g_GameAttributes = {
+ "mapType": "skirmish",
+ "mapFilter": "default",
+};
+const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes);
+var g_SetupWindow;
+
+function init()
+{
+ let cache = new MapCache();
+ let filters = new MapFilters(cache);
+ let browser = new MapBrowser(cache, filters);
+ browser.registerClosePageHandler(() => Engine.PopGuiPage());
+ browser.openPage();
+}
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowserPage.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowserPage.xml
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/MapBrowserPage.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapBrowserControls.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapBrowserControls.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapBrowserControls.js
@@ -0,0 +1,58 @@
+class MapBrowserPageControls
+{
+ constructor(mapBrowserPage, gridBrowser)
+ {
+ for (let name in this)
+ this[name] = new this[name](mapBrowserPage, gridBrowser);
+
+ this.mapBrowserPage = mapBrowserPage;
+ this.gridBrowser = gridBrowser;
+
+ this.setupButtons();
+ }
+
+ setupButtons()
+ {
+ this.pickRandom = Engine.GetGUIObjectByName("mapBrowserPagePickRandom");
+ if (!g_IsController)
+ this.pickRandom.hidden = true;
+ this.pickRandom.onPress = () => {
+ let index = randIntInclusive(0, this.gridBrowser.itemCount - 1);
+ this.gridBrowser.setSelectedIndex(index);
+ this.gridBrowser.goToPageOfSelected();
+ };
+
+ this.select = Engine.GetGUIObjectByName("mapBrowserPageSelect");
+ this.select.onPress = () => this.onSelect();
+
+ this.close = Engine.GetGUIObjectByName("mapBrowserPageClose");
+ if (g_SetupWindow)
+ this.close.tooltip = colorizeHotkey(
+ translate("%(hotkey)s: Close map browser and discard the selection."), "cancel");
+ else
+ {
+ this.close.caption = translate("Close");
+ this.close.tooltip = colorizeHotkey(
+ translate("%(hotkey)s: Close map browser."), "cancel");
+ }
+
+ this.close.onPress = () => this.mapBrowserPage.closePage();
+
+ this.select.hidden = !g_IsController;
+ if (!g_IsController)
+ this.close.size = this.select.size;
+
+ this.gridBrowser.registerSelectionChangeHandler(() => this.onSelectionChange());
+ }
+
+ onSelectionChange()
+ {
+ this.select.enabled = this.gridBrowser.selected != -1;
+ }
+
+ onSelect()
+ {
+ this.gridBrowser.submitMapSelection();
+ this.mapBrowserPage.closePage();
+ }
+}
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapBrowserControls.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapBrowserControls.xml
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapBrowserControls.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ −
+
+
+
+
+
+
+
+ Search Map:
+
+
+
+
+
+ Map Type:
+
+
+
+
+
+ Map Filter:
+
+
+
+
+
+
+
+
+
+ Pick Random Map
+
+
+ Cancel
+
+
+ Select
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapDescription.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapDescription.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapDescription.js
@@ -0,0 +1,49 @@
+MapBrowserPageControls.prototype.MapDescription = class
+{
+ constructor(mapBrowserPage, gridBrowser)
+ {
+ this.ImageRatio = 4 / 3;
+
+ this.mapBrowserPage = mapBrowserPage;
+ this.gridBrowser = gridBrowser;
+ this.mapCache = mapBrowserPage.mapCache;
+
+ this.mapBrowserSelectedName = Engine.GetGUIObjectByName("mapBrowserSelectedName");
+ this.mapBrowserSelectedPreview = Engine.GetGUIObjectByName("mapBrowserSelectedPreview");
+ this.mapBrowserSelectedDescription = Engine.GetGUIObjectByName("mapBrowserSelectedDescription");
+
+ let computedSize = this.mapBrowserSelectedPreview.getComputedSize();
+ let top = this.mapBrowserSelectedName.size.bottom;
+ let height = Math.floor((computedSize.right - computedSize.left) / this.ImageRatio);
+
+ {
+ let size = this.mapBrowserSelectedPreview.size;
+ size.top = top;
+ size.bottom = top + height;
+ this.mapBrowserSelectedPreview.size = size;
+ }
+
+ {
+ let size = this.mapBrowserSelectedDescription.size;
+ size.top = top + height + 10;
+ this.mapBrowserSelectedDescription.size = size;
+ }
+
+ gridBrowser.registerSelectionChangeHandler(this.onSelectionChange.bind(this));
+ }
+
+ onSelectionChange()
+ {
+ let map = this.gridBrowser.mapList[this.gridBrowser.selected];
+ if (!map)
+ return;
+
+ this.mapBrowserSelectedName.caption = map ? map.name : "";
+ this.mapBrowserSelectedDescription.caption = map ? map.description : "";
+
+ this.mapBrowserSelectedPreview.sprite =
+ this.mapCache.getMapPreview(
+ this.mapBrowserPage.controls.MapFiltering.getSelectedMapType(),
+ map.file);
+ }
+};
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapDescription.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapDescription.xml
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapDescription.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapFiltering.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapFiltering.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/MapFiltering.js
@@ -0,0 +1,70 @@
+MapBrowserPageControls.prototype.MapFiltering = class
+{
+ constructor(mapBrowserPage, gridBrowser)
+ {
+ this.mapBrowserPage = mapBrowserPage;
+ this.gridBrowser = gridBrowser;
+ this.mapFilters = mapBrowserPage.mapFilters;
+
+ this.searchBox = new LabelledInput("mapBrowserSearchBox")
+ .setupEvents(() => this.onChange());
+ this.mapType = new LabelledDropdown("mapBrowserMapType")
+ .setupEvents(() => this.onMapTypeChange());
+ this.mapFilter = new LabelledDropdown("mapBrowserMapFilter")
+ .setupEvents(() => this.onChange());
+
+ mapBrowserPage.registerOpenPageHandler(() => this.onOpenPage());
+ mapBrowserPage.registerClosePageHandler(() => this.onClosePage());
+
+ this.searchBox.blur();
+ }
+
+ onOpenPage()
+ {
+ // setTimeout avoids having the hotkey key inserted into the input text.
+ setTimeout(() => this.searchBox.focus(), 0);
+ this.renderMapFilter();
+ this.mapFilter.select(g_GameAttributes.mapFilter);
+ this.mapType.render(g_MapTypes.Title, g_MapTypes.Name);
+ this.mapType.select(g_GameAttributes.mapType);
+ }
+
+ onClosePage()
+ {
+ this.searchBox.blur();
+ }
+
+ onMapTypeChange()
+ {
+ this.renderMapFilter();
+ this.onChange();
+ }
+
+ onChange()
+ {
+ this.gridBrowser.updateMapList();
+ this.gridBrowser.goToPageOfSelected();
+ }
+
+ renderMapFilter()
+ {
+ let filters = this.mapFilters.getAvailableMapFilters(this.getSelectedMapType());
+ this.mapFilter.render(filters.map(f => f.Title), filters.map(f => f.Name));
+ }
+
+ // TODO: would be nicer to store this state somewhere else.
+ getSearchText()
+ {
+ return this.searchBox.getText() || "";
+ }
+
+ getSelectedMapType()
+ {
+ return this.mapType.getSelected() || g_GameAttributes.mapType;
+ }
+
+ getSelectedMapFilter()
+ {
+ return this.mapFilter.getSelected() || g_GameAttributes.mapFilter;
+ }
+};
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/Pagination.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/Pagination.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/controls/Pagination.js
@@ -0,0 +1,55 @@
+MapBrowserPageControls.prototype.Pagination = class
+{
+ constructor(mapBrowserPage, gridBrowser)
+ {
+ this.status = Engine.GetGUIObjectByName("mapBrowserPageStatus");
+
+ this.previous = Engine.GetGUIObjectByName("mapBrowserPreviousButton");
+ this.next = Engine.GetGUIObjectByName("mapBrowserNextButton");
+
+ this.zoomIn = Engine.GetGUIObjectByName("mapsZoomIn");
+ this.zoomOut = Engine.GetGUIObjectByName("mapsZoomOut");
+
+ this.gridBrowser = gridBrowser;
+ this.gridBrowser.registerPageChangeHandler(() => this.render());
+ this.gridBrowser.registerGridResizeHandler(() => this.render());
+
+ this.setup();
+ this.render();
+ }
+
+ setup()
+ {
+ this.previous.onPress = () => this.gridBrowser.previousPage();
+ this.next.onPress = () => this.gridBrowser.nextPage();
+ this.previous.caption = "←";
+ this.next.caption = "→";
+ this.previous.tooltip = translate("Go to the previous page.");
+ this.next.tooltip = translate("Go to the next page.");
+
+ this.zoomIn.onPress = () => this.gridBrowser.increaseColumnCount(-1);
+ this.zoomOut.onPress = () => this.gridBrowser.increaseColumnCount(1);
+ this.zoomIn.tooltip = translate("Increase map preview size.");
+ this.zoomOut.tooltip = translate("Decrease map preview size.");
+ }
+
+ render()
+ {
+ this.status.caption =
+ sprintf(translate("Maps: %(mapCount)s"), {
+ "mapCount": this.gridBrowser.itemCount
+ }) +
+ " " +
+ sprintf(translate("Page: %(currentPage)s/%(maxPage)s"), {
+ "currentPage": this.gridBrowser.currentPage + 1,
+ "maxPage": Math.max(1, this.gridBrowser.pageCount)
+ });
+
+ this.previous.enabled = this.gridBrowser.pageCount > 1;
+ this.next.enabled = this.gridBrowser.pageCount > 1;
+
+ this.zoomIn.enabled = this.gridBrowser.columnCount > 0;
+ this.zoomOut.enabled = this.gridBrowser.columnCount < this.gridBrowser.maxColumns;
+
+ }
+};
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/GridBrowser.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/GridBrowser.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/GridBrowser.js
@@ -0,0 +1,145 @@
+/**
+ * Class that arranges a grid of items using paging.
+ *
+ * Needs an object as container with items and a object to display the page numbering (if not
+ * make hidden object and assign it to that).
+ */
+class GridBrowser
+{
+ constructor(container)
+ {
+ this.container = container;
+
+ // These properties may be read from publicly.
+ this.pageCount = undefined;
+ this.currentPage = undefined;
+ this.columnCount = undefined;
+ this.minColumns = undefined;
+ this.maxColumns = undefined;
+ this.rowCount = undefined;
+ this.itemCount = undefined;
+ this.itemsPerPage = undefined;
+ this.selected = undefined;
+
+ this.gridResizeHandlers = new Set();
+ this.pageChangeHandlers = new Set();
+ this.selectionChangeHandlers = new Set();
+ }
+
+ registerGridResizeHandler(handler)
+ {
+ this.gridResizeHandlers.add(handler);
+ }
+
+ registerPageChangeHandler(handler)
+ {
+ this.pageChangeHandlers.add(handler);
+ }
+
+ registerSelectionChangeHandler(handler)
+ {
+ this.selectionChangeHandlers.add(handler);
+ }
+
+ // Inheriting classes must subscribe to this event.
+ onWindowResized()
+ {
+ this.resizeGrid();
+ this.goToPageOfSelected();
+ }
+
+ setSelectedIndex(index)
+ {
+ this.selected = index;
+
+ for (let handler of this.selectionChangeHandlers)
+ handler();
+ }
+
+ goToPage(pageNumber)
+ {
+ if (!Number.isInteger(pageNumber))
+ throw new Error("Given argument is not a number");
+
+ this.currentPage = pageNumber;
+
+ for (let handler of this.pageChangeHandlers)
+ handler();
+ }
+
+ nextPage(wrapAround = true)
+ {
+ let numberPages = Math.max(1, this.pageCount);
+ if (!wrapAround)
+ this.goToPage(Math.min(this.currentPage + 1, numberPages - 1));
+ else
+ this.goToPage((this.currentPage + 1) % numberPages);
+ }
+
+ previousPage(wrapAround = true)
+ {
+ let numberPages = Math.max(1, this.pageCount);
+ if (!wrapAround)
+ this.goToPage(Math.max(this.currentPage - 1, 0));
+ else
+ this.goToPage((this.currentPage + numberPages - 1) % numberPages);
+ }
+
+ goToPageOfSelected()
+ {
+ this.goToPage(
+ Math.max(Math.min(
+ Math.floor(this.selected / this.itemsPerRow) - Math.floor(this.rowCount / 2),
+ this.pageCount-1),
+ 0)
+ );
+ }
+
+ increaseColumnCount(diff)
+ {
+ let isSelectedInPage =
+ this.selected !== undefined &&
+ Math.floor(this.selected / this.itemsPerRow) >= this.currentPage &&
+ Math.floor(this.selected / this.itemsPerRow) < this.currentPage + this.rowCount;
+
+ this.columnCount += diff;
+ this.resizeGrid();
+
+ if (isSelectedInPage)
+ this.goToPageOfSelected();
+ else
+ this.goToPage(Math.min(this.currentPage, Math.max(0, this.pageCount - 1)));
+ }
+
+ resizeGrid()
+ {
+ let size = this.container.getComputedSize();
+ let width = size.right - size.left;
+ let height = size.bottom - size.top;
+
+ let maxColumns = Math.floor(width / this.MinItemWidth);
+ if (maxColumns <= 0)
+ return;
+
+ if (this.columnCount === undefined)
+ this.columnCount = Math.floor(width / this.DefaultItemWidth);
+
+ this.minColumns = Math.ceil(width / (height * this.ItemRatio));
+ this.maxColumns = maxColumns;
+
+
+ this.columnCount = Math.min(this.maxColumns, Math.max(this.minColumns, this.columnCount));
+
+ this.itemWidth = Math.floor(width / this.columnCount);
+ this.itemHeight = Math.floor(this.itemWidth / this.ItemRatio);
+
+ this.rowCount = Math.floor((size.bottom - size.top) / this.itemHeight);
+ this.itemsPerRow = Math.min(this.columnCount, this.items.length);
+ this.itemsPerPage = Math.min(this.columnCount * this.rowCount, this.items.length);
+ // NB: pages only change by one row, so items are in several pages.
+ this.pageCount = Math.ceil(this.itemCount / this.itemsPerRow) - this.rowCount + 1;
+
+ for (let handler of this.gridResizeHandlers)
+ handler();
+ }
+}
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/GridBrowserItem.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/GridBrowserItem.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/GridBrowserItem.js
@@ -0,0 +1,44 @@
+class GridBrowserItem
+{
+ constructor(gridBrowser, imageObject, itemIndex)
+ {
+ this.gridBrowser = gridBrowser;
+ this.itemIndex = itemIndex;
+ this.imageObject = imageObject;
+
+ imageObject.onMouseLeftPress = this.select.bind(this);
+ imageObject.onMouseWheelDown = () => gridBrowser.nextPage(false);
+ imageObject.onMouseWheelUp = () => gridBrowser.previousPage(false);
+
+ gridBrowser.registerGridResizeHandler(this.onGridResize.bind(this));
+ gridBrowser.registerPageChangeHandler(this.updateVisibility.bind(this));
+ }
+
+ updateVisibility()
+ {
+ this.imageObject.hidden =
+ this.itemIndex >= Math.min(
+ this.gridBrowser.itemsPerPage,
+ this.gridBrowser.itemCount - this.gridBrowser.currentPage * this.gridBrowser.itemsPerRow);
+ }
+
+ onGridResize()
+ {
+ let gridBrowser = this.gridBrowser;
+ let x = this.itemIndex % gridBrowser.columnCount;
+ let y = Math.floor(this.itemIndex / gridBrowser.columnCount);
+ let size = this.imageObject.size;
+ size.left = gridBrowser.itemWidth * x;
+ size.right = gridBrowser.itemWidth * (x + 1);
+ size.top = gridBrowser.itemHeight * y;
+ size.bottom = gridBrowser.itemHeight * (y + 1);
+ this.imageObject.size = size;
+ this.updateVisibility();
+ }
+
+ select()
+ {
+ this.gridBrowser.setSelectedIndex(
+ this.itemIndex + this.gridBrowser.currentPage * this.gridBrowser.itemsPerRow);
+ }
+}
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/MapGridBrowser.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/MapGridBrowser.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/MapGridBrowser.js
@@ -0,0 +1,102 @@
+class MapGridBrowser extends GridBrowser
+{
+ constructor(mapBrowserPage, setupWindow)
+ {
+ super(Engine.GetGUIObjectByName("mapBrowserContainer"));
+
+ this.setupWindow = setupWindow;
+ this.mapBrowserPage = mapBrowserPage;
+ this.mapCache = mapBrowserPage.mapCache;
+ this.mapFilters = mapBrowserPage.mapFilters;
+
+ this.mapList = [];
+
+ this.items = this.container.children.map((imageObject, itemIndex) =>
+ new MapGridBrowserItem(mapBrowserPage, this, imageObject, itemIndex));
+
+ this.mapBrowserPage.registerOpenPageHandler(this.onOpenPage.bind(this));
+ this.mapBrowserPage.registerClosePageHandler(this.onClosePage.bind(this));
+ this.mapBrowserPage.mapBrowserPageDialog.onMouseWheelUp = this.nextPage.bind(this);
+ this.mapBrowserPage.mapBrowserPageDialog.onMouseWheelDown = this.previousPage.bind(this);
+ }
+
+ onOpenPage()
+ {
+ this.updateMapList();
+ this.setSelectedIndex(this.mapList.findIndex(map => map.file == g_GameAttributes.map));
+ this.goToPageOfSelected();
+ this.container.onWindowResized = this.onWindowResized.bind(this);
+
+ Engine.SetGlobalHotkey(this.HotkeyConfigNext, "Press", this.nextPage.bind(this));
+ Engine.SetGlobalHotkey(this.HotkeyConfigPrevious, "Press", this.previousPage.bind(this));
+ }
+
+ onClosePage()
+ {
+ delete this.container.onWindowResized;
+ Engine.UnsetGlobalHotkey(this.HotkeyConfigNext, "Press");
+ Engine.UnsetGlobalHotkey(this.HotkeyConfigPrevious, "Press");
+ }
+
+ updateMapList()
+ {
+ let selectedMap =
+ this.mapList[this.selected] &&
+ this.mapList[this.selected].file || undefined;
+
+
+ let mapList = this.mapFilters.getFilteredMaps(
+ this.mapBrowserPage.controls.MapFiltering.getSelectedMapType(),
+ this.mapBrowserPage.controls.MapFiltering.getSelectedMapFilter());
+
+ let filterText = this.mapBrowserPage.controls.MapFiltering.getSearchText();
+ if (filterText)
+ {
+ mapList = MatchSort.get(filterText, mapList, "name");
+ if (!mapList.length)
+ {
+ let filter = "all";
+ for (let type of g_MapTypes.Name)
+ for (let map of this.mapFilters.getFilteredMaps(type, filter))
+ mapList.push(Object.assign({ "type": type, "filter": filter }, map));
+ mapList = MatchSort.get(filterText, mapList, "name");
+ }
+ }
+ if (this.mapBrowserPage.controls.MapFiltering.getSelectedMapType() == "random")
+ {
+ mapList = [{
+ "file": "random",
+ "name": "Random",
+ "description": "Pick a map at random.",
+ }, ...mapList];
+ }
+ this.mapList = mapList;
+ this.itemCount = this.mapList.length;
+ this.resizeGrid();
+
+ this.setSelectedIndex(this.mapList.findIndex(map => map.file == selectedMap));
+ }
+
+ submitMapSelection()
+ {
+ if (!g_IsController)
+ return;
+
+ let map = this.mapList[this.selected] || undefined;
+ if (!map)
+ return;
+
+ g_GameAttributes.mapType = map.type ? map.type :
+ this.mapBrowserPage.controls.MapFiltering.getSelectedMapType();
+ g_GameAttributes.mapFilter = map.filter ? map.filter :
+ this.mapBrowserPage.controls.MapFiltering.getSelectedMapFilter();
+ g_GameAttributes.map = map.file;
+ this.setupWindow.controls.gameSettingsControl.updateGameAttributes();
+ }
+}
+
+MapGridBrowser.prototype.ItemRatio = 4 / 3;
+
+MapGridBrowser.prototype.DefaultItemWidth = 200;
+
+MapGridBrowser.prototype.MinItemWidth = 100;
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/MapGridBrowser.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/MapGridBrowser.xml
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/MapGridBrowser.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/MapGridBrowserItem.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/MapGridBrowserItem.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/grid/MapGridBrowserItem.js
@@ -0,0 +1,64 @@
+class MapGridBrowserItem extends GridBrowserItem
+{
+ constructor(mapBrowserPage, mapGridBrowser, imageObject, itemIndex)
+ {
+ super(mapGridBrowser, imageObject, itemIndex);
+
+ this.mapBrowserPage = mapBrowserPage;
+ this.mapCache = mapBrowserPage.mapCache;
+
+ this.mapPreview = Engine.GetGUIObjectByName("mapPreview[" + itemIndex + "]");
+
+ mapGridBrowser.registerSelectionChangeHandler(this.onSelectionChange.bind(this));
+ mapGridBrowser.registerPageChangeHandler(this.onGridResize.bind(this));
+
+ if (g_IsController)
+ this.imageObject.onMouseLeftDoubleClick = this.onMouseLeftDoubleClick.bind(this);
+ }
+
+ onSelectionChange()
+ {
+ this.updateSprite();
+ }
+
+ onGridResize()
+ {
+ super.onGridResize();
+ this.updateMapAssignment();
+ this.updateSprite();
+ }
+
+ updateSprite()
+ {
+ this.imageObject.sprite =
+ this.gridBrowser.selected == this.itemIndex + this.gridBrowser.currentPage * this.gridBrowser.itemsPerRow ?
+ this.SelectedSprite :
+ "";
+ }
+
+ updateMapAssignment()
+ {
+ let map = this.gridBrowser.mapList[
+ this.itemIndex + this.gridBrowser.currentPage * this.gridBrowser.itemsPerRow] || undefined;
+
+ if (!map)
+ return;
+
+ this.mapPreview.caption = map.name;
+
+ this.imageObject.tooltip =
+ map.description + "\n" +
+ this.gridBrowser.container.tooltip;
+
+ this.mapPreview.sprite =
+ this.mapCache.getMapPreview(this.mapBrowserPage.controls.MapFiltering.getSelectedMapType(), map.file);
+ }
+
+ onMouseLeftDoubleClick()
+ {
+ this.gridBrowser.submitMapSelection();
+ this.mapBrowserPage.closePage();
+ }
+}
+
+MapGridBrowserItem.prototype.SelectedSprite = "color: 120 0 0 255";
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/utils/Components.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/utils/Components.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/utils/Components.js
@@ -0,0 +1,92 @@
+/**
+ * Take ownership of a Control/Label setup, and resize them horizontally
+ * depending on the size of the label.
+ * TODO: we should let JS components like that generate their XML
+ * so they can be easily reused, and then move this to another folder.
+ */
+class LabelledControl
+{
+ constructor(guiObjectName)
+ {
+ this.control = Engine.GetGUIObjectByName(guiObjectName + "Control");
+ this.label = Engine.GetGUIObjectByName(guiObjectName + "Label");
+ this.resizeLabel();
+ }
+
+ setupEvents()
+ {
+ return this;
+ }
+
+ resizeLabel()
+ {
+ let labelWidth = Engine.GetTextWidth(this.label.font, this.label.caption) + 15;
+
+ {
+ let size = this.label.size;
+ size.right = labelWidth;
+ this.label.size = size;
+ }
+ {
+ let size = this.control.size;
+ size.left = labelWidth;
+ this.control.size = size;
+ }
+ }
+}
+
+class LabelledDropdown extends LabelledControl
+{
+ setupEvents(onSelectionChange)
+ {
+ this.control.onSelectionChange = onSelectionChange;
+ return this;
+ }
+
+ render(names, data)
+ {
+ let selected = this.getSelected();
+ this.control.list = names;
+ this.control.list_data = data;
+ this.select(selected);
+ }
+
+ select(data)
+ {
+ this.control.selected = this.control.list_data.indexOf(data);
+ }
+
+ getSelected()
+ {
+ if (this.control.selected === -1)
+ return undefined;
+ return this.control.list_data[this.control.selected];
+ }
+}
+
+class LabelledInput extends LabelledControl
+{
+ setupEvents(onTextEdit, onTab = () => this.blur())
+ {
+ this.control.onTab = onTab;
+ this.control.onTextEdit = onTextEdit;
+ return this;
+ }
+
+ focus()
+ {
+ this.control.focus();
+ // focus resets cursor position
+ this.control.buffer_position = this.control.caption.length;
+ }
+
+ blur()
+ {
+ this.control.blur();
+ }
+
+ getText()
+ {
+ return this.control.caption.trim();
+ }
+}
Index: ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/utils/MatchSort.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/utils/MatchSort.js
+++ ps/trunk/binaries/data/mods/public/gui/maps/mapbrowser/utils/MatchSort.js
@@ -0,0 +1,85 @@
+const MatchSort = (function() {
+const Highscore = -10E7;
+
+return class
+{
+ /**
+ * Returns a new list filtered and sorted by the similarity with the input text
+ * Order of sorting:
+ * 1. Exact match
+ * 2. Exact lowercase match
+ * 3. Starting letters match and sorted alphabetically
+ * 4. By similarity score (lookahead match)
+ * 5. Entry is discarded if one of the previous don't apply
+ *
+ * @param {string} input text to seach for
+ * @param {string[] | object[]} list
+ * @param {string} [key] text to use if the list is made up of objects
+ */
+ static get(input, list, key)
+ {
+ input = input.toLowerCase();
+
+ let result = [];
+
+ for (let obj of list)
+ {
+ let text = key == null ? obj : obj[key];
+ let score = MatchSort.scoreText(input, text);
+ if (score !== undefined)
+ result.push([obj, score, text, text.startsWith(input)]);
+ }
+
+ return result.sort(MatchSort.sort).map(v => v[0]);
+ }
+
+ static sort([o1, s1, t1, a1], [o2, s2, t2, a2])
+ {
+ if (a1 && a2)
+ return t1.localeCompare(t2);
+
+ if (a1)
+ return -1;
+
+ if (a2)
+ return 1;
+
+ return s1 - s2;
+ }
+
+ /**
+ * The lower the score the better the match.
+ */
+ static scoreText(input, text)
+ {
+ // Exact match.
+ if (input == text)
+ return Highscore;
+
+ text = text.toLowerCase();
+
+ // Exact match relaxed.
+ if (input == text)
+ return Highscore / 2;
+
+ let score = 0;
+ let offset = -1;
+
+ for (let i = 0; i < input.length; ++i)
+ {
+ let offsetNext = text.indexOf(input[i], offset + 1);
+
+ // No match.
+ if (offsetNext == -1)
+ return undefined;
+
+ // Lower score increase if consecutive index.
+ let isConsecutive = offsetNext == offset + 1 ? 0 : 1;
+ score += offsetNext + isConsecutive * offsetNext;
+ offset = offsetNext;
+ }
+
+ return score;
+ }
+};
+})();
Index: ps/trunk/binaries/data/mods/public/gui/page_mapbrowser.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/page_mapbrowser.xml
+++ ps/trunk/binaries/data/mods/public/gui/page_mapbrowser.xml
@@ -0,0 +1,16 @@
+
+
+ common/modern/setup.xml
+ common/modern/styles.xml
+ common/modern/sprites.xml
+
+ common/setup.xml
+ common/sprites.xml
+ common/styles.xml
+
+ maps/mapbrowser/MapBrowserPage.xml
+
+
+ common/global.xml
+
Index: ps/trunk/binaries/data/mods/public/gui/pregame/MainMenuItems.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/pregame/MainMenuItems.js
+++ ps/trunk/binaries/data/mods/public/gui/pregame/MainMenuItems.js
@@ -43,6 +43,13 @@
};
Engine.PushGuiPage("page_civinfo.xml", {}, callback);
}
+ },
+ {
+ "caption": translate("Map Overview"),
+ "tooltip": translate("View the different maps featured in 0 A.D."),
+ "onPress": () => {
+ Engine.PushGuiPage("page_mapbrowser.xml");
+ },
}
]
},
Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml
+++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml
@@ -3,6 +3,7 @@
+
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml
@@ -3,6 +3,7 @@
+