From 569753125e016e5f29df13a693663ddd7e1d458c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 17 Jun 2023 18:03:57 +0200 Subject: [PATCH 1/4] Add `IStorage::NumPaths` to get number of storage locations --- src/engine/shared/storage.cpp | 5 +++++ src/engine/storage.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp index 4b01a3d1d..bfdba8efe 100644 --- a/src/engine/shared/storage.cpp +++ b/src/engine/shared/storage.cpp @@ -309,6 +309,11 @@ public: m_aBinarydir[0] = '\0'; } + int NumPaths() const override + { + return m_NumPaths; + } + void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) override { char aBuffer[IO_MAX_PATH_LENGTH]; diff --git a/src/engine/storage.h b/src/engine/storage.h index 19d4a18e0..ddabded15 100644 --- a/src/engine/storage.h +++ b/src/engine/storage.h @@ -42,6 +42,8 @@ public: STORAGETYPE_CLIENT, }; + virtual int NumPaths() const = 0; + virtual void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) = 0; virtual void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) = 0; virtual IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = nullptr, int BufferSize = 0) = 0; From b5524d6c52d237633fae308725c58d0ca7078459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 26 Nov 2022 12:01:10 +0100 Subject: [PATCH 2/4] Ensure `ListDirectory/Info` entries are unique When multiple files or folder have the same name in multiple storage locations, only pass the first entry (file or folder, whichever comes first) to the callback. To prevent files with the same name form being listed multiple times, e.g. in the demo browser, editor file browser, and asset lists. --- src/engine/shared/storage.cpp | 47 ++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp index bfdba8efe..db09e6eb2 100644 --- a/src/engine/shared/storage.cpp +++ b/src/engine/shared/storage.cpp @@ -1,11 +1,14 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ -#include "linereader.h" #include #include + #include +#include #include +#include + #ifdef CONF_PLATFORM_HAIKU #include #endif @@ -314,14 +317,33 @@ public: return m_NumPaths; } + struct SListDirectoryInfoUniqueCallbackData + { + FS_LISTDIR_CALLBACK_FILEINFO m_pfnDelegate; + void *m_pDelegateUser; + std::unordered_set m_Seen; + }; + + static int ListDirectoryInfoUniqueCallback(const CFsFileInfo *pInfo, int IsDir, int Type, void *pUser) + { + SListDirectoryInfoUniqueCallbackData *pData = static_cast(pUser); + auto [_, InsertionTookPlace] = pData->m_Seen.emplace(pInfo->m_pName); + if(InsertionTookPlace) + return pData->m_pfnDelegate(pInfo, IsDir, Type, pData->m_pDelegateUser); + return 0; + } + void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) override { char aBuffer[IO_MAX_PATH_LENGTH]; if(Type == TYPE_ALL) { + SListDirectoryInfoUniqueCallbackData Data; + Data.m_pfnDelegate = pfnCallback; + Data.m_pDelegateUser = pUser; // list all available directories for(int i = TYPE_SAVE; i < m_NumPaths; ++i) - fs_listdir_fileinfo(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser); + fs_listdir_fileinfo(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), ListDirectoryInfoUniqueCallback, i, &Data); } else if(Type >= TYPE_SAVE && Type < m_NumPaths) { @@ -334,14 +356,33 @@ public: } } + struct SListDirectoryUniqueCallbackData + { + FS_LISTDIR_CALLBACK m_pfnDelegate; + void *m_pDelegateUser; + std::unordered_set m_Seen; + }; + + static int ListDirectoryUniqueCallback(const char *pName, int IsDir, int Type, void *pUser) + { + SListDirectoryUniqueCallbackData *pData = static_cast(pUser); + auto [_, InsertionTookPlace] = pData->m_Seen.emplace(pName); + if(InsertionTookPlace) + return pData->m_pfnDelegate(pName, IsDir, Type, pData->m_pDelegateUser); + return 0; + } + void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) override { char aBuffer[IO_MAX_PATH_LENGTH]; if(Type == TYPE_ALL) { + SListDirectoryUniqueCallbackData Data; + Data.m_pfnDelegate = pfnCallback; + Data.m_pDelegateUser = pUser; // list all available directories for(int i = TYPE_SAVE; i < m_NumPaths; ++i) - fs_listdir(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser); + fs_listdir(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), ListDirectoryUniqueCallback, i, &Data); } else if(Type >= TYPE_SAVE && Type < m_NumPaths) { 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 3/4] 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") From f19d102f70458a63031858540cfdf19459aa21d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 17 Jun 2023 00:15:04 +0200 Subject: [PATCH 4/4] Support selecting storage location in editor file browser Initially the file browser (maps, images, sound) shows the files from all storage locations combined like before when opening a file for reading. When saving a map, only the save storage location is shown and used 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 "maps"/"mapres" folder exists (whichever the dialog is currently showing). 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. The "Show 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. When navigating to the parent folder, the previous folder is now initially selected instead of resetting the selection. Add a link to the "themes" folder same as the link to the "downloadedmaps" folder to allow easier access to the themes with the map editor. The "folder tree" icon which is used for the ".." folder is now also used for the folder links in the storage location selection and for the links to the "downloadedmaps" and "themes" folders. Always sort links above normal folders but below the ".." folder. Fix alignment of font icons by using the correct flags. --- src/game/editor/editor.cpp | 272 ++++++++++++++++++++++++++----------- src/game/editor/editor.h | 6 + 2 files changed, 195 insertions(+), 83 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index c7590a948..13d66de39 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -823,7 +823,7 @@ bool CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUse CEditor *pEditor = (CEditor *)pUser; if(pEditor->Load(pFileName, StorageType)) { - pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder; + pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder || (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentLink && str_comp(pEditor->m_aFileDialogCurrentLink, "themes") == 0)); pEditor->m_Dialog = DIALOG_NONE; return true; } @@ -852,6 +852,8 @@ bool CEditor::CallbackAppendMap(const char *pFileName, int StorageType, void *pU bool CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUser) { + dbg_assert(StorageType == IStorage::TYPE_SAVE, "Saving only allowed for IStorage::TYPE_SAVE"); + CEditor *pEditor = static_cast(pUser); char aBuf[IO_MAX_PATH_LENGTH]; // add map extension @@ -865,7 +867,7 @@ bool CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUse if(pEditor->Save(pFileName)) { str_copy(pEditor->m_aFileName, pFileName); - pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder; + pEditor->m_ValidSaveFilename = true; pEditor->m_Map.m_Modified = false; } else @@ -888,6 +890,8 @@ bool CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUse bool CEditor::CallbackSaveCopyMap(const char *pFileName, int StorageType, void *pUser) { + dbg_assert(StorageType == IStorage::TYPE_SAVE, "Saving only allowed for IStorage::TYPE_SAVE"); + CEditor *pEditor = static_cast(pUser); char aBuf[IO_MAX_PATH_LENGTH]; // add map extension @@ -4542,7 +4546,7 @@ static int EditorListdirCallback(const CFsFileInfo *pInfo, int IsDir, int Storag { CEditor *pEditor = (CEditor *)pUser; if((pInfo->m_pName[0] == '.' && (pInfo->m_pName[1] == 0 || - (pInfo->m_pName[1] == '.' && pInfo->m_pName[2] == 0 && (!str_comp(pEditor->m_pFileDialogPath, "maps") || !str_comp(pEditor->m_pFileDialogPath, "mapres"))))) || + (pInfo->m_pName[1] == '.' && pInfo->m_pName[2] == 0 && (pEditor->m_FileDialogShowingRoot || (!pEditor->m_FileDialogMultipleStorages && (!str_comp(pEditor->m_pFileDialogPath, "maps") || !str_comp(pEditor->m_pFileDialogPath, "mapres"))))))) || (!IsDir && ((pEditor->m_FileDialogFileType == CEditor::FILETYPE_MAP && !str_endswith(pInfo->m_pName, ".map")) || (pEditor->m_FileDialogFileType == CEditor::FILETYPE_IMG && !str_endswith(pInfo->m_pName, ".png")) || (pEditor->m_FileDialogFileType == CEditor::FILETYPE_SOUND && !str_endswith(pInfo->m_pName, ".opus"))))) @@ -4614,54 +4618,57 @@ void CEditor::RenderFileDialog() if(m_FileDialogFileType == CEditor::FILETYPE_IMG || m_FileDialogFileType == CEditor::FILETYPE_SOUND) View.VSplitMid(&View, &Preview); - // title - CUIRect ButtonTimeModified, ButtonFileName; - Title.VSplitRight(10.0f, &Title, nullptr); - Title.VSplitRight(90.0f, &Title, &ButtonTimeModified); - Title.VSplitRight(10.0f, &Title, nullptr); - Title.VSplitRight(90.0f, &Title, &ButtonFileName); - Title.VSplitRight(10.0f, &Title, nullptr); - - const char *aSortIndicator[3] = {"▼", "", "▲"}; - - static int s_ButtonTimeModified = 0; - char aBufLabelButtonTimeModified[64]; - str_format(aBufLabelButtonTimeModified, sizeof(aBufLabelButtonTimeModified), "Time modified %s", aSortIndicator[m_SortByTimeModified + 1]); - if(DoButton_Editor(&s_ButtonTimeModified, aBufLabelButtonTimeModified, 0, &ButtonTimeModified, 0, "Sort by time modified")) + // title bar + if(!m_FileDialogShowingRoot) { - if(m_SortByTimeModified == 1) + CUIRect ButtonTimeModified, ButtonFileName; + Title.VSplitRight(10.0f, &Title, nullptr); + Title.VSplitRight(90.0f, &Title, &ButtonTimeModified); + Title.VSplitRight(10.0f, &Title, nullptr); + Title.VSplitRight(90.0f, &Title, &ButtonFileName); + Title.VSplitRight(10.0f, &Title, nullptr); + + const char *aSortIndicator[3] = {"▼", "", "▲"}; + + static int s_ButtonTimeModified = 0; + char aBufLabelButtonTimeModified[64]; + str_format(aBufLabelButtonTimeModified, sizeof(aBufLabelButtonTimeModified), "Time modified %s", aSortIndicator[m_SortByTimeModified + 1]); + if(DoButton_Editor(&s_ButtonTimeModified, aBufLabelButtonTimeModified, 0, &ButtonTimeModified, 0, "Sort by time modified")) { - m_SortByTimeModified = -1; - } - else if(m_SortByTimeModified == -1) - { - m_SortByTimeModified = 0; - } - else - { - m_SortByTimeModified = 1; + if(m_SortByTimeModified == 1) + { + m_SortByTimeModified = -1; + } + else if(m_SortByTimeModified == -1) + { + m_SortByTimeModified = 0; + } + else + { + m_SortByTimeModified = 1; + } + + RefreshFilteredFileList(); } - RefreshFilteredFileList(); - } - - static int s_ButtonFileName = 0; - char aBufLabelButtonFilename[64]; - str_format(aBufLabelButtonFilename, sizeof(aBufLabelButtonFilename), "Filename %s", aSortIndicator[m_SortByFilename + 1]); - if(DoButton_Editor(&s_ButtonFileName, aBufLabelButtonFilename, 0, &ButtonFileName, 0, "Sort by file name")) - { - if(m_SortByFilename == 1) + static int s_ButtonFileName = 0; + char aBufLabelButtonFilename[64]; + str_format(aBufLabelButtonFilename, sizeof(aBufLabelButtonFilename), "Filename %s", aSortIndicator[m_SortByFilename + 1]); + if(DoButton_Editor(&s_ButtonFileName, aBufLabelButtonFilename, 0, &ButtonFileName, 0, "Sort by file name")) { - m_SortByFilename = -1; - m_SortByTimeModified = 0; - } - else - { - m_SortByFilename = 1; - m_SortByTimeModified = 0; - } + if(m_SortByFilename == 1) + { + m_SortByFilename = -1; + m_SortByTimeModified = 0; + } + else + { + m_SortByFilename = 1; + m_SortByTimeModified = 0; + } - RefreshFilteredFileList(); + RefreshFilteredFileList(); + } } Title.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f); @@ -4669,13 +4676,13 @@ void CEditor::RenderFileDialog() UI()->DoLabel(&Title, m_pFileDialogTitle, 12.0f, TEXTALIGN_ML); // pathbox - char aPath[IO_MAX_PATH_LENGTH], aBuf[128 + IO_MAX_PATH_LENGTH]; - if(m_FilesSelectedIndex != -1) + if(m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType >= IStorage::TYPE_SAVE) + { + char aPath[IO_MAX_PATH_LENGTH], aBuf[128 + IO_MAX_PATH_LENGTH]; Storage()->GetCompletePath(m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType, m_pFileDialogPath, aPath, sizeof(aPath)); - else - aPath[0] = 0; - str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath); - UI()->DoLabel(&PathBox, aBuf, 10.0f, TEXTALIGN_ML); + str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath); + UI()->DoLabel(&PathBox, aBuf, 10.0f, TEXTALIGN_ML); + } const auto &&UpdateFileNameInput = [this]() { if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir) @@ -4898,14 +4905,16 @@ void CEditor::RenderFileDialog() } else { - if(str_comp(m_vpFilteredFileList[i]->m_aFilename, "..") == 0) + if(m_vpFilteredFileList[i]->m_IsLink || str_comp(m_vpFilteredFileList[i]->m_aFilename, "..") == 0) pIconType = FONT_ICON_FOLDER_TREE; else pIconType = FONT_ICON_FOLDER; } TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + 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()->SetCurFont(nullptr); SLabelProperties Props; @@ -4945,16 +4954,39 @@ void CEditor::RenderFileDialog() CUIRect Button; ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button); - bool IsDir = m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir; + const bool IsDir = m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir; if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, nullptr) || s_ListBox.WasItemActivated()) { if(IsDir) // folder { m_FileDialogFilterInput.Clear(); - if(str_comp(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, "..") == 0) // parent folder + const bool ParentFolder = str_comp(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, "..") == 0; + if(ParentFolder) // parent folder { + str_copy(m_aFilesSelectedName, fs_filename(m_pFileDialogPath)); + str_append(m_aFilesSelectedName, "/"); if(fs_parent_dir(m_pFileDialogPath)) - m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link + { + if(str_comp(m_pFileDialogPath, m_aFileDialogCurrentFolder) == 0) + { + m_FileDialogShowingRoot = true; + if(m_FileDialogStorageType == IStorage::TYPE_ALL) + { + m_aFilesSelectedName[0] = '\0'; // will select first list item + } + else + { + Storage()->GetCompletePath(m_FileDialogStorageType, m_pFileDialogPath, m_aFilesSelectedName, sizeof(m_aFilesSelectedName)); + str_append(m_aFilesSelectedName, "/"); + } + } + else + { + m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link + str_copy(m_aFilesSelectedName, m_aFileDialogCurrentLink); + str_append(m_aFilesSelectedName, "/"); + } + } } else // sub folder { @@ -4969,28 +5001,26 @@ void CEditor::RenderFileDialog() str_copy(aTemp, m_pFileDialogPath); str_format(m_pFileDialogPath, IO_MAX_PATH_LENGTH, "%s/%s", aTemp, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); } + if(m_FileDialogShowingRoot) + m_FileDialogStorageType = m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType; + m_FileDialogShowingRoot = false; } - FilelistPopulate(!str_comp(m_pFileDialogPath, "maps") || !str_comp(m_pFileDialogPath, "mapres") ? m_FileDialogStorageType : - m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType); + FilelistPopulate(m_FileDialogStorageType, ParentFolder); UpdateFileNameInput(); } else // file { + const int StorageType = m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType; str_format(m_aFileSaveName, sizeof(m_aFileSaveName), "%s/%s", m_pFileDialogPath, m_FileDialogFileNameInput.GetString()); if(!str_endswith(m_aFileSaveName, FILETYPE_EXTENSIONS[m_FileDialogFileType])) str_append(m_aFileSaveName, FILETYPE_EXTENSIONS[m_FileDialogFileType]); - if(!str_comp(m_pFileDialogButtonText, "Save")) + if(!str_comp(m_pFileDialogButtonText, "Save") && Storage()->FileExists(m_aFileSaveName, StorageType)) { - if(Storage()->FileExists(m_aFileSaveName, IStorage::TYPE_SAVE)) - { - m_PopupEventType = m_pfnFileDialogFunc == &CallbackSaveCopyMap ? POPEVENT_SAVE_COPY : POPEVENT_SAVE; - m_PopupEventActivated = true; - } - else if(m_pfnFileDialogFunc) - m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType, m_pFileDialogUser); + m_PopupEventType = m_pfnFileDialogFunc == &CallbackSaveCopyMap ? POPEVENT_SAVE_COPY : POPEVENT_SAVE; + m_PopupEventActivated = true; } else if(m_pfnFileDialogFunc) - m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType, m_pFileDialogUser); + m_pfnFileDialogFunc(m_aFileSaveName, StorageType, m_pFileDialogUser); } } @@ -5008,9 +5038,11 @@ void CEditor::RenderFileDialog() ButtonBar.VSplitRight(90.0f, &ButtonBar, &Button); if(DoButton_Editor(&s_ShowDirectoryButton, "Show directory", 0, &Button, 0, "Open the current directory in the file browser")) { - if(!open_file(aPath)) + char aOpenPath[IO_MAX_PATH_LENGTH]; + Storage()->GetCompletePath(m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : IStorage::TYPE_SAVE, m_pFileDialogPath, aOpenPath, sizeof(aOpenPath)); + if(!open_file(aOpenPath)) { - ShowFileDialogError("Failed to open the directory '%s'.", aPath); + ShowFileDialogError("Failed to open the directory '%s'.", aOpenPath); } } @@ -5051,7 +5083,7 @@ void CEditor::RenderFileDialog() else s_ConfirmDeletePopupContext.Reset(); - if(m_FileDialogStorageType == IStorage::TYPE_SAVE) + if(!m_FileDialogShowingRoot && m_FileDialogStorageType == IStorage::TYPE_SAVE) { ButtonBar.VSplitLeft(70.0f, &Button, &ButtonBar); if(DoButton_Editor(&s_NewFolderButton, "New folder", 0, &Button, 0, nullptr)) @@ -5076,7 +5108,8 @@ void CEditor::RefreshFilteredFileList() m_vpFilteredFileList.push_back(&Item); } } - SortFilteredFileList(); + if(!m_FileDialogShowingRoot) + SortFilteredFileList(); if(!m_vpFilteredFileList.empty()) { if(m_aFilesSelectedName[0]) @@ -5104,18 +5137,66 @@ void CEditor::FilelistPopulate(int StorageType, bool KeepSelection) { m_FileDialogLastPopulatedStorageType = StorageType; m_vCompleteFileList.clear(); - if(m_FileDialogStorageType != IStorage::TYPE_SAVE && !str_comp(m_pFileDialogPath, "maps")) + if(m_FileDialogShowingRoot) { - CFilelistItem Item; - str_copy(Item.m_aFilename, "downloadedmaps"); - str_copy(Item.m_aName, "downloadedmaps/"); - Item.m_IsDir = true; - Item.m_IsLink = true; - Item.m_StorageType = IStorage::TYPE_SAVE; - Item.m_TimeModified = 0; - m_vCompleteFileList.push_back(Item); + { + CFilelistItem Item; + str_copy(Item.m_aFilename, m_pFileDialogPath); + str_copy(Item.m_aName, "All combined"); + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = IStorage::TYPE_ALL; + Item.m_TimeModified = 0; + m_vCompleteFileList.push_back(Item); + } + + for(int CheckStorageType = IStorage::TYPE_SAVE; CheckStorageType < Storage()->NumPaths(); ++CheckStorageType) + { + if(Storage()->FolderExists(m_pFileDialogPath, CheckStorageType)) + { + CFilelistItem Item; + str_copy(Item.m_aFilename, m_pFileDialogPath); + Storage()->GetCompletePath(CheckStorageType, m_pFileDialogPath, Item.m_aName, sizeof(Item.m_aName)); + str_append(Item.m_aName, "/", sizeof(Item.m_aName)); + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = CheckStorageType; + Item.m_TimeModified = 0; + m_vCompleteFileList.push_back(Item); + } + } + } + else + { + // Add links for downloadedmaps and themes + if(!str_comp(m_pFileDialogPath, "maps")) + { + if(str_comp(m_pFileDialogButtonText, "Save") != 0 && Storage()->FolderExists("downloadedmaps", StorageType)) + { + CFilelistItem Item; + str_copy(Item.m_aFilename, "downloadedmaps"); + str_copy(Item.m_aName, "downloadedmaps/"); + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = StorageType; + Item.m_TimeModified = 0; + m_vCompleteFileList.push_back(Item); + } + + if(Storage()->FolderExists("themes", StorageType)) + { + CFilelistItem Item; + str_copy(Item.m_aFilename, "themes"); + str_copy(Item.m_aName, "themes/"); + Item.m_IsDir = true; + Item.m_IsLink = true; + Item.m_StorageType = StorageType; + Item.m_TimeModified = 0; + m_vCompleteFileList.push_back(Item); + } + } + Storage()->ListDirectoryInfo(StorageType, m_pFileDialogPath, EditorListdirCallback, this); } - Storage()->ListDirectoryInfo(StorageType, m_pFileDialogPath, EditorListdirCallback, this); RefreshFilteredFileList(); if(!KeepSelection) { @@ -5132,8 +5213,25 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle const char *pBasePath, const char *pDefaultName, bool (*pfnFunc)(const char *pFileName, int StorageType, void *pUser), void *pUser) { - UI()->ClosePopupMenus(); m_FileDialogStorageType = StorageType; + if(m_FileDialogStorageType == IStorage::TYPE_ALL) + { + int NumStoragedWithFolder = 0; + for(int CheckStorageType = IStorage::TYPE_SAVE; CheckStorageType < Storage()->NumPaths(); ++CheckStorageType) + { + if(Storage()->FolderExists(m_pFileDialogPath, CheckStorageType)) + { + NumStoragedWithFolder++; + } + } + m_FileDialogMultipleStorages = NumStoragedWithFolder > 1; + } + else + { + m_FileDialogMultipleStorages = false; + } + + UI()->ClosePopupMenus(); m_pFileDialogTitle = pTitle; m_pFileDialogButtonText = pButtonText; m_pfnFileDialogFunc = pfnFunc; @@ -5146,6 +5244,7 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle m_FileDialogFileType = FileType; m_FilePreviewState = PREVIEW_UNLOADED; m_FileDialogOpening = true; + m_FileDialogShowingRoot = false; if(pDefaultName) m_FileDialogFileNameInput.Set(pDefaultName); @@ -7150,8 +7249,15 @@ void CEditor::OnRender() void CEditor::LoadCurrentMap() { - Load(m_pClient->GetCurrentMapPath(), IStorage::TYPE_ALL); - m_ValidSaveFilename = true; + if(Load(m_pClient->GetCurrentMapPath(), IStorage::TYPE_SAVE)) + { + m_ValidSaveFilename = true; + } + else + { + Load(m_pClient->GetCurrentMapPath(), IStorage::TYPE_ALL); + m_ValidSaveFilename = false; + } CGameClient *pGameClient = (CGameClient *)Kernel()->RequestInterface(); vec2 Center = pGameClient->m_Camera.m_Center; diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 06e21ba80..09b19aeb5 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -977,6 +977,8 @@ public: CLineInputBuffered m_FileDialogFilterInput; char *m_pFileDialogPath; int m_FileDialogFileType; + bool m_FileDialogMultipleStorages = false; + bool m_FileDialogShowingRoot = false; int m_FilesSelectedIndex; CLineInputBuffered m_FileDialogNewFolderNameInput; @@ -1004,6 +1006,8 @@ public: return true; if(str_comp(pRhs->m_aFilename, "..") == 0) return false; + if(pLhs->m_IsLink != pRhs->m_IsLink) + return pLhs->m_IsLink; if(pLhs->m_IsDir != pRhs->m_IsDir) return pLhs->m_IsDir; return str_comp_filenames(pLhs->m_aName, pRhs->m_aName) < 0; @@ -1015,6 +1019,8 @@ public: return true; if(str_comp(pRhs->m_aFilename, "..") == 0) return false; + if(pLhs->m_IsLink != pRhs->m_IsLink) + return pLhs->m_IsLink; if(pLhs->m_IsDir != pRhs->m_IsDir) return pLhs->m_IsDir; return str_comp_filenames(pLhs->m_aName, pRhs->m_aName) > 0;