mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 13:23:56 -07:00
Implement sortable columns for the gui, and use them in the lobby. Patch by Vladislav. Fixes #2405.
This was SVN commit r16781.
This commit is contained in:
parent
2ff4c60859
commit
b8fce56821
10 changed files with 262 additions and 50 deletions
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:230a6850ce851613b5397b6522135c1ce201d6b515a9ca485a3047e6423cb7e6
|
||||
size 292
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:17ab3802fbf0947f2dabbecd68bdb36d59535f51b61907f663c0bd7e0607a953
|
||||
size 351
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3ecb1a3a4a10be5f0015be13c54f863fb6122ce5f08dcd00f5fa2d06cb4bb29f
|
||||
size 284
|
||||
|
|
@ -585,6 +585,27 @@
|
|||
size="0 6 16 22"
|
||||
/>
|
||||
</sprite>
|
||||
<sprite name = "ModernNotSorted">
|
||||
<image texture = "global/modern/arrow-up-down.png"
|
||||
real_texture_placement = "0 0 16 16"
|
||||
texture_size="0 0 16 16"
|
||||
size="0 6 16 22"
|
||||
/>
|
||||
</sprite>
|
||||
<sprite name = "ModernArrowUp">
|
||||
<image texture = "global/modern/arrow-up.png"
|
||||
real_texture_placement = "0 0 16 16"
|
||||
texture_size="0 0 16 16"
|
||||
size="0 6 16 22"
|
||||
/>
|
||||
</sprite>
|
||||
<sprite name = "ModernArrowDown">
|
||||
<image texture = "global/modern/arrow-down.png"
|
||||
real_texture_placement = "0 0 16 16"
|
||||
texture_size="0 0 16 16"
|
||||
size="0 6 16 22"
|
||||
/>
|
||||
</sprite>
|
||||
<sprite name = "ModernDropDownArrowHighlight">
|
||||
<image texture = "global/modern/dropdown-arrow.png"
|
||||
real_texture_placement = "0 0 16 16"
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ ex_settings =
|
|||
attribute button_width { xsd:decimal }?&
|
||||
attribute checked { bool }?&
|
||||
attribute clip { bool }?&
|
||||
attribute default_column { text }?&
|
||||
attribute dropdown_size { xsd:decimal }?&
|
||||
attribute dropdown_buffer { xsd:decimal }?&
|
||||
attribute enabled { bool }?&
|
||||
|
|
@ -70,6 +71,7 @@ ex_settings =
|
|||
attribute scrollbar { bool }?&
|
||||
attribute scrollbar_style { text }?&
|
||||
attribute scroll_bottom { bool }?&
|
||||
attribute sortable { bool }?&
|
||||
attribute sound_closed { text }?&
|
||||
attribute sound_disabled { text }?&
|
||||
attribute sound_enter { text }?&
|
||||
|
|
@ -79,12 +81,15 @@ ex_settings =
|
|||
attribute sound_selected { text }?&
|
||||
attribute sprite { text }?&
|
||||
attribute sprite2 { text }?&
|
||||
attribute sprite_asc { text }?&
|
||||
attribute sprite_heading { text }?&
|
||||
attribute sprite_bar { text }?&
|
||||
attribute sprite_background { text }?&
|
||||
attribute sprite_desc { text }?&
|
||||
attribute sprite_disabled { text }?&
|
||||
attribute sprite_list { text }?&
|
||||
attribute sprite2_disabled { text }?&
|
||||
attribute sprite_not_sorted { text }?&
|
||||
attribute sprite_over { text }?&
|
||||
attribute sprite2_over { text }?&
|
||||
attribute sprite_pressed { text }?&
|
||||
|
|
|
|||
|
|
@ -199,6 +199,9 @@
|
|||
<ref name="bool"/>
|
||||
</attribute>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="default_column"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="dropdown_size">
|
||||
<data type="decimal"/>
|
||||
|
|
@ -286,6 +289,9 @@
|
|||
<ref name="bool"/>
|
||||
</attribute>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="sortable"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="sound_closed"/>
|
||||
</optional>
|
||||
|
|
@ -346,6 +352,15 @@
|
|||
<optional>
|
||||
<attribute name="sprite_selectarea"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="sprite_asc"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="sprite_desc"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="sprite_not_sorted"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<attribute name="square_side">
|
||||
<data type="decimal"/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
var g_ChatMessages = [];
|
||||
var g_Name = "unknown";
|
||||
var g_GameList = {};
|
||||
var g_GameList = {}
|
||||
var g_GameListSortBy = "name";
|
||||
var g_PlayerListSortBy = "name";
|
||||
var g_GameListOrder = 1; // 1 for ascending sort, and -1 for descending
|
||||
var g_PlayerListOrder = 1;
|
||||
var g_specialKey = Math.random();
|
||||
// This object looks like {"name":[numMessagesSinceReset, lastReset, timeBlocked]} when in use.
|
||||
var g_spamMonitor = {};
|
||||
|
|
@ -71,25 +75,37 @@ function lobbyDisconnect()
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Update functions
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function resetFilters()
|
||||
{
|
||||
// Reset states of gui objects
|
||||
// Update functions
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function updateGameListOrderSelection()
|
||||
{
|
||||
g_GameListSortBy = Engine.GetGUIObjectByName("gamesBox").selected_column;
|
||||
g_GameListOrder = Engine.GetGUIObjectByName("gamesBox").selected_column_order;
|
||||
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function updatePlayerListOrderSelection()
|
||||
{
|
||||
g_PlayerListSortBy = Engine.GetGUIObjectByName("playersBox").selected_column;
|
||||
g_PlayerListOrder = Engine.GetGUIObjectByName("playersBox").selected_column_order;
|
||||
|
||||
updatePlayerList();
|
||||
}
|
||||
|
||||
function resetFilters()
|
||||
{
|
||||
// Reset states of gui objects
|
||||
Engine.GetGUIObjectByName("mapSizeFilter").selected = 0
|
||||
Engine.GetGUIObjectByName("playersNumberFilter").selected = 0;
|
||||
Engine.GetGUIObjectByName("mapTypeFilter").selected = 0;
|
||||
Engine.GetGUIObjectByName("showFullFilter").checked = false;
|
||||
|
||||
// Update the list of games
|
||||
updateGameList();
|
||||
|
||||
// Update info box about the game currently selected
|
||||
updateGameSelection();
|
||||
}
|
||||
|
||||
function applyFilters()
|
||||
Engine.GetGUIObjectByName("mapTypeFilter").selected = 0;
|
||||
Engine.GetGUIObjectByName("showFullFilter").checked = false;
|
||||
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function applyFilters()
|
||||
{
|
||||
// Update the list of games
|
||||
updateGameList();
|
||||
|
|
@ -146,13 +162,13 @@ function updateSubject(newSubject)
|
|||
subjectBox.hidden = false;
|
||||
logo.size = "50%-110 40 50%+110 140";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a full update of the player listing, including ratings from C++.
|
||||
*
|
||||
* @return Array containing the player, presence, nickname, and rating listings.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a full update of the player listing, including ratings from cached C++ information.
|
||||
*
|
||||
* @return Array containing the player, presence, nickname, and rating listings.
|
||||
*/
|
||||
function updatePlayerList()
|
||||
{
|
||||
var playersBox = Engine.GetGUIObjectByName("playersBox");
|
||||
|
|
@ -162,12 +178,36 @@ function updatePlayerList()
|
|||
var ratingList = [];
|
||||
var cleanPlayerList = Engine.GetPlayerList();
|
||||
// Sort the player list, ignoring case.
|
||||
cleanPlayerList.sort(function(a,b)
|
||||
cleanPlayerList.sort(function(a,b)
|
||||
{
|
||||
var aName = a.name.toLowerCase();
|
||||
var bName = b.name.toLowerCase();
|
||||
return ((aName > bName) ? 1 : (bName > aName) ? -1 : 0);
|
||||
} );
|
||||
switch (g_PlayerListSortBy)
|
||||
{
|
||||
case 'rating':
|
||||
if (a.rating < b.rating)
|
||||
return -g_PlayerListOrder;
|
||||
else if (a.rating > b.rating)
|
||||
return g_PlayerListOrder;
|
||||
return 0;
|
||||
case 'status':
|
||||
let order = ["available", "away", "playing", "gone", "offline"];
|
||||
let presenceA = order.indexOf(a.presence);
|
||||
let presenceB = order.indexOf(b.presence);
|
||||
if (presenceA < presenceB)
|
||||
return -g_PlayerListOrder;
|
||||
else if (presenceA > presenceB)
|
||||
return g_PlayerListOrder;
|
||||
return 0;
|
||||
case 'name':
|
||||
default:
|
||||
var aName = a.name.toLowerCase();
|
||||
var bName = b.name.toLowerCase();
|
||||
if (aName < bName)
|
||||
return -g_PlayerListOrder;
|
||||
else if (aName > bName)
|
||||
return g_PlayerListOrder;
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
for (var i = 0; i < cleanPlayerList.length; i++)
|
||||
{
|
||||
// Identify current user's rating.
|
||||
|
|
@ -362,20 +402,47 @@ function updateGameList()
|
|||
// to update the game info panel.
|
||||
g_GameList = gameList;
|
||||
|
||||
// Sort the list of games to that games 'waiting' are displayed at the top, followed by 'init', followed by 'running'.
|
||||
// Sort the list of games to that games 'waiting' are displayed at the top, followed by 'init', followed by 'running'.
|
||||
var gameStatuses = ['waiting', 'init', 'running'];
|
||||
g_GameList.sort(function (a,b) {
|
||||
if (gameStatuses.indexOf(a.state) < gameStatuses.indexOf(b.state))
|
||||
return -1;
|
||||
else if (gameStatuses.indexOf(a.state) > gameStatuses.indexOf(b.state))
|
||||
return 1;
|
||||
switch (g_GameListSortBy)
|
||||
{
|
||||
case 'name':
|
||||
case 'mapSize':
|
||||
// mapSize contains the number of tiles for random maps
|
||||
// scenario maps always display default size
|
||||
case 'mapType':
|
||||
if (a[g_GameListSortBy] < b[g_GameListSortBy])
|
||||
return -g_GameListOrder;
|
||||
else if (a[g_GameListSortBy] > b[g_GameListSortBy])
|
||||
return g_GameListOrder;
|
||||
return 0;
|
||||
case 'mapName':
|
||||
if (translate(a.niceMapName) < translate(b.niceMapName))
|
||||
return -g_GameListOrder;
|
||||
else if (translate(a.niceMapName) > translate(b.niceMapName))
|
||||
return g_GameListOrder;
|
||||
return 0;
|
||||
case 'nPlayers':
|
||||
// Numerical comparison of player count ratio.
|
||||
if (a.nbp * b.tnbp < b.nbp * a.tnbp) // ratio a = a.nbp / a.tnbp, ratio b = b.nbp / b.tnbp
|
||||
return -g_GameListOrder;
|
||||
else if (a.nbp * b.tnbp > b.nbp * a.tnbp)
|
||||
return g_GameListOrder;
|
||||
return 0;
|
||||
default:
|
||||
if (gameStatuses.indexOf(a.state) < gameStatuses.indexOf(b.state))
|
||||
return -1;
|
||||
else if (gameStatuses.indexOf(a.state) > gameStatuses.indexOf(b.state))
|
||||
return 1;
|
||||
|
||||
// Alphabetical comparison of names as tiebreaker.
|
||||
if (a.name < b.name)
|
||||
return -1;
|
||||
else if (a.name > b.name)
|
||||
return 1;
|
||||
return 0;
|
||||
// Alphabetical comparison of names as tiebreaker.
|
||||
if (a.name < b.name)
|
||||
return -1;
|
||||
else if (a.name > b.name)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
var list_name = [];
|
||||
|
|
|
|||
|
|
@ -20,19 +20,22 @@
|
|||
|
||||
<!-- Left panel: Player list. -->
|
||||
<object name="leftPanel" size="20 30 20% 100%-280">
|
||||
<object name="playersBox" style="ModernList" type="olist" size="0 0 100% 100%" font="sans-bold-stroke-13">
|
||||
<object name="playersBox" style="ModernList" sprite_asc="ModernArrowDown" default_column="name" sprite_desc="ModernArrowUp" sprite_not_sorted="ModernNotSorted" type="olist" sortable="true" size="0 0 100% 100%" font="sans-bold-stroke-13">
|
||||
<def id="status" width="26%">
|
||||
<translatableAttribute id="heading">Status</translatableAttribute>
|
||||
</def>
|
||||
<def id="name" width="50%">
|
||||
<def id="name" width="48%">
|
||||
<translatableAttribute id="heading">Name</translatableAttribute>
|
||||
</def>
|
||||
<def id="rating" width="24%">
|
||||
<def id="rating" width="26%">
|
||||
<translatableAttribute id="heading">Rating</translatableAttribute>
|
||||
</def>
|
||||
<action on="SelectionChange">
|
||||
displayProfile("lobbylist");
|
||||
</action>
|
||||
<action on="SelectionColumnChange">
|
||||
updatePlayerListOrderSelection();
|
||||
</action>
|
||||
</object>
|
||||
</object>
|
||||
|
||||
|
|
@ -169,8 +172,9 @@
|
|||
|
||||
<!-- Middle panel: Filters, game list, chat box. -->
|
||||
<object name="middlePanel" size="20%+5 5% 100%-255 97.2%">
|
||||
<object name="gamesBox" style="ModernList" type="olist" size="0 25 100% 48%" font="sans-stroke-13">
|
||||
<object name="gamesBox" style="ModernList" sprite_asc="ModernArrowDown" default_column="name" sprite_desc="ModernArrowUp" sprite_not_sorted="ModernNotSorted" type="olist" sortable="true" size="0 25 100% 48%" font="sans-stroke-13">
|
||||
<action on="SelectionChange">updateGameSelection();</action>
|
||||
<action on="SelectionColumnChange">updateGameListOrderSelection();</action>
|
||||
<def id="name" color="0 60 0" width="27%">
|
||||
<translatableAttribute id="heading">Name</translatableAttribute>
|
||||
</def>
|
||||
|
|
|
|||
|
|
@ -19,10 +19,24 @@
|
|||
#include "i18n/L10n.h"
|
||||
|
||||
#include "ps/CLogger.h"
|
||||
#include "soundmanager/ISoundManager.h"
|
||||
|
||||
COList::COList() : CList(),m_HeadingHeight(30.f)
|
||||
COList::COList() : CList(),m_HeadingHeight(30.f),m_SelectedDef(-1),m_SelectedColumnOrder(1)
|
||||
{
|
||||
AddSetting(GUIST_CGUISpriteInstance, "sprite_heading");
|
||||
AddSetting(GUIST_bool, "sortable"); // The actual sorting is done in JS for more versatility
|
||||
AddSetting(GUIST_CStr, "selected_column");
|
||||
AddSetting(GUIST_int, "selected_column_order");
|
||||
AddSetting(GUIST_CStr, "default_column");
|
||||
AddSetting(GUIST_int, "selected_def");
|
||||
AddSetting(GUIST_CGUISpriteInstance, "sprite_asc"); // Show the order of sorting
|
||||
AddSetting(GUIST_CGUISpriteInstance, "sprite_desc");
|
||||
AddSetting(GUIST_CGUISpriteInstance, "sprite_not_sorted");
|
||||
|
||||
// Nothing is selected by default.
|
||||
GUI<CStr>::SetSetting(this, "selected_column", "");
|
||||
GUI<int>::SetSetting(this, "selected_column_order", 1);
|
||||
GUI<int>::SetSetting(this, "selected_def", -1);
|
||||
}
|
||||
|
||||
void COList::SetupText()
|
||||
|
|
@ -66,6 +80,9 @@ void COList::SetupText()
|
|||
float buffer_zone=0.f;
|
||||
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
|
||||
|
||||
CStr defaultColumn;
|
||||
GUI<CStr>::GetSetting(this, "default_column", defaultColumn);
|
||||
defaultColumn = "list_" + defaultColumn;
|
||||
|
||||
for (unsigned int c=0; c<m_ObjectsDefs.size(); ++c)
|
||||
{
|
||||
|
|
@ -74,6 +91,9 @@ void COList::SetupText()
|
|||
gui_string.SetValue(m_ObjectsDefs[c].m_Heading);
|
||||
*text = GetGUI()->GenerateText(gui_string, font, width, buffer_zone, this);
|
||||
AddText(text);
|
||||
|
||||
if (m_SelectedDef == -1 && defaultColumn == m_ObjectsDefs[c].m_Id)
|
||||
m_SelectedDef = c;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -119,6 +139,58 @@ CRect COList::GetListRect() const
|
|||
void COList::HandleMessage(SGUIMessage &Message)
|
||||
{
|
||||
CList::HandleMessage(Message);
|
||||
|
||||
switch (Message.type)
|
||||
{
|
||||
// If somebody clicks on the column heading
|
||||
case GUIM_MOUSE_PRESS_LEFT:
|
||||
{
|
||||
bool sortable;
|
||||
GUI<bool>::GetSetting(this, "sortable", sortable);
|
||||
if (!sortable)
|
||||
return;
|
||||
|
||||
CPos mouse = GetMousePos();
|
||||
if (!m_CachedActualSize.PointInside(mouse))
|
||||
return;
|
||||
|
||||
float xpos = 0;
|
||||
for (unsigned int def = 0; def < m_ObjectsDefs.size(); ++def)
|
||||
{
|
||||
float width = m_ObjectsDefs[def].m_Width;
|
||||
// Check if it's a decimal value, and if so, assume relative positioning.
|
||||
if (m_ObjectsDefs[def].m_Width < 1 && m_ObjectsDefs[def].m_Width > 0)
|
||||
width *= m_TotalAvalibleColumnWidth;
|
||||
CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 4);
|
||||
if (mouse.x >= leftTopCorner.x &&
|
||||
mouse.x < leftTopCorner.x + width &&
|
||||
mouse.y < leftTopCorner.y + m_HeadingHeight)
|
||||
{
|
||||
if (static_cast<int> (def) != m_SelectedDef)
|
||||
{
|
||||
m_SelectedColumnOrder = 1;
|
||||
m_SelectedDef = def;
|
||||
}
|
||||
else
|
||||
m_SelectedColumnOrder = -m_SelectedColumnOrder;
|
||||
GUI<CStr>::SetSetting(this, "selected_column", m_ObjectsDefs[def].m_Id.substr(5));
|
||||
GUI<int>::SetSetting(this, "selected_column_order", m_SelectedColumnOrder);
|
||||
GUI<int>::SetSetting(this, "selected_def", def);
|
||||
ScriptEvent("selectioncolumnchange");
|
||||
|
||||
CStrW soundPath;
|
||||
if (g_SoundManager && GUI<CStrW>::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty())
|
||||
g_SoundManager->PlayAsUI(soundPath.c_str(), false);
|
||||
|
||||
return;
|
||||
}
|
||||
xpos += width;
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile)
|
||||
|
|
@ -307,16 +379,33 @@ void COList::DrawList(const int &selected,
|
|||
CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right,
|
||||
m_CachedActualSize.top + m_HeadingHeight);
|
||||
GetGUI()->DrawSprite(*sprite_heading, cell_id, bz, rect_head);
|
||||
|
||||
CGUISpriteInstance *sprite_order, *sprite_not_sorted;
|
||||
if (m_SelectedColumnOrder != -1)
|
||||
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_asc", sprite_order);
|
||||
else
|
||||
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_desc", sprite_order);
|
||||
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_not_sorted", sprite_not_sorted);
|
||||
|
||||
float xpos = 0;
|
||||
for (unsigned int def=0; def< m_ObjectsDefs.size(); ++def)
|
||||
{
|
||||
DrawText(def, color, m_CachedActualSize.TopLeft() + CPos(xpos, 4), bz+0.1f, rect_head);
|
||||
// Check if it's a decimal value, and if so, assume relative positioning.
|
||||
float width = m_ObjectsDefs[def].m_Width;
|
||||
if (m_ObjectsDefs[def].m_Width < 1 && m_ObjectsDefs[def].m_Width > 0)
|
||||
xpos += m_ObjectsDefs[def].m_Width * m_TotalAvalibleColumnWidth;
|
||||
width *= m_TotalAvalibleColumnWidth;
|
||||
|
||||
CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0);
|
||||
CGUISpriteInstance *sprite;
|
||||
// If the list sorted by current column
|
||||
if (m_SelectedDef == static_cast<int> (def))
|
||||
sprite = sprite_order;
|
||||
else
|
||||
xpos += m_ObjectsDefs[def].m_Width;
|
||||
sprite = sprite_not_sorted;
|
||||
GetGUI()->DrawSprite(*sprite, cell_id, bz + 0.1f, CRect(leftTopCorner + CPos(width - 16, 0), leftTopCorner + CPos(width, 16)));
|
||||
|
||||
DrawText(def, color, leftTopCorner + CPos(0, 4), bz + 0.1f, rect_head);
|
||||
xpos += width;
|
||||
}
|
||||
|
||||
for (int i=0; i<(int)pList->m_Items.size(); ++i)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2014 Wildfire Games.
|
||||
/* Copyright (C) 2015 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -70,6 +70,8 @@ protected:
|
|||
virtual CRect GetListRect() const;
|
||||
|
||||
std::vector<ObjectDef> m_ObjectsDefs;
|
||||
int m_SelectedDef;
|
||||
int m_SelectedColumnOrder;
|
||||
|
||||
private:
|
||||
float m_HeadingHeight;
|
||||
|
|
|
|||
Loading…
Reference in a new issue