5909: Add favorite skins r=def- a=Jupeyy

Advantages:
- my friend fokkonaut can mark his favorite vanilla skins
- u can mark skin db skins which auto download if not loaded

untested, bad code, maybe buggy.
but still want feedback :)

![image](https://user-images.githubusercontent.com/6654924/193877801-6880a4f6-3f42-4e16-ad00-f44583aa2a87.png)


## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Jupeyy <jupjopjap@gmail.com>
This commit is contained in:
bors[bot] 2022-10-10 14:42:18 +00:00 committed by GitHub
commit 8de0247d00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 182 additions and 17 deletions

View file

@ -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>
@ -1037,6 +1038,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);

View file

@ -7,6 +7,7 @@
#include <base/vmath.h>
#include <chrono>
#include <unordered_set>
#include <vector>
#include <engine/console.h>
@ -521,6 +522,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);
@ -561,6 +570,7 @@ public:
void KillServer();
virtual void OnInit() override;
void OnConsoleInit() override;
virtual void OnStateChange(int NewState, int OldState) override;
virtual void OnReset() override;

View file

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

View file

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

View file

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