Index: binaries/data/mods/mod/gui/modio/modio.js =================================================================== --- binaries/data/mods/mod/gui/modio/modio.js +++ binaries/data/mods/mod/gui/modio/modio.js @@ -158,7 +158,9 @@ function displayMods() { let modsAvailableList = Engine.GetGUIObjectByName("modsAvailableList"); - let selectedMod = modsAvailableList.list[modsAvailableList.selected]; + let index = modsAvailableList.multiSelected.length ? modsAvailableList.multiSelected[0] : -1; + let selectedMod = modsAvailableList.list[index]; + modsAvailableList.multiSelected = []; modsAvailableList.selected = -1; let displayedMods = clone(g_ModsAvailableOnline); @@ -187,6 +189,7 @@ function clearModList() { let modsAvailableList = Engine.GetGUIObjectByName("modsAvailableList"); + modsAvailableList.multiSelected = []; modsAvailableList.selected = -1; for (let listIdx of Object.keys(modsAvailableList).filter(key => key.startsWith("list"))) modsAvailableList[listIdx] = []; @@ -196,10 +199,11 @@ { let modsAvailableList = Engine.GetGUIObjectByName("modsAvailableList"); - if (modsAvailableList.selected == -1) + if (!modsAvailableList.multiSelected.length) return undefined; - return +modsAvailableList.list[modsAvailableList.selected]; + let index = modsAvailableList.multiSelected.length ? modsAvailableList.multiSelected[0] : -1; + return +modsAvailableList.list[index]; } function showModDescription() Index: binaries/data/mods/mod/gui/modio/modio.xml =================================================================== --- binaries/data/mods/mod/gui/modio/modio.xml +++ binaries/data/mods/mod/gui/modio/modio.xml @@ -41,6 +41,7 @@ sortable="true" selected_column="name" selected_column_order="1" + multiSelection_enabled="false" font="sans-stroke-13" > showModDescription(); Index: binaries/data/mods/mod/gui/modmod/modmod.xml =================================================================== --- binaries/data/mods/mod/gui/modmod/modmod.xml +++ binaries/data/mods/mod/gui/modmod/modmod.xml @@ -57,7 +57,7 @@ { reallyDeleteGame(toDelete); }]); } function reallyDeleteGame(gameID) { - if (!Engine.DeleteSavedGame(gameID)) - error("Could not delete saved game: " + gameID); + for (let gameID of gameIDs) + if (!Engine.DeleteSavedGame(gameID)) + error("Could not delete saved game: " + gameID); updateSavegameList(); } Index: binaries/data/mods/public/gui/loadgame/load.js =================================================================== --- binaries/data/mods/public/gui/loadgame/load.js +++ binaries/data/mods/public/gui/loadgame/load.js @@ -21,10 +21,12 @@ updateSavegameList(); - // Select the most recent savegame to be loaded, or no savegame to be overwritten let gameSelection = Engine.GetGUIObjectByName("gameSelection"); + gameSelection.selected = []; + + // Select the most recent savegame to be loaded, or no savegame to be overwritten if (!save && gameSelection.list.length) - gameSelection.selected = 0; + gameSelection.selected = [0]; else selectionChanged(); } @@ -47,7 +49,7 @@ Engine.GetGUIObjectByName("gameSelectionFeedback").hidden = !!savedGames.length; - let selectedGameId = gameSelection.list_data[gameSelection.selected]; + let selectedGameId = gameSelection.list_data[gameSelection.selected.length ? gameSelection.selected[0] : -1]; // Save metadata for the detailed view g_SavedGamesMetadata = savedGames.map(game => { @@ -112,19 +114,25 @@ let selectedGameIndex = g_SavedGamesMetadata.findIndex(metadata => metadata.id == selectedGameId); if (selectedGameIndex != -1) - gameSelection.selected = selectedGameIndex; - else if (gameSelection.selected >= g_SavedGamesMetadata.length) // happens when deleting the last saved game - gameSelection.selected = g_SavedGamesMetadata.length - 1; + gameSelection.selected = [selectedGameIndex]; + else if (gameSelection.selected.length && gameSelection.selected[0] >= g_SavedGamesMetadata.length) // happens when deleting the last saved game + gameSelection.selected = [g_SavedGamesMetadata.length - 1]; Engine.GetGUIObjectByName("deleteGameButton").tooltip = deleteTooltip(); } function selectionChanged() { - let metadata = g_SavedGamesMetadata[Engine.GetGUIObjectByName("gameSelection").selected]; + let gameSelection = Engine.GetGUIObjectByName("gameSelection"); + let metadata = g_SavedGamesMetadata[gameSelection.selected.length ? gameSelection.selected[0] : -1]; + Engine.GetGUIObjectByName("invalidGame").hidden = !!metadata; Engine.GetGUIObjectByName("validGame").hidden = !metadata; - Engine.GetGUIObjectByName("confirmButton").enabled = !!metadata || Engine.IsGameStarted(); + + Engine.GetGUIObjectByName("confirmButton").enabled = + (!!metadata || Engine.IsGameStarted()) && + gameSelection.selected.length == 1;; + Engine.GetGUIObjectByName("deleteGameButton").enabled = !!metadata; if (!metadata) @@ -153,8 +161,8 @@ function loadGame() { let gameSelection = Engine.GetGUIObjectByName("gameSelection"); - let gameId = gameSelection.list_data[gameSelection.selected]; - let metadata = g_SavedGamesMetadata[gameSelection.selected]; + let gameId = gameSelection.list_data[gameSelection.selected[0]]; + let metadata = g_SavedGamesMetadata[gameSelection.selected[0]]; // Check compatibility before really loading it let engineInfo = Engine.GetEngineInfo(); Index: binaries/data/mods/public/gui/loadgame/load.xml =================================================================== --- binaries/data/mods/public/gui/loadgame/load.xml +++ binaries/data/mods/public/gui/loadgame/load.xml @@ -19,7 +19,7 @@ selected_column_order="-1" sortable="true" size="24 4 100%-24 100%-70" - type="olist" + type="olistmultiple" auto_scroll="true" > Index: binaries/data/mods/public/gui/lobby/lobby_panels.xml =================================================================== --- binaries/data/mods/public/gui/lobby/lobby_panels.xml +++ binaries/data/mods/public/gui/lobby/lobby_panels.xml @@ -14,7 +14,7 @@ style="ModernSortedList" selected_column="name" selected_column_order="1" - type="olist" + type="olistsingle" sortable="true" size="0 0 100% 100%" font="sans-bold-stroke-13" @@ -177,7 +177,7 @@ style="ModernSortedList" selected_column="name" selected_column_order="1" - type="olist" + type="olistsingle" sortable="true" size="0 25 100% 48%" font="sans-stroke-13" @@ -280,7 +280,7 @@ Rank Index: binaries/data/mods/public/gui/replaymenu/replay_actions.js =================================================================== --- binaries/data/mods/public/gui/replaymenu/replay_actions.js +++ binaries/data/mods/public/gui/replaymenu/replay_actions.js @@ -39,11 +39,11 @@ */ function startReplay() { - var selected = Engine.GetGUIObjectByName("replaySelection").selected; - if (selected == -1) + let selected = Engine.GetGUIObjectByName("replaySelection").selected; + if (!selected.length) return; - var replay = g_ReplaysFiltered[selected]; + let replay = g_ReplaysFiltered[selected[0]]; if (isReplayCompatible(replay)) reallyStartVisualReplay(replay.directory); else @@ -105,12 +105,12 @@ */ function showReplaySummary() { - var selected = Engine.GetGUIObjectByName("replaySelection").selected; - if (selected == -1) + let selected = Engine.GetGUIObjectByName("replaySelection").selected; + if (!selected.length) return; // Load summary screen data from the selected replay directory - let simData = Engine.GetReplayMetadata(g_ReplaysFiltered[selected].directory); + let simData = Engine.GetReplayMetadata(g_ReplaysFiltered[selected[0]].directory); if (!simData) { @@ -123,7 +123,7 @@ "gui": { "dialog": false, "isReplay": true, - "replayDirectory": g_ReplaysFiltered[selected].directory, + "replayDirectory": g_ReplaysFiltered[selected[0]].directory, "replaySelectionData": createReplaySelectionData(g_ReplaysFiltered[selected].directory) }, "selectedData": g_SummarySelectedData @@ -133,7 +133,7 @@ function reloadCache() { let selected = Engine.GetGUIObjectByName("replaySelection").selected; - loadReplays(selected > -1 ? createReplaySelectionData(g_ReplaysFiltered[selected].directory) : "", true); + loadReplays(selected.length ? createReplaySelectionData(g_ReplaysFiltered[selected[0]].directory) : "", true); } /** @@ -155,19 +155,26 @@ function deleteReplay() { // Get selected replay - var selected = Engine.GetGUIObjectByName("replaySelection").selected; - if (selected == -1) + let replaySelection = Engine.GetGUIObjectByName("replaySelection"); + if (!replaySelection.selected.length) return; - - var replay = g_ReplaysFiltered[selected]; - + let directories = []; + let directoryNames = []; + for (let selected of replaySelection.selected) + { + directories.push(g_ReplaysFiltered[selected].directory); + directoryNames.push(Engine.GetReplayDirectoryName(g_ReplaysFiltered[selected].directory)); + } messageBox( 500, 200, - translate("Are you sure you want to delete this replay permanently?") + "\n" + - escapeText(Engine.GetReplayDirectoryName(replay.directory)), - translate("Delete replay"), + translatePlural("Are you sure you want to delete this replay permanently?", + "Are you sure you want to delete these replays permanently?", + directories.length + ) + "\n" + + escapeText(directoryNames.join("\n")), + translatePlural("Delete replay", "Delete replays", directories.length), [translate("No"), translate("Yes")], - [null, function() { reallyDeleteReplay(replay.directory); }] + [null, function() { reallyDeleteReplay(directories); }] ); } @@ -176,23 +183,27 @@ */ function deleteReplayWithoutConfirmation() { - var selected = Engine.GetGUIObjectByName("replaySelection").selected; - if (selected > -1) - reallyDeleteReplay(g_ReplaysFiltered[selected].directory); + let replaySelection = Engine.GetGUIObjectByName("replaySelection"); + if (!replaySelection.selected.length) + return; + let directories = []; + for (selected of replaySelection.selected) + directories.push(g_ReplaysFiltered[selected].directory); + reallyDeleteReplay(directories); } /** * Attempts to delete the given replay directory from the disk. * - * @param replayDirectory {string} + * @param replayDirectories {string[]} */ -function reallyDeleteReplay(replayDirectory) +function reallyDeleteReplay(replayDirectories) { - var replaySelection = Engine.GetGUIObjectByName("replaySelection"); - var selectedIndex = replaySelection.selected; + let replaySelection = Engine.GetGUIObjectByName("replaySelection"); - if (!Engine.DeleteReplay(replayDirectory)) - error("Could not delete replay!"); + for (let replayDirectory of replayDirectories) + if (!Engine.DeleteReplay(replayDirectory)) + error("Could not delete replay!"); // Refresh replay list init(); Index: binaries/data/mods/public/gui/replaymenu/replay_menu.js =================================================================== --- binaries/data/mods/public/gui/replaymenu/replay_menu.js +++ binaries/data/mods/public/gui/replaymenu/replay_menu.js @@ -77,6 +77,10 @@ if (data && data.summarySelectedData) g_SummarySelectedData = data.summarySelectedData; + + let replaySelection = Engine.GetGUIObjectByName("replaySelection"); + if(replaySelection) + replaySelection.selected = []; } /** @@ -196,6 +200,20 @@ Engine.GetGUIObjectByName("deleteReplayButton").tooltip = deleteTooltip(); } +function deleteTooltip() +{ + let tooltip = colorizeHotkey( + translate("Delete the selected replay using %(hotkey)s."), + "session.savedgames.delete"); + + if (tooltip) + tooltip += colorizeHotkey( + "\n" + translate("Hold %(hotkey)s to delete without confirmation."), + "session.savedgames.noconfirmation"); + + return tooltip; +} + /** * Filter g_Replays, fill the GUI list with that data and show the description of the current replay. */ @@ -206,8 +224,8 @@ // Remember previously selected replay var replaySelection = Engine.GetGUIObjectByName("replaySelection"); - if (replaySelection.selected != -1) - g_SelectedReplayDirectory = g_ReplaysFiltered[replaySelection.selected].directory; + if (replaySelection.selected.length) + g_SelectedReplayDirectory = g_ReplaysFiltered[replaySelection.selected[0]].directory; filterReplays(); @@ -228,7 +246,7 @@ list = prepareForDropdown(list); // Push to GUI - replaySelection.selected = -1; + replaySelection.selected = []; replaySelection.list_months = list.months || []; replaySelection.list_players = list.playerNames || []; replaySelection.list_mapName = list.mapNames || []; @@ -240,7 +258,7 @@ replaySelection.list = list.directories || []; replaySelection.list_data = list.directories || []; - replaySelection.selected = replaySelection.list.findIndex(directory => directory == g_SelectedReplayDirectory); + replaySelection.selected = [replaySelection.list.findIndex(directory => directory == g_SelectedReplayDirectory)]; displayReplayDetails(); } @@ -250,21 +268,24 @@ */ function displayReplayDetails() { - let selected = Engine.GetGUIObjectByName("replaySelection").selected; - let replaySelected = selected > -1; + let countOfReplays = Engine.GetGUIObjectByName("replaySelection").selected.length; + let singleReplaySelected = countOfReplays == 1; - Engine.GetGUIObjectByName("replayInfo").hidden = !replaySelected; - Engine.GetGUIObjectByName("replayInfoEmpty").hidden = replaySelected; - Engine.GetGUIObjectByName("startReplayButton").enabled = replaySelected; - Engine.GetGUIObjectByName("deleteReplayButton").enabled = replaySelected; - Engine.GetGUIObjectByName("replayFilename").hidden = !replaySelected; + Engine.GetGUIObjectByName("replayInfo").hidden = !singleReplaySelected; + Engine.GetGUIObjectByName("replayInfoEmpty").hidden = singleReplaySelected; + Engine.GetGUIObjectByName("startReplayButton").enabled = singleReplaySelected; + Engine.GetGUIObjectByName("deleteReplayButton").enabled = countOfReplays > 0; + Engine.GetGUIObjectByName("replayFilename").hidden = !singleReplaySelected; Engine.GetGUIObjectByName("summaryButton").hidden = true; - if (!replaySelected) + if (!singleReplaySelected) return; - + let selected = Engine.GetGUIObjectByName("replaySelection").selected[0]; let replay = g_ReplaysFiltered[selected]; + if (!replay) + return; + Engine.GetGUIObjectByName("sgMapName").caption = translate(replay.attribs.settings.Name); Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(replay.attribs.settings.Size); Engine.GetGUIObjectByName("sgMapType").caption = translateMapType(replay.attribs.settings.mapType); Index: binaries/data/mods/public/gui/replaymenu/replay_menu.xml =================================================================== --- binaries/data/mods/public/gui/replaymenu/replay_menu.xml +++ binaries/data/mods/public/gui/replaymenu/replay_menu.xml @@ -51,7 +51,7 @@ ("button_width"); Index: source/gui/CGUI.cpp =================================================================== --- source/gui/CGUI.cpp +++ source/gui/CGUI.cpp @@ -29,8 +29,10 @@ #include "CDropDown.h" #include "CImage.h" #include "CInput.h" -#include "CList.h" -#include "COList.h" +#include "CListSingle.h" +#include "CListMultiple.h" +#include "COListSingle.h" +#include "COListMultiple.h" #include "CProgressBar.h" #include "CRadioButton.h" #include "CSlider.h" @@ -322,8 +324,10 @@ AddObjectType("progressbar", &CProgressBar::ConstructObject); AddObjectType("minimap", &CMiniMap::ConstructObject); AddObjectType("input", &CInput::ConstructObject); - AddObjectType("list", &CList::ConstructObject); - AddObjectType("olist", &COList::ConstructObject); + AddObjectType("listsingle", &CListSingle::ConstructObject); + AddObjectType("listmultiple", &CListMultiple::ConstructObject); + AddObjectType("olistsingle", &COListSingle::ConstructObject); + AddObjectType("olistmultiple", &COListMultiple::ConstructObject); AddObjectType("dropdown", &CDropDown::ConstructObject); AddObjectType("tooltip", &CTooltip::ConstructObject); AddObjectType("chart", &CChart::ConstructObject); Index: source/gui/CIntList.h =================================================================== --- /dev/null +++ source/gui/CIntList.h @@ -0,0 +1,32 @@ +/* Copyright (C) 2019 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_CINTLIST +#define INCLUDED_CINTLIST + +#include + +class CIntList +{ +public: + /** + * List of items (as int) + */ + std::vector m_Items; +}; + +#endif Index: source/gui/CList.h =================================================================== --- source/gui/CList.h +++ source/gui/CList.h @@ -18,10 +18,10 @@ #ifndef INCLUDED_CLIST #define INCLUDED_CLIST -#include "IGUIScrollBar.h" +#include "gui/IGUIScrollBar.h" /** - * Create a list of elements, where one can be selected + * Create a list of elements, where multiple can be selected * by the user. The control will use a pre-processed * text-object for each element, which will be managed * by the IGUITextOwner structure. @@ -31,8 +31,6 @@ */ class CList : public IGUIScrollBarOwner, public IGUITextOwner { - GUI_OBJECT(CList) - public: CList(CGUI& pGUI); virtual ~CList(); @@ -85,9 +83,20 @@ // Called every time the auto-scrolling should be checked. void UpdateAutoScroll(); + virtual bool IsEnabledDoubleClick() = 0; + + virtual int GetLastSelected() = 0; + + /** + * Draw selection on item + */ + virtual void DrawSelection(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect); + + virtual void DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) = 0; + // Extended drawing interface, this is so that classes built on the this one // can use other sprite names. - virtual void DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor); + virtual void DrawList(const int selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor); // Get the area of the list. This is so that it can easily be changed, like in CDropDown // where the area is not equal to m_CachedActualSize. @@ -106,7 +115,10 @@ std::vector m_ItemsYPositions; virtual int GetHoveredItem(); - + /** + * Control selection + */ + virtual void SelectionControl(int& select) = 0; private: // Whether the list's items have been modified since last handling a message. bool m_Modified; @@ -116,6 +128,9 @@ // Last time a click on an item was issued double m_LastItemClickTime; + + void UpdateSelection(int oldIndex, int newIndex); + }; #endif // INCLUDED_CLIST Index: source/gui/CList.cpp =================================================================== --- source/gui/CList.cpp +++ source/gui/CList.cpp @@ -24,6 +24,7 @@ #include "gui/CGUIScrollBarVertical.h" #include "lib/external_libraries/libsdl.h" #include "lib/timer.h" +#include "ps/Globals.h" CList::CList(CGUI& pGUI) : IGUIObject(pGUI), IGUITextOwner(pGUI), IGUIScrollBarOwner(pGUI), @@ -42,7 +43,6 @@ AddSetting("text_align"); AddSetting("textcolor"); AddSetting("textcolor_selected"); - AddSetting("selected"); // Index selected. -1 is none. AddSetting("auto_scroll"); AddSetting("hovered"); AddSetting("tooltip"); @@ -53,7 +53,6 @@ AddSetting("list_data"); SetSetting("scrollbar", false, true); - SetSetting("selected", -1, true); SetSetting("hovered", -1, true); SetSetting("auto_scroll", false, true); @@ -176,11 +175,15 @@ int hovered = GetHoveredItem(); if (hovered == -1) break; - SetSetting("selected", hovered, true); + + SelectionControl(hovered); + UpdateAutoScroll(); PlaySound("sound_selected"); - if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && hovered == m_PrevSelectedItem) + if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && + hovered == m_PrevSelectedItem && + IsEnabledDoubleClick()) this->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT_ITEM, "mouseleftdoubleclickitem"); else this->SendEvent(GUIM_MOUSE_PRESS_LEFT_ITEM, "mouseleftclickitem"); @@ -278,10 +281,42 @@ void CList::Draw() { - DrawList(GetSetting("selected"), "sprite", "sprite_selectarea", "textcolor"); + DrawList(GetLastSelected(), "sprite", "sprite_selectarea", "textcolor"); } -void CList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) +void CList::DrawSelection(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) +{ + if (selected < 0 || selected + 1 >= static_cast(m_ItemsYPositions.size())) + return; + + // 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) + return; + + if (rect_sel.bottom > rect.bottom) + rect_sel.bottom = rect.bottom; + if (rect_sel.top < rect.top) + rect_sel.top = rect.top; + + if (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; + } + + m_pGUI.DrawSprite(sprite_selectarea, cell_id, bz + 0.05f, rect_sel); +} + +void CList::DrawList(const int selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) { float bz = GetBufferedZ(); @@ -304,35 +339,7 @@ if (scrollbar) scroll = GetScrollBar(0).GetPos(); - if (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 (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; - } - - m_pGUI.DrawSprite(sprite_selectarea, cell_id, bz+0.05f, rect_sel); - } - } + DrawSelectedItems(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); const CGUIList& pList = GetSetting("list"); const CGUIColor& color = GetSetting(_textcolor); @@ -393,50 +400,9 @@ return false; } -void CList::SelectNextElement() -{ - int selected = GetSetting("selected"); - - const CGUIList& pList = GetSetting("list"); - - if (selected != static_cast(pList.m_Items.size()) - 1) - { - ++selected; - SetSetting("selected", selected, true); - PlaySound("sound_selected"); - } -} - -void CList::SelectPrevElement() -{ - int selected = GetSetting("selected"); - - if (selected > 0) - { - --selected; - SetSetting("selected", selected, true); - PlaySound("sound_selected"); - } -} - -void CList::SelectFirstElement() -{ - if (GetSetting("selected") >= 0) - SetSetting("selected", 0, true); -} - -void CList::SelectLastElement() -{ - const CGUIList& pList = GetSetting("list"); - const int index = static_cast(pList.m_Items.size()) - 1; - - if (GetSetting("selected") != index) - SetSetting("selected", index, true); -} - void CList::UpdateAutoScroll() { - const int selected = GetSetting("selected"); + const int selected = GetLastSelected(); const bool scrollbar = GetSetting("scrollbar"); // No scrollbar, no scrolling (at least it's not made to work properly). @@ -482,3 +448,47 @@ return -1; } + +void CList::UpdateSelection(int oldIndex, int newIndex) +{ + m_PrevSelectedItem = oldIndex; + PlaySound("sound_selected"); + + SelectionControl(newIndex); +} + +void CList::SelectNextElement() +{ + int selected = GetLastSelected(); + + const CGUIList& pList = GetSetting("list"); + + if (selected != static_cast(pList.m_Items.size()) - 1) + UpdateSelection(selected, ++selected); +} + +void CList::SelectPrevElement() +{ + int selected = GetLastSelected(); + + if (selected > 0) + UpdateSelection(selected, --selected); +} + +void CList::SelectFirstElement() +{ + int selected = GetLastSelected(); + + if (selected >= 0) + UpdateSelection(selected, 0); +} + +void CList::SelectLastElement() +{ + const CGUIList& pList = GetSetting("list"); + const int index = static_cast(pList.m_Items.size()) - 1; + int selected = GetLastSelected(); + + if (selected != index) + UpdateSelection(selected, index); +} Index: source/gui/CListMultiple.h =================================================================== --- /dev/null +++ source/gui/CListMultiple.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2019 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_CLISTMULTIPLE +#define INCLUDED_CLISTMULTIPLE + +#include "gui/CIntList.h" +#include "gui/CList.h" + +class CListMultiple: public CList +{ + GUI_OBJECT(CListMultiple); + +public: + CListMultiple(CGUI& pGUI); + virtual ~CListMultiple(); + +protected: + virtual bool IsEnabledDoubleClick(); + + virtual void DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect); + + virtual int GetLastSelected(); + + std::vector m_SelectedItems; + + virtual void SelectionControl(int &select); +private: + + // Select items between last two clicks + bool m_IsMultiSelecting; + + // Add/Remove one item to/from selection + bool m_AddMultiSelection; + + // Used for multiselection + int m_MultiSelectionFromItem; + +}; + +#endif // INCLUDED_CLISTMULTIPLE Index: source/gui/CListMultiple.cpp =================================================================== --- /dev/null +++ source/gui/CListMultiple.cpp @@ -0,0 +1,125 @@ +/* Copyright (C) 2019 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 "CListMultiple.h" + +#include "gui/CGUI.h" +#include "gui/CGUIColor.h" +#include "gui/CGUIScrollBarVertical.h" +#include "lib/external_libraries/libsdl.h" +#include "lib/timer.h" +#include "ps/Globals.h" + +CListMultiple::CListMultiple(CGUI& pGUI) + : CList(pGUI), IGUIObject(pGUI), + m_AddMultiSelection(false), m_IsMultiSelecting(false) +{ + AddSetting("selected"); + SetSetting("selected", CIntList(), true); +} + +CListMultiple::~CListMultiple() +{ +} + +void CListMultiple::SelectionControl(int &select) +{ + m_IsMultiSelecting = g_keys[SDLK_LSHIFT] || g_keys[SDLK_RSHIFT]; + m_AddMultiSelection = g_keys[SDLK_LCTRL] || g_keys[SDLK_RCTRL]; + + // Add current item to selection + if (m_AddMultiSelection) + { + CIntList sel = GetSetting("selected"); + m_MultiSelectionFromItem = select; + m_SelectedItems = sel.m_Items; + bool add = true; + size_t pos = -1; + for (size_t it = 0; it < m_SelectedItems.size(); ++it) + if (m_SelectedItems.at(it) == select) + { + add = false; + pos = it; + break; + } + if (add) + m_SelectedItems.push_back(select); + else + { + m_SelectedItems.erase(m_SelectedItems.begin() + pos); + // Select last item in multiselection + if (m_SelectedItems.size() == 1) + select = m_SelectedItems.at(0); + } + + sel.m_Items = m_SelectedItems; + SetSetting("selected", sel, true); + } + // Select everything from last clicked item to current + else if (m_IsMultiSelecting) + { + if (m_MultiSelectionFromItem == select) + return; + + CIntList sel = GetSetting("selected"); + m_SelectedItems = sel.m_Items; + + m_SelectedItems.clear(); + for (size_t itemId = m_MultiSelectionFromItem; itemId < select + 1; ++itemId) + m_SelectedItems.push_back(itemId); + + sel.m_Items = m_SelectedItems; + SetSetting( "selected", sel, true); + } + // Clear multi selection + else + { + CIntList sel = GetSetting("selected"); + m_SelectedItems = sel.m_Items; + m_SelectedItems.clear(); + m_SelectedItems.push_back(select); + sel.m_Items = m_SelectedItems; + m_MultiSelectionFromItem = select; + SetSetting("selected", sel, true); + } +} + +bool CListMultiple::IsEnabledDoubleClick() +{ + return !m_IsMultiSelecting && !m_AddMultiSelection; +} + +void CListMultiple::DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) +{ + CIntList sel = GetSetting("selected"); + m_SelectedItems = sel.m_Items; + + // Draw multi selection + if (!m_SelectedItems.empty()) + for (size_t selection = 0; selection < m_SelectedItems.size(); ++selection) + DrawSelection(m_SelectedItems[selection], scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} + +int CListMultiple::GetLastSelected() +{ + CIntList sel = GetSetting("selected"); + if (sel.m_Items.size() == 0) + return -1; + return sel.m_Items.at(sel.m_Items.size()-1); +} Index: source/gui/CListSingle.h =================================================================== --- /dev/null +++ source/gui/CListSingle.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2019 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_CLISTSINGLE +#define INCLUDED_CLISTSINGLE + +#include "gui/CList.h" + +class CListSingle: public CList +{ + GUI_OBJECT(CListSingle); + +public: + CListSingle(CGUI& pGUI); + virtual ~CListSingle(); +protected: + virtual void DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect); + + virtual bool IsEnabledDoubleClick(); + + virtual int GetLastSelected(); + + virtual void SelectionControl(int &select); +}; + +#endif // INCLUDED_CLISTSINGLE Index: source/gui/CListSingle.cpp =================================================================== --- /dev/null +++ source/gui/CListSingle.cpp @@ -0,0 +1,59 @@ +/* Copyright (C) 2019 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 "CListSingle.h" + +#include "gui/CGUI.h" +#include "gui/CGUIColor.h" +#include "gui/CGUIScrollBarVertical.h" +#include "lib/external_libraries/libsdl.h" +#include "lib/timer.h" +#include "ps/Globals.h" + +CListSingle::CListSingle(CGUI& pGUI) + : CList(pGUI), IGUIObject(pGUI) +{ + AddSetting("selected"); // Index selected. -1 is none. + SetSetting("selected", -1, true); +} + +CListSingle::~CListSingle() +{ +} + +void CListSingle::SelectionControl(int &select) +{ + SetSetting("selected", select, true); +} + +bool CListSingle::IsEnabledDoubleClick() +{ + return true; +} + +void CListSingle::DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) +{ + if (selected != -1) + DrawSelection(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} + +int CListSingle::GetLastSelected() +{ + return GetSetting("selected"); +} Index: source/gui/COList.h =================================================================== --- source/gui/COList.h +++ source/gui/COList.h @@ -46,7 +46,6 @@ */ class COList : public CList { - GUI_OBJECT(COList) public: COList(CGUI& pGUI); @@ -60,15 +59,23 @@ */ virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); - void DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor); + void DrawList(const int selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor); virtual CRect GetListRect() const; + virtual bool IsEnabledDoubleClick() = 0; + + virtual int GetLastSelected() = 0; + + virtual void DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) = 0; + /** * Available columns. */ std::vector m_Columns; + virtual void SelectionControl(int& select) = 0; + private: // Width of space available for columns float m_TotalAvailableColumnWidth; Index: source/gui/COList.cpp =================================================================== --- source/gui/COList.cpp +++ source/gui/COList.cpp @@ -284,7 +284,7 @@ return false; } -void COList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) +void COList::DrawList(const int selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) { const float bz = GetBufferedZ(); const bool scrollbar = GetSetting("scrollbar"); @@ -305,39 +305,7 @@ if (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 (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_selectarea, cell_id, bz+0.05f, rect_sel); - } - } + DrawSelectedItems(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); // Draw line above column header CGUISpriteInstance& sprite_heading = GetSetting("sprite_heading"); Index: source/gui/COListMultiple.h =================================================================== --- /dev/null +++ source/gui/COListMultiple.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2019 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_COLISTMULTIPLE +#define INCLUDED_COLISTMULTIPLE + +#include "COList.h" +#include "CListMultiple.h" + +class COListMultiple : public CListMultiple, public COList +{ + GUI_OBJECT(COListMultiple); +public: + COListMultiple(CGUI& pGUI); +protected: + virtual void DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect); + + virtual bool IsEnabledDoubleClick(); + + virtual int GetLastSelected(); + + virtual void SelectionControl(int &select); +}; + +#endif // INCLUDED_COLISTMULTIPLE Index: source/gui/COListMultiple.cpp =================================================================== --- /dev/null +++ source/gui/COListMultiple.cpp @@ -0,0 +1,46 @@ +/* Copyright (C) 2019 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 "COListMultiple.h" + +COListMultiple::COListMultiple(CGUI& pGUI) + : COList(pGUI), CListMultiple(pGUI), IGUIObject(pGUI) +{ + +} + +void COListMultiple::SelectionControl(int &select) +{ + CListMultiple::SelectionControl(select); +} + +bool COListMultiple::IsEnabledDoubleClick() +{ + return CListMultiple::IsEnabledDoubleClick(); +} + +void COListMultiple::DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) +{ + CListMultiple::DrawSelectedItems(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} + +int COListMultiple::GetLastSelected() +{ + return CListMultiple::GetLastSelected(); +} Index: source/gui/COListSingle.h =================================================================== --- /dev/null +++ source/gui/COListSingle.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2019 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_COLISTSINGLE +#define INCLUDED_COLISTSINGLE + +#include "COList.h" +#include "CListSingle.h" + +class COListSingle : public CListSingle, public COList +{ + GUI_OBJECT(COListSingle); +public: + COListSingle(CGUI& pGUI); +protected: + virtual void DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect); + + virtual bool IsEnabledDoubleClick(); + + virtual int GetLastSelected(); + + virtual void SelectionControl(int &select); +}; + +#endif // INCLUDED_COLISTSINGLE Index: source/gui/COListSingle.cpp =================================================================== --- /dev/null +++ source/gui/COListSingle.cpp @@ -0,0 +1,46 @@ +/* Copyright (C) 2019 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 "COListSingle.h" + +COListSingle::COListSingle(CGUI& pGUI) + : COList(pGUI), CListSingle(pGUI), IGUIObject(pGUI) +{ + +} + +void COListSingle::SelectionControl(int &select) +{ + CListSingle::SelectionControl(select); +} + +bool COListSingle::IsEnabledDoubleClick() +{ + return CListSingle::IsEnabledDoubleClick(); +} + +void COListSingle::DrawSelectedItems(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) +{ + CListSingle::DrawSelectedItems(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} + +int COListSingle::GetLastSelected() +{ + return CListSingle::GetLastSelected(); +} Index: source/gui/GUI.h =================================================================== --- source/gui/GUI.h +++ source/gui/GUI.h @@ -28,6 +28,7 @@ #include "CGUIList.h" #include "CGUISeries.h" #include "CGUIText.h" +#include "CIntList.h" #include "GUIbase.h" #include "IGUIButtonBehavior.h" #include "IGUIObject.h" Index: source/gui/GUIStringConversions.cpp =================================================================== --- source/gui/GUIStringConversions.cpp +++ source/gui/GUIStringConversions.cpp @@ -19,6 +19,7 @@ #include "gui/CGUI.h" #include "gui/CGUIString.h" +#include "gui/CIntList.h" #include "ps/CLogger.h" class CGUIList; @@ -244,3 +245,9 @@ { return false; } + +template <> +bool CGUI::ParseString(const CGUI* UNUSED(pGUI), const CStrW& UNUSED(Value), CIntList& UNUSED(Output)) +{ + return false; +} Index: source/gui/GUItypes.h =================================================================== --- source/gui/GUItypes.h +++ source/gui/GUItypes.h @@ -33,6 +33,7 @@ TYPE(EAlign) TYPE(EVAlign) TYPE(CPos) +TYPE(CIntList) #endif #ifndef GUITYPE_IGNORE_NONCOPYABLE Index: source/gui/scripting/GuiScriptConversions.cpp =================================================================== --- source/gui/scripting/GuiScriptConversions.cpp +++ source/gui/scripting/GuiScriptConversions.cpp @@ -20,6 +20,7 @@ #include "gui/CGUIColor.h" #include "gui/CGUIList.h" #include "gui/CGUISeries.h" +#include "gui/CIntList.h" #include "gui/GUIbase.h" #include "gui/IGUIObject.h" #include "lib/external_libraries/libsdl.h" @@ -252,6 +253,16 @@ return FromJSVal(cx, v, out.m_Series); } +template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CIntList& val) +{ + ToJSVal(cx, ret, val.m_Items); +} + +template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CIntList& out) +{ + return FromJSVal(cx, v, out.m_Items); +} + template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const EVAlign& val) { std::string word;