Index: ps/trunk/binaries/data/mods/public/gui/summary/summary.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/summary/summary.js (revision 25151)
+++ ps/trunk/binaries/data/mods/public/gui/summary/summary.js (revision 25152)
@@ -1,596 +1,596 @@
const g_CivData = loadCivData(false, false);
var g_ScorePanelsData;
var g_MaxHeadingTitle = 9;
var g_LongHeadingWidth = 250;
var g_PlayerBoxYSize = 40;
var g_PlayerBoxGap = 2;
var g_PlayerBoxAlpha = 50;
var g_TeamsBoxYStart = 40;
var g_TypeColors = {
"blue": "196 198 255",
"green": "201 255 200",
"red": "255 213 213",
"yellow": "255 255 157"
};
/**
* Colors, captions and format used for units, structures, etc. types
*/
var g_SummaryTypes = {
"percent": {
"color": "",
"caption": "%",
"postfix": "%"
},
"trained": {
"color": g_TypeColors.green,
"caption": translate("Trained"),
"postfix": " / "
},
"constructed": {
"color": g_TypeColors.green,
"caption": translate("Constructed"),
"postfix": " / "
},
"gathered": {
"color": g_TypeColors.green,
"caption": translate("Gathered"),
"postfix": " / "
},
"count": {
"caption": translate("Count"),
"hideInSummary": true
},
"sent": {
"color": g_TypeColors.green,
"caption": translate("Sent"),
"postfix": " / "
},
"bought": {
"color": g_TypeColors.green,
"caption": translate("Bought"),
"postfix": " / "
},
"income": {
"color": g_TypeColors.green,
"caption": translate("Income"),
"postfix": " / "
},
"captured": {
"color": g_TypeColors.yellow,
"caption": translate("Captured"),
"postfix": " / "
},
"succeeded": {
"color": g_TypeColors.green,
"caption": translate("Succeeded"),
"postfix": " / "
},
"destroyed": {
"color": g_TypeColors.blue,
"caption": translate("Destroyed"),
"postfix": "\n"
},
"killed": {
"color": g_TypeColors.blue,
"caption": translate("Killed"),
"postfix": "\n"
},
"lost": {
"color": g_TypeColors.red,
"caption": translate("Lost"),
"postfix": ""
},
"used": {
"color": g_TypeColors.red,
"caption": translate("Used"),
"postfix": ""
},
"received": {
"color": g_TypeColors.red,
"caption": translate("Received"),
"postfix": ""
},
"population": {
"color": g_TypeColors.red,
"caption": translate("Population"),
"postfix": ""
},
"sold": {
"color": g_TypeColors.red,
"caption": translate("Sold"),
"postfix": ""
},
"outcome": {
"color": g_TypeColors.red,
"caption": translate("Outcome"),
"postfix": ""
},
"failed": {
"color": g_TypeColors.red,
"caption": translate("Failed"),
"postfix": ""
}
};
// Translation: Unicode encoded infinity symbol indicating a division by zero in the summary screen.
var g_InfinitySymbol = translate("\u221E");
var g_Teams = [];
var g_PlayerCount;
var g_GameData;
var g_ResourceData = new Resources();
/**
* Selected chart indexes.
*/
var g_SelectedChart = {
"category": [0, 0],
"value": [0, 1],
"type": [0, 0]
};
function init(data)
{
initSummaryData(data);
initGUISummary();
}
function initSummaryData(data)
{
g_GameData = data;
g_ScorePanelsData = getScorePanelsData();
let teamCharts = false;
if (data && data.gui && data.gui.summarySelection)
{
g_TabCategorySelected = data.gui.summarySelection.panel;
g_SelectedChart = data.gui.summarySelection.charts;
teamCharts = data.gui.summarySelection.teamCharts;
}
Engine.GetGUIObjectByName("toggleTeamBox").checked = g_Teams && teamCharts;
initTeamData();
calculateTeamCounterDataHelper();
}
function initGUISummary()
{
initGUIWindow();
initPlayerBoxPositions();
initGUICharts();
initGUILabels();
initGUIButtons();
}
/**
* Sets the style and title of the page.
*/
function initGUIWindow()
{
let summaryWindow = Engine.GetGUIObjectByName("summaryWindow");
summaryWindow.sprite = g_GameData.gui.dialog ? "ModernDialog" : "ModernWindow";
summaryWindow.size = g_GameData.gui.dialog ? "16 24 100%-16 100%-24" : "0 0 100% 100%";
Engine.GetGUIObjectByName("summaryWindowTitle").size = g_GameData.gui.dialog ? "50%-128 -16 50%+128 16" : "50%-128 4 50%+128 36";
}
function selectPanelGUI(panel)
{
adjustTabDividers(Engine.GetGUIObjectByName("tabButton[" + panel + "]").size);
let generalPanel = Engine.GetGUIObjectByName("generalPanel");
let chartsPanel = Engine.GetGUIObjectByName("chartsPanel");
// We assume all scorePanels come before the charts.
let chartsHidden = panel < g_ScorePanelsData.length;
generalPanel.hidden = !chartsHidden;
chartsPanel.hidden = chartsHidden;
if (chartsHidden)
updatePanelData(g_ScorePanelsData[panel]);
else
[0, 1].forEach(updateCategoryDropdown);
}
function constructPlayersWithColor(color, playerListing)
{
return sprintf(translateWithContext("Player listing with color indicator",
"%(colorIndicator)s %(playerListing)s"),
{
"colorIndicator": setStringTags(translateWithContext(
"Charts player color indicator", "â– "), { "color": color }),
"playerListing": playerListing
});
}
function updateChartColorAndLegend()
{
let playerColors = [];
for (let i = 1; i <= g_PlayerCount; ++i)
{
let playerState = g_GameData.sim.playerStates[i];
playerColors.push(
Math.floor(playerState.color.r * 255) + " " +
Math.floor(playerState.color.g * 255) + " " +
Math.floor(playerState.color.b * 255)
);
}
for (let i = 0; i < 2; ++i)
Engine.GetGUIObjectByName("chart[" + i + "]").series_color =
Engine.GetGUIObjectByName("toggleTeamBox").checked ?
g_Teams.filter(el => el !== null).map(players => playerColors[players[0] - 1]) :
playerColors;
let chartLegend = Engine.GetGUIObjectByName("chartLegend");
chartLegend.caption = (Engine.GetGUIObjectByName("toggleTeamBox").checked ?
g_Teams.filter(el => el !== null).map(players =>
constructPlayersWithColor(playerColors[players[0] - 1], players.map(player =>
g_GameData.sim.playerStates[player].name
).join(translateWithContext("Player listing", ", ")))
) :
g_GameData.sim.playerStates.slice(1).map((state, index) =>
constructPlayersWithColor(playerColors[index], state.name))
).join(" ");
}
function initGUICharts()
{
updateChartColorAndLegend();
let chart1Part = Engine.GetGUIObjectByName("chart[1]Part");
let chart1PartSize = chart1Part.size;
chart1PartSize.rright += 50;
chart1PartSize.rleft += 50;
chart1PartSize.right -= 5;
chart1PartSize.left -= 5;
chart1Part.size = chart1PartSize;
Engine.GetGUIObjectByName("toggleTeam").hidden = !g_Teams;
}
function resizeDropdown(dropdown)
{
let size = dropdown.size;
size.bottom = dropdown.size.top +
(Engine.GetTextWidth(dropdown.font, dropdown.list[dropdown.selected]) >
dropdown.size.right - dropdown.size.left - 32 ? 42 : 27);
dropdown.size = size;
}
function updateCategoryDropdown(number)
{
let chartCategory = Engine.GetGUIObjectByName("chart[" + number + "]CategorySelection");
chartCategory.list_data = g_ScorePanelsData.map((panel, idx) => idx);
chartCategory.list = g_ScorePanelsData.map(panel => panel.label);
chartCategory.onSelectionChange = function() {
if (!this.list_data[this.selected])
return;
if (g_SelectedChart.category[number] != this.selected)
{
g_SelectedChart.category[number] = this.selected;
g_SelectedChart.value[number] = 0;
g_SelectedChart.type[number] = 0;
}
resizeDropdown(this);
updateValueDropdown(number, this.list_data[this.selected]);
};
chartCategory.selected = g_SelectedChart.category[number];
}
function updateValueDropdown(number, category)
{
let chartValue = Engine.GetGUIObjectByName("chart[" + number + "]ValueSelection");
let list = g_ScorePanelsData[category].headings.map(heading => heading.caption);
list.shift();
chartValue.list = list;
let list_data = g_ScorePanelsData[category].headings.map(heading => heading.identifier);
list_data.shift();
chartValue.list_data = list_data;
chartValue.onSelectionChange = function() {
if (!this.list_data[this.selected])
return;
if (g_SelectedChart.value[number] != this.selected)
{
g_SelectedChart.value[number] = this.selected;
g_SelectedChart.type[number] = 0;
}
resizeDropdown(this);
updateTypeDropdown(number, category, this.list_data[this.selected], this.selected);
};
chartValue.selected = g_SelectedChart.value[number];
}
function updateTypeDropdown(number, category, item, itemNumber)
{
let testValue = g_ScorePanelsData[category].counters[itemNumber].fn(g_GameData.sim.playerStates[1], 0, item);
let hide = !g_ScorePanelsData[category].counters[itemNumber].fn ||
typeof testValue != "object" || Object.keys(testValue).length < 2;
Engine.GetGUIObjectByName("chart[" + number + "]TypeLabel").hidden = hide;
let chartType = Engine.GetGUIObjectByName("chart[" + number + "]TypeSelection");
chartType.hidden = hide;
if (hide)
{
updateChart(number, category, item, itemNumber, Object.keys(testValue)[0] || undefined);
return;
}
chartType.list = Object.keys(testValue).map(type => g_SummaryTypes[type].caption);
chartType.list_data = Object.keys(testValue);
chartType.onSelectionChange = function() {
if (!this.list_data[this.selected])
return;
g_SelectedChart.type[number] = this.selected;
resizeDropdown(this);
updateChart(number, category, item, itemNumber, this.list_data[this.selected]);
};
chartType.selected = g_SelectedChart.type[number];
}
function updateChart(number, category, item, itemNumber, type)
{
if (!g_ScorePanelsData[category].counters[itemNumber].fn)
return;
let chart = Engine.GetGUIObjectByName("chart[" + number + "]");
chart.format_y = g_ScorePanelsData[category].headings[itemNumber + 1].format || "INTEGER";
Engine.GetGUIObjectByName("chart[" + number + "]XAxisLabel").caption = translate("Time elapsed");
let series = [];
if (Engine.GetGUIObjectByName("toggleTeamBox").checked)
for (let team in g_Teams)
{
let data = [];
for (let index in g_GameData.sim.playerStates[1].sequences.time)
{
let value = g_ScorePanelsData[category].teamCounterFn(team, index, item,
g_ScorePanelsData[category].counters, g_ScorePanelsData[category].headings);
if (type)
value = value[type];
- data.push([g_GameData.sim.playerStates[1].sequences.time[index], value]);
+ data.push({ "x": g_GameData.sim.playerStates[1].sequences.time[index], "y": value });
}
series.push(data);
}
else
for (let j = 1; j <= g_PlayerCount; ++j)
{
let playerState = g_GameData.sim.playerStates[j];
let data = [];
for (let index in playerState.sequences.time)
{
let value = g_ScorePanelsData[category].counters[itemNumber].fn(playerState, index, item);
if (type)
value = value[type];
- data.push([playerState.sequences.time[index], value]);
+ data.push({ "x": playerState.sequences.time[index], "y": value });
}
series.push(data);
}
chart.series = series;
}
function adjustTabDividers(tabSize)
{
let tabButtonsLeft = Engine.GetGUIObjectByName("tabButtonsFrame").size.left;
let leftSpacer = Engine.GetGUIObjectByName("tabDividerLeft");
let leftSpacerSize = leftSpacer.size;
leftSpacerSize.right = tabSize.left + tabButtonsLeft + 2;
leftSpacer.size = leftSpacerSize;
let rightSpacer = Engine.GetGUIObjectByName("tabDividerRight");
let rightSpacerSize = rightSpacer.size;
rightSpacerSize.left = tabSize.right + tabButtonsLeft - 2;
rightSpacer.size = rightSpacerSize;
}
function updatePanelData(panelInfo)
{
resetGeneralPanel();
updateGeneralPanelHeadings(panelInfo.headings);
updateGeneralPanelTitles(panelInfo.titleHeadings);
let rowPlayerObjectWidth = updateGeneralPanelCounter(panelInfo.counters);
updateGeneralPanelTeams();
let index = g_GameData.sim.playerStates[1].sequences.time.length - 1;
let playerBoxesCounts = [];
for (let i = 0; i < g_PlayerCount; ++i)
{
let playerState = g_GameData.sim.playerStates[i + 1];
if (!playerBoxesCounts[playerState.team + 1])
playerBoxesCounts[playerState.team + 1] = 1;
else
playerBoxesCounts[playerState.team + 1] += 1;
let positionObject = playerBoxesCounts[playerState.team + 1] - 1;
let rowPlayer = "playerBox[" + positionObject + "]";
let playerOutcome = "playerOutcome[" + positionObject + "]";
let playerNameColumn = "playerName[" + positionObject + "]";
let playerCivicBoxColumn = "civIcon[" + positionObject + "]";
let playerCounterValue = "valueData[" + positionObject + "]";
if (playerState.team != -1)
{
rowPlayer = "playerBoxt[" + playerState.team + "][" + positionObject + "]";
playerOutcome = "playerOutcomet[" + playerState.team + "][" + positionObject + "]";
playerNameColumn = "playerNamet[" + playerState.team + "][" + positionObject + "]";
playerCivicBoxColumn = "civIcont[" + playerState.team + "][" + positionObject + "]";
playerCounterValue = "valueDataTeam[" + playerState.team + "][" + positionObject + "]";
}
let colorString = "color: " +
Math.floor(playerState.color.r * 255) + " " +
Math.floor(playerState.color.g * 255) + " " +
Math.floor(playerState.color.b * 255);
let rowPlayerObject = Engine.GetGUIObjectByName(rowPlayer);
rowPlayerObject.hidden = false;
rowPlayerObject.sprite = colorString + " " + g_PlayerBoxAlpha;
let boxSize = rowPlayerObject.size;
boxSize.right = rowPlayerObjectWidth;
rowPlayerObject.size = boxSize;
setOutcomeIcon(playerState.state, Engine.GetGUIObjectByName(playerOutcome));
playerNameColumn = Engine.GetGUIObjectByName(playerNameColumn);
playerNameColumn.caption = g_GameData.sim.playerStates[i + 1].name;
playerNameColumn.tooltip = translateAISettings(g_GameData.sim.mapSettings.PlayerData[i + 1]);
let civIcon = Engine.GetGUIObjectByName(playerCivicBoxColumn);
civIcon.sprite = "stretched:" + g_CivData[playerState.civ].Emblem;
civIcon.tooltip = g_CivData[playerState.civ].Name;
updateCountersPlayer(playerState, panelInfo.counters, panelInfo.headings, playerCounterValue, index);
}
let teamCounterFn = panelInfo.teamCounterFn;
if (g_Teams && teamCounterFn)
updateCountersTeam(teamCounterFn, panelInfo.counters, panelInfo.headings, index);
}
function continueButton()
{
let summarySelection = {
"panel": g_TabCategorySelected,
"charts": g_SelectedChart,
"teamCharts": Engine.GetGUIObjectByName("toggleTeamBox").checked
};
if (g_GameData.gui.isInGame)
Engine.PopGuiPage({
"summarySelection": summarySelection
});
else if (g_GameData.gui.dialog)
Engine.PopGuiPage();
else if (Engine.HasXmppClient())
Engine.SwitchGuiPage("page_lobby.xml", { "dialog": false });
else if (g_GameData.gui.isReplay)
Engine.SwitchGuiPage("page_replaymenu.xml", {
"replaySelectionData": g_GameData.gui.replaySelectionData,
"summarySelection": summarySelection
});
else if (g_GameData.campaignData)
Engine.SwitchGuiPage(g_GameData.nextPage, g_GameData.campaignData);
else
Engine.SwitchGuiPage("page_pregame.xml");
}
function startReplay()
{
if (!Engine.StartVisualReplay(g_GameData.gui.replayDirectory))
{
warn("Replay file not found!");
return;
}
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": Engine.GetReplayAttributes(g_GameData.gui.replayDirectory),
"playerAssignments": {
"local": {
"name": singleplayerName(),
"player": -1
}
},
"savedGUIData": "",
"isReplay": true,
"replaySelectionData": g_GameData.gui.replaySelectionData
});
}
function initGUILabels()
{
let assignedState = g_GameData.sim.playerStates[g_GameData.gui.assignedPlayer || -1];
Engine.GetGUIObjectByName("summaryText").caption =
g_GameData.gui.isInGame ?
translate("Current Scores") :
g_GameData.gui.isReplay ?
translate("Scores at the end of the game.") :
g_GameData.gui.disconnected ?
translate("You have been disconnected.") :
!assignedState ?
translate("You have left the game.") :
assignedState.state == "won" ?
translate("You have won the battle!") :
assignedState.state == "defeated" ?
translate("You have been defeated…") :
translate("You have abandoned the game.");
Engine.GetGUIObjectByName("timeElapsed").caption = sprintf(
translate("Game time elapsed: %(time)s"), {
"time": timeToString(g_GameData.sim.timeElapsed)
});
let mapType = g_Settings.MapTypes.find(type => type.Name == g_GameData.sim.mapSettings.mapType);
let mapSize = g_Settings.MapSizes.find(size => size.Tiles == g_GameData.sim.mapSettings.Size || 0);
Engine.GetGUIObjectByName("mapName").caption = sprintf(
translate("%(mapName)s - %(mapType)s"), {
"mapName": translate(g_GameData.sim.mapSettings.Name),
"mapType": mapSize ? mapSize.Name : (mapType ? mapType.Title : "")
});
}
function initGUIButtons()
{
let replayButton = Engine.GetGUIObjectByName("replayButton");
replayButton.hidden = g_GameData.gui.isInGame || !g_GameData.gui.replayDirectory;
let lobbyButton = Engine.GetGUIObjectByName("lobbyButton");
lobbyButton.tooltip = colorizeHotkey(translate("%(hotkey)s: Toggle the multiplayer lobby in a dialog window."), "lobby");
lobbyButton.hidden = g_GameData.gui.isInGame || !Engine.HasXmppClient();
// Right-align lobby button
let lobbyButtonSize = lobbyButton.size;
let lobbyButtonWidth = lobbyButtonSize.right - lobbyButtonSize.left;
lobbyButtonSize.right = (replayButton.hidden ? Engine.GetGUIObjectByName("continueButton").size.left : replayButton.size.left) - 10;
lobbyButtonSize.left = lobbyButtonSize.right - lobbyButtonWidth;
lobbyButton.size = lobbyButtonSize;
let allPanelsData = g_ScorePanelsData.concat(g_ChartPanelsData);
for (let tab in allPanelsData)
allPanelsData[tab].tooltip =
sprintf(translate("Toggle the %(name)s summary tab."), { "name": allPanelsData[tab].label }) +
colorizeHotkey("\n" + translate("Use %(hotkey)s to move a summary tab right."), "tab.next") +
colorizeHotkey("\n" + translate("Use %(hotkey)s to move a summary tab left."), "tab.prev");
placeTabButtons(
allPanelsData,
true,
g_TabButtonWidth,
g_TabButtonDist,
selectPanel,
selectPanelGUI);
}
function initTeamData()
{
// Panels
g_PlayerCount = g_GameData.sim.playerStates.length - 1;
if (g_GameData.sim.mapSettings.LockTeams)
{
// Count teams
for (let player = 1; player <= g_PlayerCount; ++player)
{
let playerTeam = g_GameData.sim.playerStates[player].team;
if (!g_Teams[playerTeam])
g_Teams[playerTeam] = [];
g_Teams[playerTeam].push(player);
}
if (g_Teams.every(team => team && team.length < 2))
g_Teams = false; // Each player has his own team. Displaying teams makes no sense.
}
else
g_Teams = false;
// Erase teams data if teams are not displayed
if (!g_Teams)
for (let p = 0; p < g_PlayerCount; ++p)
g_GameData.sim.playerStates[p+1].team = -1;
}
Index: ps/trunk/source/maths/Vector2D.cpp
===================================================================
--- ps/trunk/source/maths/Vector2D.cpp (nonexistent)
+++ ps/trunk/source/maths/Vector2D.cpp (revision 25152)
@@ -0,0 +1,58 @@
+/* Copyright (C) 2021 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "Vector2D.h"
+
+#include "maths/Size2D.h"
+
+CVector2D::CVector2D(const CSize2D& size) : X(size.Width), Y(size.Height)
+{
+}
+
+bool CVector2D::operator==(const CVector2D& v) const
+{
+ return X == v.X && Y == v.Y;
+}
+
+bool CVector2D::operator!=(const CVector2D& v) const
+{
+ return !(*this == v);
+}
+
+CVector2D CVector2D::operator+(const CSize2D& size) const
+{
+ return CVector2D(X + size.Width, Y + size.Height);
+}
+
+CVector2D CVector2D::operator-(const CSize2D& size) const
+{
+ return CVector2D(X - size.Width, Y - size.Height);
+}
+
+void CVector2D::operator+=(const CSize2D& size)
+{
+ X += size.Width;
+ Y += size.Height;
+}
+
+void CVector2D::operator-=(const CSize2D& size)
+{
+ X -= size.Width;
+ Y -= size.Height;
+}
Property changes on: ps/trunk/source/maths/Vector2D.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/ps/Shapes.h
===================================================================
--- ps/trunk/source/ps/Shapes.h (revision 25151)
+++ ps/trunk/source/ps/Shapes.h (revision 25152)
@@ -1,157 +1,121 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_SHAPES
#define INCLUDED_SHAPES
-class CPos;
class CSize2D;
+class CVector2D;
/**
* Rectangle class used for screen rectangles. It's very similar to the MS
* CRect, but with FLOATS because it's meant to be used with OpenGL which
* takes float values.
*/
class CRect
{
public:
CRect();
- CRect(const CPos &pos);
- CRect(const CSize2D &size);
- CRect(const CPos &upperleft, const CPos &bottomright);
- CRect(const CPos &pos, const CSize2D &size);
+ CRect(const CVector2D& pos);
+ CRect(const CSize2D& size);
+ CRect(const CVector2D& upperleft, const CVector2D& bottomright);
+ CRect(const CVector2D& pos, const CSize2D& size);
CRect(const float l, const float t, const float r, const float b);
CRect(const CRect&);
CRect& operator=(const CRect& a);
bool operator==(const CRect& a) const;
bool operator!=(const CRect& a) const;
CRect operator-() const;
CRect operator+() const;
CRect operator+(const CRect& a) const;
- CRect operator+(const CPos& a) const;
+ CRect operator+(const CVector2D& a) const;
CRect operator+(const CSize2D& a) const;
CRect operator-(const CRect& a) const;
- CRect operator-(const CPos& a) const;
+ CRect operator-(const CVector2D& a) const;
CRect operator-(const CSize2D& a) const;
void operator+=(const CRect& a);
- void operator+=(const CPos& a);
+ void operator+=(const CVector2D& a);
void operator+=(const CSize2D& a);
void operator-=(const CRect& a);
- void operator-=(const CPos& a);
+ void operator-=(const CVector2D& a);
void operator-=(const CSize2D& a);
/**
* @return Width of Rectangle
*/
float GetWidth() const;
/**
* @return Height of Rectangle
*/
float GetHeight() const;
/**
* Get Size
*/
CSize2D GetSize() const;
/**
* Get Position equivalent to top/left corner
*/
- CPos TopLeft() const;
+ CVector2D TopLeft() const;
/**
* Get Position equivalent to top/right corner
*/
- CPos TopRight() const;
+ CVector2D TopRight() const;
/**
* Get Position equivalent to bottom/left corner
*/
- CPos BottomLeft() const;
+ CVector2D BottomLeft() const;
/**
* Get Position equivalent to bottom/right corner
*/
- CPos BottomRight() const;
+ CVector2D BottomRight() const;
/**
* Get Position equivalent to the center of the rectangle
*/
- CPos CenterPoint() const;
+ CVector2D CenterPoint() const;
/**
* Evalutates if point is within the rectangle
- * @param point CPos representing point
+ * @param point CVector2D representing point
* @return true if inside.
*/
- bool PointInside(const CPos &point) const;
+ bool PointInside(const CVector2D &point) const;
CRect Scale(float x, float y) const;
/**
- * Returning CPos representing each corner.
+ * Returning CVector2D representing each corner.
*/
public:
/**
* Dimensions
*/
float left, top, right, bottom;
};
-/**
- * Made to represent screen positions and delta values.
- * @see CRect
- * @see CSize2D
- */
-class CPos
-{
-public:
- CPos();
- CPos(const CPos& pos);
- CPos(const CSize2D &pos);
- CPos(const float px, const float py);
-
- CPos& operator=(const CPos& a);
- bool operator==(const CPos& a) const;
- bool operator!=(const CPos& a) const;
- CPos operator-() const;
- CPos operator+() const;
-
- CPos operator+(const CPos& a) const;
- CPos operator+(const CSize2D& a) const;
- CPos operator-(const CPos& a) const;
- CPos operator-(const CSize2D& a) const;
-
- void operator+=(const CPos& a);
- void operator+=(const CSize2D& a);
- void operator-=(const CPos& a);
- void operator-=(const CSize2D& a);
-
-public:
- /**
- * Position
- */
- float x, y;
-};
-
#endif // INCLUDED_SHAPES
Index: ps/trunk/source/gui/ObjectTypes/COList.cpp
===================================================================
--- ps/trunk/source/gui/ObjectTypes/COList.cpp (revision 25151)
+++ ps/trunk/source/gui/ObjectTypes/COList.cpp (revision 25152)
@@ -1,442 +1,442 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "COList.h"
#include "gui/CGUI.h"
#include "gui/IGUIScrollBar.h"
#include "gui/SettingTypes/CGUIColor.h"
#include "gui/SettingTypes/CGUIList.h"
#include "i18n/L10n.h"
#include "ps/CLogger.h"
const float SORT_SPRITE_DIM = 16.0f;
-const CPos COLUMN_SHIFT = CPos(0, 4);
+const CVector2D COLUMN_SHIFT = CVector2D(0, 4);
const CStr COList::EventNameSelectionColumnChange = "SelectionColumnChange";
COList::COList(CGUI& pGUI)
: CList(pGUI),
m_SpriteHeading(),
m_Sortable(),
m_SelectedColumn(),
m_SelectedColumnOrder(),
m_SpriteAsc(),
m_SpriteDesc(),
m_SpriteNotSorted()
{
RegisterSetting("sprite_heading", m_SpriteHeading);
RegisterSetting("sortable", m_Sortable); // The actual sorting is done in JS for more versatility
RegisterSetting("selected_column", m_SelectedColumn);
RegisterSetting("selected_column_order", m_SelectedColumnOrder);
RegisterSetting("sprite_asc", m_SpriteAsc); // Show the order of sorting
RegisterSetting("sprite_desc", m_SpriteDesc);
RegisterSetting("sprite_not_sorted", m_SpriteNotSorted);
}
void COList::SetupText()
{
m_ItemsYPositions.resize(m_List.m_Items.size() + 1);
// Delete all generated texts. Some could probably be saved,
// but this is easier, and this function will never be called
// continuously, or even often, so it'll probably be okay.
m_GeneratedTexts.clear();
m_TotalAvailableColumnWidth = GetListRect().GetWidth();
// remove scrollbar if applicable
if (m_ScrollBar && GetScrollBar(0).GetStyle())
m_TotalAvailableColumnWidth -= GetScrollBar(0).GetStyle()->m_Width;
m_HeadingHeight = SORT_SPRITE_DIM; // At least the size of the sorting sprite
for (const COListColumn& column : m_Columns)
{
float width = column.m_Width;
if (column.m_Width > 0 && column.m_Width < 1)
width *= m_TotalAvailableColumnWidth;
CGUIString gui_string;
gui_string.SetValue(column.m_Heading);
const CGUIText& text = AddText(gui_string, m_Font, width, m_BufferZone);
- m_HeadingHeight = std::max(m_HeadingHeight, text.GetSize().Height + COLUMN_SHIFT.y);
+ m_HeadingHeight = std::max(m_HeadingHeight, text.GetSize().Height + COLUMN_SHIFT.Y);
}
// Generate texts
float buffered_y = 0.f;
for (size_t i = 0; i < m_List.m_Items.size(); ++i)
{
m_ItemsYPositions[i] = buffered_y;
float shift = 0.0f;
for (const COListColumn& column : m_Columns)
{
float width = column.m_Width;
if (column.m_Width > 0 && column.m_Width < 1)
width *= m_TotalAvailableColumnWidth;
CGUIText* text;
if (!column.m_List.m_Items[i].GetOriginalString().empty())
text = &AddText(column.m_List.m_Items[i], m_Font, width, m_BufferZone);
else
{
// Minimum height of a space character of the current font size
CGUIString align_string;
align_string.SetValue(L" ");
text = &AddText(align_string, m_Font, width, m_BufferZone);
}
shift = std::max(shift, text->GetSize().Height);
}
buffered_y += shift;
}
m_ItemsYPositions[m_List.m_Items.size()] = buffered_y;
if (m_ScrollBar)
{
CRect rect = GetListRect();
GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back());
GetScrollBar(0).SetScrollSpace(rect.GetHeight());
GetScrollBar(0).SetX(rect.right);
GetScrollBar(0).SetY(rect.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(rect.bottom - rect.top);
}
}
CRect COList::GetListRect() const
{
return m_CachedActualSize + CRect(0, m_HeadingHeight, 0, 0);
}
void COList::HandleMessage(SGUIMessage& Message)
{
CList::HandleMessage(Message);
switch (Message.type)
{
// If somebody clicks on the column heading
case GUIM_MOUSE_PRESS_LEFT:
{
if (!m_Sortable)
return;
- const CPos& mouse = m_pGUI.GetMousePos();
+ const CVector2D& mouse = m_pGUI.GetMousePos();
if (!m_CachedActualSize.PointInside(mouse))
return;
float xpos = 0;
for (const COListColumn& column : m_Columns)
{
if (column.m_Hidden)
continue;
float width = column.m_Width;
// Check if it's a decimal value, and if so, assume relative positioning.
if (column.m_Width < 1 && column.m_Width > 0)
width *= m_TotalAvailableColumnWidth;
- CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0);
- if (mouse.x >= leftTopCorner.x &&
- mouse.x < leftTopCorner.x + width &&
- mouse.y < leftTopCorner.y + m_HeadingHeight)
+ CVector2D leftTopCorner = m_CachedActualSize.TopLeft() + CVector2D(xpos, 0);
+ if (mouse.X >= leftTopCorner.X &&
+ mouse.X < leftTopCorner.X + width &&
+ mouse.Y < leftTopCorner.Y + m_HeadingHeight)
{
if (column.m_Id != m_SelectedColumn)
{
SetSetting("selected_column_order", -1, true);
CStr selected_column = column.m_Id;
SetSetting("selected_column", selected_column, true);
}
else
SetSetting("selected_column_order", -m_SelectedColumnOrder, true);
ScriptEvent(EventNameSelectionColumnChange);
PlaySound(m_SoundSelected);
return;
}
xpos += width;
}
return;
}
default:
return;
}
}
bool COList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile)
{
#define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
#define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
ELMT(item);
ELMT(column);
ELMT(translatableAttribute);
ATTR(id);
ATTR(context);
if (child.GetNodeName() == elmt_item)
{
CGUIString vlist;
vlist.SetValue(child.GetText().FromUTF8());
AddItem(vlist, vlist);
return true;
}
else if (child.GetNodeName() == elmt_column)
{
COListColumn column;
for (XMBAttribute attr : child.GetAttributes())
{
CStr attr_name(pFile->GetAttributeString(attr.Name));
CStr attr_value(attr.Value);
if (attr_name == "color")
{
if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), column.m_TextColor))
LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str());
}
else if (attr_name == "id")
{
column.m_Id = attr_value;
}
else if (attr_name == "hidden")
{
bool hidden = false;
if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), hidden))
LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str());
else
column.m_Hidden = hidden;
}
else if (attr_name == "width")
{
float width;
if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), width))
LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str());
else
{
// Check if it's a relative value, and save as decimal if so.
if (attr_value.find("%") != std::string::npos)
width = width / 100.f;
column.m_Width = width;
}
}
else if (attr_name == "heading")
{
column.m_Heading = attr_value.FromUTF8();
}
}
for (XMBElement grandchild : child.GetChildNodes())
{
if (grandchild.GetNodeName() != elmt_translatableAttribute)
continue;
CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id));
// only the heading is translatable for list column
if (attributeName.empty() || attributeName != "heading")
{
LOGERROR("GUI: translatable attribute in olist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str());
continue;
}
CStr value(grandchild.GetText());
if (value.empty())
continue;
CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any.
if (!context.empty())
{
CStr translatedValue(g_L10n.TranslateWithContext(context, value));
column.m_Heading = translatedValue.FromUTF8();
}
else
{
CStr translatedValue(g_L10n.Translate(value));
column.m_Heading = translatedValue.FromUTF8();
}
}
m_Columns.emplace_back(std::move(column));
return true;
}
return false;
}
void COList::AdditionalChildrenHandled()
{
SetupText();
// Do this after the last push_back call to avoid iterator invalidation
for (COListColumn& column : m_Columns)
{
RegisterSetting("list_" + column.m_Id, column.m_List);
RegisterSetting("hidden_" + column.m_Id, column.m_Hidden);
}
}
void COList::DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selected, const CGUIColor& textcolor)
{
const float bz = GetBufferedZ();
if (m_ScrollBar)
IGUIScrollBarOwner::Draw();
CRect rect = GetListRect();
m_pGUI.DrawSprite(sprite, bz, rect);
float scroll = 0.f;
if (m_ScrollBar)
scroll = GetScrollBar(0).GetPos();
// Draw item selection
if (selected != -1)
{
ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size());
// Get rectangle of selection:
CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
if (rect_sel.top <= rect.bottom &&
rect_sel.bottom >= rect.top)
{
if (rect_sel.bottom > rect.bottom)
rect_sel.bottom = rect.bottom;
if (rect_sel.top < rect.top)
rect_sel.top = rect.top;
if (m_ScrollBar)
{
// Remove any overlapping area of the scrollbar.
if (rect_sel.right > GetScrollBar(0).GetOuterRect().left &&
rect_sel.right <= GetScrollBar(0).GetOuterRect().right)
rect_sel.right = GetScrollBar(0).GetOuterRect().left;
if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left &&
rect_sel.left < GetScrollBar(0).GetOuterRect().right)
rect_sel.left = GetScrollBar(0).GetOuterRect().right;
}
// Draw item selection
m_pGUI.DrawSprite(sprite_selected, bz + 0.05f, rect_sel);
}
}
// Draw line above column header
CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right,
m_CachedActualSize.top + m_HeadingHeight);
m_pGUI.DrawSprite(m_SpriteHeading, bz, rect_head);
// Draw column headers
float xpos = 0;
size_t col = 0;
for (const COListColumn& column : m_Columns)
{
if (column.m_Hidden)
{
++col;
continue;
}
// Check if it's a decimal value, and if so, assume relative positioning.
float width = column.m_Width;
if (column.m_Width < 1 && column.m_Width > 0)
width *= m_TotalAvailableColumnWidth;
- CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0);
+ CVector2D leftTopCorner = m_CachedActualSize.TopLeft() + CVector2D(xpos, 0);
// Draw sort arrows in colum header
if (m_Sortable)
{
const CGUISpriteInstance* pSprite;
if (m_SelectedColumn == column.m_Id)
{
if (m_SelectedColumnOrder == 0)
LOGERROR("selected_column_order must not be 0");
if (m_SelectedColumnOrder != -1)
pSprite = &m_SpriteAsc;
else
pSprite = &m_SpriteDesc;
}
else
pSprite = &m_SpriteNotSorted;
- m_pGUI.DrawSprite(*pSprite, bz + 0.1f, CRect(leftTopCorner + CPos(width - SORT_SPRITE_DIM, 0), leftTopCorner + CPos(width, SORT_SPRITE_DIM)));
+ m_pGUI.DrawSprite(*pSprite, bz + 0.1f, CRect(leftTopCorner + CVector2D(width - SORT_SPRITE_DIM, 0), leftTopCorner + CVector2D(width, SORT_SPRITE_DIM)));
}
// Draw column header text
DrawText(col, textcolor, leftTopCorner + COLUMN_SHIFT, bz + 0.1f, rect_head);
xpos += width;
++col;
}
// Draw list items for each column
const size_t objectsCount = m_Columns.size();
for (size_t i = 0; i < m_List.m_Items.size(); ++i)
{
if (m_ItemsYPositions[i+1] - scroll < 0 ||
m_ItemsYPositions[i] - scroll > rect.GetHeight())
continue;
const float rowHeight = m_ItemsYPositions[i+1] - m_ItemsYPositions[i];
// Clipping area (we'll have to substract the scrollbar)
CRect cliparea = GetListRect();
if (m_ScrollBar)
{
if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
cliparea.right <= GetScrollBar(0).GetOuterRect().right)
cliparea.right = GetScrollBar(0).GetOuterRect().left;
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
cliparea.left < GetScrollBar(0).GetOuterRect().right)
cliparea.left = GetScrollBar(0).GetOuterRect().right;
}
// Draw all items for that column
xpos = 0;
for (size_t colIdx = 0; colIdx < m_Columns.size(); ++colIdx)
{
const COListColumn& column = m_Columns[colIdx];
if (column.m_Hidden)
continue;
// Determine text position and width
- const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]);
+ const CVector2D textPos = rect.TopLeft() + CVector2D(xpos, -scroll + m_ItemsYPositions[i]);
float width = column.m_Width;
// Check if it's a decimal value, and if so, assume relative positioning.
if (column.m_Width < 1 && column.m_Width > 0)
width *= m_TotalAvailableColumnWidth;
// Clip text to the column (to prevent drawing text into the neighboring column)
CRect cliparea2 = cliparea;
- cliparea2.right = std::min(cliparea2.right, textPos.x + width);
- cliparea2.bottom = std::min(cliparea2.bottom, textPos.y + rowHeight);
+ cliparea2.right = std::min(cliparea2.right, textPos.X + width);
+ cliparea2.bottom = std::min(cliparea2.bottom, textPos.Y + rowHeight);
// Draw list item
DrawText(objectsCount * (i +/*Heading*/1) + colIdx, column.m_TextColor, textPos, bz + 0.1f, cliparea2);
xpos += width;
}
}
}
Index: ps/trunk/source/gui/ObjectTypes/CSlider.h
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CSlider.h (revision 25151)
+++ ps/trunk/source/gui/ObjectTypes/CSlider.h (revision 25152)
@@ -1,74 +1,75 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_CSLIDER
#define INCLUDED_CSLIDER
#include "gui/CGUISprite.h"
#include "gui/ObjectBases/IGUIButtonBehavior.h"
#include "gui/ObjectBases/IGUIObject.h"
+#include "maths/Vector2D.h"
class CSlider : public IGUIObject, public IGUIButtonBehavior
{
GUI_OBJECT(CSlider)
public:
CSlider(CGUI& pGUI);
virtual ~CSlider();
protected:
static const CStr EventNameValueChange;
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates();
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
virtual void Draw();
/**
* Change settings and send the script event
*/
void UpdateValue();
CRect GetButtonRect() const;
/**
* @return ratio between the value of the slider and its actual size in the GUI
*/
float GetSliderRatio() const;
void IncrementallyChangeValue(const float value);
// Settings
float m_ButtonSide;
float m_MinValue;
float m_MaxValue;
CGUISpriteInstance m_Sprite;
CGUISpriteInstance m_SpriteBar;
float m_Value;
private:
- CPos m_Mouse;
+ CVector2D m_Mouse;
};
#endif // INCLUDED_CSLIDER
Index: ps/trunk/source/gui/ObjectTypes/CText.h
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CText.h (revision 25151)
+++ ps/trunk/source/gui/ObjectTypes/CText.h (revision 25152)
@@ -1,98 +1,98 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_CTEXT
#define INCLUDED_CTEXT
#include "gui/CGUISprite.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectBases/IGUIScrollBarOwner.h"
#include "gui/ObjectBases/IGUITextOwner.h"
#include "gui/SettingTypes/CGUIString.h"
/**
* Text field that just displays static text.
*/
class CText : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner
{
GUI_OBJECT(CText)
public:
CText(CGUI& pGUI);
virtual ~CText();
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates();
/**
* @see IGUIObject#UpdateCachedSize()
*/
virtual void UpdateCachedSize();
/**
* Test if mouse position is over an icon
*/
virtual bool MouseOverIcon();
/**
* Populate @param ret with the object's text size.
*/
void getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
protected:
/**
* Sets up text, should be called every time changes has been
* made that can change the visual.
*/
void SetupText();
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* Draws the Text
*/
virtual void Draw();
virtual void CreateJSObject();
/**
* Placement of text. Ignored when scrollbars are active.
*/
- CPos m_TextPos;
+ CVector2D m_TextPos;
// Settings
float m_BufferZone;
CGUIString m_Caption;
bool m_Clip;
CStrW m_Font;
bool m_ScrollBar;
CStr m_ScrollBarStyle;
bool m_ScrollBottom;
bool m_ScrollTop;
CGUISpriteInstance m_Sprite;
EAlign m_TextAlign;
EVAlign m_TextVAlign;
CGUIColor m_TextColor;
CGUIColor m_TextColorDisabled;
CStrW m_IconTooltip;
CStr m_IconTooltipStyle;
};
#endif // INCLUDED_CTEXT
Index: ps/trunk/source/gui/ObjectTypes/CTooltip.h
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CTooltip.h (revision 25151)
+++ ps/trunk/source/gui/ObjectTypes/CTooltip.h (revision 25152)
@@ -1,69 +1,70 @@
-/* Copyright (C) 2020 Wildfire Games.
+/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_CTOOLTIP
#define INCLUDED_CTOOLTIP
#include "gui/CGUISprite.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectBases/IGUITextOwner.h"
#include "gui/SettingTypes/CGUIString.h"
+#include "maths/Vector2D.h"
/**
* Dynamic tooltips. Similar to CText.
*/
class CTooltip : public IGUIObject, public IGUITextOwner
{
GUI_OBJECT(CTooltip)
public:
CTooltip(CGUI& pGUI);
virtual ~CTooltip();
protected:
void SetupText();
/**
* @see IGUIObject#UpdateCachedSize()
*/
void UpdateCachedSize();
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
virtual void Draw();
// Settings
float m_BufferZone;
CGUIString m_Caption;
CStrW m_Font;
CGUISpriteInstance m_Sprite;
i32 m_Delay;
CGUIColor m_TextColor;
float m_MaxWidth;
- CPos m_Offset;
+ CVector2D m_Offset;
EVAlign m_Anchor;
EAlign m_TextAlign;
bool m_Independent;
- CPos m_MousePos;
+ CVector2D m_MousePos;
CStr m_UseObject;
bool m_HideObject;
};
#endif // INCLUDED_CTOOLTIP
Index: ps/trunk/source/gui/tests/test_ParseString.h
===================================================================
--- ps/trunk/source/gui/tests/test_ParseString.h (revision 25151)
+++ ps/trunk/source/gui/tests/test_ParseString.h (revision 25152)
@@ -1,104 +1,104 @@
-/* Copyright (C) 2019 Wildfire Games.
+/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "lib/self_test.h"
#include "gui/SettingTypes/CGUISize.h"
#include "gui/CGUI.h"
#include "ps/CLogger.h"
class TestGuiParseString : public CxxTest::TestSuite
{
public:
void test_guisize()
{
TestLogger nolog;
CGUISize size;
// Test only pixels
TS_ASSERT(size.FromString("0.0 -10 20.0 -30"));
TS_ASSERT_EQUALS(size, CGUISize(CRect(0, -10, 20, -30), CRect(0, 0, 0, 0)));
// Test only pixels, but with math
TS_ASSERT(size.FromString("0 -100-10+100 20+200-200 -30"));
TS_ASSERT_EQUALS(size, CGUISize(CRect(0, -10, 20, -30), CRect(0, 0, 0, 0)));
// Test only percent
TS_ASSERT(size.FromString("-5% 10.0% -20% 30.0%"));
TS_ASSERT_EQUALS(size, CGUISize(CRect(0, 0, 0, 0), CRect(-5, 10, -20, 30)));
// Test only percent, but with math
TS_ASSERT(size.FromString("15%-5%-15% 10% -20% 30%+500%-500%"));
TS_ASSERT_EQUALS(size, CGUISize(CRect(0, 0, 0, 0), CRect(-5, 10, -20, 30)));
// Test mixed
TS_ASSERT(size.FromString("5% -10 -20% 30"));
TS_ASSERT_EQUALS(size, CGUISize(CRect(0, -10, 0, 30), CRect(5, 0, -20, 0)));
// Test mixed with math
TS_ASSERT(size.FromString("5%+10%-10% 30%-10-30% 50-20%-50 30-100+100"));
TS_ASSERT_EQUALS(size, CGUISize(CRect(0, -10, 0, 30), CRect(5, 0, -20, 0)));
// Test for fail with too many/few parameters
TS_ASSERT(!size.FromString("10 20 30 40 50"));
TS_ASSERT(!size.FromString("10 20 30"));
// Test for fail with garbage data
TS_ASSERT(!size.FromString("Hello world!"));
TS_ASSERT(!size.FromString("abc 123 xyz 789"));
TS_ASSERT(!size.FromString("300 wide, 400 high"));
}
void test_rect()
{
TestLogger nolog;
CRect test;
TS_ASSERT(CGUI::ParseString(nullptr, CStrW(L"0.0 10.0 20.0 30.0"), test));
TS_ASSERT_EQUALS(CRect(0.0, 10.0, 20.0, 30.0), test);
TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0 10 20"), test));
TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0 10 20 30 40"), test));
TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0,0 10,0 20,0 30,0"), test));
}
void test_size()
{
TestLogger nolog;
CSize2D test;
TS_ASSERT(CGUI::ParseString(nullptr, CStrW(L"0.0 10.0"), test));
TS_ASSERT_EQUALS(CSize2D(0.0, 10.0), test);
TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0"), test));
TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0 10 20"), test));
TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0,0 10,0"), test));
}
void test_pos()
{
TestLogger nolog;
- CPos test;
+ CVector2D test;
- TS_ASSERT(CGUI::ParseString(nullptr, CStrW(L"0.0 10.0"), test));
- TS_ASSERT_EQUALS(CPos(0.0, 10.0), test);
+ TS_ASSERT(CGUI::ParseString(nullptr, CStrW(L"0.0 10.0"), test));
+ TS_ASSERT_EQUALS(CVector2D(0.0, 10.0), test);
- TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0"), test));
- TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0 10 20"), test));
- TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0,0 10,0"), test));
+ TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0"), test));
+ TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0 10 20"), test));
+ TS_ASSERT(!CGUI::ParseString(nullptr, CStrW(L"0,0 10,0"), test));
}
};
Index: ps/trunk/source/ps/Shapes.cpp
===================================================================
--- ps/trunk/source/ps/Shapes.cpp (revision 25151)
+++ ps/trunk/source/ps/Shapes.cpp (revision 25152)
@@ -1,309 +1,221 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "Shapes.h"
#include "maths/Size2D.h"
+#include "maths/Vector2D.h"
CRect::CRect() :
left(0.f), top(0.f), right(0.f), bottom(0.f)
{
}
CRect::CRect(const CRect& rect) :
left(rect.left), top(rect.top), right(rect.right), bottom(rect.bottom)
{
}
-CRect::CRect(const CPos &pos) :
- left(pos.x), top(pos.y), right(pos.x), bottom(pos.y)
+CRect::CRect(const CVector2D& pos) :
+ left(pos.X), top(pos.Y), right(pos.X), bottom(pos.Y)
{
}
CRect::CRect(const CSize2D& size) :
left(0.f), top(0.f), right(size.Width), bottom(size.Height)
{
}
-CRect::CRect(const CPos& upperleft, const CPos& bottomright) :
- left(upperleft.x), top(upperleft.y), right(bottomright.x), bottom(bottomright.y)
+CRect::CRect(const CVector2D& upperleft, const CVector2D& bottomright) :
+ left(upperleft.X), top(upperleft.Y), right(bottomright.X), bottom(bottomright.Y)
{
}
-CRect::CRect(const CPos& pos, const CSize2D& size) :
- left(pos.x), top(pos.y), right(pos.x + size.Width), bottom(pos.y + size.Height)
+CRect::CRect(const CVector2D& pos, const CSize2D& size) :
+ left(pos.X), top(pos.Y), right(pos.X + size.Width), bottom(pos.Y + size.Height)
{
}
CRect::CRect(const float l, const float t, const float r, const float b) :
left(l), top(t), right(r), bottom(b)
{
}
CRect& CRect::operator=(const CRect& a)
{
left = a.left;
top = a.top;
right = a.right;
bottom = a.bottom;
return *this;
}
bool CRect::operator==(const CRect &a) const
{
return (left == a.left &&
top == a.top &&
right == a.right &&
bottom == a.bottom);
}
bool CRect::operator!=(const CRect& a) const
{
return !(*this == a);
}
CRect CRect::operator-() const
{
return CRect(-left, -top, -right, -bottom);
}
CRect CRect::operator+() const
{
return *this;
}
CRect CRect::operator+(const CRect& a) const
{
return CRect(left + a.left, top + a.top, right + a.right, bottom + a.bottom);
}
-CRect CRect::operator+(const CPos& a) const
+CRect CRect::operator+(const CVector2D& a) const
{
- return CRect(left + a.x, top + a.y, right + a.x, bottom + a.y);
+ return CRect(left + a.X, top + a.Y, right + a.X, bottom + a.Y);
}
CRect CRect::operator+(const CSize2D& a) const
{
return CRect(left + a.Width, top + a.Height, right + a.Width, bottom + a.Height);
}
CRect CRect::operator-(const CRect& a) const
{
return CRect(left - a.left, top - a.top, right - a.right, bottom - a.bottom);
}
-CRect CRect::operator-(const CPos& a) const
+CRect CRect::operator-(const CVector2D& a) const
{
- return CRect(left - a.x, top - a.y, right - a.x, bottom - a.y);
+ return CRect(left - a.X, top - a.Y, right - a.X, bottom - a.Y);
}
CRect CRect::operator-(const CSize2D& a) const
{
return CRect(left - a.Width, top - a.Height, right - a.Width, bottom - a.Height);
}
void CRect::operator+=(const CRect& a)
{
left += a.left;
top += a.top;
right += a.right;
bottom += a.bottom;
}
-void CRect::operator+=(const CPos& a)
+void CRect::operator+=(const CVector2D& a)
{
- left += a.x;
- top += a.y;
- right += a.x;
- bottom += a.y;
+ left += a.X;
+ top += a.Y;
+ right += a.X;
+ bottom += a.Y;
}
void CRect::operator+=(const CSize2D& a)
{
left += a.Width;
top += a.Height;
right += a.Width;
bottom += a.Height;
}
void CRect::operator-=(const CRect& a)
{
left -= a.left;
top -= a.top;
right -= a.right;
bottom -= a.bottom;
}
-void CRect::operator-=(const CPos& a)
+void CRect::operator-=(const CVector2D& a)
{
- left -= a.x;
- top -= a.y;
- right -= a.x;
- bottom -= a.y;
+ left -= a.X;
+ top -= a.Y;
+ right -= a.X;
+ bottom -= a.Y;
}
void CRect::operator-=(const CSize2D& a)
{
left -= a.Width;
top -= a.Height;
right -= a.Width;
bottom -= a.Height;
}
float CRect::GetWidth() const
{
return right-left;
}
float CRect::GetHeight() const
{
return bottom-top;
}
CSize2D CRect::GetSize() const
{
return CSize2D(right - left, bottom - top);
}
-CPos CRect::TopLeft() const
+CVector2D CRect::TopLeft() const
{
- return CPos(left, top);
+ return CVector2D(left, top);
}
-CPos CRect::TopRight() const
+CVector2D CRect::TopRight() const
{
- return CPos(right, top);
+ return CVector2D(right, top);
}
-CPos CRect::BottomLeft() const
+CVector2D CRect::BottomLeft() const
{
- return CPos(left, bottom);
+ return CVector2D(left, bottom);
}
-CPos CRect::BottomRight() const
+CVector2D CRect::BottomRight() const
{
- return CPos(right, bottom);
+ return CVector2D(right, bottom);
}
-CPos CRect::CenterPoint() const
+CVector2D CRect::CenterPoint() const
{
- return CPos((left + right) / 2.f, (top + bottom) / 2.f);
+ return CVector2D((left + right) / 2.f, (top + bottom) / 2.f);
}
-bool CRect::PointInside(const CPos &point) const
+bool CRect::PointInside(const CVector2D& point) const
{
- return (point.x >= left &&
- point.x <= right &&
- point.y >= top &&
- point.y <= bottom);
+ return (point.X >= left &&
+ point.X <= right &&
+ point.Y >= top &&
+ point.Y <= bottom);
}
CRect CRect::Scale(float x, float y) const
{
return CRect(left * x, top * y, right * x, bottom * y);
}
-
-/*************************************************************************/
-
-CPos::CPos() : x(0.f), y(0.f)
-{
-}
-
-CPos::CPos(const CPos& pos) : x(pos.x), y(pos.y)
-{
-}
-
-CPos::CPos(const CSize2D& s) : x(s.Width), y(s.Height)
-{
-}
-
-CPos::CPos(const float px, const float py) : x(px), y(py)
-{
-}
-
-CPos& CPos::operator=(const CPos& a)
-{
- x = a.x;
- y = a.y;
- return *this;
-}
-
-bool CPos::operator==(const CPos &a) const
-{
- return x == a.x && y == a.y;
-}
-
-bool CPos::operator!=(const CPos& a) const
-{
- return !(*this == a);
-}
-
-CPos CPos::operator-() const
-{
- return CPos(-x, -y);
-}
-
-CPos CPos::operator+() const
-{
- return *this;
-}
-
-CPos CPos::operator+(const CPos& a) const
-{
- return CPos(x + a.x, y + a.y);
-}
-
-CPos CPos::operator+(const CSize2D& a) const
-{
- return CPos(x + a.Width, y + a.Height);
-}
-
-CPos CPos::operator-(const CPos& a) const
-{
- return CPos(x - a.x, y - a.y);
-}
-
-CPos CPos::operator-(const CSize2D& a) const
-{
- return CPos(x - a.Width, y - a.Height);
-}
-
-void CPos::operator+=(const CPos& a)
-{
- x += a.x;
- y += a.y;
-}
-
-void CPos::operator+=(const CSize2D& a)
-{
- x += a.Width;
- y += a.Height;
-}
-
-void CPos::operator-=(const CPos& a)
-{
- x -= a.x;
- y -= a.y;
-}
-
-void CPos::operator-=(const CSize2D& a)
-{
- x -= a.Width;
- y -= a.Height;
-}
Index: ps/trunk/source/gui/CGUI.cpp
===================================================================
--- ps/trunk/source/gui/CGUI.cpp (revision 25151)
+++ ps/trunk/source/gui/CGUI.cpp (revision 25152)
@@ -1,1285 +1,1285 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "CGUI.h"
#include "gui/IGUIScrollBar.h"
#include "gui/ObjectTypes/CGUIDummyObject.h"
#include "gui/ObjectTypes/CTooltip.h"
#include "gui/Scripting/ScriptFunctions.h"
#include "gui/Scripting/JSInterface_GUIProxy.h"
#include "i18n/L10n.h"
#include "lib/bits.h"
#include "lib/input.h"
#include "lib/sysdep/sysdep.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/GameSetup/Config.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/Renderer.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptInterface.h"
#include
#include
#include
extern int g_yres;
const double SELECT_DBLCLICK_RATE = 0.5;
const u32 MAX_OBJECT_DEPTH = 100; // Max number of nesting for GUI includes. Used to detect recursive inclusion
const CStr CGUI::EventNameLoad = "Load";
const CStr CGUI::EventNameTick = "Tick";
const CStr CGUI::EventNamePress = "Press";
const CStr CGUI::EventNameKeyDown = "KeyDown";
const CStr CGUI::EventNameRelease = "Release";
const CStr CGUI::EventNameMouseRightPress = "MouseRightPress";
const CStr CGUI::EventNameMouseLeftPress = "MouseLeftPress";
const CStr CGUI::EventNameMouseWheelDown = "MouseWheelDown";
const CStr CGUI::EventNameMouseWheelUp = "MouseWheelUp";
const CStr CGUI::EventNameMouseLeftDoubleClick = "MouseLeftDoubleClick";
const CStr CGUI::EventNameMouseLeftRelease = "MouseLeftRelease";
const CStr CGUI::EventNameMouseRightDoubleClick = "MouseRightDoubleClick";
const CStr CGUI::EventNameMouseRightRelease = "MouseRightRelease";
CGUI::CGUI(const shared_ptr& context)
: m_BaseObject(std::make_unique(*this)),
m_FocusedObject(nullptr),
m_InternalNameNumber(0),
m_MouseButtons(0)
{
m_ScriptInterface.reset(new ScriptInterface("Engine", "GUIPage", context));
m_ScriptInterface->SetCallbackData(this);
GuiScriptingInit(*m_ScriptInterface);
m_ScriptInterface->LoadGlobalScripts();
}
CGUI::~CGUI()
{
for (const std::pair& p : m_pAllObjects)
delete p.second;
for (const std::pair& p : m_Sprites)
delete p.second;
}
InReaction CGUI::HandleEvent(const SDL_Event_* ev)
{
InReaction ret = IN_PASS;
if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYPRESS || ev->ev.type == SDL_HOTKEYUP)
{
const char* hotkey = static_cast(ev->ev.user.data1);
const CStr& eventName = ev->ev.type == SDL_HOTKEYPRESS ? EventNamePress : ev->ev.type == SDL_HOTKEYDOWN ? EventNameKeyDown : EventNameRelease;
if (m_GlobalHotkeys.find(hotkey) != m_GlobalHotkeys.end() && m_GlobalHotkeys[hotkey].find(eventName) != m_GlobalHotkeys[hotkey].end())
{
ret = IN_HANDLED;
ScriptRequest rq(m_ScriptInterface);
JS::RootedObject globalObj(rq.cx, rq.glob);
JS::RootedValue result(rq.cx);
if (!JS_CallFunctionValue(rq.cx, globalObj, m_GlobalHotkeys[hotkey][eventName], JS::HandleValueArray::empty(), &result))
ScriptException::CatchPending(rq);
}
std::map >::iterator it = m_HotkeyObjects.find(hotkey);
if (it != m_HotkeyObjects.end())
for (IGUIObject* const& obj : it->second)
{
if (ev->ev.type == SDL_HOTKEYPRESS)
ret = obj->SendEvent(GUIM_PRESSED, EventNamePress);
else if (ev->ev.type == SDL_HOTKEYDOWN)
ret = obj->SendEvent(GUIM_KEYDOWN, EventNameKeyDown);
else
ret = obj->SendEvent(GUIM_RELEASED, EventNameRelease);
}
}
else if (ev->ev.type == SDL_MOUSEMOTION)
{
// Yes the mouse position is stored as float to avoid
// constant conversions when operating in a
// float-based environment.
- m_MousePos = CPos((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale);
+ m_MousePos = CVector2D((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale);
SGUIMessage msg(GUIM_MOUSE_MOTION);
m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::HandleMessage, msg);
}
// Update m_MouseButtons. (BUTTONUP is handled later.)
else if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
case SDL_BUTTON_RIGHT:
case SDL_BUTTON_MIDDLE:
m_MouseButtons |= Bit(ev->ev.button.button);
break;
default:
break;
}
}
// Update m_MousePos (for delayed mouse button events)
- CPos oldMousePos = m_MousePos;
+ CVector2D oldMousePos = m_MousePos;
if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
{
- m_MousePos = CPos((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale);
+ m_MousePos = CVector2D((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale);
}
// Allow the focused object to pre-empt regular GUI events.
if (GetFocusedObject())
ret = GetFocusedObject()->PreemptEvent(ev);
// Only one object can be hovered
// pNearest will after this point at the hovered object, possibly nullptr
IGUIObject* pNearest = FindObjectUnderMouse();
if (ret == IN_PASS)
{
// Now we'll call UpdateMouseOver on *all* objects,
// we'll input the one hovered, and they will each
// update their own data and send messages accordingly
m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest));
if (ev->ev.type == SDL_MOUSEBUTTONDOWN)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
// Focus the clicked object (or focus none if nothing clicked on)
SetFocusedObject(pNearest);
if (pNearest)
ret = pNearest->SendMouseEvent(GUIM_MOUSE_PRESS_LEFT, EventNameMouseLeftPress);
break;
case SDL_BUTTON_RIGHT:
if (pNearest)
ret = pNearest->SendMouseEvent(GUIM_MOUSE_PRESS_RIGHT, EventNameMouseRightPress);
break;
default:
break;
}
}
else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest)
{
if (ev->ev.wheel.y < 0)
ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_DOWN, EventNameMouseWheelDown);
else if (ev->ev.wheel.y > 0)
ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_UP, EventNameMouseWheelUp);
}
else if (ev->ev.type == SDL_MOUSEBUTTONUP)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
if (pNearest)
{
double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT];
pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time();
if (timeElapsed < SELECT_DBLCLICK_RATE)
ret = pNearest->SendMouseEvent(GUIM_MOUSE_DBLCLICK_LEFT, EventNameMouseLeftDoubleClick);
else
ret = pNearest->SendMouseEvent(GUIM_MOUSE_RELEASE_LEFT, EventNameMouseLeftRelease);
}
break;
case SDL_BUTTON_RIGHT:
if (pNearest)
{
double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT];
pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time();
if (timeElapsed < SELECT_DBLCLICK_RATE)
ret = pNearest->SendMouseEvent(GUIM_MOUSE_DBLCLICK_RIGHT, EventNameMouseRightDoubleClick);
else
ret = pNearest->SendMouseEvent(GUIM_MOUSE_RELEASE_RIGHT, EventNameMouseRightRelease);
}
break;
}
// Reset all states on all visible objects
m_BaseObject->RecurseObject(&IGUIObject::IsHidden, &IGUIObject::ResetStates);
// Since the hover state will have been reset, we reload it.
m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest));
}
}
// BUTTONUP's effect on m_MouseButtons is handled after
// everything else, so that e.g. 'press' handlers (activated
// on button up) see which mouse button had been pressed.
if (ev->ev.type == SDL_MOUSEBUTTONUP)
{
switch (ev->ev.button.button)
{
case SDL_BUTTON_LEFT:
case SDL_BUTTON_RIGHT:
case SDL_BUTTON_MIDDLE:
m_MouseButtons &= ~Bit(ev->ev.button.button);
break;
default:
break;
}
}
// Restore m_MousePos (for delayed mouse button events)
if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP)
m_MousePos = oldMousePos;
// Let GUI items handle keys after everything else, e.g. for input boxes.
if (ret == IN_PASS && GetFocusedObject())
{
if (ev->ev.type == SDL_KEYUP || ev->ev.type == SDL_KEYDOWN ||
ev->ev.type == SDL_HOTKEYUP || ev->ev.type == SDL_HOTKEYDOWN ||
ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING)
ret = GetFocusedObject()->ManuallyHandleKeys(ev);
// else will return IN_PASS because we never used the button.
}
return ret;
}
void CGUI::TickObjects()
{
m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::Tick);
SendEventToAll(EventNameTick);
m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, *this);
}
void CGUI::SendEventToAll(const CStr& eventName)
{
std::unordered_map>::iterator it = m_EventObjects.find(eventName);
if (it == m_EventObjects.end())
return;
std::vector copy = it->second;
for (IGUIObject* object : copy)
object->ScriptEvent(eventName);
}
void CGUI::SendEventToAll(const CStr& eventName, const JS::HandleValueArray& paramData)
{
std::unordered_map>::iterator it = m_EventObjects.find(eventName);
if (it == m_EventObjects.end())
return;
std::vector copy = it->second;
for (IGUIObject* object : copy)
object->ScriptEvent(eventName, paramData);
}
void CGUI::Draw()
{
// Clear the depth buffer, so the GUI is
// drawn on top of everything else
glClear(GL_DEPTH_BUFFER_BIT);
m_BaseObject->RecurseObject(&IGUIObject::IsHidden, &IGUIObject::Draw);
}
void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping))
{
// If the sprite doesn't exist (name == ""), don't bother drawing anything
if (!Sprite)
return;
// TODO: Clipping?
Sprite.Draw(*this, Rect, m_Sprites, Z);
}
void CGUI::UpdateResolution()
{
m_BaseObject->RecurseObject(nullptr, &IGUIObject::UpdateCachedSize);
}
IGUIObject* CGUI::ConstructObject(const CStr& str)
{
std::map::iterator it = m_ObjectTypes.find(str);
if (it == m_ObjectTypes.end())
return nullptr;
return (*it->second)(*this);
}
bool CGUI::AddObject(IGUIObject& parent, IGUIObject& child)
{
if (child.m_Name.empty())
{
LOGERROR("Can't register an object without name!");
return false;
}
if (m_pAllObjects.find(child.m_Name) != m_pAllObjects.end())
{
LOGERROR("Can't register more than one object of the name %s", child.m_Name.c_str());
return false;
}
m_pAllObjects[child.m_Name] = &child;
parent.AddChild(child);
return true;
}
IGUIObject* CGUI::GetBaseObject()
{
return m_BaseObject.get();
};
bool CGUI::ObjectExists(const CStr& Name) const
{
return m_pAllObjects.find(Name) != m_pAllObjects.end();
}
IGUIObject* CGUI::FindObjectByName(const CStr& Name) const
{
map_pObjects::const_iterator it = m_pAllObjects.find(Name);
if (it == m_pAllObjects.end())
return nullptr;
return it->second;
}
IGUIObject* CGUI::FindObjectUnderMouse()
{
IGUIObject* pNearest = nullptr;
m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::ChooseMouseOverAndClosest, pNearest);
return pNearest;
}
void CGUI::SetFocusedObject(IGUIObject* pObject)
{
if (pObject == m_FocusedObject)
return;
if (m_FocusedObject)
{
SGUIMessage msg(GUIM_LOST_FOCUS);
m_FocusedObject->HandleMessage(msg);
}
m_FocusedObject = pObject;
if (m_FocusedObject)
{
SGUIMessage msg(GUIM_GOT_FOCUS);
m_FocusedObject->HandleMessage(msg);
}
}
void CGUI::SetObjectStyle(IGUIObject* pObject, const CStr& styleName)
{
// If the style is not recognised (or an empty string) then ApplyStyle will
// emit an error message. Thus we don't need to handle it here.
if (pObject->ApplyStyle(styleName))
pObject->m_Style = styleName;
}
void CGUI::UnsetObjectStyle(IGUIObject* pObject)
{
SetObjectStyle(pObject, "default");
}
void CGUI::SetObjectHotkey(IGUIObject* pObject, const CStr& hotkeyTag)
{
if (!hotkeyTag.empty())
m_HotkeyObjects[hotkeyTag].push_back(pObject);
}
void CGUI::UnsetObjectHotkey(IGUIObject* pObject, const CStr& hotkeyTag)
{
if (hotkeyTag.empty())
return;
std::vector& assignment = m_HotkeyObjects[hotkeyTag];
assignment.erase(
std::remove_if(
assignment.begin(),
assignment.end(),
[&pObject](const IGUIObject* hotkeyObject)
{ return pObject == hotkeyObject; }),
assignment.end());
}
void CGUI::SetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName, JS::HandleValue function)
{
ScriptRequest rq(*m_ScriptInterface);
if (hotkeyTag.empty())
{
ScriptException::Raise(rq, "Cannot assign a function to an empty hotkey identifier!");
return;
}
// Only support "Press", "Keydown" and "Release" events.
if (eventName != EventNamePress && eventName != EventNameKeyDown && eventName != EventNameRelease)
{
ScriptException::Raise(rq, "Cannot assign a function to an unsupported event!");
return;
}
if (!function.isObject() || !JS_ObjectIsFunction(&function.toObject()))
{
ScriptException::Raise(rq, "Cannot assign non-function value to global hotkey '%s'", hotkeyTag.c_str());
return;
}
UnsetGlobalHotkey(hotkeyTag, eventName);
m_GlobalHotkeys[hotkeyTag][eventName].init(rq.cx, function);
}
void CGUI::UnsetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName)
{
std::map>::iterator it = m_GlobalHotkeys.find(hotkeyTag);
if (it == m_GlobalHotkeys.end())
return;
m_GlobalHotkeys[hotkeyTag].erase(eventName);
if (m_GlobalHotkeys.count(hotkeyTag) == 0)
m_GlobalHotkeys.erase(it);
}
const SGUIScrollBarStyle* CGUI::GetScrollBarStyle(const CStr& style) const
{
std::map::const_iterator it = m_ScrollBarStyles.find(style);
if (it == m_ScrollBarStyles.end())
return nullptr;
return &it->second;
}
/**
* @callgraph
*/
void CGUI::LoadXmlFile(const VfsPath& Filename, std::unordered_set& Paths)
{
Paths.insert(Filename);
CXeromyces XeroFile;
if (XeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK)
return;
XMBElement node = XeroFile.GetRoot();
CStr root_name(XeroFile.GetElementString(node.GetNodeName()));
if (root_name == "objects")
Xeromyces_ReadRootObjects(node, &XeroFile, Paths);
else if (root_name == "sprites")
Xeromyces_ReadRootSprites(node, &XeroFile);
else if (root_name == "styles")
Xeromyces_ReadRootStyles(node, &XeroFile);
else if (root_name == "setup")
Xeromyces_ReadRootSetup(node, &XeroFile);
else
LOGERROR("CGUI::LoadXmlFile encountered an unknown XML root node type: %s", root_name.c_str());
}
void CGUI::LoadedXmlFiles()
{
m_BaseObject->RecurseObject(nullptr, &IGUIObject::UpdateCachedSize);
SGUIMessage msg(GUIM_LOAD);
m_BaseObject->RecurseObject(nullptr, &IGUIObject::HandleMessage, msg);
SendEventToAll(EventNameLoad);
}
//===================================================================
// XML Reading Xeromyces Specific Sub-Routines
//===================================================================
void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, std::unordered_set& Paths)
{
int el_script = pFile->GetElementID("script");
std::vector > subst;
// Iterate main children
// they should all be