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 or