mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Add favorite skins
This commit is contained in:
parent
56131dc01f
commit
fbc4c9cd6e
|
@ -11,6 +11,7 @@
|
|||
#include <base/vmath.h>
|
||||
|
||||
#include <engine/client.h>
|
||||
#include <engine/config.h>
|
||||
#include <engine/editor.h>
|
||||
#include <engine/friends.h>
|
||||
#include <engine/graphics.h>
|
||||
|
@ -1048,6 +1049,15 @@ void CMenus::OnInit()
|
|||
Storage()->ListDirectory(IStorage::TYPE_ALL, "menuimages", MenuImageScan, this);
|
||||
}
|
||||
|
||||
void CMenus::OnConsoleInit()
|
||||
{
|
||||
auto *pConfigManager = Kernel()->RequestInterface<IConfigManager>();
|
||||
if(pConfigManager != nullptr)
|
||||
pConfigManager->RegisterCallback(CMenus::ConfigSaveCallback, this);
|
||||
Console()->Register("add_favorite_skin", "s[skin_name]", CFGFLAG_CLIENT, Con_AddFavoriteSkin, this, "Add a skin as a favorite");
|
||||
Console()->Register("remove_favorite_skin", "s[skin_name]", CFGFLAG_CLIENT, Con_RemFavoriteSkin, this, "Remove a skin from the favorites");
|
||||
}
|
||||
|
||||
void CMenus::ConchainUpdateMusicState(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
||||
{
|
||||
pfnCallback(pResult, pCallbackUserData);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <base/vmath.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <engine/console.h>
|
||||
|
@ -520,6 +521,14 @@ protected:
|
|||
static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
static void ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
|
||||
|
||||
// skin favorite list
|
||||
bool m_SkinFavoritesChanged = false;
|
||||
std::unordered_set<std::string> m_SkinFavorites;
|
||||
static void Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData);
|
||||
static void Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData);
|
||||
static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData);
|
||||
void OnConfigSave(IConfigManager *pConfigManager);
|
||||
|
||||
// found in menus_settings.cpp
|
||||
void RenderLanguageSelection(CUIRect MainView);
|
||||
void RenderThemeSelection(CUIRect MainView, bool Header = true);
|
||||
|
@ -560,6 +569,7 @@ public:
|
|||
void KillServer();
|
||||
|
||||
virtual void OnInit() override;
|
||||
void OnConsoleInit() override;
|
||||
|
||||
virtual void OnStateChange(int NewState, int OldState) override;
|
||||
virtual void OnReset() override;
|
||||
|
|
|
@ -426,6 +426,49 @@ void CMenus::RefreshSkins()
|
|||
}
|
||||
}
|
||||
|
||||
void CMenus::Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
auto *pSelf = (CMenus *)pUserData;
|
||||
if(pResult->NumArguments() >= 1)
|
||||
{
|
||||
pSelf->m_SkinFavorites.emplace(pResult->GetString(0));
|
||||
pSelf->m_SkinFavoritesChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CMenus::Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
auto *pSelf = (CMenus *)pUserData;
|
||||
if(pResult->NumArguments() >= 1)
|
||||
{
|
||||
const auto it = pSelf->m_SkinFavorites.find(pResult->GetString(0));
|
||||
if(it != pSelf->m_SkinFavorites.end())
|
||||
{
|
||||
pSelf->m_SkinFavorites.erase(it);
|
||||
pSelf->m_SkinFavoritesChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CMenus::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData)
|
||||
{
|
||||
auto *pSelf = (CMenus *)pUserData;
|
||||
pSelf->OnConfigSave(pConfigManager);
|
||||
}
|
||||
|
||||
void CMenus::OnConfigSave(IConfigManager *pConfigManager)
|
||||
{
|
||||
for(const auto &Entry : m_SkinFavorites)
|
||||
{
|
||||
char aBuffer[256];
|
||||
char aNameEscaped[256];
|
||||
char *pDst = aNameEscaped;
|
||||
str_escape(&pDst, Entry.c_str(), aNameEscaped + std::size(aNameEscaped));
|
||||
str_format(aBuffer, std::size(aBuffer), "add_favorite_skin \"%s\"", Entry.c_str());
|
||||
pConfigManager->WriteLine(aBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void CMenus::RenderSettingsTee(CUIRect MainView)
|
||||
{
|
||||
CUIRect Button, Label, Dummy, DummyLabel, SkinList, QuickSearch, QuickSearchClearButton, SkinDB, SkinPrefix, SkinPrefixLabel, DirectoryButton, RefreshButton, Eyes, EyesLabel, EyesTee, EyesRight;
|
||||
|
@ -658,33 +701,92 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
MainView.HSplitTop(20.0f, 0, &MainView);
|
||||
MainView.HSplitTop(230.0f - RenderEyesBelow * 25.0f, &SkinList, &MainView);
|
||||
static std::vector<CUISkin> s_vSkinList;
|
||||
static std::vector<CUISkin> s_vSkinListHelper;
|
||||
static std::vector<CUISkin> s_vFavoriteSkinListHelper;
|
||||
static int s_SkinCount = 0;
|
||||
static float s_ScrollValue = 0.0f;
|
||||
if(s_InitSkinlist || m_pClient->m_Skins.Num() != s_SkinCount)
|
||||
// be nice to the CPU
|
||||
static auto s_SkinLastRebuildTime = time_get_nanoseconds();
|
||||
const auto CurTime = time_get_nanoseconds();
|
||||
if(s_InitSkinlist || m_pClient->m_Skins.Num() != s_SkinCount || m_SkinFavoritesChanged || (m_pClient->m_Skins.IsDownloadingSkins() && (CurTime - s_SkinLastRebuildTime > 500ms)))
|
||||
{
|
||||
s_SkinLastRebuildTime = CurTime;
|
||||
s_vSkinList.clear();
|
||||
s_vSkinListHelper.clear();
|
||||
s_vFavoriteSkinListHelper.clear();
|
||||
// set skin count early, since Find of the skin class might load
|
||||
// a downloading skin
|
||||
s_SkinCount = m_pClient->m_Skins.Num();
|
||||
m_SkinFavoritesChanged = false;
|
||||
bool RequiresRebuild = false;
|
||||
|
||||
auto &&SkinNotFiltered = [&](const CSkin *pSkinToBeSelected) {
|
||||
// filter quick search
|
||||
if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pSkinToBeSelected->m_aName, g_Config.m_ClSkinFilterString))
|
||||
return false;
|
||||
|
||||
// no special skins
|
||||
if((pSkinToBeSelected->m_aName[0] == 'x' && pSkinToBeSelected->m_aName[1] == '_'))
|
||||
return false;
|
||||
|
||||
if(pSkinToBeSelected == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
for(const auto &it : m_SkinFavorites)
|
||||
{
|
||||
const auto FirstSkinIndex = m_pClient->m_Skins.Find(it.c_str());
|
||||
// second call is intended, our implemention doesnt return the index in the call where the download finished
|
||||
const auto SkinIndex = m_pClient->m_Skins.Find(it.c_str());
|
||||
if(SkinIndex == -1)
|
||||
continue;
|
||||
if(FirstSkinIndex == -1 && SkinIndex != -1)
|
||||
{
|
||||
// skin list changed, rebuild next frame
|
||||
RequiresRebuild = true;
|
||||
}
|
||||
const CSkin *pSkinToBeSelected = m_pClient->m_Skins.Get(SkinIndex);
|
||||
|
||||
if(!SkinNotFiltered(pSkinToBeSelected))
|
||||
continue;
|
||||
|
||||
s_vFavoriteSkinListHelper.emplace_back(pSkinToBeSelected);
|
||||
}
|
||||
for(int i = 0; i < m_pClient->m_Skins.Num(); ++i)
|
||||
{
|
||||
const CSkin *pSkinToBeSelected = m_pClient->m_Skins.Get(i);
|
||||
|
||||
// filter quick search
|
||||
if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pSkinToBeSelected->m_aName, g_Config.m_ClSkinFilterString))
|
||||
if(!SkinNotFiltered(pSkinToBeSelected))
|
||||
continue;
|
||||
|
||||
// no special skins
|
||||
if((pSkinToBeSelected->m_aName[0] == 'x' && pSkinToBeSelected->m_aName[1] == '_'))
|
||||
continue;
|
||||
|
||||
if(pSkinToBeSelected == 0)
|
||||
continue;
|
||||
|
||||
s_vSkinList.emplace_back(pSkinToBeSelected);
|
||||
if(std::find(m_SkinFavorites.begin(), m_SkinFavorites.end(), pSkinToBeSelected->m_aName) == m_SkinFavorites.end())
|
||||
s_vSkinListHelper.emplace_back(pSkinToBeSelected);
|
||||
}
|
||||
std::sort(s_vSkinList.begin(), s_vSkinList.end());
|
||||
s_InitSkinlist = false;
|
||||
s_SkinCount = m_pClient->m_Skins.Num();
|
||||
std::sort(s_vSkinListHelper.begin(), s_vSkinListHelper.end());
|
||||
std::sort(s_vFavoriteSkinListHelper.begin(), s_vFavoriteSkinListHelper.end());
|
||||
s_vSkinList = s_vFavoriteSkinListHelper;
|
||||
s_vSkinList.insert(s_vSkinList.end(), s_vSkinListHelper.begin(), s_vSkinListHelper.end());
|
||||
s_InitSkinlist = RequiresRebuild;
|
||||
}
|
||||
|
||||
auto &&RenderFavIcon = [&](const CUIRect &FavIcon, bool AsFav) {
|
||||
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 | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||||
if(AsFav)
|
||||
TextRender()->TextColor({1, 1, 0, 1});
|
||||
else
|
||||
TextRender()->TextColor({0.5f, 0.5f, 0.5f, 1});
|
||||
TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor());
|
||||
SLabelProperties Props;
|
||||
Props.m_MaxWidth = FavIcon.w;
|
||||
UI()->DoLabel(&FavIcon, "\xef\x80\x85", 12.0f, TEXTALIGN_RIGHT, Props);
|
||||
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||||
TextRender()->SetRenderFlags(0);
|
||||
TextRender()->SetCurFont(nullptr);
|
||||
};
|
||||
|
||||
int OldSelected = -1;
|
||||
UiDoListboxStart(&s_InitSkinlist, &SkinList, 50.0f, Localize("Skins"), "", s_vSkinList.size(), 4, OldSelected, s_ScrollValue);
|
||||
for(size_t i = 0; i < s_vSkinList.size(); ++i)
|
||||
|
@ -697,6 +799,8 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
CListboxItem Item = UiDoListboxNextItem(pSkinToBeDraw, OldSelected >= 0 && (size_t)OldSelected == i);
|
||||
if(Item.m_Visible)
|
||||
{
|
||||
auto OriginalRect = Item.m_Rect;
|
||||
|
||||
CTeeRenderInfo Info = OwnSkinInfo;
|
||||
Info.m_CustomColoredSkin = *pUseCustomColor;
|
||||
|
||||
|
@ -709,9 +813,11 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
RenderTools()->RenderTee(pIdleState, &Info, Emote, vec2(1.0f, 0.0f), TeeRenderPos);
|
||||
|
||||
Item.m_Rect.VSplitLeft(60.0f, 0, &Item.m_Rect);
|
||||
SLabelProperties Props;
|
||||
Props.m_MaxWidth = Item.m_Rect.w;
|
||||
UI()->DoLabel(&Item.m_Rect, pSkinToBeDraw->m_aName, 12.0f, TEXTALIGN_LEFT, Props);
|
||||
{
|
||||
SLabelProperties Props;
|
||||
Props.m_MaxWidth = Item.m_Rect.w;
|
||||
UI()->DoLabel(&Item.m_Rect, pSkinToBeDraw->m_aName, 12.0f, TEXTALIGN_LEFT, Props);
|
||||
}
|
||||
if(g_Config.m_Debug)
|
||||
{
|
||||
ColorRGBA BloodColor = *pUseCustomColor ? color_cast<ColorRGBA>(ColorHSLA(*pColorBody)) : pSkinToBeDraw->m_BloodColor;
|
||||
|
@ -722,6 +828,38 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
|
|||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||||
Graphics()->QuadsEnd();
|
||||
}
|
||||
|
||||
// render skin favorite icon
|
||||
{
|
||||
const auto SkinItFav = m_SkinFavorites.find(pSkinToBeDraw->m_aName);
|
||||
const auto IsFav = SkinItFav != m_SkinFavorites.end();
|
||||
CUIRect FavIcon;
|
||||
OriginalRect.HSplitTop(20.0f, &FavIcon, nullptr);
|
||||
FavIcon.VSplitRight(20.0f, nullptr, &FavIcon);
|
||||
if(IsFav)
|
||||
{
|
||||
RenderFavIcon(FavIcon, IsFav);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(UI()->MouseInside(&FavIcon))
|
||||
{
|
||||
RenderFavIcon(FavIcon, IsFav);
|
||||
}
|
||||
}
|
||||
if(UI()->DoButtonLogic(pSkinToBeDraw->m_aName, 0, &FavIcon))
|
||||
{
|
||||
if(IsFav)
|
||||
{
|
||||
m_SkinFavorites.erase(SkinItFav);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_SkinFavorites.emplace(pSkinToBeDraw->m_aName);
|
||||
}
|
||||
s_InitSkinlist = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -349,6 +349,7 @@ void CSkins::Refresh(TSkinLoadedCBFunc &&SkinLoadedFunc)
|
|||
|
||||
m_vSkins.clear();
|
||||
m_vDownloadSkins.clear();
|
||||
m_DownloadingSkins = 0;
|
||||
SSkinScanUser SkinScanUser;
|
||||
SkinScanUser.m_pThis = this;
|
||||
SkinScanUser.m_SkinLoadedFunc = SkinLoadedFunc;
|
||||
|
@ -432,10 +433,12 @@ int CSkins::FindImpl(const char *pName)
|
|||
Storage()->RenameFile(RangeBegin->m_aPath, aPath, IStorage::TYPE_SAVE);
|
||||
LoadSkin(RangeBegin->m_aName, RangeBegin->m_pTask->m_Info);
|
||||
RangeBegin->m_pTask = nullptr;
|
||||
--m_DownloadingSkins;
|
||||
}
|
||||
if(RangeBegin->m_pTask && (RangeBegin->m_pTask->State() == HTTP_ERROR || RangeBegin->m_pTask->State() == HTTP_ABORTED))
|
||||
{
|
||||
RangeBegin->m_pTask = nullptr;
|
||||
--m_DownloadingSkins;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
@ -452,5 +455,6 @@ int CSkins::FindImpl(const char *pName)
|
|||
Skin.m_pTask = std::make_shared<CGetPngFile>(this, aUrl, Storage(), Skin.m_aPath);
|
||||
m_pClient->Engine()->AddJob(Skin.m_pTask);
|
||||
m_vDownloadSkins.insert(std::lower_bound(m_vDownloadSkins.begin(), m_vDownloadSkins.end(), Skin), std::move(Skin));
|
||||
++m_DownloadingSkins;
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -54,9 +54,12 @@ public:
|
|||
const CSkin *Get(int Index);
|
||||
int Find(const char *pName);
|
||||
|
||||
bool IsDownloadingSkins() { return m_DownloadingSkins; }
|
||||
|
||||
private:
|
||||
std::vector<CSkin> m_vSkins;
|
||||
std::vector<CDownloadSkin> m_vDownloadSkins;
|
||||
size_t m_DownloadingSkins = 0;
|
||||
char m_aEventSkinPrefix[24];
|
||||
|
||||
bool LoadSkinPNG(CImageInfo &Info, const char *pName, const char *pPath, int DirType);
|
||||
|
|
Loading…
Reference in a new issue