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 @@ -35,7 +35,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" font="sans-bold-stroke-13" > @@ -176,7 +176,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" @@ -274,7 +274,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,26 +183,30 @@ */ 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"); + let selectedIndex = replaySelection.selected[0]; - 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(); - - replaySelection.selected = Math.min(selectedIndex, g_ReplaysFiltered.length - 1); + replaySelection.selected = [Math.min(selectedIndex, g_ReplaysFiltered.length - 1)]; } 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 = []; } /** @@ -205,6 +209,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. */ @@ -215,8 +233,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(); @@ -237,7 +255,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 || []; @@ -249,7 +267,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(); } @@ -259,21 +277,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 @@ . */ -/* -GUI Object - Drop Down (list) - ---Overview-- - - Works just like a list-box, but it hides - all the elements that aren't selected. They - can be brought up by pressing the control. -*/ - #ifndef INCLUDED_CDROPDOWN #define INCLUDED_CDROPDOWN -#include "gui/CGUIList.h" -#include "gui/CList.h" -#include "gui/IGUIScrollBar.h" +#include"gui/IGUIObject.h" +#include "gui/CGUISprite.h" +#include "gui/IList.h" -#include +class IListSelection; -/** - * Drop Down - * - * The control can be pressed, but we will not inherent - * this behavior from IGUIButtonBehavior, because when - * you press this control, the list with elements will - * immediately appear, and not first after release - * (which is the whole gist of the IGUIButtonBehavior). - */ -class CDropDown : public CList +class CDropDown : public IList, IGUIObject { - GUI_OBJECT(CDropDown) + GUI_OBJECT(CDropDown); public: CDropDown(CGUI& pGUI); virtual ~CDropDown(); - -// virtual void ResetStates() { IGUIButtonBehavior::ResetStates(); } - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * Handle events manually to catch keyboard inputting. - */ + virtual void AddItem(const CStrW& str, const CStrW& data); + virtual void SetSelection(class IListSelection* selection); + virtual void SetupText(); virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); - - /** - * Draws the Button - */ virtual void Draw(); - - // This is one of the few classes we actually need to redefine this function - // this is because the size of the control changes whether it is open - // or closed. - virtual bool IsMouseOver() const; - - virtual float GetBufferedZ() const; - -protected: - /** - * If the size changed, the texts have to be updated as - * the word wrapping depends on the size. - */ - virtual void UpdateCachedSize(); - - /** - * Sets up text, should be called every time changes has been - * made that can change the visual. - */ - void SetupText(); - - // Sets up the cached GetListRect. Decided whether it should - // have a scrollbar, and so on. - virtual void SetupListRect(); - - // Specify a new List rectangle. - virtual CRect GetListRect() const; - - /** - * Placement of text. - */ - CPos m_TextPos; - - // Is the dropdown opened? - bool m_Open; - - // I didn't cache this at first, but it's just as easy as caching - // m_CachedActualSize, so I thought, what the heck it's used a lot. - CRect m_CachedListRect; - - // Hide scrollbar when it's not needed - bool m_HideScrollBar; - - // Not necessarily the element that is selected, this is just - // which element should be highlighted. When opening the dropdown - // it is set to "selected", but then when moving the mouse it will - // change. - int m_ElementHighlight; - - // Stores any text entered by the user for quick access to an element - // (ie if you type "acro" it will take you to acropolis). - std::string m_InputBuffer; - - // used to know if we want to restart anew or add to m_inputbuffer. - double m_TimeOfLastInput; - + 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 SelectNextElement(); + virtual void SelectPrevElement(); + virtual void SelectFirstElement(); + virtual void SelectLastElement(); + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); +private: + IList* m_listImpl; + IListSelection* m_listSelection; }; -#endif // INCLUDED_CDROPDOWN +#endif // INCLUDED_CLISTSINGLE Index: source/gui/CDropDown.cpp =================================================================== --- source/gui/CDropDown.cpp +++ source/gui/CDropDown.cpp @@ -19,490 +19,67 @@ #include "CDropDown.h" +#include "CDropDownImpl.h" +#include "CListSelectionSingleImpl.h" + +#include "IListSelection.h" + #include "gui/CGUI.h" -#include "gui/CGUIColor.h" -#include "lib/external_libraries/libsdl.h" -#include "lib/ogl.h" -#include "lib/timer.h" -#include "ps/Profile.h" CDropDown::CDropDown(CGUI& pGUI) - : CList(pGUI), IGUIObject(pGUI), - m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1) + : IGUIObject(pGUI) { - AddSetting("button_width"); - AddSetting("dropdown_size"); - AddSetting("dropdown_buffer"); - AddSetting("minimum_visible_items"); -// AddSetting("sound_closed"); - AddSetting("sound_disabled"); - AddSetting("sound_enter"); - AddSetting("sound_leave"); - AddSetting("sound_opened"); - AddSetting("sprite"); // Background that sits around the size - AddSetting("sprite_disabled"); - AddSetting("sprite_list"); // Background of the drop down list - AddSetting("sprite2"); // Button that sits to the right - AddSetting("sprite2_over"); - AddSetting("sprite2_pressed"); - AddSetting("sprite2_disabled"); - AddSetting("text_valign"); - - // Add these in CList! And implement TODO - //AddSetting("textcolor_over"); - //AddSetting("textcolor_pressed"); - AddSetting("textcolor_selected"); - AddSetting("textcolor_disabled"); - - // Scrollbar is forced to be true. - SetSetting("scrollbar", true, true); + m_listImpl = new CDropDownImpl(pGUI); + m_listSelection = new CListSelectionSingleImpl(pGUI); + m_listSelection->SetImplementation(m_listImpl); + m_listImpl->SetSelection(m_listSelection); } CDropDown::~CDropDown() { -} -void CDropDown::SetupText() +} +void CDropDown::DrawSelection(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) { - SetupListRect(); - CList::SetupText(); + m_listImpl->DrawSelection(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); } +void CDropDown::SetSelection(class IListSelection* selection) +{ -void CDropDown::UpdateCachedSize() +} +void CDropDown::AddItem(const CStrW& str, const CStrW& data) { - CList::UpdateCachedSize(); - SetupText(); + m_listImpl->AddItem(str, data); } - -void CDropDown::HandleMessage(SGUIMessage& Message) +void CDropDown::SetupText() { - // Important - - switch (Message.type) - { - case GUIM_SETTINGS_UPDATED: - { - // Update cached list rect - if (Message.value == "size" || - Message.value == "absolute" || - Message.value == "dropdown_size" || - Message.value == "dropdown_buffer" || - Message.value == "minimum_visible_items" || - Message.value == "scrollbar_style" || - Message.value == "button_width") - { - SetupListRect(); - } - - break; - } - - case GUIM_MOUSE_MOTION: - { - if (!m_Open) - break; - - CPos mouse = m_pGUI.GetMousePos(); - - if (!GetListRect().PointInside(mouse)) - break; - - const CGUIList& pList = GetSetting("list"); - const bool scrollbar = GetSetting("scrollbar"); - const float scroll = scrollbar ? GetScrollBar(0).GetPos() : 0.f; - - CRect rect = GetListRect(); - mouse.y += scroll; - int set = -1; - for (int i = 0; i < static_cast(pList.m_Items.size()); ++i) - { - if (mouse.y >= rect.top + m_ItemsYPositions[i] && - mouse.y < rect.top + m_ItemsYPositions[i+1] && - // mouse is not over scroll-bar - (m_HideScrollBar || - mouse.x < GetScrollBar(0).GetOuterRect().left || - mouse.x > GetScrollBar(0).GetOuterRect().right)) - { - set = i; - } - } - - if (set != -1) - { - m_ElementHighlight = set; - //UpdateAutoScroll(); - } - - break; - } - - case GUIM_MOUSE_ENTER: - { - if (GetSetting("enabled")) - PlaySound("sound_enter"); - break; - } - - case GUIM_MOUSE_LEAVE: - { - m_ElementHighlight = GetSetting("selected"); - - if (GetSetting("enabled")) - PlaySound("sound_leave"); - break; - } - - // We can't inherent this routine from CList, because we need to include - // a mouse click to open the dropdown, also the coordinates are changed. - case GUIM_MOUSE_PRESS_LEFT: - { - if (!GetSetting("enabled")) - { - PlaySound("sound_disabled"); - break; - } - - if (!m_Open) - { - const CGUIList& pList = GetSetting("list"); - if (pList.m_Items.empty()) - return; - - m_Open = true; - GetScrollBar(0).SetZ(GetBufferedZ()); - m_ElementHighlight = GetSetting("selected"); - - // Start at the position of the selected item, if possible. - GetScrollBar(0).SetPos(m_ItemsYPositions.empty() ? 0 : m_ItemsYPositions[m_ElementHighlight] - 60); - - PlaySound("sound_opened"); - return; // overshadow - } - else - { - const CPos& mouse = m_pGUI.GetMousePos(); - - // If the regular area is pressed, then abort, and close. - if (m_CachedActualSize.PointInside(mouse)) - { - m_Open = false; - GetScrollBar(0).SetZ(GetBufferedZ()); - PlaySound("sound_closed"); - return; // overshadow - } - - if (m_HideScrollBar || - mouse.x < GetScrollBar(0).GetOuterRect().left || - mouse.x > GetScrollBar(0).GetOuterRect().right || - mouse.y < GetListRect().top) - { - m_Open = false; - GetScrollBar(0).SetZ(GetBufferedZ()); - } - } - break; - } - - case GUIM_MOUSE_WHEEL_DOWN: - { - // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. - if (m_Open || !GetSetting("enabled")) - break; - - m_ElementHighlight = GetSetting("selected"); - - if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1) - break; - - ++m_ElementHighlight; - SetSetting("selected", m_ElementHighlight, true); - break; - } - - case GUIM_MOUSE_WHEEL_UP: - { - // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. - if (m_Open || !GetSetting("enabled")) - break; - - m_ElementHighlight = GetSetting("selected"); - if (m_ElementHighlight - 1 < 0) - break; - - --m_ElementHighlight; - SetSetting("selected", m_ElementHighlight, true); - break; - } - - case GUIM_LOST_FOCUS: - { - if (m_Open) - PlaySound("sound_closed"); - - m_Open = false; - break; - } - - case GUIM_LOAD: - SetupListRect(); - break; - - default: - break; - } - - // Important that this is after, so that overshadowed implementations aren't processed - CList::HandleMessage(Message); - - // As HandleMessage functions return void, we need to manually verify - // whether the child list's items were modified. - if (CList::GetModified()) - SetupText(); + m_listImpl->SetupText(); } - InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev) { - InReaction result = IN_PASS; - bool update_highlight = false; - - if (ev->ev.type == SDL_KEYDOWN) - { - int szChar = ev->ev.key.keysym.sym; - - switch (szChar) - { - case '\r': - m_Open = false; - result = IN_HANDLED; - break; - - case SDLK_HOME: - case SDLK_END: - case SDLK_UP: - case SDLK_DOWN: - case SDLK_PAGEUP: - case SDLK_PAGEDOWN: - if (!m_Open) - return IN_PASS; - // Set current selected item to highlighted, before - // then really processing these in CList::ManuallyHandleEvent() - SetSetting("selected", m_ElementHighlight, true); - update_highlight = true; - break; - - default: - // If we have inputed a character try to get the closest element to it. - // TODO: not too nice and doesn't deal with dashes. - if (m_Open && ((szChar >= SDLK_a && szChar <= SDLK_z) || szChar == SDLK_SPACE - || (szChar >= SDLK_0 && szChar <= SDLK_9) - || (szChar >= SDLK_KP_0 && szChar <= SDLK_KP_9))) - { - // arbitrary 1 second limit to add to string or start fresh. - // maximal amount of characters is 100, which imo is far more than enough. - if (timer_Time() - m_TimeOfLastInput > 1.0 || m_InputBuffer.length() >= 100) - m_InputBuffer = szChar; - else - m_InputBuffer += szChar; - - m_TimeOfLastInput = timer_Time(); - - const CGUIList& pList = GetSetting("list"); - // let's look for the closest element - // basically it's alphabetic order and "as many letters as we can get". - int closest = -1; - int bestIndex = -1; - int difference = 1250; - for (int i = 0; i < static_cast(pList.m_Items.size()); ++i) - { - int indexOfDifference = 0; - int diff = 0; - for (size_t j = 0; j < m_InputBuffer.length(); ++j) - { - diff = std::abs(static_cast(pList.m_Items[i].GetRawString().LowerCase()[j]) - static_cast(m_InputBuffer[j])); - if (diff == 0) - indexOfDifference = j+1; - else - break; - } - if (indexOfDifference > bestIndex || (indexOfDifference >= bestIndex && diff < difference)) - { - bestIndex = indexOfDifference; - closest = i; - difference = diff; - } - } - // let's select the closest element. There should basically always be one. - if (closest != -1) - { - SetSetting("selected", closest, true); - update_highlight = true; - GetScrollBar(0).SetPos(m_ItemsYPositions[closest] - 60); - } - result = IN_HANDLED; - } - break; - } - } - - if (CList::ManuallyHandleEvent(ev) == IN_HANDLED) - result = IN_HANDLED; - - if (update_highlight) - m_ElementHighlight = GetSetting("selected"); - - return result; + return m_listImpl->ManuallyHandleEvent(ev); } - -void CDropDown::SetupListRect() +void CDropDown::Draw() { - extern int g_yres; - extern float g_GuiScale; - const float yres = g_yres / g_GuiScale; - - const float size = GetSetting("dropdown_size"); - const float buffer = GetSetting("dropdown_buffer"); - const u32 minimumVisibleItems = GetSetting("minimum_visible_items"); - - if (m_ItemsYPositions.empty()) - { - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, - m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size); - m_HideScrollBar = false; - } - // Too many items so use a scrollbar - else if (m_ItemsYPositions.back() > size) - { - // Place items below if at least some items can be placed below - if (m_CachedActualSize.bottom + buffer + size <= yres) - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, - m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size); - else if ((m_ItemsYPositions.size() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) || - m_CachedActualSize.top < yres - m_CachedActualSize.bottom) - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, - m_CachedActualSize.right, yres); - // Not enough space below, thus place items above - else - m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - buffer - size), - m_CachedActualSize.right, m_CachedActualSize.top - buffer); - - m_HideScrollBar = false; - } - else - { - // Enough space below, no scrollbar needed - if (m_CachedActualSize.bottom + buffer + m_ItemsYPositions.back() <= yres) - { - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, - m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + m_ItemsYPositions.back()); - m_HideScrollBar = true; - } - // Enough space below for some items, but not all, so place items below and use a scrollbar - else if ((m_ItemsYPositions.size() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) || - m_CachedActualSize.top < yres - m_CachedActualSize.bottom) - { - m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, - m_CachedActualSize.right, yres); - m_HideScrollBar = false; - } - // Not enough space below, thus place items above. Hide the scrollbar accordingly - else - { - m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - buffer - m_ItemsYPositions.back()), - m_CachedActualSize.right, m_CachedActualSize.top - buffer); - m_HideScrollBar = m_CachedActualSize.top > m_ItemsYPositions.back() + buffer; - } - } + m_listImpl->Draw(); } - -CRect CDropDown::GetListRect() const +void CDropDown::SelectNextElement() { - return m_CachedListRect; + m_listImpl->SelectNextElement(); } - -bool CDropDown::IsMouseOver() const +void CDropDown::SelectPrevElement() { - if (m_Open) - { - CRect rect(m_CachedActualSize.left, std::min(m_CachedActualSize.top, GetListRect().top), - m_CachedActualSize.right, std::max(m_CachedActualSize.bottom, GetListRect().bottom)); - return rect.PointInside(m_pGUI.GetMousePos()); - } - else - return m_CachedActualSize.PointInside(m_pGUI.GetMousePos()); + m_listImpl->SelectNextElement(); } - -void CDropDown::Draw() +void CDropDown::SelectFirstElement() { - const float bz = GetBufferedZ(); - const float button_width = GetSetting("button_width"); - const bool enabled = GetSetting("enabled"); - const int cell_id = GetSetting("cell_id"); - const int selected = GetSetting("selected"); - CGUISpriteInstance& sprite = GetSetting(enabled ? "sprite" : "sprite_disabled"); - CGUISpriteInstance& sprite2 = GetSetting("sprite2"); - - m_pGUI.DrawSprite(sprite, cell_id, bz, m_CachedActualSize); - - if (button_width > 0.f) - { - CRect rect(m_CachedActualSize.right-button_width, m_CachedActualSize.top, - m_CachedActualSize.right, m_CachedActualSize.bottom); - - if (!enabled) - { - CGUISpriteInstance& sprite2_second = GetSetting("sprite2_disabled"); - m_pGUI.DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); - } - else if (m_Open) - { - CGUISpriteInstance& sprite2_second = GetSetting("sprite2_pressed"); - m_pGUI.DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); - } - else if (m_MouseHovering) - { - CGUISpriteInstance& sprite2_second = GetSetting("sprite2_over"); - m_pGUI.DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); - } - else - m_pGUI.DrawSprite(sprite2, cell_id, bz + 0.05f, rect); - } - - if (selected != -1) // TODO: Maybe check validity completely? - { - CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top, - m_CachedActualSize.right-button_width, m_CachedActualSize.bottom); - - const CGUIColor& color = GetSetting(enabled ? "textcolor_selected" : "textcolor_disabled"); - - CPos pos(m_CachedActualSize.left, m_CachedActualSize.top); - DrawText(selected, color, pos, bz+0.1f, cliparea); - } - - // Disable scrollbar during drawing without sending a setting-changed message - bool& scrollbar = GetSetting("scrollbar"); - bool old = scrollbar; - - if (m_Open) - { - if (m_HideScrollBar) - scrollbar = false; - - DrawList(m_ElementHighlight, "sprite_list", "sprite_selectarea", "textcolor"); - - if (m_HideScrollBar) - scrollbar = old; - } + m_listImpl->SelectFirstElement(); } - -// When a dropdown list is opened, it needs to be visible above all the other -// controls on the page. The only way I can think of to do this is to increase -// its z value when opened, so that it's probably on top. -float CDropDown::GetBufferedZ() const -{ - float bz = CList::GetBufferedZ(); - if (m_Open) - return std::min(bz + 500.f, 1000.f); // TODO - don't use magic number for max z value - else - return bz; +void CDropDown::SelectLastElement() +{ + m_listImpl->SelectLastElement(); +} +bool CDropDown::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) +{ + return m_listImpl->HandleAdditionalChildren(child, pFile); } Index: source/gui/CDropDownImpl.h =================================================================== --- /dev/null +++ source/gui/CDropDownImpl.h @@ -0,0 +1,64 @@ +#ifndef INCLUDED_DROPDOWNIMPL +#define INCLUDED_DROPDOWNIMPL + +#include "gui/CListImpl.h" +class CDropDownImpl: public CListImpl +{ +public: + CDropDownImpl(CGUI& pGUI); + virtual ~CDropDownImpl(); + + virtual void HandleMessage(SGUIMessage& Message); + InReaction ManuallyHandleEvent(const SDL_Event_* ev); + virtual bool IsMouseOver() const; + virtual void Draw(); + virtual float CDropDownImpl::GetBufferedZ() const; +protected: + /** + * If the size changed, the texts have to be updated as + * the word wrapping depends on the size. + */ + virtual void UpdateCachedSize(); + + /** + * Sets up text, should be called every time changes has been + * made that can change the visual. + */ + void SetupText(); + + // Sets up the cached GetListRect. Decided whether it should + // have a scrollbar, and so on. + virtual void SetupListRect(); + + // Specify a new List rectangle. + virtual CRect GetListRect() const; + + /** + * Placement of text. + */ + CPos m_TextPos; + + // Is the dropdown opened? + bool m_Open; + + // I didn't cache this at first, but it's just as easy as caching + // m_CachedActualSize, so I thought, what the heck it's used a lot. + CRect m_CachedListRect; + + // Hide scrollbar when it's not needed + bool m_HideScrollBar; + + // Not necessarily the element that is selected, this is just + // which element should be highlighted. When opening the dropdown + // it is set to "selected", but then when moving the mouse it will + // change. + int m_ElementHighlight; + + // Stores any text entered by the user for quick access to an element + // (ie if you type "acro" it will take you to acropolis). + std::string m_InputBuffer; + + // used to know if we want to restart anew or add to m_inputbuffer. + double m_TimeOfLastInput; +}; +#endif Index: source/gui/CDropDownImpl.cpp =================================================================== --- /dev/null +++ source/gui/CDropDownImpl.cpp @@ -0,0 +1,512 @@ +/* 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 "CDropDownImpl.h" + +#include "gui/CGUI.h" +#include "gui/CGUIColor.h" +#include "lib/external_libraries/libsdl.h" +#include "lib/ogl.h" +#include "lib/timer.h" +#include "ps/Profile.h" + +#include "gui/CGUIList.h" +#include "gui/IGUIScrollBar.h" +#include "gui/IListSelection.h" + +CDropDownImpl::CDropDownImpl(CGUI& pGUI) + : CListImpl(pGUI), IGUIObject(pGUI), + m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1) +{ + AddSetting("button_width"); + AddSetting("dropdown_size"); + AddSetting("dropdown_buffer"); + AddSetting("minimum_visible_items"); +// AddSetting("sound_closed"); + AddSetting("sound_disabled"); + AddSetting("sound_enter"); + AddSetting("sound_leave"); + AddSetting("sound_opened"); + AddSetting("sprite"); // Background that sits around the size + AddSetting("sprite_disabled"); + AddSetting("sprite_list"); // Background of the drop down list + AddSetting("sprite2"); // Button that sits to the right + AddSetting("sprite2_over"); + AddSetting("sprite2_pressed"); + AddSetting("sprite2_disabled"); + AddSetting("text_valign"); + + // Add these in CList! And implement TODO + //AddSetting("textcolor_over"); + //AddSetting("textcolor_pressed"); + AddSetting("textcolor_selected"); + AddSetting("textcolor_disabled"); + + // Scrollbar is forced to be true. + SetSetting("scrollbar", true, true); +} + +CDropDownImpl::~CDropDownImpl() +{ +} + +void CDropDownImpl::SetupText() +{ + SetupListRect(); + CListImpl::SetupText(); +} + +void CDropDownImpl::UpdateCachedSize() +{ + CListImpl::UpdateCachedSize(); + SetupText(); +} + +void CDropDownImpl::HandleMessage(SGUIMessage& Message) +{ + // Important + + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + { + // Update cached list rect + if (Message.value == "size" || + Message.value == "absolute" || + Message.value == "dropdown_size" || + Message.value == "dropdown_buffer" || + Message.value == "minimum_visible_items" || + Message.value == "scrollbar_style" || + Message.value == "button_width") + { + SetupListRect(); + } + + break; + } + + case GUIM_MOUSE_MOTION: + { + if (!m_Open) + break; + + CPos mouse = m_pGUI.GetMousePos(); + + if (!GetListRect().PointInside(mouse)) + break; + + const CGUIList& pList = GetSetting("list"); + const bool scrollbar = GetSetting("scrollbar"); + const float scroll = scrollbar ? GetScrollBar(0).GetPos() : 0.f; + + CRect rect = GetListRect(); + mouse.y += scroll; + int set = -1; + for (int i = 0; i < static_cast(pList.m_Items.size()); ++i) + { + if (mouse.y >= rect.top + m_ItemsYPositions[i] && + mouse.y < rect.top + m_ItemsYPositions[i+1] && + // mouse is not over scroll-bar + (m_HideScrollBar || + mouse.x < GetScrollBar(0).GetOuterRect().left || + mouse.x > GetScrollBar(0).GetOuterRect().right)) + { + set = i; + } + } + + if (set != -1) + { + m_ElementHighlight = set; + //UpdateAutoScroll(); + } + + break; + } + + case GUIM_MOUSE_ENTER: + { + if (GetSetting("enabled")) + PlaySound("sound_enter"); + break; + } + + case GUIM_MOUSE_LEAVE: + { + m_ElementHighlight = GetSetting("selected"); + + if (GetSetting("enabled")) + PlaySound("sound_leave"); + break; + } + + // We can't inherent this routine from CList, because we need to include + // a mouse click to open the dropdown, also the coordinates are changed. + case GUIM_MOUSE_PRESS_LEFT: + { + if (!GetSetting("enabled")) + { + PlaySound("sound_disabled"); + break; + } + + if (!m_Open) + { + const CGUIList& pList = GetSetting("list"); + if (pList.m_Items.empty()) + return; + + m_Open = true; + GetScrollBar(0).SetZ(GetBufferedZ()); + m_ElementHighlight = GetSetting("selected"); + + // Start at the position of the selected item, if possible. + GetScrollBar(0).SetPos(m_ItemsYPositions.empty() ? 0 : m_ItemsYPositions[m_ElementHighlight] - 60); + + PlaySound("sound_opened"); + return; // overshadow + } + else + { + const CPos& mouse = m_pGUI.GetMousePos(); + + // If the regular area is pressed, then abort, and close. + if (m_CachedActualSize.PointInside(mouse)) + { + m_Open = false; + GetScrollBar(0).SetZ(GetBufferedZ()); + PlaySound("sound_closed"); + return; // overshadow + } + + if (m_HideScrollBar || + mouse.x < GetScrollBar(0).GetOuterRect().left || + mouse.x > GetScrollBar(0).GetOuterRect().right || + mouse.y < GetListRect().top) + { + m_Open = false; + GetScrollBar(0).SetZ(GetBufferedZ()); + } + } + break; + } + + case GUIM_MOUSE_WHEEL_DOWN: + { + // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. + if (m_Open || !GetSetting("enabled")) + break; + + m_ElementHighlight = GetSetting("selected"); + + if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1) + break; + + ++m_ElementHighlight; + SetSetting("selected", m_ElementHighlight, true); + break; + } + + case GUIM_MOUSE_WHEEL_UP: + { + // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. + if (m_Open || !GetSetting("enabled")) + break; + + m_ElementHighlight = GetSetting("selected"); + if (m_ElementHighlight - 1 < 0) + break; + + --m_ElementHighlight; + SetSetting("selected", m_ElementHighlight, true); + break; + } + + case GUIM_LOST_FOCUS: + { + if (m_Open) + PlaySound("sound_closed"); + + m_Open = false; + break; + } + + case GUIM_LOAD: + SetupListRect(); + break; + + default: + break; + } + + // Important that this is after, so that overshadowed implementations aren't processed + CListImpl::HandleMessage(Message); + + // As HandleMessage functions return void, we need to manually verify + // whether the child list's items were modified. + if (CListImpl::GetModified()) + SetupText(); +} + +InReaction CDropDownImpl::ManuallyHandleEvent(const SDL_Event_* ev) +{ + InReaction result = IN_PASS; + bool update_highlight = false; + + if (ev->ev.type == SDL_KEYDOWN) + { + int szChar = ev->ev.key.keysym.sym; + + switch (szChar) + { + case '\r': + m_Open = false; + result = IN_HANDLED; + break; + + case SDLK_HOME: + case SDLK_END: + case SDLK_UP: + case SDLK_DOWN: + case SDLK_PAGEUP: + case SDLK_PAGEDOWN: + if (!m_Open) + return IN_PASS; + // Set current selected item to highlighted, before + // then really processing these in CList::ManuallyHandleEvent() + m_selection->SelectionControl(m_ElementHighlight); + update_highlight = true; + break; + + default: + // If we have inputed a character try to get the closest element to it. + // TODO: not too nice and doesn't deal with dashes. + if (m_Open && ((szChar >= SDLK_a && szChar <= SDLK_z) || szChar == SDLK_SPACE + || (szChar >= SDLK_0 && szChar <= SDLK_9) + || (szChar >= SDLK_KP_0 && szChar <= SDLK_KP_9))) + { + // arbitrary 1 second limit to add to string or start fresh. + // maximal amount of characters is 100, which imo is far more than enough. + if (timer_Time() - m_TimeOfLastInput > 1.0 || m_InputBuffer.length() >= 100) + m_InputBuffer = szChar; + else + m_InputBuffer += szChar; + + m_TimeOfLastInput = timer_Time(); + + const CGUIList& pList = GetSetting("list"); + // let's look for the closest element + // basically it's alphabetic order and "as many letters as we can get". + int closest = -1; + int bestIndex = -1; + int difference = 1250; + for (int i = 0; i < static_cast(pList.m_Items.size()); ++i) + { + int indexOfDifference = 0; + int diff = 0; + for (size_t j = 0; j < m_InputBuffer.length(); ++j) + { + diff = std::abs(static_cast(pList.m_Items[i].GetRawString().LowerCase()[j]) - static_cast(m_InputBuffer[j])); + if (diff == 0) + indexOfDifference = j+1; + else + break; + } + if (indexOfDifference > bestIndex || (indexOfDifference >= bestIndex && diff < difference)) + { + bestIndex = indexOfDifference; + closest = i; + difference = diff; + } + } + // let's select the closest element. There should basically always be one. + if (closest != -1) + { + m_selection->SelectionControl(closest); + update_highlight = true; + GetScrollBar(0).SetPos(m_ItemsYPositions[closest] - 60); + } + result = IN_HANDLED; + } + break; + } + } + + if (CListImpl::ManuallyHandleEvent(ev) == IN_HANDLED) + result = IN_HANDLED; + + if (update_highlight) + m_ElementHighlight = m_selection->GetLastSelected(); + + return result; +} + +void CDropDownImpl::SetupListRect() +{ + extern int g_yres; + extern float g_GuiScale; + const float yres = g_yres / g_GuiScale; + + const float size = GetSetting("dropdown_size"); + const float buffer = GetSetting("dropdown_buffer"); + const u32 minimumVisibleItems = GetSetting("minimum_visible_items"); + + if (m_ItemsYPositions.empty()) + { + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, + m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size); + m_HideScrollBar = false; + } + // Too many items so use a scrollbar + else if (m_ItemsYPositions.back() > size) + { + // Place items below if at least some items can be placed below + if (m_CachedActualSize.bottom + buffer + size <= yres) + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, + m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + size); + else if ((m_ItemsYPositions.size() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) || + m_CachedActualSize.top < yres - m_CachedActualSize.bottom) + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, + m_CachedActualSize.right, yres); + // Not enough space below, thus place items above + else + m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - buffer - size), + m_CachedActualSize.right, m_CachedActualSize.top - buffer); + + m_HideScrollBar = false; + } + else + { + // Enough space below, no scrollbar needed + if (m_CachedActualSize.bottom + buffer + m_ItemsYPositions.back() <= yres) + { + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, + m_CachedActualSize.right, m_CachedActualSize.bottom + buffer + m_ItemsYPositions.back()); + m_HideScrollBar = true; + } + // Enough space below for some items, but not all, so place items below and use a scrollbar + else if ((m_ItemsYPositions.size() > minimumVisibleItems && yres - m_CachedActualSize.bottom - buffer >= m_ItemsYPositions[minimumVisibleItems]) || + m_CachedActualSize.top < yres - m_CachedActualSize.bottom) + { + m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + buffer, + m_CachedActualSize.right, yres); + m_HideScrollBar = false; + } + // Not enough space below, thus place items above. Hide the scrollbar accordingly + else + { + m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - buffer - m_ItemsYPositions.back()), + m_CachedActualSize.right, m_CachedActualSize.top - buffer); + m_HideScrollBar = m_CachedActualSize.top > m_ItemsYPositions.back() + buffer; + } + } +} + +CRect CDropDownImpl::GetListRect() const +{ + return m_CachedListRect; +} + +bool CDropDownImpl::IsMouseOver() const +{ + if (m_Open) + { + CRect rect(m_CachedActualSize.left, std::min(m_CachedActualSize.top, GetListRect().top), + m_CachedActualSize.right, std::max(m_CachedActualSize.bottom, GetListRect().bottom)); + return rect.PointInside(m_pGUI.GetMousePos()); + } + else + return m_CachedActualSize.PointInside(m_pGUI.GetMousePos()); +} + +void CDropDownImpl::Draw() +{ + const float bz = GetBufferedZ(); + const float button_width = GetSetting("button_width"); + const bool enabled = GetSetting("enabled"); + const int cell_id = GetSetting("cell_id"); + const int selected = m_selection->GetLastSelected(); + CGUISpriteInstance& sprite = GetSetting(enabled ? "sprite" : "sprite_disabled"); + CGUISpriteInstance& sprite2 = GetSetting("sprite2"); + + m_pGUI.DrawSprite(sprite, cell_id, bz, m_CachedActualSize); + + if (button_width > 0.f) + { + CRect rect(m_CachedActualSize.right-button_width, m_CachedActualSize.top, + m_CachedActualSize.right, m_CachedActualSize.bottom); + + if (!enabled) + { + CGUISpriteInstance& sprite2_second = GetSetting("sprite2_disabled"); + m_pGUI.DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); + } + else if (m_Open) + { + CGUISpriteInstance& sprite2_second = GetSetting("sprite2_pressed"); + m_pGUI.DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); + } + else if (m_MouseHovering) + { + CGUISpriteInstance& sprite2_second = GetSetting("sprite2_over"); + m_pGUI.DrawSprite(sprite2_second || sprite2, cell_id, bz + 0.05f, rect); + } + else + m_pGUI.DrawSprite(sprite2, cell_id, bz + 0.05f, rect); + } + + if (selected != -1) // TODO: Maybe check validity completely? + { + CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top, + m_CachedActualSize.right-button_width, m_CachedActualSize.bottom); + + const CGUIColor& color = GetSetting(enabled ? "textcolor_selected" : "textcolor_disabled"); + + CPos pos(m_CachedActualSize.left, m_CachedActualSize.top); + DrawText(selected, color, pos, bz+0.1f, cliparea); + } + + // Disable scrollbar during drawing without sending a setting-changed message + bool& scrollbar = GetSetting("scrollbar"); + bool old = scrollbar; + + if (m_Open) + { + if (m_HideScrollBar) + scrollbar = false; + + DrawList(m_ElementHighlight, "sprite_list", "sprite_selectarea", "textcolor"); + + if (m_HideScrollBar) + scrollbar = old; + } +} + +// When a dropdown list is opened, it needs to be visible above all the other +// controls on the page. The only way I can think of to do this is to increase +// its z value when opened, so that it's probably on top. +float CDropDownImpl::GetBufferedZ() const +{ + float bz = CListImpl::GetBufferedZ(); + if (m_Open) + return std::min(bz + 500.f, 1000.f); // TODO - don't use magic number for max z value + else + return bz; +} 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" @@ -58,6 +60,8 @@ #include "scripting/ScriptFunctions.h" #include "scriptinterface/ScriptInterface.h" +#include "gui/IGUIScrollBar.h" + extern int g_yres; const double SELECT_DBLCLICK_RATE = 0.5; @@ -325,8 +329,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 +++ /dev/null @@ -1,124 +0,0 @@ -/* 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_CLIST -#define INCLUDED_CLIST - -#include "gui/IGUIScrollBarOwner.h" -#include "gui/IGUITextOwner.h" - -#include - -/** - * Create a list of elements, where one 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. - * - * A scroll-bar will appear when needed. This will be - * achieved with the IGUIScrollBarOwner structure. - */ -class CList : public IGUIScrollBarOwner, public IGUITextOwner -{ - GUI_OBJECT(CList) - -public: - CList(CGUI& pGUI); - virtual ~CList(); - - /** - * @see IGUIObject#ResetStates() - */ - virtual void ResetStates() { IGUIScrollBarOwner::ResetStates(); } - - /** - * Adds an item last to the list. - */ - virtual void AddItem(const CStrW& str, const CStrW& data); - -protected: - /** - * Sets up text, should be called every time changes has been - * made that can change the visual. - */ - virtual void SetupText(); - - /** - * @see IGUIObject#HandleMessage() - */ - virtual void HandleMessage(SGUIMessage& Message); - - /** - * Handle events manually to catch keyboard inputting. - */ - virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); - - /** - * Draws the List box - */ - virtual void Draw(); - - /** - * Easy select elements functions - */ - virtual void SelectNextElement(); - virtual void SelectPrevElement(); - virtual void SelectFirstElement(); - virtual void SelectLastElement(); - - /** - * Handle the \ tag. - */ - virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); - - // Called every time the auto-scrolling should be checked. - void UpdateAutoScroll(); - - // 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); - - // 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. - virtual CRect GetListRect() const { return m_CachedActualSize; } - - // Returns whether SetupText() has run since the last message was received - // (and thus whether list items have possibly changed). - virtual bool GetModified() const { return m_Modified; } - - /** - * List of each element's relative y position. Will be - * one larger than m_Items, because it will end with the - * bottom of the last element. First element will always - * be zero, but still stored for easy handling. - */ - std::vector m_ItemsYPositions; - - virtual int GetHoveredItem(); - -private: - // Whether the list's items have been modified since last handling a message. - bool m_Modified; - - // Used for doubleclick registration - int m_PrevSelectedItem; - - // Last time a click on an item was issued - double m_LastItemClickTime; -}; - -#endif // INCLUDED_CLIST Index: source/gui/CList.cpp =================================================================== --- source/gui/CList.cpp +++ /dev/null @@ -1,483 +0,0 @@ -/* 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 "CList.h" - -#include "gui/CGUI.h" -#include "gui/CGUIColor.h" -#include "gui/CGUIList.h" -#include "gui/CGUIScrollBarVertical.h" -#include "lib/external_libraries/libsdl.h" -#include "lib/timer.h" - -CList::CList(CGUI& pGUI) - : IGUIObject(pGUI), IGUITextOwner(pGUI), IGUIScrollBarOwner(pGUI), - m_Modified(false), m_PrevSelectedItem(-1), m_LastItemClickTime(0) -{ - // Add sprite_disabled! TODO - AddSetting("buffer_zone"); - AddSetting("font"); - AddSetting("scrollbar"); - AddSetting("scrollbar_style"); - AddSetting("sound_disabled"); - AddSetting("sound_selected"); - AddSetting("sprite"); - AddSetting("sprite_selectarea"); - AddSetting("cell_id"); - AddSetting("text_align"); - AddSetting("textcolor"); - AddSetting("textcolor_selected"); - AddSetting("selected"); // Index selected. -1 is none. - AddSetting("auto_scroll"); - AddSetting("hovered"); - - // Each list item has both a name (in 'list') and an associated data string (in 'list_data') - AddSetting("list"); - AddSetting("list_data"); - - SetSetting("scrollbar", false, true); - SetSetting("selected", -1, true); - SetSetting("hovered", -1, true); - SetSetting("auto_scroll", false, true); - - // Add scroll-bar - CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); - bar->SetRightAligned(true); - AddScrollBar(bar); -} - -CList::~CList() -{ -} - -void CList::SetupText() -{ - m_Modified = true; - const CGUIList& pList = GetSetting("list"); - - //ENSURE(m_GeneratedTexts.size()>=1); - - m_ItemsYPositions.resize(pList.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(); - - const CStrW& font = GetSetting("font"); - - const bool scrollbar = GetSetting("scrollbar"); - - float width = GetListRect().GetWidth(); - // remove scrollbar if applicable - if (scrollbar && GetScrollBar(0).GetStyle()) - width -= GetScrollBar(0).GetStyle()->m_Width; - - const float buffer_zone = GetSetting("buffer_zone"); - - // Generate texts - float buffered_y = 0.f; - - for (size_t i = 0; i < pList.m_Items.size(); ++i) - { - CGUIText* text; - - if (!pList.m_Items[i].GetOriginalString().empty()) - text = &AddText(pList.m_Items[i], font, width, buffer_zone, this); - else - { - // Minimum height of a space character of the current font size - CGUIString align_string; - align_string.SetValue(L" "); - text = &AddText(align_string, font, width, buffer_zone, this); - } - - m_ItemsYPositions[i] = buffered_y; - buffered_y += text->GetSize().cy; - } - - m_ItemsYPositions[pList.m_Items.size()] = buffered_y; - - // Setup scrollbar - if (scrollbar) - { - GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back()); - GetScrollBar(0).SetScrollSpace(GetListRect().GetHeight()); - - CRect rect = GetListRect(); - GetScrollBar(0).SetX(rect.right); - GetScrollBar(0).SetY(rect.top); - GetScrollBar(0).SetZ(GetBufferedZ()); - GetScrollBar(0).SetLength(rect.bottom - rect.top); - } -} - -void CList::HandleMessage(SGUIMessage& Message) -{ - IGUIScrollBarOwner::HandleMessage(Message); - //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! - - m_Modified = false; - switch (Message.type) - { - case GUIM_SETTINGS_UPDATED: - if (Message.value == "list") - SetupText(); - - // If selected is changed, call "SelectionChange" - if (Message.value == "selected") - { - // TODO: Check range - - if (GetSetting("auto_scroll")) - UpdateAutoScroll(); - - // TODO only works if lower-case, shouldn't it be made case sensitive instead? - ScriptEvent("selectionchange"); - } - - if (Message.value == "scrollbar") - SetupText(); - - // Update scrollbar - if (Message.value == "scrollbar_style") - { - GetScrollBar(0).SetScrollBarStyle(GetSetting(Message.value)); - SetupText(); - } - - break; - - case GUIM_MOUSE_PRESS_LEFT: - { - if (!GetSetting("enabled")) - { - PlaySound("sound_disabled"); - break; - } - - int hovered = GetHoveredItem(); - if (hovered == -1) - break; - SetSetting("selected", hovered, true); - UpdateAutoScroll(); - PlaySound("sound_selected"); - - if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && hovered == m_PrevSelectedItem) - this->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT_ITEM, "mouseleftdoubleclickitem"); - else - this->SendEvent(GUIM_MOUSE_PRESS_LEFT_ITEM, "mouseleftclickitem"); - - m_LastItemClickTime = timer_Time(); - m_PrevSelectedItem = hovered; - break; - } - - case GUIM_MOUSE_LEAVE: - { - if (GetSetting("hovered") == -1) - break; - - SetSetting("hovered", -1, true); - ScriptEvent("hoverchange"); - break; - } - - case GUIM_MOUSE_OVER: - { - int hovered = GetHoveredItem(); - if (hovered == GetSetting("hovered")) - break; - - SetSetting("hovered", hovered, true); - ScriptEvent("hoverchange"); - break; - } - - case GUIM_LOAD: - { - GetScrollBar(0).SetScrollBarStyle(GetSetting("scrollbar_style")); - break; - } - - default: - break; - } - - IGUITextOwner::HandleMessage(Message); -} - -InReaction CList::ManuallyHandleEvent(const SDL_Event_* ev) -{ - InReaction result = IN_PASS; - - if (ev->ev.type == SDL_KEYDOWN) - { - int szChar = ev->ev.key.keysym.sym; - - switch (szChar) - { - case SDLK_HOME: - SelectFirstElement(); - UpdateAutoScroll(); - result = IN_HANDLED; - break; - - case SDLK_END: - SelectLastElement(); - UpdateAutoScroll(); - result = IN_HANDLED; - break; - - case SDLK_UP: - SelectPrevElement(); - UpdateAutoScroll(); - result = IN_HANDLED; - break; - - case SDLK_DOWN: - SelectNextElement(); - UpdateAutoScroll(); - result = IN_HANDLED; - break; - - case SDLK_PAGEUP: - GetScrollBar(0).ScrollMinusPlenty(); - result = IN_HANDLED; - break; - - case SDLK_PAGEDOWN: - GetScrollBar(0).ScrollPlusPlenty(); - result = IN_HANDLED; - break; - - default: // Do nothing - result = IN_PASS; - } - } - - return result; -} - -void CList::Draw() -{ - DrawList(GetSetting("selected"), "sprite", "sprite_selectarea", "textcolor"); -} - -void CList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) -{ - float bz = GetBufferedZ(); - - // First call draw on ScrollBarOwner - const bool scrollbar = GetSetting("scrollbar"); - - if (scrollbar) - IGUIScrollBarOwner::Draw(); - - { - CRect rect = GetListRect(); - - CGUISpriteInstance& sprite = GetSetting(_sprite); - CGUISpriteInstance& sprite_selectarea = GetSetting(_sprite_selected); - - const int cell_id = GetSetting("cell_id"); - m_pGUI.DrawSprite(sprite, cell_id, bz, rect); - - float scroll = 0.f; - 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); - } - } - - const CGUIList& pList = GetSetting("list"); - const CGUIColor& color = GetSetting(_textcolor); - - for (size_t i = 0; i < pList.m_Items.size(); ++i) - { - if (m_ItemsYPositions[i+1] - scroll < 0 || - m_ItemsYPositions[i] - scroll > rect.GetHeight()) - continue; - - // Clipping area (we'll have to substract the scrollbar) - CRect cliparea = GetListRect(); - - if (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; - } - - DrawText(i, color, rect.TopLeft() - CPos(0.f, scroll - m_ItemsYPositions[i]), bz + 0.1f, cliparea); - } - } -} - -void CList::AddItem(const CStrW& str, const CStrW& data) -{ - CGUIString gui_string; - gui_string.SetValue(str); - - // Do not send a settings-changed message - CGUIList& pList = GetSetting("list"); - pList.m_Items.push_back(gui_string); - - CGUIString data_string; - data_string.SetValue(data); - CGUIList& pListData = GetSetting("list_data"); - pListData.m_Items.push_back(data_string); - - // TODO Temp - SetupText(); -} - -bool CList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) -{ - int elmt_item = pFile->GetElementID("item"); - - if (child.GetNodeName() == elmt_item) - { - AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); - return true; - } - - 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 bool scrollbar = GetSetting("scrollbar"); - - // No scrollbar, no scrolling (at least it's not made to work properly). - if (!scrollbar || selected < 0 || static_cast(selected) >= m_ItemsYPositions.size()) - return; - - float scroll = GetScrollBar(0).GetPos(); - - // Check upper boundary - if (m_ItemsYPositions[selected] < scroll) - { - GetScrollBar(0).SetPos(m_ItemsYPositions[selected]); - return; // this means, if it wants to align both up and down at the same time - // this will have precedence. - } - - // Check lower boundary - CRect rect = GetListRect(); - if (m_ItemsYPositions[selected+1]-rect.GetHeight() > scroll) - GetScrollBar(0).SetPos(m_ItemsYPositions[selected+1]-rect.GetHeight()); -} - -int CList::GetHoveredItem() -{ - const bool scrollbar = GetSetting("scrollbar"); - const float scroll = scrollbar ? GetScrollBar(0).GetPos() : 0.f; - - const CRect& rect = GetListRect(); - CPos mouse = m_pGUI.GetMousePos(); - mouse.y += scroll; - - // Mouse is over scrollbar - if (scrollbar && GetScrollBar(0).IsVisible() && - mouse.x >= GetScrollBar(0).GetOuterRect().left && - mouse.x <= GetScrollBar(0).GetOuterRect().right) - return -1; - - const CGUIList& pList = GetSetting("list"); - for (size_t i = 0; i < pList.m_Items.size(); ++i) - if (mouse.y >= rect.top + m_ItemsYPositions[i] && - mouse.y < rect.top + m_ItemsYPositions[i + 1]) - return i; - - return -1; -} Index: source/gui/CListImpl.h =================================================================== --- /dev/null +++ source/gui/CListImpl.h @@ -0,0 +1,131 @@ +/* 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_CLIST +#define INCLUDED_CLIST + +#include "gui/IGUIScrollBarOwner.h" +#include "gui/IGUITextOwner.h" +#include "gui/IList.h" + +#include + +/** + * Create a list of elements, where one 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. + * + * A scroll-bar will appear when needed. This will be + * achieved with the IGUIScrollBarOwner structure. + */ +class CListImpl : public IList, public IGUIScrollBarOwner, public IGUITextOwner +{ +public: + CListImpl(CGUI& pGUI); + virtual ~CListImpl(); + + /** + * @see IGUIObject#ResetStates() + */ + virtual void ResetStates() { IGUIScrollBarOwner::ResetStates(); } + + /** + * @see IList#AddItem(). + */ + virtual void AddItem(const CStrW& str, const CStrW& data); + + virtual void SetSelection(class IListSelection*); + /** + * @see IList::SetupText(). + */ + virtual void SetupText(); + + /** + * @see IGUIObject#HandleMessage() + */ + virtual void HandleMessage(SGUIMessage& Message); + + /** + * Handle events manually to catch keyboard inputting. + */ + virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); + + /** + * @see IList::Draw() + */ + virtual void Draw(); + + /** + * @see: IList::SelectNextElement(). + */ + virtual void SelectNextElement(); + virtual void SelectPrevElement(); + virtual void SelectFirstElement(); + virtual void SelectLastElement(); + + /** + * @see IList::HandleAdditionalChildren + */ + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); + + // Called every time the auto-scrolling should be checked. + void UpdateAutoScroll(); + + /** + * 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); + + // 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); + + // 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. + virtual CRect GetListRect() const { return m_CachedActualSize; } + + // Returns whether SetupText() has run since the last message was received + // (and thus whether list items have possibly changed). + virtual bool GetModified() const { return m_Modified; } + + /** + * List of each element's relative y position. Will be + * one larger than m_Items, because it will end with the + * bottom of the last element. First element will always + * be zero, but still stored for easy handling. + */ + std::vector m_ItemsYPositions; + + virtual int GetHoveredItem(); + + class IListSelection* m_selection; +private: + // Whether the list's items have been modified since last handling a message. + bool m_Modified; + + // Used for doubleclick registration + int m_PrevSelectedItem; + + // Last time a click on an item was issued + double m_LastItemClickTime; + + void UpdateSelection(int oldIndex, int newIndex); + +}; + +#endif // INCLUDED_CLIST Index: source/gui/CListImpl.cpp =================================================================== --- /dev/null +++ source/gui/CListImpl.cpp @@ -0,0 +1,502 @@ +/* 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 "CListImpl.h" + +#include "gui/CGUI.h" +#include "gui/CGUIColor.h" +#include "gui/CGUIList.h" +#include "gui/CGUIScrollBarVertical.h" +#include "lib/external_libraries/libsdl.h" +#include "lib/timer.h" +#include "ps/Globals.h" + +#include "gui/IListSelection.h" + + +CListImpl::CListImpl(CGUI& pGUI) + : IGUIObject(pGUI), IGUITextOwner(pGUI), IGUIScrollBarOwner(pGUI), + m_Modified(false), m_PrevSelectedItem(-1), m_LastItemClickTime(0) +{ + // Add sprite_disabled! TODO + AddSetting("buffer_zone"); + AddSetting("font"); + AddSetting("scrollbar"); + AddSetting("scrollbar_style"); + AddSetting("sound_disabled"); + AddSetting("sound_selected"); + AddSetting("sprite"); + AddSetting("sprite_selectarea"); + AddSetting("cell_id"); + AddSetting("text_align"); + AddSetting("textcolor"); + AddSetting("textcolor_selected"); + AddSetting("auto_scroll"); + AddSetting("hovered"); + + // Each list item has both a name (in 'list') and an associated data string (in 'list_data') + AddSetting("list"); + AddSetting("list_data"); + + SetSetting("scrollbar", false, true); + SetSetting("hovered", -1, true); + SetSetting("auto_scroll", false, true); + + // Add scroll-bar + CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI); + bar->SetRightAligned(true); + AddScrollBar(bar); +} + +CListImpl::~CListImpl() +{ +} + +void CListImpl::SetSelection(class IListSelection* selection) +{ + m_selection = selection; +} + +void CListImpl::SetupText() +{ + m_Modified = true; + const CGUIList& pList = GetSetting("list"); + + //ENSURE(m_GeneratedTexts.size()>=1); + + m_ItemsYPositions.resize(pList.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(); + + const CStrW& font = GetSetting("font"); + + const bool scrollbar = GetSetting("scrollbar"); + + float width = GetListRect().GetWidth(); + // remove scrollbar if applicable + if (scrollbar && GetScrollBar(0).GetStyle()) + width -= GetScrollBar(0).GetStyle()->m_Width; + + const float buffer_zone = GetSetting("buffer_zone"); + + // Generate texts + float buffered_y = 0.f; + + for (size_t i = 0; i < pList.m_Items.size(); ++i) + { + CGUIText* text; + + if (!pList.m_Items[i].GetOriginalString().empty()) + text = &AddText(pList.m_Items[i], font, width, buffer_zone, this); + else + { + // Minimum height of a space character of the current font size + CGUIString align_string; + align_string.SetValue(L" "); + text = &AddText(align_string, font, width, buffer_zone, this); + } + + m_ItemsYPositions[i] = buffered_y; + buffered_y += text->GetSize().cy; + } + + m_ItemsYPositions[pList.m_Items.size()] = buffered_y; + + // Setup scrollbar + if (scrollbar) + { + GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back()); + GetScrollBar(0).SetScrollSpace(GetListRect().GetHeight()); + + CRect rect = GetListRect(); + GetScrollBar(0).SetX(rect.right); + GetScrollBar(0).SetY(rect.top); + GetScrollBar(0).SetZ(GetBufferedZ()); + GetScrollBar(0).SetLength(rect.bottom - rect.top); + } +} + +void CListImpl::HandleMessage(SGUIMessage& Message) +{ + IGUIScrollBarOwner::HandleMessage(Message); + //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead! + + m_Modified = false; + switch (Message.type) + { + case GUIM_SETTINGS_UPDATED: + if (Message.value == "list") { + SetupText(); + } + + // If selected is changed, call "SelectionChange" + if (Message.value == "selected") + { + // TODO: Check range + + if (GetSetting("auto_scroll")) + UpdateAutoScroll(); + + // TODO only works if lower-case, shouldn't it be made case sensitive instead? + ScriptEvent("selectionchange"); + } + + if (Message.value == "scrollbar") { + SetupText(); + } + + // Update scrollbar + if (Message.value == "scrollbar_style") + { + GetScrollBar(0).SetScrollBarStyle(GetSetting(Message.value)); + SetupText(); + } + + break; + + case GUIM_MOUSE_PRESS_LEFT: + { + if (!GetSetting("enabled")) + { + PlaySound("sound_disabled"); + break; + } + + int hovered = GetHoveredItem(); + if (hovered == -1) + break; + + m_selection->SelectionControl(hovered); + + UpdateAutoScroll(); + PlaySound("sound_selected"); + + if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && + hovered == m_PrevSelectedItem && + m_selection->IsEnabledDoubleClick()) + this->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT_ITEM, "mouseleftdoubleclickitem"); + else + this->SendEvent(GUIM_MOUSE_PRESS_LEFT_ITEM, "mouseleftclickitem"); + + m_LastItemClickTime = timer_Time(); + m_PrevSelectedItem = hovered; + break; + } + + case GUIM_MOUSE_LEAVE: + { + if (GetSetting("hovered") == -1) + break; + + SetSetting("hovered", -1, true); + ScriptEvent("hoverchange"); + break; + } + + case GUIM_MOUSE_OVER: + { + int hovered = GetHoveredItem(); + if (hovered == GetSetting("hovered")) + break; + + SetSetting("hovered", hovered, true); + ScriptEvent("hoverchange"); + break; + } + + case GUIM_LOAD: + { + GetScrollBar(0).SetScrollBarStyle(GetSetting("scrollbar_style")); + break; + } + + default: + break; + } + + IGUITextOwner::HandleMessage(Message); +} + +InReaction CListImpl::ManuallyHandleEvent(const SDL_Event_* ev) +{ + InReaction result = IN_PASS; + + if (ev->ev.type == SDL_KEYDOWN) + { + int szChar = ev->ev.key.keysym.sym; + + switch (szChar) + { + case SDLK_HOME: + SelectFirstElement(); + UpdateAutoScroll(); + result = IN_HANDLED; + break; + + case SDLK_END: + SelectLastElement(); + UpdateAutoScroll(); + result = IN_HANDLED; + break; + + case SDLK_UP: + SelectPrevElement(); + UpdateAutoScroll(); + result = IN_HANDLED; + break; + + case SDLK_DOWN: + SelectNextElement(); + UpdateAutoScroll(); + result = IN_HANDLED; + break; + + case SDLK_PAGEUP: + GetScrollBar(0).ScrollMinusPlenty(); + result = IN_HANDLED; + break; + + case SDLK_PAGEDOWN: + GetScrollBar(0).ScrollPlusPlenty(); + result = IN_HANDLED; + break; + + default: // Do nothing + result = IN_PASS; + } + } + + return result; +} + +void CListImpl::Draw() +{ + DrawList(m_selection->GetLastSelected(), "sprite", "sprite_selectarea", "textcolor"); +} + +void CListImpl::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 CListImpl::DrawList(const int selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) +{ + float bz = GetBufferedZ(); + + // First call draw on ScrollBarOwner + const bool scrollbar = GetSetting("scrollbar"); + + if (scrollbar) + IGUIScrollBarOwner::Draw(); + + { + CRect rect = GetListRect(); + + CGUISpriteInstance& sprite = GetSetting(_sprite); + CGUISpriteInstance& sprite_selectarea = GetSetting(_sprite_selected); + + const int cell_id = GetSetting("cell_id"); + m_pGUI.DrawSprite(sprite, cell_id, bz, rect); + + float scroll = 0.f; + if (scrollbar) + scroll = GetScrollBar(0).GetPos(); + + m_selection->DrawSelectedItems(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); + + const CGUIList& pList = GetSetting("list"); + const CGUIColor& color = GetSetting(_textcolor); + + for (size_t i = 0; i < pList.m_Items.size(); ++i) + { + if (m_ItemsYPositions[i+1] - scroll < 0 || + m_ItemsYPositions[i] - scroll > rect.GetHeight()) + continue; + + // Clipping area (we'll have to substract the scrollbar) + CRect cliparea = GetListRect(); + + if (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; + } + + DrawText(i, color, rect.TopLeft() - CPos(0.f, scroll - m_ItemsYPositions[i]), bz + 0.1f, cliparea); + } + } +} + +void CListImpl::AddItem(const CStrW& str, const CStrW& data) +{ + CGUIString gui_string; + gui_string.SetValue(str); + + // Do not send a settings-changed message + CGUIList& pList = GetSetting("list"); + pList.m_Items.push_back(gui_string); + + CGUIString data_string; + data_string.SetValue(data); + CGUIList& pListData = GetSetting("list_data"); + pListData.m_Items.push_back(data_string); + + // TODO Temp + SetupText(); +} + +bool CListImpl::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) +{ + int elmt_item = pFile->GetElementID("item"); + + if (child.GetNodeName() == elmt_item) + { + AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); + return true; + } + + return false; +} + +void CListImpl::UpdateAutoScroll() +{ + const int selected = m_selection->GetLastSelected(); + const bool scrollbar = GetSetting("scrollbar"); + + // No scrollbar, no scrolling (at least it's not made to work properly). + if (!scrollbar || selected < 0 || static_cast(selected) >= m_ItemsYPositions.size()) + return; + + float scroll = GetScrollBar(0).GetPos(); + + // Check upper boundary + if (m_ItemsYPositions[selected] < scroll) + { + GetScrollBar(0).SetPos(m_ItemsYPositions[selected]); + return; // this means, if it wants to align both up and down at the same time + // this will have precedence. + } + + // Check lower boundary + CRect rect = GetListRect(); + if (m_ItemsYPositions[selected+1]-rect.GetHeight() > scroll) + GetScrollBar(0).SetPos(m_ItemsYPositions[selected+1]-rect.GetHeight()); +} + +int CListImpl::GetHoveredItem() +{ + const bool scrollbar = GetSetting("scrollbar"); + const float scroll = scrollbar ? GetScrollBar(0).GetPos() : 0.f; + + const CRect& rect = GetListRect(); + CPos mouse = m_pGUI.GetMousePos(); + mouse.y += scroll; + + // Mouse is over scrollbar + if (scrollbar && GetScrollBar(0).IsVisible() && + mouse.x >= GetScrollBar(0).GetOuterRect().left && + mouse.x <= GetScrollBar(0).GetOuterRect().right) + return -1; + + const CGUIList& pList = GetSetting("list"); + for (size_t i = 0; i < pList.m_Items.size(); ++i) + if (mouse.y >= rect.top + m_ItemsYPositions[i] && + mouse.y < rect.top + m_ItemsYPositions[i + 1]) + return i; + + return -1; +} + +void CListImpl::UpdateSelection(int oldIndex, int newIndex) +{ + m_PrevSelectedItem = oldIndex; + PlaySound("sound_selected"); + + m_selection->SelectionControl(newIndex); +} + +void CListImpl::SelectNextElement() +{ + int selected = m_selection->GetLastSelected(); + + const CGUIList& pList = GetSetting("list"); + + if (selected != static_cast(pList.m_Items.size()) - 1) + UpdateSelection(selected, ++selected); +} + +void CListImpl::SelectPrevElement() +{ + int selected = m_selection->GetLastSelected(); + + if (selected > 0) + UpdateSelection(selected, --selected); +} + +void CListImpl::SelectFirstElement() +{ + int selected = m_selection->GetLastSelected(); + + if (selected >= 0) + UpdateSelection(selected, 0); +} + +void CListImpl::SelectLastElement() +{ + const CGUIList& pList = GetSetting("list"); + const int index = static_cast(pList.m_Items.size()) - 1; + int selected = m_selection->GetLastSelected(); + + if (selected != index) + UpdateSelection(selected, index); +} Index: source/gui/CListMultiple.h =================================================================== --- /dev/null +++ source/gui/CListMultiple.h @@ -0,0 +1,49 @@ +/* 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/IGUIObject.h" +#include "gui/IList.h" + +class IListSelection; + +class CListMultiple: public IList, IGUIObject +{ + GUI_OBJECT(CListMultiple); + +public: + CListMultiple(CGUI& pGUI); + virtual ~CListMultiple(); + virtual void AddItem(const CStrW& str, const CStrW& data); + virtual void SetSelection(class IListSelection* selection); + virtual void SetupText(); + virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); + virtual void Draw(); + 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 SelectNextElement(); + virtual void SelectPrevElement(); + virtual void SelectFirstElement(); + virtual void SelectLastElement(); + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); +private: + IList* m_listImpl; + IListSelection* m_listSelection; +}; + +#endif // INCLUDED_CLISTSINGLE Index: source/gui/CListMultiple.cpp =================================================================== --- /dev/null +++ source/gui/CListMultiple.cpp @@ -0,0 +1,83 @@ +/* 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 "CListImpl.h" +#include "CListSelectionMultipleImpl.h" + +#include "gui/CGUI.h" + +CListMultiple::CListMultiple(CGUI& pGUI) + : IGUIObject(pGUI) +{ + m_listImpl = new CListImpl(pGUI); + m_listSelection = new CListSelectionMultipleImpl(pGUI); + m_listSelection->SetImplementation(m_listImpl); + m_listImpl->SetSelection(m_listSelection); +} + +CListMultiple::~CListMultiple() +{ + +} +void CListMultiple::DrawSelection(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) +{ + m_listImpl->DrawSelection(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} +void CListMultiple::SetSelection(class IListSelection* selection) +{ + +} +void CListMultiple::AddItem(const CStrW& str, const CStrW& data) +{ + m_listImpl->AddItem(str, data); +} +void CListMultiple::SetupText() +{ + m_listImpl->SetupText(); +} +InReaction CListMultiple::ManuallyHandleEvent(const SDL_Event_* ev) +{ + return m_listImpl->ManuallyHandleEvent(ev); +} +void CListMultiple::Draw() +{ + m_listImpl->Draw(); +} +void CListMultiple::SelectNextElement() +{ + m_listImpl->SelectNextElement(); +} +void CListMultiple::SelectPrevElement() +{ + m_listImpl->SelectNextElement(); +} +void CListMultiple::SelectFirstElement() +{ + m_listImpl->SelectFirstElement(); +} +void CListMultiple::SelectLastElement() +{ + m_listImpl->SelectLastElement(); +} +bool CListMultiple::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) +{ + return m_listImpl->HandleAdditionalChildren(child, pFile); +} Index: source/gui/CListSelectionMultipleImpl.h =================================================================== --- /dev/null +++ source/gui/CListSelectionMultipleImpl.h @@ -0,0 +1,20 @@ +#include "gui/IListSelection.h" +#include "gui/CIntList.h" + +class CListSelectionMultipleImpl: public IListSelection, IGUIObject +{ +public: + CListSelectionMultipleImpl(CGUI& pGUI); + virtual void Draw(); + 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); + virtual void SetImplementation(IList*); +private: + IList* m_listImpl; + bool m_IsMultiSelecting; + bool m_AddMultiSelection; + int m_MultiSelectionFromItem; + std::vector m_SelectedItems; +}; Index: source/gui/CListSelectionMultipleImpl.cpp =================================================================== --- /dev/null +++ source/gui/CListSelectionMultipleImpl.cpp @@ -0,0 +1,116 @@ +#include "precompiled.h" + +#include "gui/CListSelectionMultipleImpl.h" +#include "gui/IList.h" + +#include "gui/IGUIObject.h" + +#include "ps/Globals.h" + +CListSelectionMultipleImpl::CListSelectionMultipleImpl(CGUI& pGUI) : + IGUIObject(pGUI) +{ + AddSetting("selected"); + SetSetting("selected", CIntList(), true); +} + +void CListSelectionMultipleImpl::Draw() +{ + +} + +void CListSelectionMultipleImpl::SetImplementation(class IList* impl) +{ + m_listImpl = impl; +} + +void CListSelectionMultipleImpl::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(); + if (m_MultiSelectionFromItem < select) + { + for (size_t itemId = m_MultiSelectionFromItem; itemId < select + 1; ++itemId) + m_SelectedItems.push_back(itemId); + } + else + { + for (int 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 CListSelectionMultipleImpl::IsEnabledDoubleClick() +{ + return !m_IsMultiSelecting && !m_AddMultiSelection; +} +void CListSelectionMultipleImpl::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; + + if (m_SelectedItems.empty()) + return; + for (size_t selection = 0; selection < m_SelectedItems.size(); ++selection) + m_listImpl->DrawSelection(m_SelectedItems[selection], scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} +int CListSelectionMultipleImpl::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/CListSelectionSingleImpl.h =================================================================== --- /dev/null +++ source/gui/CListSelectionSingleImpl.h @@ -0,0 +1,15 @@ +#include "gui/IListSelection.h" + +class CListSelectionSingleImpl: public IListSelection, IGUIObject +{ +public: + CListSelectionSingleImpl(CGUI& pGUI); + virtual void Draw(); + 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); + virtual void SetImplementation(IList*); +private: + IList* m_listImpl; +}; Index: source/gui/CListSelectionSingleImpl.cpp =================================================================== --- /dev/null +++ source/gui/CListSelectionSingleImpl.cpp @@ -0,0 +1,40 @@ +#include "precompiled.h" + +#include "gui/CListSelectionSingleImpl.h" +#include "gui/IList.h" + +#include "gui/IGUIObject.h" +CListSelectionSingleImpl::CListSelectionSingleImpl(CGUI& pGUI) : + IGUIObject(pGUI) +{ + AddSetting("selected");// Index selected. -1 is none. + SetSetting("selected", -1, true); +} + +void CListSelectionSingleImpl::Draw() +{ + +} + +void CListSelectionSingleImpl::SetImplementation(class IList* impl) +{ + m_listImpl = impl; +} + +void CListSelectionSingleImpl::SelectionControl(int &select) +{ + SetSetting("selected", select, true); +} +bool CListSelectionSingleImpl::IsEnabledDoubleClick() +{ + return true; +} +void CListSelectionSingleImpl::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) + m_listImpl->DrawSelection(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} +int CListSelectionSingleImpl::GetLastSelected() +{ + return GetSetting("selected"); +} Index: source/gui/CListSingle.h =================================================================== --- /dev/null +++ source/gui/CListSingle.h @@ -0,0 +1,50 @@ +/* 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/IGUIObject.h" +#include "gui/CGUISprite.h" +#include "gui/IList.h" + +class IListSelection; + +class CListSingle: public IList, IGUIObject +{ + GUI_OBJECT(CListSingle); + +public: + CListSingle(CGUI& pGUI); + virtual ~CListSingle(); + virtual void AddItem(const CStrW& str, const CStrW& data); + virtual void SetSelection(class IListSelection* selection); + virtual void SetupText(); + virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); + virtual void Draw(); + 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 SelectNextElement(); + virtual void SelectPrevElement(); + virtual void SelectFirstElement(); + virtual void SelectLastElement(); + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); +private: + IList* m_listImpl; + IListSelection* m_listSelection; +}; + +#endif // INCLUDED_CLISTSINGLE Index: source/gui/CListSingle.cpp =================================================================== --- /dev/null +++ source/gui/CListSingle.cpp @@ -0,0 +1,85 @@ +/* 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 "CListImpl.h" +#include "CListSelectionSingleImpl.h" + +#include "IListSelection.h" + +#include "gui/CGUI.h" + +CListSingle::CListSingle(CGUI& pGUI) + : IGUIObject(pGUI) +{ + m_listImpl = new CListImpl(pGUI); + m_listSelection = new CListSelectionSingleImpl(pGUI); + m_listSelection->SetImplementation(m_listImpl); + m_listImpl->SetSelection(m_listSelection); +} + +CListSingle::~CListSingle() +{ + +} +void CListSingle::DrawSelection(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) +{ + m_listImpl->DrawSelection(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} +void CListSingle::SetSelection(class IListSelection* selection) +{ + +} +void CListSingle::AddItem(const CStrW& str, const CStrW& data) +{ + m_listImpl->AddItem(str, data); +} +void CListSingle::SetupText() +{ + m_listImpl->SetupText(); +} +InReaction CListSingle::ManuallyHandleEvent(const SDL_Event_* ev) +{ + return m_listImpl->ManuallyHandleEvent(ev); +} +void CListSingle::Draw() +{ + m_listImpl->Draw(); +} +void CListSingle::SelectNextElement() +{ + m_listImpl->SelectNextElement(); +} +void CListSingle::SelectPrevElement() +{ + m_listImpl->SelectNextElement(); +} +void CListSingle::SelectFirstElement() +{ + m_listImpl->SelectFirstElement(); +} +void CListSingle::SelectLastElement() +{ + m_listImpl->SelectLastElement(); +} +bool CListSingle::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) +{ + return m_listImpl->HandleAdditionalChildren(child, pFile); +} Index: source/gui/COList.h =================================================================== --- source/gui/COList.h +++ /dev/null @@ -1,79 +0,0 @@ -/* 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_COLIST -#define INCLUDED_COLIST - -#include "CList.h" - -#include - -/** - * Represents a column. - */ -struct COListColumn -{ - // Avoid copying the strings. - NONCOPYABLE(COListColumn); - MOVABLE(COListColumn); - COListColumn() = default; - - CGUIColor m_TextColor; - CStr m_Id; - float m_Width; - CStrW m_Heading; -}; - -/** - * Multi-column list. One row can be selected by the user. - * Individual cells are clipped if the contained text is too long. - * - * The list can be sorted dynamically by JS code when a - * heading is clicked. - * A scroll-bar will appear when needed. - */ -class COList : public CList -{ - GUI_OBJECT(COList) - -public: - COList(CGUI& pGUI); - -protected: - void SetupText(); - void HandleMessage(SGUIMessage& Message); - - /** - * Handle the \ tag. - */ - virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); - - void DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor); - - virtual CRect GetListRect() const; - - /** - * Available columns. - */ - std::vector m_Columns; - -private: - // Width of space available for columns - float m_TotalAvailableColumnWidth; - float m_HeadingHeight; -}; - -#endif // INCLUDED_COLIST Index: source/gui/COList.cpp =================================================================== --- source/gui/COList.cpp +++ /dev/null @@ -1,445 +0,0 @@ -/* 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 "COList.h" - -#include "gui/CGUI.h" -#include "gui/CGUIColor.h" -#include "gui/CGUIList.h" -#include "gui/IGUIScrollBar.h" -#include "i18n/L10n.h" -#include "ps/CLogger.h" - -const float SORT_SPRITE_DIM = 16.0f; -const CPos COLUMN_SHIFT = CPos(0, 4); - -COList::COList(CGUI& pGUI) - : CList(pGUI), IGUIObject(pGUI) -{ - AddSetting("sprite_heading"); - AddSetting("sortable"); // The actual sorting is done in JS for more versatility - AddSetting("selected_column"); - AddSetting("selected_column_order"); - AddSetting("sprite_asc"); // Show the order of sorting - AddSetting("sprite_desc"); - AddSetting("sprite_not_sorted"); -} - -void COList::SetupText() -{ - const CGUIList& pList = GetSetting("list"); - m_ItemsYPositions.resize(pList.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(); - - const CStrW& font = GetSetting("font"); - const bool scrollbar = GetSetting("scrollbar"); - - m_TotalAvailableColumnWidth = GetListRect().GetWidth(); - // remove scrollbar if applicable - if (scrollbar && GetScrollBar(0).GetStyle()) - m_TotalAvailableColumnWidth -= GetScrollBar(0).GetStyle()->m_Width; - - const float buffer_zone = GetSetting("buffer_zone"); - - 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, font, width, buffer_zone, this); - m_HeadingHeight = std::max(m_HeadingHeight, text.GetSize().cy + COLUMN_SHIFT.y); - } - - // Generate texts - float buffered_y = 0.f; - - for (size_t i = 0; i < pList.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; - - CGUIList& pList_c = GetSetting("list_" + column.m_Id); - CGUIText* text; - if (!pList_c.m_Items[i].GetOriginalString().empty()) - text = &AddText(pList_c.m_Items[i], font, width, buffer_zone, this); - else - { - // Minimum height of a space character of the current font size - CGUIString align_string; - align_string.SetValue(L" "); - text = &AddText(align_string, font, width, buffer_zone, this); - } - shift = std::max(shift, text->GetSize().cy); - } - buffered_y += shift; - } - - m_ItemsYPositions[pList.m_Items.size()] = buffered_y; - - if (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 (!GetSetting("sortable")) - return; - - const CPos& mouse = m_pGUI.GetMousePos(); - if (!m_CachedActualSize.PointInside(mouse)) - return; - - // Copies, so that these settings are only modfied via SetSettings later. - CStr selectedColumn = GetSetting("selected_column"); - int selectedColumnOrder = GetSetting("selected_column_order"); - - float xpos = 0; - for (const COListColumn& column : m_Columns) - { - if (GetSetting("hidden_" + column.m_Id)) - 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) - { - if (column.m_Id != selectedColumn) - { - selectedColumnOrder = 1; - selectedColumn = column.m_Id; - } - else - selectedColumnOrder = -selectedColumnOrder; - - SetSetting("selected_column", selectedColumn, true); - SetSetting("selected_column_order", selectedColumnOrder, true); - - ScriptEvent("selectioncolumnchange"); - PlaySound("sound_selected"); - 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) - { - AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); - return true; - } - else if (child.GetNodeName() == elmt_column) - { - COListColumn column; - bool hidden = false; - - 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") - { - if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), hidden)) - LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); - } - 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(); - } - } - - AddSetting("list_" + column.m_Id); - AddSetting("hidden_" + column.m_Id); - SetSetting("hidden_" + column.m_Id, hidden, true); - - m_Columns.emplace_back(std::move(column)); - - SetupText(); - - return true; - } - - return false; -} - -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"); - - if (scrollbar) - IGUIScrollBarOwner::Draw(); - - CRect rect = GetListRect(); - - CGUISpriteInstance& sprite = GetSetting(_sprite); - CGUISpriteInstance& sprite_selectarea = GetSetting(_sprite_selected); - - const int cell_id = GetSetting("cell_id"); - - m_pGUI.DrawSprite(sprite, cell_id, bz, rect); - - float scroll = 0.f; - 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); - } - } - - // Draw line above column header - CGUISpriteInstance& sprite_heading = GetSetting("sprite_heading"); - CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, - m_CachedActualSize.top + m_HeadingHeight); - m_pGUI.DrawSprite(sprite_heading, cell_id, bz, rect_head); - - // Draw column headers - const bool sortable = GetSetting("sortable"); - const CStr& selectedColumn = GetSetting("selected_column"); - const int selectedColumnOrder = GetSetting("selected_column_order"); - const CGUIColor& color = GetSetting(_textcolor); - - float xpos = 0; - for (size_t col = 0; col < m_Columns.size(); ++col) - { - if (GetSetting("hidden_" + m_Columns[col].m_Id)) - continue; - - // Check if it's a decimal value, and if so, assume relative positioning. - float width = m_Columns[col].m_Width; - if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0) - width *= m_TotalAvailableColumnWidth; - - CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0); - - // Draw sort arrows in colum header - if (sortable) - { - CStr spriteName; - if (selectedColumn == m_Columns[col].m_Id) - { - if (selectedColumnOrder == 0) - LOGERROR("selected_column_order must not be 0"); - - if (selectedColumnOrder != -1) - spriteName = "sprite_asc"; - else - spriteName = "sprite_desc"; - } - else - spriteName = "sprite_not_sorted"; - - CGUISpriteInstance& sprite = GetSetting(spriteName); - m_pGUI.DrawSprite(sprite, cell_id, bz + 0.1f, CRect(leftTopCorner + CPos(width - SORT_SPRITE_DIM, 0), leftTopCorner + CPos(width, SORT_SPRITE_DIM))); - } - - // Draw column header text - DrawText(col, color, leftTopCorner + COLUMN_SHIFT, bz + 0.1f, rect_head); - xpos += width; - } - - // Draw list items for each column - const CGUIList& pList = GetSetting("list"); - const size_t objectsCount = m_Columns.size(); - for (size_t i = 0; i < pList.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 (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 col = 0; col < objectsCount; ++col) - { - if (GetSetting("hidden_" + m_Columns[col].m_Id)) - continue; - - // Determine text position and width - const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]); - - float width = m_Columns[col].m_Width; - // Check if it's a decimal value, and if so, assume relative positioning. - if (m_Columns[col].m_Width < 1 && m_Columns[col].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); - - // Draw list item - DrawText(objectsCount * (i +/*Heading*/1) + col, m_Columns[col].m_TextColor, textPos, bz + 0.1f, cliparea2); - xpos += width; - } - } -} Index: source/gui/COListImpl.h =================================================================== --- /dev/null +++ source/gui/COListImpl.h @@ -0,0 +1,43 @@ +#ifndef INCLUDED_COLISTIMPL +#define INCLUDED_COLISTIMPL + +#include "CListImpl.h" + +#include + +/** + * Represents a column. + */ +struct COListColumn +{ + // Avoid copying the strings. + NONCOPYABLE(COListColumn); + MOVABLE(COListColumn); + COListColumn() = default; + + CGUIColor m_TextColor; + CStr m_Id; + float m_Width; + CStrW m_Heading; +}; + +class COListImpl: public CListImpl +{ +public: + COListImpl(CGUI& pGUI); + virtual void SetupText(); + CRect GetListRect() const; + virtual void HandleMessage(SGUIMessage& Message); + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); + virtual void DrawList(const int selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor); + +private: + // Width of space available for columns + float m_TotalAvailableColumnWidth; + float m_HeadingHeight; + /** + * Available columns. + */ + std::vector m_Columns; +}; +#endif Index: source/gui/COListImpl.cpp =================================================================== --- /dev/null +++ source/gui/COListImpl.cpp @@ -0,0 +1,412 @@ +/* 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 "COListImpl.h" + +#include "gui/CGUI.h" +#include "gui/CGUIColor.h" +#include "gui/CGUIList.h" +#include "gui/CGUIScrollBarVertical.h" +#include "lib/external_libraries/libsdl.h" +#include "lib/timer.h" +#include "ps/Globals.h" +#include "i18n/L10n.h" + +#include "gui/IListSelection.h" + +const float SORT_SPRITE_DIM = 16.0f; +const CPos COLUMN_SHIFT = CPos(0, 4); + +COListImpl::COListImpl(CGUI& pGUI) + : IGUIObject(pGUI), CListImpl(pGUI) +{ + AddSetting("sprite_heading"); + AddSetting("sortable"); // The actual sorting is done in JS for more versatility + AddSetting("selected_column"); + AddSetting("selected_column_order"); + AddSetting("sprite_asc"); // Show the order of sorting + AddSetting("sprite_desc"); + AddSetting("sprite_not_sorted"); +} + +void COListImpl::SetupText() +{ + const CGUIList& pList = GetSetting("list"); + m_ItemsYPositions.resize(pList.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(); + + const CStrW& font = GetSetting("font"); + const bool scrollbar = GetSetting("scrollbar"); + + m_TotalAvailableColumnWidth = GetListRect().GetWidth(); + // remove scrollbar if applicable + if (scrollbar && GetScrollBar(0).GetStyle()) + m_TotalAvailableColumnWidth -= GetScrollBar(0).GetStyle()->m_Width; + + const float buffer_zone = GetSetting("buffer_zone"); + + 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, font, width, buffer_zone, this); + m_HeadingHeight = std::max(m_HeadingHeight, text.GetSize().cy + COLUMN_SHIFT.y); + } + + // Generate texts + float buffered_y = 0.f; + + for (size_t i = 0; i < pList.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; + + CGUIList& pList_c = GetSetting("list_" + column.m_Id); + CGUIText* text; + if (!pList_c.m_Items[i].GetOriginalString().empty()) + text = &AddText(pList_c.m_Items[i], font, width, buffer_zone, this); + else + { + // Minimum height of a space character of the current font size + CGUIString align_string; + align_string.SetValue(L" "); + text = &AddText(align_string, font, width, buffer_zone, this); + } + shift = std::max(shift, text->GetSize().cy); + } + buffered_y += shift; + } + + m_ItemsYPositions[pList.m_Items.size()] = buffered_y; + + if (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 COListImpl::GetListRect() const +{ + return m_CachedActualSize + CRect(0, m_HeadingHeight, 0, 0); +} +void COListImpl::HandleMessage(SGUIMessage& Message) +{ + CListImpl::HandleMessage(Message); + + switch (Message.type) + { + // If somebody clicks on the column heading + case GUIM_MOUSE_PRESS_LEFT: + { + if (!GetSetting("sortable")) + return; + + const CPos& mouse = m_pGUI.GetMousePos(); + if (!m_CachedActualSize.PointInside(mouse)) + return; + + // Copies, so that these settings are only modfied via SetSettings later. + CStr selectedColumn = GetSetting("selected_column"); + int selectedColumnOrder = GetSetting("selected_column_order"); + + float xpos = 0; + for (const COListColumn& column : m_Columns) + { + if (GetSetting("hidden_" + column.m_Id)) + 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) + { + if (column.m_Id != selectedColumn) + { + selectedColumnOrder = 1; + selectedColumn = column.m_Id; + } + else + selectedColumnOrder = -selectedColumnOrder; + + SetSetting("selected_column", selectedColumn, true); + SetSetting("selected_column_order", selectedColumnOrder, true); + + ScriptEvent("selectioncolumnchange"); + PlaySound("sound_selected"); + return; + } + xpos += width; + } + return; + } + default: + return; + } +} +bool COListImpl::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) + { + AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); + return true; + } + else if (child.GetNodeName() == elmt_column) + { + COListColumn column; + bool hidden = false; + + 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") + { + if (!CGUI::ParseString(&m_pGUI, attr_value.FromUTF8(), hidden)) + LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); + } + 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(); + } + } + + AddSetting("list_" + column.m_Id); + AddSetting("hidden_" + column.m_Id); + SetSetting("hidden_" + column.m_Id, hidden, true); + + m_Columns.emplace_back(std::move(column)); + + SetupText(); + return true; + } + + return false; +} +void COListImpl::DrawList(const int selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) +{ + const float bz = GetBufferedZ(); + const bool scrollbar = GetSetting("scrollbar"); + + if (scrollbar) + IGUIScrollBarOwner::Draw(); + + CRect rect = GetListRect(); + + CGUISpriteInstance& sprite = GetSetting(_sprite); + CGUISpriteInstance& sprite_selectarea = GetSetting(_sprite_selected); + + const int cell_id = GetSetting("cell_id"); + + m_pGUI.DrawSprite(sprite, cell_id, bz, rect); + + float scroll = 0.f; + if (scrollbar) + scroll = GetScrollBar(0).GetPos(); + + m_selection->DrawSelectedItems(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); + + // Draw line above column header + CGUISpriteInstance& sprite_heading = GetSetting("sprite_heading"); + CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, + m_CachedActualSize.top + m_HeadingHeight); + m_pGUI.DrawSprite(sprite_heading, cell_id, bz, rect_head); + + // Draw column headers + const bool sortable = GetSetting("sortable"); + const CStr& selectedColumn = GetSetting("selected_column"); + const int selectedColumnOrder = GetSetting("selected_column_order"); + const CGUIColor& color = GetSetting(_textcolor); + + float xpos = 0; + for (size_t col = 0; col < m_Columns.size(); ++col) + { + if (GetSetting("hidden_" + m_Columns[col].m_Id)) + continue; + + // Check if it's a decimal value, and if so, assume relative positioning. + float width = m_Columns[col].m_Width; + if (m_Columns[col].m_Width < 1 && m_Columns[col].m_Width > 0) + width *= m_TotalAvailableColumnWidth; + + CPos leftTopCorner = m_CachedActualSize.TopLeft() + CPos(xpos, 0); + + // Draw sort arrows in colum header + if (sortable) + { + CStr spriteName; + if (selectedColumn == m_Columns[col].m_Id) + { + if (selectedColumnOrder == 0) + LOGERROR("selected_column_order must not be 0"); + + if (selectedColumnOrder != -1) + spriteName = "sprite_asc"; + else + spriteName = "sprite_desc"; + } + else + spriteName = "sprite_not_sorted"; + + CGUISpriteInstance& sprite = GetSetting(spriteName); + m_pGUI.DrawSprite(sprite, cell_id, bz + 0.1f, CRect(leftTopCorner + CPos(width - SORT_SPRITE_DIM, 0), leftTopCorner + CPos(width, SORT_SPRITE_DIM))); + } + + // Draw column header text + DrawText(col, color, leftTopCorner + COLUMN_SHIFT, bz + 0.1f, rect_head); + xpos += width; + } + + // Draw list items for each column + const CGUIList& pList = GetSetting("list"); + const size_t objectsCount = m_Columns.size(); + for (size_t i = 0; i < pList.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 (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 col = 0; col < objectsCount; ++col) + { + if (GetSetting("hidden_" + m_Columns[col].m_Id)) + continue; + + // Determine text position and width + const CPos textPos = rect.TopLeft() + CPos(xpos, -scroll + m_ItemsYPositions[i]); + + float width = m_Columns[col].m_Width; + // Check if it's a decimal value, and if so, assume relative positioning. + if (m_Columns[col].m_Width < 1 && m_Columns[col].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); + + // Draw list item + DrawText(objectsCount * (i +/*Heading*/1) + col, m_Columns[col].m_TextColor, textPos, bz + 0.1f, cliparea2); + xpos += width; + } + } +} Index: source/gui/COListMultiple.h =================================================================== --- /dev/null +++ source/gui/COListMultiple.h @@ -0,0 +1,48 @@ +/* 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"gui/IGUIObject.h" +#include "gui/IList.h" + +class IListSelection; + +class COListMultiple: public IList, IGUIObject +{ + GUI_OBJECT(COListMultiple); + +public: + COListMultiple(CGUI& pGUI); + virtual void AddItem(const CStrW& str, const CStrW& data); + virtual void SetSelection(class IListSelection* selection); + virtual void SetupText(); + virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); + virtual void Draw(); + 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 SelectNextElement(); + virtual void SelectPrevElement(); + virtual void SelectFirstElement(); + virtual void SelectLastElement(); + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); +private: + IList* m_listImpl; + IListSelection* m_listSelection; +}; + +#endif // INCLUDED_COLISTMULTIPLE Index: source/gui/COListMultiple.cpp =================================================================== --- /dev/null +++ source/gui/COListMultiple.cpp @@ -0,0 +1,81 @@ +/* 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" + +#include "COListImpl.h" +#include "CListSelectionMultipleImpl.h" + +#include "IListSelection.h" + +#include "gui/CGUI.h" + +COListMultiple::COListMultiple(CGUI& pGUI) + : IGUIObject(pGUI) +{ + m_listImpl = new COListImpl(pGUI); + m_listSelection = new CListSelectionMultipleImpl(pGUI); + m_listSelection->SetImplementation(m_listImpl); + m_listImpl->SetSelection(m_listSelection); +} + +void COListMultiple::DrawSelection(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) +{ + m_listImpl->DrawSelection(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} +void COListMultiple::SetSelection(class IListSelection* selection) +{ + +} +void COListMultiple::AddItem(const CStrW& str, const CStrW& data) +{ + m_listImpl->AddItem(str, data); +} +void COListMultiple::SetupText() +{ + m_listImpl->SetupText(); +} +InReaction COListMultiple::ManuallyHandleEvent(const SDL_Event_* ev) +{ + return m_listImpl->ManuallyHandleEvent(ev); +} +void COListMultiple::Draw() +{ + m_listImpl->Draw(); +} +void COListMultiple::SelectNextElement() +{ + m_listImpl->SelectNextElement(); +} +void COListMultiple::SelectPrevElement() +{ + m_listImpl->SelectNextElement(); +} +void COListMultiple::SelectFirstElement() +{ + m_listImpl->SelectFirstElement(); +} +void COListMultiple::SelectLastElement() +{ + m_listImpl->SelectLastElement(); +} +bool COListMultiple::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) +{ + return m_listImpl->HandleAdditionalChildren(child, pFile); +} Index: source/gui/COListSingle.h =================================================================== --- /dev/null +++ source/gui/COListSingle.h @@ -0,0 +1,48 @@ +/* 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"gui/IGUIObject.h" +#include "gui/IList.h" + +class IListSelection; + +class COListSingle: public IList, IGUIObject +{ + GUI_OBJECT(COListSingle); + +public: + COListSingle(CGUI& pGUI); + virtual void AddItem(const CStrW& str, const CStrW& data); + virtual void SetSelection(class IListSelection* selection); + virtual void SetupText(); + virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); + virtual void Draw(); + 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 SelectNextElement(); + virtual void SelectPrevElement(); + virtual void SelectFirstElement(); + virtual void SelectLastElement(); + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile); +private: + IList* m_listImpl; + IListSelection* m_listSelection; +}; + +#endif // INCLUDED_COLISTSINGLE Index: source/gui/COListSingle.cpp =================================================================== --- /dev/null +++ source/gui/COListSingle.cpp @@ -0,0 +1,81 @@ +/* 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" + +#include "COListImpl.h" +#include "CListSelectionSingleImpl.h" + +#include "IListSelection.h" + +#include "gui/CGUI.h" + +COListSingle::COListSingle(CGUI& pGUI) + : IGUIObject(pGUI) +{ + m_listImpl = new COListImpl(pGUI); + m_listSelection = new CListSelectionSingleImpl(pGUI); + m_listSelection->SetImplementation(m_listImpl); + m_listImpl->SetSelection(m_listSelection); +} + +void COListSingle::DrawSelection(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) +{ + m_listImpl->DrawSelection(selected, scrollbar, scroll, sprite_selectarea, cell_id, bz, rect); +} +void COListSingle::SetSelection(class IListSelection* selection) +{ + +} +void COListSingle::AddItem(const CStrW& str, const CStrW& data) +{ + m_listImpl->AddItem(str, data); +} +void COListSingle::SetupText() +{ + m_listImpl->SetupText(); +} +InReaction COListSingle::ManuallyHandleEvent(const SDL_Event_* ev) +{ + return m_listImpl->ManuallyHandleEvent(ev); +} +void COListSingle::Draw() +{ + m_listImpl->Draw(); +} +void COListSingle::SelectNextElement() +{ + m_listImpl->SelectNextElement(); +} +void COListSingle::SelectPrevElement() +{ + m_listImpl->SelectNextElement(); +} +void COListSingle::SelectFirstElement() +{ + m_listImpl->SelectFirstElement(); +} +void COListSingle::SelectLastElement() +{ + m_listImpl->SelectLastElement(); +} +bool COListSingle::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) +{ + return m_listImpl->HandleAdditionalChildren(child, pFile); +} 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 @@ -27,6 +27,7 @@ #include "gui/CGUIList.h" #include "gui/CGUISeries.h" +#include "gui/CIntList.h" #ifndef GUITYPE_IGNORE_COPYABLE TYPE(bool) @@ -36,6 +37,7 @@ TYPE(EAlign) TYPE(EVAlign) TYPE(CPos) +TYPE(CIntList) #endif #ifndef GUITYPE_IGNORE_NONCOPYABLE Index: source/gui/IList.h =================================================================== --- /dev/null +++ source/gui/IList.h @@ -0,0 +1,38 @@ +#ifndef INCLUDE_ILIST +#define INCLUDE_ILIST +class IListSelection; +class IList +{ +public: + virtual void DrawSelection(const int selected, const bool scrollbar, const float scroll, CGUISpriteInstance& sprite_selectarea, const int cell_id, const float bz, CRect& rect) = 0; + /** + * Adds an item last to the list. + */ + virtual void AddItem(const CStrW& str, const CStrW& data) = 0; + virtual void SetSelection(class IListSelection*) = 0; + /** + * Sets up text, should be called every time changes has been + * made that can change the visual. + */ + virtual void SetupText() = 0; + /** + * Handle events manually to catch keyboard inputting. + */ + virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev) = 0; + /** + * Draws the List box + */ + virtual void Draw() = 0; + /** + * Easy select elements functions + */ + virtual void SelectNextElement() = 0; + virtual void SelectPrevElement() = 0; + virtual void SelectFirstElement() = 0; + virtual void SelectLastElement() = 0; + /** + * Handle the \ tag. + */ + virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) = 0; +}; +#endif Index: source/gui/IListSelection.h =================================================================== --- /dev/null +++ source/gui/IListSelection.h @@ -0,0 +1,13 @@ +#ifndef INCLUDE_ILISTSELECTION +#define INCLUDE_ILISTSELECTION +class IList; +class IListSelection +{ +public: + 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; + virtual bool IsEnabledDoubleClick() = 0; + virtual int GetLastSelected() = 0; + virtual void SelectionControl(int &select) = 0; + virtual void SetImplementation(IList*) = 0; +}; +#endif 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" @@ -260,6 +261,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; Index: source/gui/scripting/JSInterface_IGUIObject.cpp =================================================================== --- source/gui/scripting/JSInterface_IGUIObject.cpp +++ source/gui/scripting/JSInterface_IGUIObject.cpp @@ -22,7 +22,6 @@ #include "gui/CGUI.h" #include "gui/CGUIColor.h" #include "gui/CGUISetting.h" -#include "gui/CList.h" #include "gui/GUIManager.h" #include "gui/IGUIObject.h" #include "gui/IGUIScrollBar.h"