diff --git a/datasrc/ui/heavens_day.map b/datasrc/ui/themes/heavens_day.map similarity index 100% rename from datasrc/ui/heavens_day.map rename to datasrc/ui/themes/heavens_day.map diff --git a/datasrc/ui/heavens_night.map b/datasrc/ui/themes/heavens_night.map similarity index 100% rename from datasrc/ui/heavens_night.map rename to datasrc/ui/themes/heavens_night.map diff --git a/datasrc/ui/menu_day.map b/datasrc/ui/themes/jungle_day.map similarity index 100% rename from datasrc/ui/menu_day.map rename to datasrc/ui/themes/jungle_day.map diff --git a/datasrc/ui/menu_night.map b/datasrc/ui/themes/jungle_night.map similarity index 100% rename from datasrc/ui/menu_night.map rename to datasrc/ui/themes/jungle_night.map diff --git a/src/game/client/components/maplayers.cpp b/src/game/client/components/maplayers.cpp index fd54cd8c5..bf6f6a030 100644 --- a/src/game/client/components/maplayers.cpp +++ b/src/game/client/components/maplayers.cpp @@ -44,12 +44,23 @@ void CMapLayers::LoadBackgroundMap() int HourOfTheDay = time_houroftheday(); char aBuf[128]; - str_format(aBuf, sizeof(aBuf), "ui/%s_%s.map", g_Config.m_ClMenuMap, (HourOfTheDay >= 6 && HourOfTheDay < 18) ? "day" : "night"); + // check for the appropriate day/night map + str_format(aBuf, sizeof(aBuf), "ui/themes/%s_%s.map", g_Config.m_ClMenuMap, (HourOfTheDay >= 6 && HourOfTheDay < 18) ? "day" : "night"); if(!m_pMenuMap->Load(aBuf, m_pClient->Storage())) { - str_format(aBuf, sizeof(aBuf), "map '%s' not found", g_Config.m_ClMenuMap); - Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); - return; + // fall back on generic map + str_format(aBuf, sizeof(aBuf), "ui/themes/%s.map", g_Config.m_ClMenuMap); + if(!m_pMenuMap->Load(aBuf, m_pClient->Storage())) + { + // fall back on day/night alternative map + str_format(aBuf, sizeof(aBuf), "ui/themes/%s_%s.map", g_Config.m_ClMenuMap, (HourOfTheDay >= 6 && HourOfTheDay < 18) ? "night" : "day"); + if(!m_pMenuMap->Load(aBuf, m_pClient->Storage())) + { + str_format(aBuf, sizeof(aBuf), "map '%s' not found", g_Config.m_ClMenuMap); + Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); + return; + } + } } str_format(aBuf, sizeof(aBuf), "loaded map '%s'", g_Config.m_ClMenuMap); diff --git a/src/game/client/components/maplayers.h b/src/game/client/components/maplayers.h index 07a90cb33..09044959c 100644 --- a/src/game/client/components/maplayers.h +++ b/src/game/client/components/maplayers.h @@ -21,8 +21,8 @@ class CMapLayers : public CComponent static void EnvelopeEval(float TimeOffset, int Env, float *pChannels, void *pUser); - void LoadBackgroundMap(); void LoadEnvPoints(const CLayers *pLayers, array& lEnvPoints); + void LoadBackgroundMap(); public: enum diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index dd37c9529..f4ca0b261 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -202,6 +202,24 @@ private: const CMenuImage *FindMenuImage(const char* pName); + // themes + class CTheme + { + public: + CTheme() {} + CTheme(const char *n, bool HasDay, bool HasNight) : m_Name(n), m_HasDay(HasDay), m_HasNight(HasNight) {} + + string m_Name; + bool m_HasDay; + bool m_HasNight; + IGraphics::CTextureHandle m_IconTexture; + bool operator<(const CTheme &Other) { return m_Name < Other.m_Name; } + }; + sorted_array m_lThemes; + + static int ThemeScan(const char *pName, int IsDir, int DirType, void *pUser); + static int ThemeIconScan(const char *pName, int IsDir, int DirType, void *pUser); + int64 m_LastInput; // loading @@ -511,6 +529,7 @@ private: // found in menus_settings.cpp void RenderLanguageSelection(CUIRect MainView, bool Header=true); + void RenderThemeSelection(CUIRect MainView, bool Header=true); void RenderHSLPicker(CUIRect Picker); void RenderSkinSelection(CUIRect MainView); void RenderSkinPartSelection(CUIRect MainView); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 9016a24a2..95226d5bc 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -566,6 +566,90 @@ public: bool operator<(const CLanguage &Other) { return m_Name < Other.m_Name; } }; + +int CMenus::ThemeScan(const char *pName, int IsDir, int DirType, void *pUser) +{ + CMenus *pSelf = (CMenus *)pUser; + int l = str_length(pName); + + if(l < 5 || IsDir || str_comp(pName+l-4, ".map") != 0) + return 0; + char aFullName[128]; + char aThemeName[128]; + str_copy(aFullName, pName, min((int)sizeof(aFullName),l-3)); + + l = str_length(aFullName); + bool isDay = false; + bool isNight = false; + if(l > 4 && str_comp(aFullName+l-4, "_day") == 0) + { + str_copy(aThemeName, pName, min((int)sizeof(aThemeName),l-3)); + isDay = true; + } + else if(l > 6 && str_comp(aFullName+l-6, "_night") == 0) + { + str_copy(aThemeName, pName, min((int)sizeof(aThemeName),l-5)); + isNight = true; + } + else + str_copy(aThemeName, aFullName, sizeof(aThemeName)); + + // try to edit an existing theme + for(int i = 0; i < pSelf->m_lThemes.size(); i++) + { + if(str_comp(pSelf->m_lThemes[i].m_Name, aThemeName) == 0) + { + if(isDay) + pSelf->m_lThemes[i].m_HasDay = true; + if(isNight) + pSelf->m_lThemes[i].m_HasNight = true; + return 0; + } + } + + // make new theme + CTheme Theme(aThemeName, isDay, isNight); + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "added theme %s from ui/themes/%s", aThemeName, pName); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf); + pSelf->m_lThemes.add(Theme); + return 0; +} + +int CMenus::ThemeIconScan(const char *pName, int IsDir, int DirType, void *pUser) +{ + CMenus *pSelf = (CMenus *)pUser; + int l = str_length(pName); + + if(l < 4 || IsDir || str_comp(pName+l-4, ".png") != 0) + return 0; + char aThemeName[128]; + str_copy(aThemeName, pName, min((int)sizeof(aThemeName),l-3)); + + // save icon for an existing theme + for(sorted_array::range r = pSelf->m_lThemes.all(); !r.empty(); r.pop_front()) // bit slow but whatever + { + if(str_comp(r.front().m_Name, aThemeName) == 0) + { + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "ui/themes/%s", pName); + CImageInfo Info; + if(!pSelf->Graphics()->LoadPNG(&Info, aBuf, DirType)) + { + str_format(aBuf, sizeof(aBuf), "failed to load theme icon from %s", pName); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf); + return 0; + } + str_format(aBuf, sizeof(aBuf), "loaded theme icon %s", pName); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf); + + r.front().m_IconTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0); + return 0; + } + } + return 0; // no existing theme +} + void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, sorted_array *pLanguages) { // read file data into buffer @@ -670,6 +754,96 @@ void CMenus::RenderLanguageSelection(CUIRect MainView, bool Header) } } +void CMenus::RenderThemeSelection(CUIRect MainView, bool Header) +{ + static int s_ThemeList = 0; + static int s_SelectedTheme = 0; + static CListBoxState s_ListBoxState_Theme; + + if(m_lThemes.size() == 0) // not loaded yet + { + m_lThemes.add(CTheme("", false, false)); // no theme + Storage()->ListDirectory(IStorage::TYPE_ALL, "ui/themes", ThemeScan, (CMenus*)this); + Storage()->ListDirectory(IStorage::TYPE_ALL, "ui/themes", ThemeIconScan, (CMenus*)this); + for(int i = 0; i < m_lThemes.size(); i++) + if(str_comp(m_lThemes[i].m_Name, g_Config.m_ClMenuMap) == 0) + { + s_SelectedTheme = i; + break; + } + } + + int OldSelected = s_SelectedTheme; + + if(Header) + UiDoListboxHeader(&s_ListBoxState_Theme, &MainView, Localize("Theme"), 20.0f, 2.0f); + UiDoListboxStart(&s_ListBoxState_Theme, &s_ThemeList, 20.0f, 0, m_lThemes.size(), 1, s_SelectedTheme, Header?0:&MainView, Header?true:false); + + for(sorted_array::range r = m_lThemes.all(); !r.empty(); r.pop_front()) + { + CListboxItem Item = UiDoListboxNextItem(&s_ListBoxState_Theme, &r.front()); + + if(Item.m_Visible) + { + CUIRect Rect; + Item.m_Rect.VSplitLeft(Item.m_Rect.h*2.0f, &Rect, &Item.m_Rect); + Rect.VMargin(6.0f, &Rect); + Rect.HMargin(3.0f, &Rect); + vec4 Color(1.0f, 1.0f, 1.0f, 1.0f); + + // draw icon if it exists + if(r.front().m_IconTexture.IsValid()) + { + Graphics()->TextureSet(r.front().m_IconTexture); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f); + IGraphics::CQuadItem QuadItem(Rect.x, Rect.y, Rect.w, Rect.h); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + } + + Item.m_Rect.y += 2.0f; + char aName[128]; + if(r.front().m_Name[0]) + { + if(r.front().m_HasDay && r.front().m_HasNight) + str_format(aName, sizeof(aName), "%s", r.front().m_Name.cstr()); + else if(r.front().m_HasDay && !r.front().m_HasNight) + str_format(aName, sizeof(aName), "%s (day)", r.front().m_Name.cstr()); + else if(!r.front().m_HasDay && r.front().m_HasNight) + str_format(aName, sizeof(aName), "%s (night)", r.front().m_Name.cstr()); + else // generic + str_format(aName, sizeof(aName), "%s", r.front().m_Name.cstr()); + } + else + str_copy(aName, "(none)", sizeof(aName)); + + if(!str_comp(m_lThemes[s_SelectedTheme].m_Name, r.front().m_Name)) + { + TextRender()->TextColor(0.0f, 0.0f, 0.0f, 1.0f); + TextRender()->TextOutlineColor(1.0f, 1.0f, 1.0f, 0.25f); + UI()->DoLabelScaled(&Item.m_Rect, aName, Item.m_Rect.h*ms_FontmodHeight*0.8f, CUI::ALIGN_LEFT); + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 0.3f); + } + else + UI()->DoLabelScaled(&Item.m_Rect, aName, Item.m_Rect.h*ms_FontmodHeight*0.8f, CUI::ALIGN_LEFT); + } + } + + s_SelectedTheme = UiDoListboxEnd(&s_ListBoxState_Theme, 0); + + if(OldSelected != s_SelectedTheme) + { + str_copy(g_Config.m_ClMenuMap, m_lThemes[s_SelectedTheme].m_Name, sizeof(g_Config.m_ClMenuMap)); + if(m_lThemes[s_SelectedTheme].m_Name[0]) + g_Config.m_ClShowMenuMap = 1; + else + g_Config.m_ClShowMenuMap = 0; + m_pClient->m_pMapLayersBackGround->BackgroundMapUpdate(); + } +} + void CMenus::RenderSettingsGeneral(CUIRect MainView) { CUIRect Label, Button, Game, Client, BottomView; @@ -690,7 +864,7 @@ void CMenus::RenderSettingsGeneral(CUIRect MainView) RenderTools()->DrawUIRect(&Game, vec4(0.0f, 0.0f, 0.0f, 0.25f), CUI::CORNER_ALL, 5.0f); // render client menu background - NumOptions = 4; + NumOptions = 3; if(g_Config.m_ClAutoDemoRecord) NumOptions += 1; if(g_Config.m_ClAutoScreenshot) NumOptions += 1; BackgroundHeight = (float)(NumOptions+1)*ButtonHeight+(float)NumOptions*Spacing; @@ -819,15 +993,6 @@ void CMenus::RenderSettingsGeneral(CUIRect MainView) if(DoButton_CheckBox(&s_SkipMainMenu, Localize("Skip the main menu"), g_Config.m_ClSkipStartMenu, &Button)) g_Config.m_ClSkipStartMenu ^= 1; - Client.HSplitTop(Spacing, 0, &Client); - Client.HSplitTop(ButtonHeight, &Button, &Client); - static int s_DisplayAnimatedBackgrounds = 0; - if(DoButton_CheckBox(&s_DisplayAnimatedBackgrounds, Localize("Display animated backgrounds"), g_Config.m_ClShowMenuMap, &Button)) - { - g_Config.m_ClShowMenuMap ^= 1; - m_pClient->m_pMapLayersBackGround->BackgroundMapUpdate(); - } - Client.HSplitTop(Spacing, 0, &Client); Client.HSplitTop(ButtonHeight, &Button, &Client); static int s_AutoDemoRecord = 0; @@ -858,8 +1023,13 @@ void CMenus::RenderSettingsGeneral(CUIRect MainView) MainView.HSplitTop(10.0f, 0, &MainView); - // render language selection - RenderLanguageSelection(MainView); + // render language and theme selection + CUIRect LanguageView, ThemeView; + MainView.VSplitMid(&LanguageView, &ThemeView); + LanguageView.VSplitRight(1, &LanguageView, 0); + ThemeView.VSplitLeft(1, 0, &ThemeView); + RenderLanguageSelection(LanguageView); + RenderThemeSelection(ThemeView); // reset button Spacing = 3.0f; diff --git a/src/game/variables.h b/src/game/variables.h index a5eb8aea8..c4fe17f8d 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -84,7 +84,7 @@ MACRO_CONFIG_INT(UiAutoswitchInfotab, ui_autoswitch_infotab, 1, 0, 1, CFGFLAG_SA MACRO_CONFIG_INT(GfxNoclip, gfx_noclip, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Disable clipping") -MACRO_CONFIG_STR(ClMenuMap, cl_menu_map, 64, "menu", CFGFLAG_CLIENT|CFGFLAG_SAVE, "Background map in the menu") +MACRO_CONFIG_STR(ClMenuMap, cl_menu_map, 64, "jungle", CFGFLAG_CLIENT|CFGFLAG_SAVE, "Background map in the menu") MACRO_CONFIG_INT(ClShowMenuMap, cl_show_menu_map, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Display background map in the menu") MACRO_CONFIG_INT(ClRotationRadius, cl_rotation_radius, 30, 1, 500, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Camera rotation radius") MACRO_CONFIG_INT(ClRotationSpeed, cl_rotation_speed, 40, 1, 120, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Camera rotations in seconds")