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.
This commit is contained in:
Robert Müller 2023-06-09 16:39:31 +02:00
parent b5524d6c52
commit e1035c3319
4 changed files with 115 additions and 52 deletions

View file

@ -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);
}

View file

@ -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<IO_MAX_PATH_LENGTH> m_DemoRenameInput;
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoSliceInput;
CLineInputBuffered<IO_MAX_PATH_LENGTH> 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};

View file

@ -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
}

View file

@ -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")