forked from mirrors/0ad
Map browser, used in the gamesetup and in the main menu.
This grid-based system allows browsing all available maps at a glance, encouraging more diversity and making it nicer to pick a map. Moves the mapCache and the map filters controller to the gui maps/ folders, and include that folder where it is used, instead of them being in common/ or the gamesetup. Comments By: Freagarach Patch By: Nani (reworked by elexis then wraitii) Fixes #5315 (though further work, such as proper scrolling, would be nice). Differential Revision: https://code.wildfiregames.com/D1703 This was SVN commit r24459.
This commit is contained in:
@@ -288,6 +288,9 @@ offscreen = Alt ; Include offscreen units in selection
|
||||
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
|
||||
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object name="buttonSettingFrame[n]" size="0 2 100% 32" hidden="true">
|
||||
|
||||
<object
|
||||
name="buttonSettingControl[n]"
|
||||
type="button"
|
||||
size="175 0 100% 28"
|
||||
style="ModernButtonRed"
|
||||
tooltip_style="onscreenToolTip"
|
||||
hidden="true"
|
||||
z="1"
|
||||
/>
|
||||
</object>
|
||||
+1
@@ -9,6 +9,7 @@ var g_GameSettingsLayout = [
|
||||
"MapType",
|
||||
"MapFilter",
|
||||
"MapSelection",
|
||||
"MapBrowser",
|
||||
"MapSize",
|
||||
"TeamPlacement",
|
||||
"Landscape",
|
||||
|
||||
+29
@@ -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.");
|
||||
@@ -6,6 +6,7 @@
|
||||
<script directory="gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/"/>
|
||||
<script directory="gui/gamesetup/Pages/GameSetupPage/GameSettings/PerPlayer/Dropdowns/"/>
|
||||
<script directory="gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/"/>
|
||||
<script directory="gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Buttons/"/>
|
||||
<script directory="gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Checkboxes/"/>
|
||||
<script directory="gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Dropdowns/"/>
|
||||
<script directory="gui/gamesetup/Pages/GameSetupPage/GameSettings/Single/Sliders/"/>
|
||||
|
||||
+7
@@ -5,6 +5,13 @@
|
||||
|
||||
<object name="settingsPanel" size="0 5 100% 100%-5" z="1">
|
||||
|
||||
<!-- There is currently only one button, so don't add un-necessary objects. -->
|
||||
<repeat count="1" var="n">
|
||||
<object>
|
||||
<include file="gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControlButton.xml"/>
|
||||
</object>
|
||||
</repeat>
|
||||
|
||||
<repeat count="40" var="n">
|
||||
<object>
|
||||
<include file="gui/gamesetup/Pages/GameSetupPage/GameSettings/GameSettingControlDropdown.xml"/>
|
||||
|
||||
@@ -2,16 +2,24 @@ class MapPreview
|
||||
{
|
||||
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 @@ class MapPreview
|
||||
this.mapCache.getMapPreview(g_GameAttributes.mapType, g_GameAttributes.map, g_GameAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
MapPreview.prototype.Tooltip =
|
||||
translate("Click to view the list of available maps.");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object type="image" sprite="ModernDarkBoxGold" name="gamePreviewBox">
|
||||
|
||||
<object name="mapPreview" type="image" size="1 1 401 294"/>
|
||||
<object name="mapPreview" type="image" size="1 1 401 294" tooltip_style="onscreenToolTip"/>
|
||||
<object name="mapInfoName" type="text" size="5 100%-20 100% 100%-1" style="ModernLeftLabelText"/>
|
||||
|
||||
</object>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object>
|
||||
<include file="gui/maps/mapbrowser/MapBrowser.xml"/>
|
||||
<script directory="gui/gamesetup/Pages/MapBrowserPage/"/>
|
||||
</object>
|
||||
@@ -2,6 +2,7 @@
|
||||
<objects>
|
||||
|
||||
<script directory="gui/common/"/>
|
||||
<script directory="gui/maps/"/>
|
||||
<script directory="gui/gamesetup/"/>
|
||||
<script directory="gui/gamesetup/Controls/"/>
|
||||
<script directory="gui/gamesetup/NetMessages/"/>
|
||||
@@ -15,6 +16,7 @@
|
||||
<include file="gui/gamesetup/Pages/LoadingPage/LoadingPage.xml"/>
|
||||
<include file="gui/gamesetup/Pages/GameSetupPage/GameSetupPage.xml"/>
|
||||
<include file="gui/gamesetup/Pages/AIConfigPage/AIConfigPage.xml"/>
|
||||
<include file="gui/gamesetup/Pages/MapBrowserPage/MapBrowserPage.xml"/>
|
||||
</object>
|
||||
|
||||
</objects>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<objects>
|
||||
|
||||
<script directory="gui/common/"/>
|
||||
<script directory="gui/maps/"/>
|
||||
<script directory="gui/loadgame/"/>
|
||||
|
||||
<!-- Add a translucent black background to fade out the menu page -->
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<objects>
|
||||
|
||||
<script directory="gui/common/"/>
|
||||
<script directory="gui/maps/"/>
|
||||
<script directory="gui/lobby/"/>
|
||||
|
||||
<object>
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ class MapFilters
|
||||
}
|
||||
|
||||
Engine.ProfileStop();
|
||||
return existence ? false : maps;
|
||||
return existence ? false : maps.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object name="mapBrowserPage" z="300">
|
||||
|
||||
<script file="gui/maps/mapbrowser/MapBrowser.js"/>
|
||||
<script directory="gui/maps/mapbrowser/controls/"/>
|
||||
<script directory="gui/maps/mapbrowser/grid/"/>
|
||||
<script directory="gui/maps/mapbrowser/utils/"/>
|
||||
|
||||
<object type="image" sprite="ModernFade"/>
|
||||
|
||||
<object name="mapBrowserPageDialog" type="image" style="ModernDialog" size="5% 5% 95% 95%">
|
||||
|
||||
<object style="ModernLabelText" type="text" size="50%-128 -18 50%+128 14" z="200">
|
||||
<translatableAttribute id="caption">Map Browser</translatableAttribute>
|
||||
</object>
|
||||
|
||||
<object name="mapBrowserTopPanel" size="15 15 100%-10 55">
|
||||
|
||||
</object>
|
||||
|
||||
<object name="mapBrowserLeftPanel" size="15 15 100%-400 100%-15">
|
||||
<include file="gui/maps/mapbrowser/grid/MapGridBrowser.xml"/>
|
||||
</object>
|
||||
|
||||
<object name="mapBrowserRightPanel" size="100%-390 15 100%-15 100%-15">
|
||||
<include file="gui/maps/mapbrowser/controls/MapBrowserControls.xml"/>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
</object>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<objects>
|
||||
|
||||
<script directory="gui/common/"/>
|
||||
<script directory="gui/maps/"/>
|
||||
<object>
|
||||
<include file="gui/maps/mapbrowser/MapBrowser.xml"/>
|
||||
</object>
|
||||
<script file="gui/maps/mapbrowser/MapBrowserPage.js"/>
|
||||
|
||||
</objects>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object>
|
||||
<object type="image" sprite="ModernFade" size="-5 0 100%+5 100%"/>
|
||||
|
||||
<!-- Page controls -->
|
||||
<object>
|
||||
<object name="mapBrowserPageStatus" type="text" style="ModernLabelText" size="0 0 180 30"/>
|
||||
|
||||
<object size="180 0 100%-60 30">
|
||||
<object name="mapBrowserPreviousButton" type="button" style="ModernButtonRed" size="0 0 50% 100%"/>
|
||||
<object name="mapBrowserNextButton" type="button" style="ModernButtonRed" size="50% 0 100% 100%"/>
|
||||
</object>
|
||||
<object size="100%-60 0 100% 30">
|
||||
<object name="mapsZoomIn" type="button" style="ModernButtonRed" size="0 0 50% 100%">
|
||||
<translatableAttribute id="caption" context="zoom in">+</translatableAttribute>
|
||||
</object>
|
||||
<object name="mapsZoomOut" type="button" style="ModernButtonRed" size="50% 0 100% 100%">
|
||||
<translatableAttribute id="caption" context="zoom out">−</translatableAttribute>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<object size="0 35 100% 100%-60">
|
||||
<object size="0 0 100% 40">
|
||||
<object name="mapBrowserSearchBoxLabel" type="text" style="ModernLeftLabelText" size="2 0 0 35">
|
||||
<translatableAttribute id="caption">Search Map:</translatableAttribute>
|
||||
</object>
|
||||
<object name="mapBrowserSearchBoxControl" type="input" size="0 5 100% 30" style="ModernInput" font="sans-16"/>
|
||||
</object>
|
||||
<object size="0 40 100% 80">
|
||||
<object name="mapBrowserMapTypeLabel" type="text" size="2 0 0 35" style="ModernLeftLabelText">
|
||||
<translatableAttribute id="caption">Map Type:</translatableAttribute>
|
||||
</object>
|
||||
<object name="mapBrowserMapTypeControl" type="dropdown" size="0 5 100% 30" style="ModernDropDown"/>
|
||||
</object>
|
||||
<object size="0 80 100% 120">
|
||||
<object name="mapBrowserMapFilterLabel" type="text" size="2 0 0 35" style="ModernLeftLabelText">
|
||||
<translatableAttribute id="caption">Map Filter:</translatableAttribute>
|
||||
</object>
|
||||
<object name="mapBrowserMapFilterControl" type="dropdown" size="0 5 100% 30" style="ModernDropDown"/>
|
||||
</object>
|
||||
<object size="0 120 100% 100%">
|
||||
<include file="gui/maps/mapbrowser/controls/MapDescription.xml"/>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
<object name="mapBrowserPagePickRandom" type="button" style="ModernButtonRed" size="0 100%-60 100% 100%-30">
|
||||
<translatableAttribute id="caption">Pick Random Map</translatableAttribute>
|
||||
</object>
|
||||
<object name="mapBrowserPageClose" type="button" style="ModernButtonRed" hotkey="cancel" size="0 100%-30 50% 100%">
|
||||
<translatableAttribute id="caption">Cancel</translatableAttribute>
|
||||
</object>
|
||||
<object name="mapBrowserPageSelect" type="button" style="ModernButtonRed" size="50% 100%-30 100% 100%">
|
||||
<translatableAttribute id="caption" context="map selection dialog">Select</translatableAttribute>
|
||||
</object>
|
||||
|
||||
</object>
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object>
|
||||
<object name="mapBrowserSelectedPreview" type="image" size="0 0 100% 0">
|
||||
<object name="mapBrowserSelectedName" type="text" style="ModernLabelText" text_valign="bottom" font="sans-bold-16" size="0 0 100% 100%"/>
|
||||
</object>
|
||||
<object name="mapBrowserSelectedDescription" type="text" style="ModernText" size="10 0 100%-10 100%-40"/>
|
||||
</object>
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object name="mapBrowserContainer">
|
||||
<repeat count="100" var="n">
|
||||
<object
|
||||
name="map[n]"
|
||||
type="button"
|
||||
size="0 0 0 0"
|
||||
sprite_over="color: 255 0 0 100"
|
||||
sprite_pressed="color: 255 0 0 150"
|
||||
>
|
||||
<object ghost="true" type="image" sprite="ModernDarkBoxGold" size="3 3 100%-3 100%-3">
|
||||
<object
|
||||
name="mapPreview[n]"
|
||||
style="ModernLabelText"
|
||||
type="button"
|
||||
size="1 1 100%-1 100%-1"
|
||||
text_align="center"
|
||||
text_valign="bottom"
|
||||
buffer_zone="10"
|
||||
/>
|
||||
</object>
|
||||
</object>
|
||||
</repeat>
|
||||
|
||||
</object>
|
||||
@@ -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";
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<page>
|
||||
<include>common/modern/setup.xml</include>
|
||||
<include>common/modern/styles.xml</include>
|
||||
<include>common/modern/sprites.xml</include>
|
||||
|
||||
<include>common/setup.xml</include>
|
||||
<include>common/sprites.xml</include>
|
||||
<include>common/styles.xml</include>
|
||||
|
||||
<include>maps/mapbrowser/MapBrowserPage.xml</include>
|
||||
|
||||
<!-- Work around a render bug:
|
||||
Needs to be drawn last, as it should overlay everything -->
|
||||
<include>common/global.xml</include>
|
||||
</page>
|
||||
@@ -43,6 +43,13 @@ var g_MainMenuItems = [
|
||||
};
|
||||
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");
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<objects>
|
||||
|
||||
<script directory="gui/common/"/>
|
||||
<script directory="gui/maps/"/>
|
||||
<script directory="gui/replaymenu/" />
|
||||
|
||||
<!-- Everything displayed in the replay menu. -->
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<objects>
|
||||
|
||||
<script directory="gui/common/"/>
|
||||
<script directory="gui/maps/"/>
|
||||
<script directory="gui/session/"/>
|
||||
<script directory="gui/session/chat/"/>
|
||||
<script directory="gui/session/developer_overlay/"/>
|
||||
|
||||
Reference in New Issue
Block a user