diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp
index 4b01a3d1d..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
@@ -309,14 +312,38 @@ public:
m_aBinarydir[0] = '\0';
}
+ int NumPaths() const override
+ {
+ 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)
{
@@ -329,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)
{
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;
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/editor/editor.cpp b/src/game/editor/editor.cpp
index 24d291111..157cb18a6 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);
@@ -7151,8 +7250,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;
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")