From e1035c331930cc07f4af689a57093e2f20881a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 9 Jun 2023 16:39:31 +0200 Subject: [PATCH] Support selecting storage location in demo browser Initially the demo browser shows the demos from all storage locations combined like before. The folder ".." is now also shown in the root folder, to navigate up to the storage location selection, if more than one storage location is present where a "demos" folder exists. Only the locations where one of those folders exists are shown in the storage location selection. Additionally "All combined" can be selected to go back to the combined view. When navigating to the parent folder, the previous folder is now initially selected instead of resetting the selection. The "Demos directory" button behavior is adjusted so that the folder that contains the currently selected item is opened. When the storage location selection is shown, the button opens the selected storage location instead. The "folder tree" icon which is used for the ".." folder is now also for the folder links in the storage location selection. Fix alignment of font icons by using the correct flags. The config variable `ui_demo_selected` is removed and replaced with an internal buffer. The selected demo was always reset when restarting anyway and this would also not work with demos in subfolders either. --- src/game/client/components/menus.cpp | 7 +- src/game/client/components/menus.h | 3 + src/game/client/components/menus_demo.cpp | 156 +++++++++++++++------- src/game/variables.h | 1 - 4 files changed, 115 insertions(+), 52 deletions(-) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index c3fae894a..27852d98d 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -70,6 +70,7 @@ CMenus::CMenus() m_ShowStart = true; str_copy(m_aCurrentDemoFolder, "demos"); + m_DemolistStorageType = IStorage::TYPE_ALL; m_DemoPlayerState = DEMOPLAYER_NONE; m_Dummy = false; @@ -1530,9 +1531,9 @@ int CMenus::Render() } else if(Storage()->RenameFile(aBufOld, aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) { - str_copy(g_Config.m_UiDemoSelected, m_DemoRenameInput.GetString()); - if(str_endswith(g_Config.m_UiDemoSelected, ".demo")) - g_Config.m_UiDemoSelected[str_length(g_Config.m_UiDemoSelected) - str_length(".demo")] = '\0'; + str_copy(m_aCurrentDemoSelectionName, m_DemoRenameInput.GetString()); + if(str_endswith(m_aCurrentDemoSelectionName, ".demo")) + m_aCurrentDemoSelectionName[str_length(m_aCurrentDemoSelectionName) - str_length(".demo")] = '\0'; DemolistPopulate(); DemolistOnUpdate(false); } diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index b63cedb21..3a8764006 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -259,6 +259,7 @@ protected: char m_aFilename[IO_MAX_PATH_LENGTH]; char m_aName[IO_MAX_PATH_LENGTH]; bool m_IsDir; + bool m_IsLink; int m_StorageType; time_t m_Date; @@ -318,6 +319,7 @@ protected: }; char m_aCurrentDemoFolder[IO_MAX_PATH_LENGTH]; + char m_aCurrentDemoSelectionName[IO_MAX_PATH_LENGTH]; CLineInputBuffered m_DemoRenameInput; CLineInputBuffered m_DemoSliceInput; CLineInputBuffered m_DemoRenderInput; @@ -325,6 +327,7 @@ protected: bool m_DemolistSelectedIsDir; bool m_DemolistSelectedReveal = false; int m_DemolistStorageType; + bool m_DemolistMultipleStorages = false; int m_Speed = 4; std::chrono::nanoseconds m_DemoPopulateStartTime{0}; diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index 73b078692..bbe0a36ba 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -724,9 +724,9 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView) { char aPath[IO_MAX_PATH_LENGTH]; str_format(aPath, sizeof(aPath), "%s/%s", m_aCurrentDemoFolder, m_DemoSliceInput.GetString()); - str_copy(g_Config.m_UiDemoSelected, m_DemoSliceInput.GetString()); - if(str_endswith(g_Config.m_UiDemoSelected, ".demo")) - g_Config.m_UiDemoSelected[str_length(g_Config.m_UiDemoSelected) - str_length(".demo")] = '\0'; + str_copy(m_aCurrentDemoSelectionName, m_DemoSliceInput.GetString()); + if(str_endswith(m_aCurrentDemoSelectionName, ".demo")) + m_aCurrentDemoSelectionName[str_length(m_aCurrentDemoSelectionName) - str_length(".demo")] = '\0'; m_DemoPlayerState = DEMOPLAYER_NONE; Client()->DemoSlice(aPath, CMenus::DemoFilterChat, &s_RemoveChat); DemolistPopulate(); @@ -741,7 +741,9 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView) int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser) { CMenus *pSelf = (CMenus *)pUser; - if(str_comp(pInfo->m_pName, ".") == 0 || (str_comp(pInfo->m_pName, "..") == 0 && str_comp(pSelf->m_aCurrentDemoFolder, "demos") == 0) || (!IsDir && !str_endswith(pInfo->m_pName, ".demo"))) + if(str_comp(pInfo->m_pName, ".") == 0 || + (str_comp(pInfo->m_pName, "..") == 0 && (pSelf->m_aCurrentDemoFolder[0] == '\0' || (!pSelf->m_DemolistMultipleStorages && str_comp(pSelf->m_aCurrentDemoFolder, "demos") == 0))) || + (!IsDir && !str_endswith(pInfo->m_pName, ".demo"))) { return 0; } @@ -762,6 +764,7 @@ int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int Stora Item.m_Date = pInfo->m_TimeModified; } Item.m_IsDir = IsDir != 0; + Item.m_IsLink = false; Item.m_StorageType = StorageType; pSelf->m_vDemos.push_back(Item); @@ -776,21 +779,66 @@ int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int Stora void CMenus::DemolistPopulate() { m_vDemos.clear(); - if(!str_comp(m_aCurrentDemoFolder, "demos")) - m_DemolistStorageType = IStorage::TYPE_ALL; - m_DemoPopulateStartTime = time_get_nanoseconds(); - Storage()->ListDirectoryInfo(m_DemolistStorageType, m_aCurrentDemoFolder, DemolistFetchCallback, this); - if(g_Config.m_BrDemoFetchInfo) - FetchAllHeaders(); + int NumStoragesWithDemos = 0; + for(int StorageType = IStorage::TYPE_SAVE; StorageType < Storage()->NumPaths(); ++StorageType) + { + if(Storage()->FolderExists("demos", StorageType)) + { + NumStoragesWithDemos++; + } + } + m_DemolistMultipleStorages = NumStoragesWithDemos > 1; - std::stable_sort(m_vDemos.begin(), m_vDemos.end()); + if(m_aCurrentDemoFolder[0] == '\0') + { + { + CDemoItem Item; + str_copy(Item.m_aFilename, "demos"); + str_copy(Item.m_aName, Localize("All combined")); + Item.m_InfosLoaded = false; + Item.m_Valid = false; + Item.m_Date = 0; + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = IStorage::TYPE_ALL; + m_vDemos.push_back(Item); + } + + for(int StorageType = IStorage::TYPE_SAVE; StorageType < Storage()->NumPaths(); ++StorageType) + { + if(Storage()->FolderExists("demos", StorageType)) + { + CDemoItem Item; + str_copy(Item.m_aFilename, "demos"); + Storage()->GetCompletePath(StorageType, "demos", Item.m_aName, sizeof(Item.m_aName)); + str_append(Item.m_aName, "/", sizeof(Item.m_aName)); + Item.m_InfosLoaded = false; + Item.m_Valid = false; + Item.m_Date = 0; + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = StorageType; + m_vDemos.push_back(Item); + } + } + } + else + { + m_DemoPopulateStartTime = time_get_nanoseconds(); + Storage()->ListDirectoryInfo(m_DemolistStorageType, m_aCurrentDemoFolder, DemolistFetchCallback, this); + + if(g_Config.m_BrDemoFetchInfo) + FetchAllHeaders(); + + std::stable_sort(m_vDemos.begin(), m_vDemos.end()); + } } void CMenus::DemolistOnUpdate(bool Reset) { if(Reset) - g_Config.m_UiDemoSelected[0] = '\0'; + m_aCurrentDemoSelectionName[0] = '\0'; else { bool Found = false; @@ -800,7 +848,7 @@ void CMenus::DemolistOnUpdate(bool Reset) { SelectedIndex++; - if(str_comp(g_Config.m_UiDemoSelected, Item.m_aName) == 0) + if(str_comp(m_aCurrentDemoSelectionName, Item.m_aName) == 0) { Found = true; break; @@ -813,7 +861,6 @@ void CMenus::DemolistOnUpdate(bool Reset) m_DemolistSelectedIndex = Reset ? !m_vDemos.empty() ? 0 : -1 : m_DemolistSelectedIndex >= (int)m_vDemos.size() ? m_vDemos.size() - 1 : m_DemolistSelectedIndex; - m_DemolistSelectedIsDir = m_DemolistSelectedIndex < 0 ? false : m_vDemos[m_DemolistSelectedIndex].m_IsDir; m_DemolistSelectedReveal = true; } @@ -854,7 +901,9 @@ void CMenus::RenderDemoList(CUIRect MainView) CDemoItem &Item = m_vDemos[m_DemolistSelectedIndex]; if(str_comp(Item.m_aFilename, "..") == 0) str_copy(aFooterLabel, Localize("Parent Folder")); - else if(m_DemolistSelectedIsDir) + else if(m_vDemos[m_DemolistSelectedIndex].m_IsLink) + str_copy(aFooterLabel, Localize("Folder Link")); + else if(m_vDemos[m_DemolistSelectedIndex].m_IsDir) str_copy(aFooterLabel, Localize("Folder")); else if(!FetchHeader(Item)) str_copy(aFooterLabel, Localize("Invalid Demo")); @@ -1090,7 +1139,7 @@ void CMenus::RenderDemoList(CUIRect MainView) FileIcon.x += 2.0f; const char *pIconType; - if(str_comp(Item.m_aFilename, "..") == 0) + if(Item.m_IsLink || str_comp(Item.m_aFilename, "..") == 0) pIconType = FONT_ICON_FOLDER_TREE; else if(Item.m_IsDir) pIconType = FONT_ICON_FOLDER; @@ -1103,7 +1152,9 @@ void CMenus::RenderDemoList(CUIRect MainView) TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); TextRender()->TextColor(IconColor); + TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_ML); + TextRender()->SetRenderFlags(0); TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->SetCurFont(nullptr); @@ -1155,7 +1206,7 @@ void CMenus::RenderDemoList(CUIRect MainView) { m_DemolistSelectedIndex = NewSelected; if(m_DemolistSelectedIndex >= 0) - str_copy(g_Config.m_UiDemoSelected, m_vDemos[m_DemolistSelectedIndex].m_aName); + str_copy(m_aCurrentDemoSelectionName, m_vDemos[m_DemolistSelectedIndex].m_aName); DemolistOnUpdate(false); } @@ -1174,22 +1225,41 @@ void CMenus::RenderDemoList(CUIRect MainView) } static CButtonContainer s_PlayButton; - if(DoButton_Menu(&s_PlayButton, m_DemolistSelectedIsDir ? Localize("Open") : Localize("Play", "Demo browser"), 0, &PlayRect) || s_ListBox.WasItemActivated() || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (Input()->KeyPress(KEY_P) && m_pClient->m_GameConsole.IsClosed())) + if(DoButton_Menu(&s_PlayButton, (m_DemolistSelectedIndex >= 0 && m_vDemos[m_DemolistSelectedIndex].m_IsDir) ? Localize("Open") : Localize("Play", "Demo browser"), 0, &PlayRect) || s_ListBox.WasItemActivated() || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (Input()->KeyPress(KEY_P) && m_pClient->m_GameConsole.IsClosed())) { if(m_DemolistSelectedIndex >= 0) { - if(m_DemolistSelectedIsDir) // folder + if(m_vDemos[m_DemolistSelectedIndex].m_IsDir) // folder { - if(str_comp(m_vDemos[m_DemolistSelectedIndex].m_aFilename, "..") == 0) // parent folder - fs_parent_dir(m_aCurrentDemoFolder); + const bool ParentFolder = str_comp(m_vDemos[m_DemolistSelectedIndex].m_aFilename, "..") == 0; + if(ParentFolder) // parent folder + { + str_copy(m_aCurrentDemoSelectionName, fs_filename(m_aCurrentDemoFolder)); + str_append(m_aCurrentDemoSelectionName, "/"); + if(fs_parent_dir(m_aCurrentDemoFolder)) + { + m_aCurrentDemoFolder[0] = '\0'; + if(m_DemolistStorageType == IStorage::TYPE_ALL) + { + m_aCurrentDemoSelectionName[0] = '\0'; // will select first list item + } + else + { + Storage()->GetCompletePath(m_DemolistStorageType, "demos", m_aCurrentDemoSelectionName, sizeof(m_aCurrentDemoSelectionName)); + str_append(m_aCurrentDemoSelectionName, "/"); + } + } + } else // sub folder { - str_append(m_aCurrentDemoFolder, "/"); + if(m_aCurrentDemoFolder[0] != '\0') + str_append(m_aCurrentDemoFolder, "/"); + else + m_DemolistStorageType = m_vDemos[m_DemolistSelectedIndex].m_StorageType; str_append(m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); - m_DemolistStorageType = m_vDemos[m_DemolistSelectedIndex].m_StorageType; } DemolistPopulate(); - DemolistOnUpdate(true); + DemolistOnUpdate(!ParentFolder); } else // file { @@ -1211,8 +1281,7 @@ void CMenus::RenderDemoList(CUIRect MainView) if(DoButton_Menu(&s_DirectoryButtonID, Localize("Demos directory"), 0, &DirectoryButton)) { char aBuf[IO_MAX_PATH_LENGTH]; - Storage()->GetCompletePath(IStorage::TYPE_SAVE, "demos", aBuf, sizeof(aBuf)); - Storage()->CreateFolder("demos", IStorage::TYPE_SAVE); + Storage()->GetCompletePath(m_DemolistSelectedIndex >= 0 ? m_vDemos[m_DemolistSelectedIndex].m_StorageType : IStorage::TYPE_SAVE, m_aCurrentDemoFolder[0] == '\0' ? "demos" : m_aCurrentDemoFolder, aBuf, sizeof(aBuf)); if(!open_file(aBuf)) { dbg_msg("menus", "couldn't open file '%s'", aBuf); @@ -1220,43 +1289,34 @@ void CMenus::RenderDemoList(CUIRect MainView) } GameClient()->m_Tooltips.DoToolTip(&s_DirectoryButtonID, &DirectoryButton, Localize("Open the directory that contains the demo files")); - if(!m_DemolistSelectedIsDir) + if(m_DemolistSelectedIndex >= 0 && !m_vDemos[m_DemolistSelectedIndex].m_IsDir) { static CButtonContainer s_DeleteButton; if(DoButton_Menu(&s_DeleteButton, Localize("Delete"), 0, &DeleteRect) || UI()->ConsumeHotkey(CUI::HOTKEY_DELETE) || (Input()->KeyPress(KEY_D) && m_pClient->m_GameConsole.IsClosed())) { - if(m_DemolistSelectedIndex >= 0) - { - char aBuf[128 + IO_MAX_PATH_LENGTH]; - str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete the demo '%s'?"), m_vDemos[m_DemolistSelectedIndex].m_aFilename); - PopupConfirm(Localize("Delete demo"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteDemo); - return; - } + char aBuf[128 + IO_MAX_PATH_LENGTH]; + str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete the demo '%s'?"), m_vDemos[m_DemolistSelectedIndex].m_aFilename); + PopupConfirm(Localize("Delete demo"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteDemo); + return; } static CButtonContainer s_RenameButton; if(DoButton_Menu(&s_RenameButton, Localize("Rename"), 0, &RenameRect)) { - if(m_DemolistSelectedIndex >= 0) - { - m_Popup = POPUP_RENAME_DEMO; - m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); - UI()->SetActiveItem(&m_DemoRenameInput); - return; - } + m_Popup = POPUP_RENAME_DEMO; + m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); + UI()->SetActiveItem(&m_DemoRenameInput); + return; } #if defined(CONF_VIDEORECORDER) static CButtonContainer s_RenderButton; if(DoButton_Menu(&s_RenderButton, Localize("Render"), 0, &RenderRect) || (Input()->KeyPress(KEY_R) && m_pClient->m_GameConsole.IsClosed())) { - if(m_DemolistSelectedIndex >= 0) - { - m_Popup = POPUP_RENDER_DEMO; - m_DemoRenderInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); - UI()->SetActiveItem(&m_DemoRenderInput); - return; - } + m_Popup = POPUP_RENDER_DEMO; + m_DemoRenderInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); + UI()->SetActiveItem(&m_DemoRenderInput); + return; } #endif } diff --git a/src/game/variables.h b/src/game/variables.h index f67d3ff6d..d7e01b4a7 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -140,7 +140,6 @@ MACRO_CONFIG_COL(UiColor, ui_color, 0xE4A046AF, CFGFLAG_CLIENT | CFGFLAG_SAVE | MACRO_CONFIG_INT(UiColorizePing, ui_colorize_ping, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight ping") MACRO_CONFIG_INT(UiColorizeGametype, ui_colorize_gametype, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight gametype") -MACRO_CONFIG_STR(UiDemoSelected, ui_demo_selected, 256, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Selected demo file") MACRO_CONFIG_INT(UiCloseWindowAfterChangingSetting, ui_close_window_after_changing_setting, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Close window after changing setting") MACRO_CONFIG_INT(UiUnreadNews, ui_unread_news, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Whether there is unread news")