diff --git a/src/base/system.c b/src/base/system.c index df457f386..7c373ac26 100644 --- a/src/base/system.c +++ b/src/base/system.c @@ -1569,6 +1569,59 @@ int net_init() return 0; } +int fs_listdir_info(const char *dir, FS_LISTDIR_INFO_CALLBACK cb, int type, void *user) +{ +#if defined(CONF_FAMILY_WINDOWS) + WIN32_FIND_DATA finddata; + HANDLE handle; + char buffer[1024*2]; + int length; + str_format(buffer, sizeof(buffer), "%s/*", dir); + + handle = FindFirstFileA(buffer, &finddata); + + if (handle == INVALID_HANDLE_VALUE) + return 0; + + str_format(buffer, sizeof(buffer), "%s/", dir); + length = str_length(buffer); + + /* add all the entries */ + do + { + str_copy(buffer+length, finddata.cFileName, (int)sizeof(buffer)-length); + if(cb(finddata.cFileName, fs_getmtime(buffer), fs_is_dir(buffer), type, user)) + break; + } + while (FindNextFileA(handle, &finddata)); + + FindClose(handle); + return 0; +#else + struct dirent *entry; + char buffer[1024*2]; + int length; + DIR *d = opendir(dir); + + if(!d) + return 0; + + str_format(buffer, sizeof(buffer), "%s/", dir); + length = str_length(buffer); + + while((entry = readdir(d)) != NULL) + { + str_copy(buffer+length, entry->d_name, (int)sizeof(buffer)-length); + if(cb(entry->d_name, fs_getmtime(buffer), fs_is_dir(buffer), type, user)) + break; + } + + /* close the directory and return */ + closedir(d); + return 0; +#endif +} + int fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user) { #if defined(CONF_FAMILY_WINDOWS) @@ -2060,15 +2113,20 @@ void str_hex(char *dst, int dst_size, const void *data, int data_size) } } +void str_timestamp_ex(time_t time_data, char *buffer, int buffer_size, const char *format) +{ + struct tm *time_info; + + time_info = localtime(&time_data); + strftime(buffer, buffer_size, format, time_info); + buffer[buffer_size-1] = 0; /* assure null termination */ +} + void str_timestamp(char *buffer, int buffer_size) { time_t time_data; - struct tm *time_info; - time(&time_data); - time_info = localtime(&time_data); - strftime(buffer, buffer_size, "%Y-%m-%d_%H-%M-%S", time_info); - buffer[buffer_size-1] = 0; /* assure null termination */ + str_timestamp_ex(time_data, buffer, buffer_size, "%Y-%m-%d_%H-%M-%S"); } int mem_comp(const void *a, const void *b, int size) diff --git a/src/base/system.h b/src/base/system.h index c112dd0fd..594541b98 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -1027,6 +1027,7 @@ void str_hex(char *dst, int dst_size, const void *data, int data_size); - Guarantees that buffer string will contain zero-termination. */ void str_timestamp(char *buffer, int buffer_size); +void str_timestamp_ex(time_t time, char *buffer, int buffer_size, const char *format); /* Group: Filesystem */ @@ -1044,7 +1045,9 @@ void str_timestamp(char *buffer, int buffer_size); Always returns 0. */ typedef int (*FS_LISTDIR_CALLBACK)(const char *name, int is_dir, int dir_type, void *user); +typedef int (*FS_LISTDIR_INFO_CALLBACK)(const char *name, time_t date, int is_dir, int dir_type, void *user); int fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user); +int fs_listdir_info(const char *dir, FS_LISTDIR_INFO_CALLBACK cb, int type, void *user); /* Function: fs_makedir diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index b94c87887..ab680c38b 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -62,6 +62,9 @@ MACRO_CONFIG_INT(BrSort, br_sort, 4, 0, 256, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") MACRO_CONFIG_INT(BrSortOrder, br_sort_order, 1, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") MACRO_CONFIG_INT(BrMaxRequests, br_max_requests, 25, 0, 1000, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Number of requests to use when refreshing server browser") +MACRO_CONFIG_INT(BrDemoSort, br_demo_sort, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") +MACRO_CONFIG_INT(BrDemoSortOrder, br_demo_sort_order, 0, 0, 1, CFGFLAG_SAVE|CFGFLAG_CLIENT, "") + MACRO_CONFIG_INT(SndBufferSize, snd_buffer_size, 512, 128, 32768, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound buffer size") #if defined(__ANDROID__) MACRO_CONFIG_INT(SndRate, snd_rate, 44100, 0, 0, CFGFLAG_SAVE|CFGFLAG_CLIENT, "Sound mixing rate") diff --git a/src/engine/shared/storage.cpp b/src/engine/shared/storage.cpp index 439044aac..e9c3cdb2e 100644 --- a/src/engine/shared/storage.cpp +++ b/src/engine/shared/storage.cpp @@ -230,6 +230,23 @@ public: dbg_msg("storage", "warning no data directory found"); } + + virtual void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_INFO_CALLBACK pfnCallback, void *pUser) + { + char aBuffer[MAX_PATH_LENGTH]; + if(Type == TYPE_ALL) + { + // list all available directories + for(int i = 0; i < m_NumPaths; ++i) + fs_listdir_info(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser); + } + else if(Type >= 0 && Type < m_NumPaths) + { + // list wanted directory + fs_listdir_info(GetPath(Type, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, Type, pUser); + } + } + virtual void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) { char aBuffer[MAX_PATH_LENGTH]; diff --git a/src/engine/shared/storage.h b/src/engine/shared/storage.h index d77cdac74..0781a067f 100644 --- a/src/engine/shared/storage.h +++ b/src/engine/shared/storage.h @@ -27,6 +27,7 @@ public: int FindDatadir(const char *pArgv0); virtual void ListDirectory(int Types, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser); + virtual void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_INFO_CALLBACK pfnCallback, void *pUser) = 0; virtual IOHANDLE OpenFile(const char *pFilename, int Flags, char *pBuffer = 0, int BufferSize = 0); diff --git a/src/engine/storage.h b/src/engine/storage.h index 9cb0a5a19..ed904d51b 100644 --- a/src/engine/storage.h +++ b/src/engine/storage.h @@ -20,6 +20,7 @@ public: }; 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_INFO_CALLBACK pfnCallback, void *pUser) = 0; virtual IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = 0, int BufferSize = 0) = 0; virtual bool FindFile(const char *pFilename, const char *pPath, int Type, char *pBuffer, int BufferSize) = 0; virtual bool RemoveFile(const char *pFilename, int Type) = 0; diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index c4b28e88e..79809f167 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -155,14 +155,45 @@ class CMenus : public CComponent char m_aName[128]; bool m_IsDir; int m_StorageType; + time_t m_Date; bool m_InfosLoaded; bool m_Valid; CDemoHeader m_Info; - bool operator<(const CDemoItem &Other) { return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : + bool operator<(const CDemoItem &Other) + { + if (g_Config.m_BrDemoSort) + { + if (g_Config.m_BrDemoSortOrder) + { + return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : m_IsDir && !Other.m_IsDir ? true : !m_IsDir && Other.m_IsDir ? false : - str_comp_filenames(m_aFilename, Other.m_aFilename) < 0; } + m_Date < Other.m_Date; + } + else + { + return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : + m_IsDir && !Other.m_IsDir ? true : !m_IsDir && Other.m_IsDir ? false : + m_Date > Other.m_Date; + } + } + else + { + if (g_Config.m_BrDemoSortOrder) + { + return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : + m_IsDir && !Other.m_IsDir ? true : !m_IsDir && Other.m_IsDir ? false : + str_comp_filenames(m_aFilename, Other.m_aFilename) < 0; + } + else + { + return !str_comp(m_aFilename, "..") ? true : !str_comp(Other.m_aFilename, "..") ? false : + m_IsDir && !Other.m_IsDir ? true : !m_IsDir && Other.m_IsDir ? false : + str_comp_filenames(m_aFilename, Other.m_aFilename) > 0; + } + } + } }; //sorted_array m_lDemos; @@ -174,7 +205,7 @@ class CMenus : public CComponent void DemolistOnUpdate(bool Reset); //void DemolistPopulate(); - static int DemolistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser); + static int DemolistFetchCallback(const char *pName, time_t Date, int IsDir, int StorageType, void *pUser); // friends struct CFriendItem diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index 4c331bf7a..d4abee08e 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -644,7 +644,7 @@ int CMenus::UiDoListboxEnd(float *pScrollValue, bool *pItemActivated) return gs_ListBoxNewSelected; } -int CMenus::DemolistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser) +int CMenus::DemolistFetchCallback(const char *pName, time_t Date, int IsDir, int StorageType, void *pUser) { CMenus *pSelf = (CMenus *)pUser; int Length = str_length(pName); @@ -664,6 +664,7 @@ int CMenus::DemolistFetchCallback(const char *pName, int IsDir, int StorageType, { str_copy(Item.m_aName, pName, min(static_cast(sizeof(Item.m_aName)), Length-4)); Item.m_InfosLoaded = false; + Item.m_Date = Date; } Item.m_IsDir = IsDir != 0; Item.m_StorageType = StorageType; @@ -677,12 +678,34 @@ void CMenus::DemolistPopulate() m_lDemos.clear(); if(!str_comp(m_aCurrentDemoFolder, "demos")) m_DemolistStorageType = IStorage::TYPE_ALL; - Storage()->ListDirectory(m_DemolistStorageType, m_aCurrentDemoFolder, DemolistFetchCallback, this); + Storage()->ListDirectoryInfo(m_DemolistStorageType, m_aCurrentDemoFolder, DemolistFetchCallback, this); m_lDemos.sort_range(); } void CMenus::DemolistOnUpdate(bool Reset) { + if (Reset) + g_Config.m_UiDemoSelected[0] = '\0'; + else + { + bool Found = false; + int SelectedIndex = -1; + // search for selected index + for(sorted_array::range r = m_lDemos.all(); !r.empty(); r.pop_front()) + { + SelectedIndex++; + + if (str_comp(g_Config.m_UiDemoSelected, r.front().m_aName) == 0) + { + Found = true; + break; + } + } + + if (Found) + m_DemolistSelectedIndex = SelectedIndex; + } + m_DemolistSelectedIndex = Reset ? m_lDemos.size() > 0 ? 0 : -1 : m_DemolistSelectedIndex >= m_lDemos.size() ? m_lDemos.size()-1 : m_DemolistSelectedIndex; m_DemolistSelectedIsDir = m_DemolistSelectedIndex < 0 ? false : m_lDemos[m_DemolistSelectedIndex].m_IsDir; @@ -807,6 +830,7 @@ void CMenus::RenderDemoList(CUIRect MainView) UI()->DoLabelScaled(&Right, m_lDemos[m_DemolistSelectedIndex].m_Info.m_aNetversion, 14.0f, -1); } + // demo list CUIRect Headers; @@ -827,19 +851,20 @@ void CMenus::RenderDemoList(CUIRect MainView) enum { - COL_DEMONAME=0, + COL_ICON=0, + COL_DEMONAME, COL_DATE, - SORT_DEMONAME, + SORT_DEMONAME=0, SORT_DATE, }; static CColumn s_aCols[] = { + {COL_ICON, -1, " ", -1, 14.0f, 0, {0}, {0}}, {COL_DEMONAME, SORT_DEMONAME, "Demo", 0, 0.0f, 0, {0}, {0}}, - {COL_DATE, SORT_DATE, "Date", 1, 300.0f, 0, {0}, {0}} + {COL_DATE, SORT_DATE, "Date", 1, 150.0f, 0, {0}, {0}}, }; - RenderTools()->DrawUIRect(&Headers, vec4(0.0f,0,0,0.15f), 0, 0); int NumCols = sizeof(s_aCols)/sizeof(CColumn); @@ -877,34 +902,66 @@ void CMenus::RenderDemoList(CUIRect MainView) // do headers for(int i = 0; i < NumCols; i++) { - if(DoButton_GridHeader(s_aCols[i].m_Caption, s_aCols[i].m_Caption, g_Config.m_BrSort == s_aCols[i].m_Sort, &s_aCols[i].m_Rect)) + if(DoButton_GridHeader(s_aCols[i].m_Caption, s_aCols[i].m_Caption, g_Config.m_BrDemoSort == s_aCols[i].m_Sort, &s_aCols[i].m_Rect)) { if(s_aCols[i].m_Sort != -1) { - if(g_Config.m_BrSort == s_aCols[i].m_Sort) - g_Config.m_BrSortOrder ^= 1; + if(g_Config.m_BrDemoSort == s_aCols[i].m_Sort) + g_Config.m_BrDemoSortOrder ^= 1; else - g_Config.m_BrSortOrder = 0; - g_Config.m_BrSort = s_aCols[i].m_Sort; + g_Config.m_BrDemoSortOrder = 0; + g_Config.m_BrDemoSort = s_aCols[i].m_Sort; } + + DemolistPopulate(); + DemolistOnUpdate(false); } } - + + // scrollbar + CUIRect Scroll; +#if defined(__ANDROID__) + ListBox.VSplitRight(50, &ListBox, &Scroll); +#else + ListBox.VSplitRight(15, &ListBox, &Scroll); +#endif + + int Num = (int)(ListBox.h/s_aCols[0].m_Rect.h) + 1; + static int s_ScrollBar = 0; + static float s_ScrollValue = 0; + + Scroll.HMargin(5.0f, &Scroll); + s_ScrollValue = DoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); + + int ScrollNum = m_lDemos.size()-Num+1; + if(ScrollNum > 0) + { + if(m_ScrollOffset) + { + s_ScrollValue = (float)(m_ScrollOffset)/ScrollNum; + m_ScrollOffset = 0; + } + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&ListBox)) + s_ScrollValue -= 3.0f/ScrollNum; + if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&ListBox)) + s_ScrollValue += 3.0f/ScrollNum; + } + else + ScrollNum = 0; + + if(s_ScrollValue < 0) s_ScrollValue = 0; + if(s_ScrollValue > 1) s_ScrollValue = 1; + + // set clipping + UI()->ClipEnable(&ListBox); + CUIRect OriginalView = ListBox; + ListBox.y -= s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h; + int NewSelected = -1; int ItemIndex = -1; for(sorted_array::range r = m_lDemos.all(); !r.empty(); r.pop_front()) - /*{ - CListboxItem Item = UiDoListboxNextItem((void*)(&r.front())); - if(Item.m_Visible) - { - Item.m_Rect.VSplitLeft(Item.m_Rect.h, &FileIcon, &Item.m_Rect); - Item.m_Rect.VSplitLeft(5.0f, 0, &Item.m_Rect); - DoButton_Icon(IMAGE_FILEICONS, r.front().m_IsDir?SPRITE_FILE_FOLDER:SPRITE_FILE_DEMO1, &FileIcon); - UI()->DoLabel(&Item.m_Rect, r.front().m_aName, Item.m_Rect.h*ms_FontmodHeight, -1); - } - }*/ { ItemIndex++; @@ -914,7 +971,7 @@ void CMenus::RenderDemoList(CUIRect MainView) ListBox.HSplitTop(ms_ListheaderHeight, &Row, &ListBox); SelectHitBox = Row; - int Selected = str_comp(g_Config.m_UiDemoSelected, r.front().m_aName) == 0; + int Selected = ItemIndex == m_DemolistSelectedIndex; // make sure that only those in view can be selected if(Row.y+Row.h > OriginalView.y && Row.y < OriginalView.y+OriginalView.h) @@ -939,19 +996,17 @@ void CMenus::RenderDemoList(CUIRect MainView) { NewSelected = ItemIndex; str_copy(g_Config.m_UiDemoSelected, r.front().m_aName, sizeof(g_Config.m_UiDemoSelected)); + DemolistOnUpdate(false); #if defined(__ANDROID__) if(NewSelected == m_DoubleClickIndex) DoubleClicked = 1; #endif + m_DoubleClickIndex = NewSelected; } } else { - // reset active item, if not visible - //if(UI()->ActiveItem() == pItem) - // UI()->SetActiveItem(0); - // don't render invisible items continue; } @@ -967,7 +1022,11 @@ void CMenus::RenderDemoList(CUIRect MainView) int ID = s_aCols[c].m_ID; - if(ID == COL_DEMONAME) + if (ID == COL_ICON) + { + DoButton_Icon(IMAGE_FILEICONS, r.front().m_IsDir?SPRITE_FILE_FOLDER:SPRITE_FILE_DEMO1, &Button); + } + else if(ID == COL_DEMONAME) { CTextCursor Cursor; TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); @@ -976,46 +1035,29 @@ void CMenus::RenderDemoList(CUIRect MainView) TextRender()->TextEx(&Cursor, r.front().m_aName, -1); } - else if (ID == COL_DATE) + else if (ID == COL_DATE && !r.front().m_IsDir) { CTextCursor Cursor; TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Button.w; - TextRender()->TextEx(&Cursor, "somedate", -1); + char aBuf[256]; + str_timestamp_ex(r.front().m_Date, aBuf, sizeof(aBuf), "%Y-%m-%d %H:%M:%S"); + TextRender()->TextEx(&Cursor, aBuf, -1); } - } } -/* CUIRect Scroll; -#if defined(__ANDROID__) - View.VSplitRight(50, &View, &Scroll); -#else - View.VSplitRight(15, &View, &Scroll); -#endif*/ + UI()->ClipDisable(); + - /*static int s_DemoListId = 0; - static float s_ScrollValue = 0; -#if defined(__ANDROID__) - UiDoListboxStart(&s_DemoListId, &ListBox, 50.0f, Localize("Demos"), aFooterLabel, m_lDemos.size(), 1, m_DemolistSelectedIndex, s_ScrollValue); -#else - UiDoListboxStart(&s_DemoListId, &ListBox, 17.0f, Localize("Demos"), aFooterLabel, m_lDemos.size(), 1, m_DemolistSelectedIndex, s_ScrollValue); -#endif - for(sorted_array::range r = m_lDemos.all(); !r.empty(); r.pop_front()) - { - CListboxItem Item = UiDoListboxNextItem((void*)(&r.front())); - if(Item.m_Visible) - { - Item.m_Rect.VSplitLeft(Item.m_Rect.h, &FileIcon, &Item.m_Rect); - Item.m_Rect.VSplitLeft(5.0f, 0, &Item.m_Rect); - DoButton_Icon(IMAGE_FILEICONS, r.front().m_IsDir?SPRITE_FILE_FOLDER:SPRITE_FILE_DEMO1, &FileIcon); - UI()->DoLabel(&Item.m_Rect, r.front().m_aName, Item.m_Rect.h*ms_FontmodHeight, -1); - } - }*/ bool Activated = false; - /*m_DemolistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, &Activated); - DemolistOnUpdate(false);*/ + + if (m_EnterPressed || (Input()->MouseDoubleClick() && UI()->ActiveItem() == m_lDemos[m_DemolistSelectedIndex].m_aName)) + { + UI()->SetActiveItem(0); + Activated = true; + } static int s_RefreshButton = 0; if(DoButton_Menu(&s_RefreshButton, Localize("Refresh"), 0, &RefreshRect))