Port CListBox from upstream, smooth scrolling for all lists

Replace existing listbox implementations (`CMenus::UiDoListbox*` and `HandleListInputs` functions) with `CListBox` from upstream.

Reimplement additional feature that was already present in ddnet: page up/down, home and end key handling.

Affects the following lists:

- server browser
- server browser scoreboard
- server browser friends
- country / region selection popup (server browser filter)
- player skin list
- player country / region list
- theme list
- assets list
- graphics resolutions list
- dropdown menus (e.g. graphics fullscreen mode)
- ingame player list
- vote options list
- kick/specvote lists
- ghost list
- language list (in settings and in popup on first launch)
- demo browser
- editor file browser (saving, loading, adding images / sounds)
  - The search / filename input is also improved so navigating a filtered list works correctly by porting the logic from upstream.

There are minor changes to the visual appearance of some lists, due to changed margins.

The vertical alignment of some list item texts is improved so the text is centered vertically.
This commit is contained in:
Robert Müller 2023-01-05 12:21:10 +01:00
parent f79daac222
commit 91a23f00cb
13 changed files with 900 additions and 1062 deletions

View file

@ -2277,6 +2277,8 @@ if(CLIENT)
skin.h skin.h
ui.cpp ui.cpp
ui.h ui.h
ui_listbox.cpp
ui_listbox.h
ui_rect.cpp ui_rect.cpp
ui_rect.h ui_rect.h
ui_scrollregion.cpp ui_scrollregion.cpp

View file

@ -30,6 +30,7 @@
#include <game/client/components/menu_background.h> #include <game/client/components/menu_background.h>
#include <game/client/components/sounds.h> #include <game/client/components/sounds.h>
#include <game/client/gameclient.h> #include <game/client/gameclient.h>
#include <game/client/ui_listbox.h>
#include <game/generated/client_data.h> #include <game/generated/client_data.h>
#include <game/localization.h> #include <game/localization.h>
@ -79,7 +80,6 @@ CMenus::CMenus()
m_aCallvoteReason[0] = 0; m_aCallvoteReason[0] = 0;
m_FriendlistSelectedIndex = -1; m_FriendlistSelectedIndex = -1;
m_DoubleClickIndex = -1;
m_DemoPlayerState = DEMOPLAYER_NONE; m_DemoPlayerState = DEMOPLAYER_NONE;
m_Dummy = false; m_Dummy = false;
@ -705,7 +705,6 @@ int CMenus::RenderMenubar(CUIRect r)
if(DoButton_MenuTab(&s_StartButton, pHomeScreenButtonLabel, false, &Button, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_HOME], pHomeButtonColor, pHomeButtonColor, pHomeButtonColorHover, 10.0f, 0)) if(DoButton_MenuTab(&s_StartButton, pHomeScreenButtonLabel, false, &Button, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_HOME], pHomeButtonColor, pHomeButtonColor, pHomeButtonColorHover, 10.0f, 0))
{ {
m_ShowStart = true; m_ShowStart = true;
m_DoubleClickIndex = -1;
} }
TextRender()->SetRenderFlags(0); TextRender()->SetRenderFlags(0);
@ -721,7 +720,6 @@ int CMenus::RenderMenubar(CUIRect r)
if(DoButton_MenuTab(&s_NewsButton, Localize("News"), m_ActivePage == PAGE_NEWS, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_NEWS])) if(DoButton_MenuTab(&s_NewsButton, Localize("News"), m_ActivePage == PAGE_NEWS, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_NEWS]))
{ {
NewPage = PAGE_NEWS; NewPage = PAGE_NEWS;
m_DoubleClickIndex = -1;
} }
} }
else if(m_ActivePage == PAGE_DEMOS) else if(m_ActivePage == PAGE_DEMOS)
@ -732,7 +730,6 @@ int CMenus::RenderMenubar(CUIRect r)
{ {
DemolistPopulate(); DemolistPopulate();
NewPage = PAGE_DEMOS; NewPage = PAGE_DEMOS;
m_DoubleClickIndex = -1;
} }
} }
else else
@ -744,7 +741,6 @@ int CMenus::RenderMenubar(CUIRect r)
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET) if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET)
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
NewPage = PAGE_INTERNET; NewPage = PAGE_INTERNET;
m_DoubleClickIndex = -1;
} }
Box.VSplitLeft(100.0f, &Button, &Box); Box.VSplitLeft(100.0f, &Button, &Box);
@ -754,7 +750,6 @@ int CMenus::RenderMenubar(CUIRect r)
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN) if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN)
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
NewPage = PAGE_LAN; NewPage = PAGE_LAN;
m_DoubleClickIndex = -1;
} }
Box.VSplitLeft(100.0f, &Button, &Box); Box.VSplitLeft(100.0f, &Button, &Box);
@ -764,7 +759,6 @@ int CMenus::RenderMenubar(CUIRect r)
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES) if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES)
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
NewPage = PAGE_FAVORITES; NewPage = PAGE_FAVORITES;
m_DoubleClickIndex = -1;
} }
Box.VSplitLeft(90.0f, &Button, &Box); Box.VSplitLeft(90.0f, &Button, &Box);
@ -777,7 +771,6 @@ int CMenus::RenderMenubar(CUIRect r)
ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET);
} }
NewPage = PAGE_DDNET; NewPage = PAGE_DDNET;
m_DoubleClickIndex = -1;
} }
Box.VSplitLeft(90.0f, &Button, &Box); Box.VSplitLeft(90.0f, &Button, &Box);
@ -790,7 +783,6 @@ int CMenus::RenderMenubar(CUIRect r)
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG); ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
} }
NewPage = PAGE_KOG; NewPage = PAGE_KOG;
m_DoubleClickIndex = -1;
} }
} }
} }
@ -1323,7 +1315,6 @@ int CMenus::Render()
{ {
UpdateMusicState(); UpdateMusicState();
s_Frame++; s_Frame++;
m_DoubleClickIndex = -1;
RefreshBrowserTab(g_Config.m_UiPage); RefreshBrowserTab(g_Config.m_UiPage);
if(g_Config.m_UiPage == PAGE_INTERNET) if(g_Config.m_UiPage == PAGE_INTERNET)
@ -1397,7 +1388,6 @@ int CMenus::Render()
{ {
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
SetMenuPage(PAGE_INTERNET); SetMenuPage(PAGE_INTERNET);
m_DoubleClickIndex = -1;
} }
// render current page // render current page
@ -1837,36 +1827,38 @@ int CMenus::Render()
} }
else if(m_Popup == POPUP_LANGUAGE) else if(m_Popup == POPUP_LANGUAGE)
{ {
Box = Screen; CUIRect Button;
Box.Margin(150.0f, &Box); Screen.Margin(150.0f, &Box);
Box.HSplitTop(20.f, &Part, &Box); Box.HSplitTop(20.0f, nullptr, &Box);
Box.HSplitBottom(20.f, &Box, &Part); Box.HSplitBottom(20.0f, &Box, nullptr);
Box.HSplitBottom(24.f, &Box, &Part); Box.HSplitBottom(24.0f, &Box, &Button);
Box.HSplitBottom(20.f, &Box, 0); Box.HSplitBottom(20.0f, &Box, nullptr);
Box.VMargin(20.0f, &Box); Box.VMargin(20.0f, &Box);
RenderLanguageSelection(Box); const bool Activated = RenderLanguageSelection(Box);
Part.VMargin(120.0f, &Part); Button.VMargin(120.0f, &Button);
static CButtonContainer s_Button; static CButtonContainer s_Button;
if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Part) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)) if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Button) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || Activated)
m_Popup = POPUP_FIRST_LAUNCH; m_Popup = POPUP_FIRST_LAUNCH;
} }
else if(m_Popup == POPUP_COUNTRY) else if(m_Popup == POPUP_COUNTRY)
{ {
Box = Screen; CUIRect ButtonBar;
Box.Margin(150.0f, &Box); Screen.Margin(150.0f, &Box);
Box.HSplitTop(20.f, &Part, &Box); Box.HSplitTop(20.0f, nullptr, &Box);
Box.HSplitBottom(20.f, &Box, &Part); Box.HSplitBottom(20.0f, &Box, nullptr);
Box.HSplitBottom(24.f, &Box, &Part); Box.HSplitBottom(24.0f, &Box, &ButtonBar);
Box.HSplitBottom(20.f, &Box, 0); Box.HSplitBottom(20.0f, &Box, nullptr);
Box.VMargin(20.0f, &Box); Box.VMargin(20.0f, &Box);
ButtonBar.VMargin(100.0f, &ButtonBar);
static int s_CurSelection = -2; static int s_CurSelection = -2;
if(s_CurSelection == -2) if(s_CurSelection == -2)
s_CurSelection = g_Config.m_BrFilterCountryIndex; s_CurSelection = g_Config.m_BrFilterCountryIndex;
static float s_ScrollValue = 0.0f;
static CListBox s_ListBox;
int OldSelected = -1; int OldSelected = -1;
UiDoListboxStart(&s_ScrollValue, &Box, 50.0f, Localize("Country / Region"), "", m_pClient->m_CountryFlags.Num(), 6, OldSelected, s_ScrollValue); s_ListBox.DoStart(50.0f, m_pClient->m_CountryFlags.Num(), 10, 1, OldSelected, &Box);
for(size_t i = 0; i < m_pClient->m_CountryFlags.Num(); ++i) for(size_t i = 0; i < m_pClient->m_CountryFlags.Num(); ++i)
{ {
@ -1874,29 +1866,31 @@ int CMenus::Render()
if(pEntry->m_CountryCode == s_CurSelection) if(pEntry->m_CountryCode == s_CurSelection)
OldSelected = i; OldSelected = i;
CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected >= 0 && (size_t)OldSelected == i); const CListboxItem Item = s_ListBox.DoNextItem(pEntry, OldSelected >= 0 && (size_t)OldSelected == i);
if(Item.m_Visible) if(!Item.m_Visible)
{ continue;
CUIRect Label;
Item.m_Rect.Margin(5.0f, &Item.m_Rect); CUIRect FlagRect, Label;
Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); Item.m_Rect.Margin(5.0f, &FlagRect);
float OldWidth = Item.m_Rect.w; FlagRect.HSplitBottom(12.0f, &FlagRect, &Label);
Item.m_Rect.w = Item.m_Rect.h * 2; Label.HSplitTop(2.0f, nullptr, &Label);
Item.m_Rect.x += (OldWidth - Item.m_Rect.w) / 2.0f; const float OldWidth = FlagRect.w;
ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f); FlagRect.w = FlagRect.h * 2.0f;
m_pClient->m_CountryFlags.Render(pEntry->m_CountryCode, &Color, Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h); FlagRect.x += (OldWidth - FlagRect.w) / 2.0f;
UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_CENTER); ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f);
} m_pClient->m_CountryFlags.Render(pEntry->m_CountryCode, &Color, FlagRect.x, FlagRect.y, FlagRect.w, FlagRect.h);
SLabelProperties ItemLabelProps;
ItemLabelProps.m_AlignVertically = 0;
UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_CENTER, ItemLabelProps);
} }
bool Activated = false; const int NewSelected = s_ListBox.DoEnd();
const int NewSelected = UiDoListboxEnd(&s_ScrollValue, &Activated);
if(OldSelected != NewSelected) if(OldSelected != NewSelected)
s_CurSelection = m_pClient->m_CountryFlags.GetByIndex(NewSelected)->m_CountryCode; s_CurSelection = m_pClient->m_CountryFlags.GetByIndex(NewSelected)->m_CountryCode;
CUIRect CancelButton, OkButton; CUIRect CancelButton, OkButton;
Part.VMargin(100.0f, &Part); ButtonBar.VSplitMid(&CancelButton, &OkButton, 40.0f);
Part.VSplitMid(&CancelButton, &OkButton, 40.0f);
static CButtonContainer s_CancelButton; static CButtonContainer s_CancelButton;
if(DoButton_Menu(&s_CancelButton, Localize("Cancel"), 0, &CancelButton) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE)) if(DoButton_Menu(&s_CancelButton, Localize("Cancel"), 0, &CancelButton) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
@ -1906,7 +1900,7 @@ int CMenus::Render()
} }
static CButtonContainer s_OkButton; static CButtonContainer s_OkButton;
if(DoButton_Menu(&s_OkButton, Localize("Ok"), 0, &OkButton) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || Activated) if(DoButton_Menu(&s_OkButton, Localize("Ok"), 0, &OkButton) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || s_ListBox.WasItemActivated())
{ {
g_Config.m_BrFilterCountryIndex = s_CurSelection; g_Config.m_BrFilterCountryIndex = s_CurSelection;
Client()->ServerBrowserUpdate(); Client()->ServerBrowserUpdate();
@ -2195,35 +2189,35 @@ void CMenus::PopupConfirmDemoReplaceVideo()
} }
#endif #endif
void CMenus::RenderThemeSelection(CUIRect MainView, bool Header) void CMenus::RenderThemeSelection(CUIRect MainView)
{ {
std::vector<CTheme> &vThemesRef = m_pBackground->GetThemes(); const std::vector<CTheme> &vThemes = m_pBackground->GetThemes();
int SelectedTheme = -1; int SelectedTheme = -1;
for(int i = 0; i < (int)vThemesRef.size(); i++) for(int i = 0; i < (int)vThemes.size(); i++)
{ {
if(str_comp(vThemesRef[i].m_Name.c_str(), g_Config.m_ClMenuMap) == 0) if(str_comp(vThemes[i].m_Name.c_str(), g_Config.m_ClMenuMap) == 0)
{ {
SelectedTheme = i; SelectedTheme = i;
break; break;
} }
} }
const int OldSelected = SelectedTheme;
static int s_ListBox = 0; static CListBox s_ListBox;
static float s_ScrollValue = 0.0f; s_ListBox.DoHeader(&MainView, Localize("Theme"), 20.0f);
UiDoListboxStart(&s_ListBox, &MainView, 26.0f, Localize("Theme"), "", vThemesRef.size(), 1, -1, s_ScrollValue); s_ListBox.DoStart(20.0f, vThemes.size(), 1, 3, SelectedTheme, nullptr, true);
for(int i = 0; i < (int)vThemesRef.size(); i++) for(int i = 0; i < (int)vThemes.size(); i++)
{ {
CListboxItem Item = UiDoListboxNextItem(&vThemesRef[i].m_Name, i == SelectedTheme); const CTheme &Theme = vThemes[i];
const CListboxItem Item = s_ListBox.DoNextItem(&Theme.m_Name, i == SelectedTheme);
CTheme &Theme = vThemesRef[i];
if(!Item.m_Visible) if(!Item.m_Visible)
continue; continue;
CUIRect Icon; CUIRect Icon, Label;
Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &Icon, &Item.m_Rect); Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &Icon, &Label);
// draw icon if it exists // draw icon if it exists
if(Theme.m_IconTexture.IsValid()) if(Theme.m_IconTexture.IsValid())
@ -2254,16 +2248,18 @@ void CMenus::RenderThemeSelection(CUIRect MainView, bool Header)
else // generic else // generic
str_copy(aName, Theme.m_Name.c_str()); str_copy(aName, Theme.m_Name.c_str());
UI()->DoLabel(&Item.m_Rect, aName, 16 * CUI::ms_FontmodHeight, TEXTALIGN_LEFT); SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Label, aName, 16.0f * CUI::ms_FontmodHeight, TEXTALIGN_LEFT, Props);
} }
bool ItemActive = false; SelectedTheme = s_ListBox.DoEnd();
int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0, &ItemActive);
if(ItemActive && NewSelected != SelectedTheme) if(OldSelected != SelectedTheme)
{ {
str_copy(g_Config.m_ClMenuMap, vThemesRef[NewSelected].m_Name.c_str()); const CTheme &Theme = vThemes[SelectedTheme];
m_pBackground->LoadMenuBackground(vThemesRef[NewSelected].m_HasDay, vThemesRef[NewSelected].m_HasNight); str_copy(g_Config.m_ClMenuMap, Theme.m_Name.c_str());
m_pBackground->LoadMenuBackground(Theme.m_HasDay, Theme.m_HasNight);
} }
} }
@ -2631,76 +2627,3 @@ void CMenus::RefreshBrowserTab(int UiPage)
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG); ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
} }
} }
bool CMenus::HandleListInputs(const CUIRect &View, float &ScrollValue, const float ScrollAmount, int *pScrollOffset, const float ElemHeight, int &SelectedIndex, const int NumElems)
{
if(NumElems == 0)
{
ScrollValue = 0;
SelectedIndex = 0;
return false;
}
int NewIndex = -1;
int Num = (int)(View.h / ElemHeight);
int ScrollNum = maximum(NumElems - Num, 0);
if(ScrollNum > 0)
{
if(pScrollOffset && *pScrollOffset >= 0)
{
ScrollValue = (float)(*pScrollOffset) / ScrollNum;
*pScrollOffset = -1;
}
if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View))
ScrollValue -= 3.0f / ScrollNum;
if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View))
ScrollValue += 3.0f / ScrollNum;
}
ScrollValue = clamp(ScrollValue, 0.0f, 1.0f);
SelectedIndex = clamp(SelectedIndex, 0, NumElems - 1);
for(int i = 0; i < m_NumInputEvents; i++)
{
if(m_aInputEvents[i].m_Flags & IInput::FLAG_PRESS)
{
if(UI()->LastActiveItem() == &g_Config.m_UiServerAddress)
return false;
else if(m_aInputEvents[i].m_Key == KEY_DOWN)
NewIndex = minimum(SelectedIndex + 1, NumElems - 1);
else if(m_aInputEvents[i].m_Key == KEY_UP)
NewIndex = maximum(SelectedIndex - 1, 0);
else if(m_aInputEvents[i].m_Key == KEY_PAGEUP)
NewIndex = maximum(SelectedIndex - 25, 0);
else if(m_aInputEvents[i].m_Key == KEY_PAGEDOWN)
NewIndex = minimum(SelectedIndex + 25, NumElems - 1);
else if(m_aInputEvents[i].m_Key == KEY_HOME)
NewIndex = 0;
else if(m_aInputEvents[i].m_Key == KEY_END)
NewIndex = NumElems - 1;
}
if(NewIndex > -1 && NewIndex < NumElems)
{
//scroll
float IndexY = View.y - ScrollValue * ScrollNum * ElemHeight + NewIndex * ElemHeight;
int Scroll = View.y > IndexY ? -1 : View.y + View.h < IndexY + ElemHeight ? 1 : 0;
if(Scroll)
{
if(Scroll < 0)
{
int NumScrolls = (View.y - IndexY + ElemHeight - 1.0f) / ElemHeight;
ScrollValue -= (1.0f / ScrollNum) * NumScrolls;
}
else
{
int NumScrolls = (IndexY + ElemHeight - (View.y + View.h) + ElemHeight - 1.0f) / ElemHeight;
ScrollValue += (1.0f / ScrollNum) * NumScrolls;
}
}
SelectedIndex = NewIndex;
}
}
return NewIndex != -1;
}

View file

@ -195,22 +195,6 @@ class CMenus : public CComponent
return UI()->DoButtonLogic(pID, Checked, pRect); return UI()->DoButtonLogic(pID, Checked, pRect);
} }
struct CListboxItem
{
int m_Visible;
int m_Selected;
CUIRect m_Rect;
CUIRect m_HitRect;
};
void UiDoListboxStart(const void *pID, const CUIRect *pRect, float RowHeight, const char *pTitle, const char *pBottomText, int NumItems,
int ItemsPerRow, int SelectedIndex, float ScrollValue, bool LogicOnly = false);
CListboxItem UiDoListboxNextItem(const void *pID, bool Selected = false, bool KeyEvents = true, bool NoHoverEffects = false);
CListboxItem UiDoListboxNextRow();
int UiDoListboxEnd(float *pScrollValue, bool *pItemActivated, bool *pListBoxActive = nullptr);
int UiLogicGetCurrentClickedItem();
/** /**
* Places and renders a tooltip near pNearRect. * Places and renders a tooltip near pNearRect.
* For now only works correctly with single line tooltips, since Text width calculation gets broken when there are multiple lines. * For now only works correctly with single line tooltips, since Text width calculation gets broken when there are multiple lines.
@ -541,8 +525,6 @@ protected:
// found in menus_browser.cpp // found in menus_browser.cpp
int m_SelectedIndex; int m_SelectedIndex;
int m_DoubleClickIndex;
int m_ScrollOffset;
void RenderServerbrowserServerList(CUIRect View); void RenderServerbrowserServerList(CUIRect View);
void Connect(const char *pAddress); void Connect(const char *pAddress);
void PopupConfirmSwitchServer(); void PopupConfirmSwitchServer();
@ -565,8 +547,8 @@ protected:
void OnConfigSave(IConfigManager *pConfigManager); void OnConfigSave(IConfigManager *pConfigManager);
// found in menus_settings.cpp // found in menus_settings.cpp
void RenderLanguageSelection(CUIRect MainView); bool RenderLanguageSelection(CUIRect MainView);
void RenderThemeSelection(CUIRect MainView, bool Header = true); void RenderThemeSelection(CUIRect MainView);
void RenderSettingsGeneral(CUIRect MainView); void RenderSettingsGeneral(CUIRect MainView);
void RenderSettingsPlayer(CUIRect MainView); void RenderSettingsPlayer(CUIRect MainView);
void RenderSettingsDummyPlayer(CUIRect MainView); void RenderSettingsDummyPlayer(CUIRect MainView);
@ -746,7 +728,6 @@ private:
static int GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser); static int GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser);
void SetMenuPage(int NewPage); void SetMenuPage(int NewPage);
void RefreshBrowserTab(int UiPage); void RefreshBrowserTab(int UiPage);
bool HandleListInputs(const CUIRect &View, float &ScrollValue, float ScrollAmount, int *pScrollOffset, float ElemHeight, int &SelectedIndex, int NumElems);
// found in menus_ingame.cpp // found in menus_ingame.cpp
void RenderInGameNetwork(CUIRect MainView); void RenderInGameNetwork(CUIRect MainView);

View file

@ -12,12 +12,12 @@
#include <game/client/components/console.h> #include <game/client/components/console.h>
#include <game/client/components/countryflags.h> #include <game/client/components/countryflags.h>
#include <game/client/gameclient.h>
#include <game/client/render.h> #include <game/client/render.h>
#include <game/client/ui.h> #include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/localization.h> #include <game/localization.h>
#include <game/client/gameclient.h>
#include "menus.h" #include "menus.h"
static const int gs_OffsetColFlagLock = 2; static const int gs_OffsetColFlagLock = 2;
@ -160,9 +160,6 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 0); View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 0);
CUIRect Scroll;
View.VSplitRight(20.0f, &View, &Scroll);
int NumServers = ServerBrowser()->NumSortedServers(); int NumServers = ServerBrowser()->NumSortedServers();
// display important messages in the middle of the screen so no // display important messages in the middle of the screen so no
@ -178,34 +175,22 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
UI()->DoLabel(&MsgBox, Localize("No servers match your filter criteria"), 16.0f, TEXTALIGN_CENTER); UI()->DoLabel(&MsgBox, Localize("No servers match your filter criteria"), 16.0f, TEXTALIGN_CENTER);
} }
static float s_ScrollValue = 0;
s_ScrollValue = UI()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue);
if(UI()->ConsumeHotkey(CUI::HOTKEY_TAB)) if(UI()->ConsumeHotkey(CUI::HOTKEY_TAB))
{ {
const int Direction = Input()->ShiftIsPressed() ? -1 : 1; const int Direction = Input()->ShiftIsPressed() ? -1 : 1;
g_Config.m_UiToolboxPage = (g_Config.m_UiToolboxPage + 3 + Direction) % 3; g_Config.m_UiToolboxPage = (g_Config.m_UiToolboxPage + 3 + Direction) % 3;
} }
if(HandleListInputs(View, s_ScrollValue, 3.0f, &m_ScrollOffset, s_aCols[0].m_Rect.h, m_SelectedIndex, NumServers)) static CListBox s_ListBox;
{ s_ListBox.DoStart(ms_ListheaderHeight, NumServers, 1, 3, m_SelectedIndex, &View, false);
const CServerInfo *pItem = ServerBrowser()->SortedGet(m_SelectedIndex);
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
}
// set clipping
UI()->ClipEnable(&View);
CUIRect OriginalView = View;
int Num = (int)(View.h / s_aCols[0].m_Rect.h) + 1;
int ScrollNum = maximum(NumServers - Num + 1, 0);
View.y -= s_ScrollValue * ScrollNum * s_aCols[0].m_Rect.h;
int NewSelected = -1;
bool DoubleClicked = false;
int NumPlayers = 0; int NumPlayers = 0;
static int s_PrevSelectedIndex = -1;
if(s_PrevSelectedIndex != m_SelectedIndex)
{
s_ListBox.ScrollToSelected();
s_PrevSelectedIndex = m_SelectedIndex;
}
m_SelectedIndex = -1; m_SelectedIndex = -1;
// reset friend counter // reset friend counter
@ -229,23 +214,17 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
for(int i = 0; i < NumServers; i++) for(int i = 0; i < NumServers; i++)
{ {
int ItemIndex = i; const CServerInfo *pItem = ServerBrowser()->SortedGet(i);
const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
NumPlayers += pItem->m_NumFilteredPlayers; NumPlayers += pItem->m_NumFilteredPlayers;
CUIRect Row;
const int UIRectCount = 2 + (COL_VERSION + 1) * 3; if(pItem->m_pUIElement == nullptr)
//initialize
if(pItem->m_pUIElement == NULL)
{ {
const int UIRectCount = 2 + (COL_VERSION + 1) * 3;
pItem->m_pUIElement = UI()->GetNewUIElement(UIRectCount); pItem->m_pUIElement = UI()->GetNewUIElement(UIRectCount);
} }
int Selected = str_comp(pItem->m_aAddress, g_Config.m_UiServerAddress) == 0; //selected_index==ItemIndex; const CListboxItem ListItem = s_ListBox.DoNextItem(pItem, str_comp(pItem->m_aAddress, g_Config.m_UiServerAddress) == 0);
if(ListItem.m_Selected)
View.HSplitTop(ms_ListheaderHeight, &Row, &View);
if(Selected)
m_SelectedIndex = i; m_SelectedIndex = i;
// update friend counter // update friend counter
@ -273,31 +252,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
} }
} }
// make sure that only those in view can be selected if(!ListItem.m_Visible)
if(Row.y + Row.h > OriginalView.y && Row.y < OriginalView.y + OriginalView.h)
{
if(Selected)
{
CUIRect r = Row;
r.Margin(0.5f, &r);
pItem->m_pUIElement->Rect(0)->Draw(&r, ColorRGBA(1, 1, 1, 0.5f), IGraphics::CORNER_ALL, 4.0f);
}
else if(UI()->MouseHovered(&Row))
{
CUIRect r = Row;
r.Margin(0.5f, &r);
pItem->m_pUIElement->Rect(0)->Draw(&r, ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f);
}
if(UI()->DoButtonLogic(pItem, Selected, &Row))
{
NewSelected = ItemIndex;
if(NewSelected == m_DoubleClickIndex)
DoubleClicked = true;
m_DoubleClickIndex = NewSelected;
}
}
else
{ {
// reset active item, if not visible // reset active item, if not visible
if(UI()->CheckActiveItem(pItem)) if(UI()->CheckActiveItem(pItem))
@ -312,8 +267,8 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
CUIRect Button; CUIRect Button;
char aTemp[64]; char aTemp[64];
Button.x = s_aCols[c].m_Rect.x; Button.x = s_aCols[c].m_Rect.x;
Button.y = Row.y; Button.y = ListItem.m_Rect.y;
Button.h = Row.h; Button.h = ListItem.m_Rect.h;
Button.w = s_aCols[c].m_Rect.w; Button.w = s_aCols[c].m_Rect.w;
int ID = s_aCols[c].m_ID; int ID = s_aCols[c].m_ID;
@ -469,16 +424,22 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
} }
} }
UI()->ClipDisable(); const int NewSelected = s_ListBox.DoEnd();
if(NewSelected != m_SelectedIndex)
if(NewSelected != -1)
{ {
// select the new server m_SelectedIndex = NewSelected;
const CServerInfo *pItem = ServerBrowser()->SortedGet(NewSelected); if(m_SelectedIndex >= 0)
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress); {
if(DoubleClicked && Input()->MouseDoubleClick()) // select the new server
Connect(g_Config.m_UiServerAddress); const CServerInfo *pItem = ServerBrowser()->SortedGet(NewSelected);
if(pItem)
{
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
}
}
} }
if(s_ListBox.WasItemActivated())
Connect(g_Config.m_UiServerAddress);
// Render bar that shows the loading progression. // Render bar that shows the loading progression.
// The bar is only shown while loading and fades out when it's done. // The bar is only shown while loading and fades out when it's done.
@ -1123,30 +1084,22 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
if(pSelectedServer) if(pSelectedServer)
{ {
static int s_VoteList = 0; static CListBox s_ListBox;
static float s_ScrollValue = 0; s_ListBox.DoAutoSpacing(1.0f);
UiDoListboxStart(&s_VoteList, &ServerScoreBoard, 26.0f, Localize("Scoreboard"), "", pSelectedServer->m_NumReceivedClients, 1, -1, s_ScrollValue); s_ListBox.DoStart(25.0f, pSelectedServer->m_NumReceivedClients, 1, 3, -1, &ServerScoreBoard);
for(int i = 0; i < pSelectedServer->m_NumReceivedClients; i++) for(int i = 0; i < pSelectedServer->m_NumReceivedClients; i++)
{ {
CListboxItem Item = UiDoListboxNextItem(&pSelectedServer->m_aClients[i]); const CServerInfo::CClient &CurrentClient = pSelectedServer->m_aClients[i];
const CListboxItem Item = s_ListBox.DoNextItem(&CurrentClient);
if(!Item.m_Visible) if(!Item.m_Visible)
continue; continue;
CUIRect Name, Clan, Score, Flag; CUIRect Name, Clan, Score, Flag;
Item.m_Rect.HSplitTop(25.0f, &Name, &Item.m_Rect); Name = Item.m_Rect;
if(UiLogicGetCurrentClickedItem() == i)
{
if(pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_PLAYER)
m_pClient->Friends()->RemoveFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan);
else
m_pClient->Friends()->AddFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan);
FriendlistOnUpdate();
Client()->ServerBrowserUpdate();
}
ColorRGBA Color = pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_NO ? ColorRGBA Color = CurrentClient.m_FriendState == IFriends::FRIEND_NO ?
ColorRGBA(1.0f, 1.0f, 1.0f, (i % 2 + 1) * 0.05f) : ColorRGBA(1.0f, 1.0f, 1.0f, (i % 2 + 1) * 0.05f) :
ColorRGBA(0.5f, 1.0f, 0.5f, 0.15f + (i % 2 + 1) * 0.05f); ColorRGBA(0.5f, 1.0f, 0.5f, 0.15f + (i % 2 + 1) * 0.05f);
Name.Draw(Color, IGraphics::CORNER_ALL, 4.0f); Name.Draw(Color, IGraphics::CORNER_ALL, 4.0f);
@ -1159,17 +1112,17 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
// score // score
char aTemp[16]; char aTemp[16];
if(!pSelectedServer->m_aClients[i].m_Player) if(!CurrentClient.m_Player)
str_copy(aTemp, "SPEC"); str_copy(aTemp, "SPEC");
else if((str_find_nocase(pSelectedServer->m_aGameType, "race") || str_find_nocase(pSelectedServer->m_aGameType, "fastcap")) && g_Config.m_ClDDRaceScoreBoard) else if((str_find_nocase(pSelectedServer->m_aGameType, "race") || str_find_nocase(pSelectedServer->m_aGameType, "fastcap")) && g_Config.m_ClDDRaceScoreBoard)
{ {
if(pSelectedServer->m_aClients[i].m_Score == -9999 || pSelectedServer->m_aClients[i].m_Score == 0) if(CurrentClient.m_Score == -9999 || CurrentClient.m_Score == 0)
aTemp[0] = 0; aTemp[0] = 0;
else else
str_time((int64_t)abs(pSelectedServer->m_aClients[i].m_Score) * 100, TIME_HOURS, aTemp, sizeof(aTemp)); str_time((int64_t)abs(CurrentClient.m_Score) * 100, TIME_HOURS, aTemp, sizeof(aTemp));
} }
else else
str_format(aTemp, sizeof(aTemp), "%d", pSelectedServer->m_aClients[i].m_Score); str_format(aTemp, sizeof(aTemp), "%d", CurrentClient.m_Score);
float ScoreFontSize = 12.0f; float ScoreFontSize = 12.0f;
while(ScoreFontSize >= 4.0f && TextRender()->TextWidth(0, ScoreFontSize, aTemp, -1, -1.0f) > Score.w) while(ScoreFontSize >= 4.0f && TextRender()->TextWidth(0, ScoreFontSize, aTemp, -1, -1.0f) > Score.w)
@ -1182,7 +1135,7 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
// name // name
TextRender()->SetCursor(&Cursor, Name.x, Name.y + (Name.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); TextRender()->SetCursor(&Cursor, Name.x, Name.y + (Name.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Name.w; Cursor.m_LineWidth = Name.w;
const char *pName = pSelectedServer->m_aClients[i].m_aName; const char *pName = CurrentClient.m_aName;
bool Printed = false; bool Printed = false;
if(g_Config.m_BrFilterString[0]) if(g_Config.m_BrFilterString[0])
Printed = PrintHighlighted(pName, [this, &Cursor, pName](const char *pFilteredStr, const int FilterLen) { Printed = PrintHighlighted(pName, [this, &Cursor, pName](const char *pFilteredStr, const int FilterLen) {
@ -1198,7 +1151,7 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
// clan // clan
TextRender()->SetCursor(&Cursor, Clan.x, Clan.y + (Clan.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); TextRender()->SetCursor(&Cursor, Clan.x, Clan.y + (Clan.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Clan.w; Cursor.m_LineWidth = Clan.w;
const char *pClan = pSelectedServer->m_aClients[i].m_aClan; const char *pClan = CurrentClient.m_aClan;
Printed = false; Printed = false;
if(g_Config.m_BrFilterString[0]) if(g_Config.m_BrFilterString[0])
Printed = PrintHighlighted(pClan, [this, &Cursor, pClan](const char *pFilteredStr, const int FilterLen) { Printed = PrintHighlighted(pClan, [this, &Cursor, pClan](const char *pFilteredStr, const int FilterLen) {
@ -1213,10 +1166,20 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
// flag // flag
ColorRGBA FColor(1.0f, 1.0f, 1.0f, 0.5f); ColorRGBA FColor(1.0f, 1.0f, 1.0f, 0.5f);
m_pClient->m_CountryFlags.Render(pSelectedServer->m_aClients[i].m_Country, &FColor, Flag.x, Flag.y, Flag.w, Flag.h); m_pClient->m_CountryFlags.Render(CurrentClient.m_Country, &FColor, Flag.x, Flag.y, Flag.w, Flag.h);
} }
UiDoListboxEnd(&s_ScrollValue, 0); const int NewSelected = s_ListBox.DoEnd();
if(s_ListBox.WasItemSelected())
{
const CServerInfo::CClient &SelectedClient = pSelectedServer->m_aClients[NewSelected];
if(SelectedClient.m_FriendState == IFriends::FRIEND_PLAYER)
m_pClient->Friends()->RemoveFriend(SelectedClient.m_aName, SelectedClient.m_aClan);
else
m_pClient->Friends()->AddFriend(SelectedClient.m_aName, SelectedClient.m_aClan);
FriendlistOnUpdate();
Client()->ServerBrowserUpdate();
}
} }
} }
@ -1276,49 +1239,48 @@ void CMenus::RenderServerbrowserFriends(CUIRect View)
FilterHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f); FilterHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f);
ServerFriends.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 4.0f); ServerFriends.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 4.0f);
UI()->DoLabel(&FilterHeader, Localize("Friends"), FontSize + 4.0f, TEXTALIGN_CENTER); UI()->DoLabel(&FilterHeader, Localize("Friends"), FontSize + 4.0f, TEXTALIGN_CENTER);
CUIRect Button, List;
CUIRect List;
ServerFriends.Margin(3.0f, &ServerFriends); ServerFriends.Margin(3.0f, &ServerFriends);
ServerFriends.VMargin(3.0f, &ServerFriends); ServerFriends.VMargin(3.0f, &ServerFriends);
ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends); ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends);
// friends list(remove friend) // friends list(remove friend)
static float s_ScrollValue = 0; static CListBox s_ListBox;
if(m_FriendlistSelectedIndex >= (int)m_vFriends.size()) if(m_FriendlistSelectedIndex >= (int)m_vFriends.size())
m_FriendlistSelectedIndex = m_vFriends.size() - 1; m_FriendlistSelectedIndex = m_vFriends.size() - 1;
UiDoListboxStart(&m_vFriends, &List, 30.0f, "", "", m_vFriends.size(), 1, m_FriendlistSelectedIndex, s_ScrollValue); s_ListBox.DoAutoSpacing(3.0f);
s_ListBox.DoStart(30.0f, m_vFriends.size(), 1, 3, m_FriendlistSelectedIndex, &List);
std::sort(m_vFriends.begin(), m_vFriends.end()); std::sort(m_vFriends.begin(), m_vFriends.end());
for(auto &Friend : m_vFriends) for(size_t i = 0; i < m_vFriends.size(); ++i)
{ {
CListboxItem Item = UiDoListboxNextItem(&Friend.m_NumFound, false, false); const auto &Friend = m_vFriends[i];
const CListboxItem Item = s_ListBox.DoNextItem(&Friend, m_FriendlistSelectedIndex >= 0 && (size_t)m_FriendlistSelectedIndex == i);
if(!Item.m_Visible)
continue;
if(Item.m_Visible) CUIRect NameClanLabels, NameLabel, ClanLabel, OnState;
{ Item.m_Rect.VSplitRight(30.0f, &NameClanLabels, &OnState);
Item.m_Rect.Margin(1.5f, &Item.m_Rect); NameClanLabels.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.1f), IGraphics::CORNER_L, 4.0f);
CUIRect OnState;
Item.m_Rect.VSplitRight(30.0f, &Item.m_Rect, &OnState);
Item.m_Rect.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.1f), IGraphics::CORNER_L, 4.0f);
Item.m_Rect.VMargin(2.5f, &Item.m_Rect); NameClanLabels.VMargin(2.5f, &NameClanLabels);
Item.m_Rect.HSplitTop(12.0f, &Item.m_Rect, &Button); NameClanLabels.HSplitTop(12.0f, &NameLabel, &ClanLabel);
UI()->DoLabel(&Item.m_Rect, Friend.m_pFriendInfo->m_aName, FontSize, TEXTALIGN_LEFT); UI()->DoLabel(&NameLabel, Friend.m_pFriendInfo->m_aName, FontSize, TEXTALIGN_LEFT);
UI()->DoLabel(&Button, Friend.m_pFriendInfo->m_aClan, FontSize, TEXTALIGN_LEFT); UI()->DoLabel(&ClanLabel, Friend.m_pFriendInfo->m_aClan, FontSize, TEXTALIGN_LEFT);
OnState.Draw(Friend.m_NumFound ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(1.0f, 0.0f, 0.0f, 0.25f), IGraphics::CORNER_R, 4.0f); OnState.Draw(Friend.m_NumFound ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(1.0f, 0.0f, 0.0f, 0.25f), IGraphics::CORNER_R, 4.0f);
OnState.HMargin((OnState.h - FontSize) / 3, &OnState); OnState.HMargin((OnState.h - FontSize) / 3, &OnState);
OnState.VMargin(5.0f, &OnState); OnState.VMargin(5.0f, &OnState);
char aBuf[64]; char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%i", Friend.m_NumFound); str_format(aBuf, sizeof(aBuf), "%i", Friend.m_NumFound);
UI()->DoLabel(&OnState, aBuf, FontSize + 2, TEXTALIGN_RIGHT); UI()->DoLabel(&OnState, aBuf, FontSize + 2, TEXTALIGN_RIGHT);
}
} }
bool Activated = false; m_FriendlistSelectedIndex = s_ListBox.DoEnd();
m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, &Activated);
// activate found server with friend // activate found server with friend
if(Activated && m_vFriends[m_FriendlistSelectedIndex].m_NumFound) if(s_ListBox.WasItemActivated() && m_vFriends[m_FriendlistSelectedIndex].m_NumFound)
{ {
bool Found = false; bool Found = false;
int NumServers = ServerBrowser()->NumSortedServers(); int NumServers = ServerBrowser()->NumSortedServers();
@ -1336,7 +1298,6 @@ void CMenus::RenderServerbrowserFriends(CUIRect View)
str_quickhash(pItem->m_aClients[j].m_aName) == m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash)) str_quickhash(pItem->m_aClients[j].m_aName) == m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash))
{ {
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress); str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
m_ScrollOffset = ItemIndex;
m_SelectedIndex = ItemIndex; m_SelectedIndex = ItemIndex;
Found = true; Found = true;
} }
@ -1345,6 +1306,7 @@ void CMenus::RenderServerbrowserFriends(CUIRect View)
} }
} }
CUIRect Button;
ServerFriends.HSplitTop(2.5f, 0, &ServerFriends); ServerFriends.HSplitTop(2.5f, 0, &ServerFriends);
ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends); ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
if(m_FriendlistSelectedIndex != -1) if(m_FriendlistSelectedIndex != -1)

View file

@ -14,11 +14,10 @@
#include <game/client/components/console.h> #include <game/client/components/console.h>
#include <game/client/gameclient.h> #include <game/client/gameclient.h>
#include <game/client/render.h> #include <game/client/render.h>
#include <game/localization.h>
#include <game/client/ui.h> #include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/generated/client_data.h> #include <game/generated/client_data.h>
#include <game/localization.h>
#include "maplayers.h" #include "maplayers.h"
#include "menus.h" #include "menus.h"
@ -636,246 +635,6 @@ void CMenus::RenderDemoPlayer(CUIRect MainView)
HandleDemoSeeking(PositionToSeek, TimeToSeek); HandleDemoSeeking(PositionToSeek, TimeToSeek);
} }
static CUIRect gs_ListBoxOriginalView;
static CUIRect gs_ListBoxView;
static float gs_ListBoxRowHeight;
static int gs_ListBoxItemIndex;
static int gs_ListBoxSelectedIndex;
static int gs_ListBoxNewSelected;
static int gs_ListBoxDoneEvents;
static int gs_ListBoxNumItems;
static int gs_ListBoxItemsPerRow;
static float gs_ListBoxScrollValue;
static bool gs_ListBoxItemActivated;
static bool gs_ListBoxClicked;
void CMenus::UiDoListboxStart(const void *pID, const CUIRect *pRect, float RowHeight, const char *pTitle, const char *pBottomText, int NumItems,
int ItemsPerRow, int SelectedIndex, float ScrollValue, bool LogicOnly)
{
CUIRect Scroll, Row;
CUIRect View = *pRect;
if(!LogicOnly)
{
// background
View.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_ALL, 5.0f);
}
View.VSplitRight(20.0f, &View, &Scroll);
// setup the variables
gs_ListBoxOriginalView = View;
gs_ListBoxSelectedIndex = SelectedIndex;
gs_ListBoxNewSelected = SelectedIndex;
gs_ListBoxItemIndex = 0;
gs_ListBoxRowHeight = RowHeight;
gs_ListBoxNumItems = NumItems;
gs_ListBoxItemsPerRow = ItemsPerRow;
gs_ListBoxDoneEvents = 0;
gs_ListBoxScrollValue = ScrollValue;
gs_ListBoxItemActivated = false;
gs_ListBoxClicked = false;
// do the scrollbar
View.HSplitTop(gs_ListBoxRowHeight, &Row, 0);
int NumViewable = (int)(gs_ListBoxOriginalView.h / Row.h) * gs_ListBoxItemsPerRow;
//int Num = (NumItems + gs_ListBoxItemsPerRow - 1) / gs_ListBoxItemsPerRow - NumViewable + 1;
int Num = ceil((NumItems - NumViewable) / (float)gs_ListBoxItemsPerRow);
if(Num <= 0)
{
Num = 0;
}
else
{
if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View))
gs_ListBoxScrollValue -= Num == 1 ? 0.1f : 3.0f / Num;
if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View))
gs_ListBoxScrollValue += Num == 1 ? 0.1f : 3.0f / Num;
}
if(Num == 0)
gs_ListBoxScrollValue = 0;
else
gs_ListBoxScrollValue = UI()->DoScrollbarV(pID, &Scroll, gs_ListBoxScrollValue);
// the list
gs_ListBoxView = gs_ListBoxOriginalView;
gs_ListBoxView.VMargin(5.0f, &gs_ListBoxView);
UI()->ClipEnable(&gs_ListBoxView);
gs_ListBoxView.y -= gs_ListBoxScrollValue * Num * Row.h;
}
CMenus::CListboxItem CMenus::UiDoListboxNextRow()
{
static CUIRect s_RowView;
CListboxItem Item = {0};
if(gs_ListBoxItemIndex % gs_ListBoxItemsPerRow == 0)
gs_ListBoxView.HSplitTop(gs_ListBoxRowHeight /*-2.0f*/, &s_RowView, &gs_ListBoxView);
s_RowView.VSplitLeft(s_RowView.w / (gs_ListBoxItemsPerRow - gs_ListBoxItemIndex % gs_ListBoxItemsPerRow), &Item.m_Rect, &s_RowView);
Item.m_Visible = 1;
//item.rect = row;
Item.m_HitRect = Item.m_Rect;
//CUIRect select_hit_box = item.rect;
if(gs_ListBoxSelectedIndex == gs_ListBoxItemIndex)
Item.m_Selected = 1;
// make sure that only those in view can be selected
if(Item.m_Rect.y + Item.m_Rect.h > gs_ListBoxOriginalView.y)
{
if(Item.m_HitRect.y < Item.m_HitRect.y) // clip the selection
{
Item.m_HitRect.h -= gs_ListBoxOriginalView.y - Item.m_HitRect.y;
Item.m_HitRect.y = gs_ListBoxOriginalView.y;
}
}
else
Item.m_Visible = 0;
// check if we need to do more
if(Item.m_Rect.y > gs_ListBoxOriginalView.y + gs_ListBoxOriginalView.h)
Item.m_Visible = 0;
gs_ListBoxItemIndex++;
return Item;
}
CMenus::CListboxItem CMenus::UiDoListboxNextItem(const void *pId, bool Selected, bool KeyEvents, bool NoHoverEffects)
{
int ThisItemIndex = gs_ListBoxItemIndex;
if(Selected)
{
if(gs_ListBoxSelectedIndex == gs_ListBoxNewSelected)
gs_ListBoxNewSelected = ThisItemIndex;
gs_ListBoxSelectedIndex = ThisItemIndex;
}
CListboxItem Item = UiDoListboxNextRow();
CUIRect HitRect = Item.m_HitRect;
if(HitRect.y < gs_ListBoxOriginalView.y)
{
float TmpDiff = gs_ListBoxOriginalView.y - HitRect.y;
HitRect.y = gs_ListBoxOriginalView.y;
HitRect.h -= TmpDiff;
}
HitRect.h = minimum(HitRect.h, (gs_ListBoxOriginalView.y + gs_ListBoxOriginalView.h) - HitRect.y);
bool DoubleClickable = false;
if(Item.m_Visible && UI()->DoButtonLogic(pId, gs_ListBoxSelectedIndex == gs_ListBoxItemIndex, &HitRect))
{
DoubleClickable |= gs_ListBoxNewSelected == ThisItemIndex;
gs_ListBoxClicked = true;
gs_ListBoxNewSelected = ThisItemIndex;
}
// process input, regard selected index
if(gs_ListBoxSelectedIndex == ThisItemIndex)
{
if(!gs_ListBoxDoneEvents)
{
gs_ListBoxDoneEvents = 1;
if(UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (DoubleClickable && Input()->MouseDoubleClick()))
{
gs_ListBoxItemActivated = true;
UI()->SetActiveItem(nullptr);
}
else if(KeyEvents)
{
for(int i = 0; i < m_NumInputEvents; i++)
{
int NewIndex = -1;
if(m_aInputEvents[i].m_Flags & IInput::FLAG_PRESS)
{
if(m_aInputEvents[i].m_Key == KEY_DOWN)
NewIndex = gs_ListBoxNewSelected + 1;
else if(m_aInputEvents[i].m_Key == KEY_UP)
NewIndex = gs_ListBoxNewSelected - 1;
else if(m_aInputEvents[i].m_Key == KEY_PAGEUP)
NewIndex = maximum(gs_ListBoxNewSelected - 20, 0);
else if(m_aInputEvents[i].m_Key == KEY_PAGEDOWN)
NewIndex = minimum(gs_ListBoxNewSelected + 20, gs_ListBoxNumItems - 1);
else if(m_aInputEvents[i].m_Key == KEY_HOME)
NewIndex = 0;
else if(m_aInputEvents[i].m_Key == KEY_END)
NewIndex = gs_ListBoxNumItems - 1;
}
if(NewIndex > -1 && NewIndex < gs_ListBoxNumItems)
{
// scroll
float Offset = (NewIndex / gs_ListBoxItemsPerRow - gs_ListBoxNewSelected / gs_ListBoxItemsPerRow) * gs_ListBoxRowHeight;
int Scroll = gs_ListBoxOriginalView.y > Item.m_Rect.y + Offset ? -1 :
gs_ListBoxOriginalView.y + gs_ListBoxOriginalView.h < Item.m_Rect.y + Item.m_Rect.h + Offset ? 1 : 0;
if(Scroll)
{
int NumViewable = (int)(gs_ListBoxOriginalView.h / gs_ListBoxRowHeight) + 1;
int ScrollNum = (gs_ListBoxNumItems + gs_ListBoxItemsPerRow - 1) / gs_ListBoxItemsPerRow - NumViewable + 1;
if(Scroll < 0)
{
int Num = (gs_ListBoxOriginalView.y - Item.m_Rect.y - Offset + gs_ListBoxRowHeight - 1.0f) / gs_ListBoxRowHeight;
gs_ListBoxScrollValue -= (1.0f / ScrollNum) * Num;
}
else
{
int Num = (Item.m_Rect.y + Item.m_Rect.h + Offset - (gs_ListBoxOriginalView.y + gs_ListBoxOriginalView.h) + gs_ListBoxRowHeight - 1.0f) /
gs_ListBoxRowHeight;
gs_ListBoxScrollValue += (1.0f / ScrollNum) * Num;
}
if(gs_ListBoxScrollValue < 0.0f)
gs_ListBoxScrollValue = 0.0f;
if(gs_ListBoxScrollValue > 1.0f)
gs_ListBoxScrollValue = 1.0f;
}
gs_ListBoxNewSelected = NewIndex;
}
}
}
}
//selected_index = i;
CUIRect r = Item.m_Rect;
r.Margin(1.5f, &r);
r.Draw(ColorRGBA(1, 1, 1, 0.5f), IGraphics::CORNER_ALL, 4.0f);
}
else if(UI()->MouseInside(&HitRect) && !NoHoverEffects)
{
CUIRect r = Item.m_Rect;
r.Margin(1.5f, &r);
r.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f);
}
return Item;
}
int CMenus::UiDoListboxEnd(float *pScrollValue, bool *pItemActivated, bool *pListBoxActive)
{
UI()->ClipDisable();
if(pScrollValue)
*pScrollValue = gs_ListBoxScrollValue;
if(pItemActivated)
*pItemActivated = gs_ListBoxItemActivated;
if(pListBoxActive)
*pListBoxActive = gs_ListBoxClicked;
return gs_ListBoxNewSelected;
}
int CMenus::UiLogicGetCurrentClickedItem()
{
if(gs_ListBoxClicked)
return gs_ListBoxNewSelected;
else
return -1;
}
int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser) int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser)
{ {
CMenus *pSelf = (CMenus *)pUser; CMenus *pSelf = (CMenus *)pUser;
@ -1206,78 +965,26 @@ void CMenus::RenderDemoList(CUIRect MainView)
} }
} }
// scrollbar static CListBox s_ListBox;
CUIRect Scroll; s_ListBox.DoStart(ms_ListheaderHeight, m_vDemos.size(), 1, 3, m_DemolistSelectedIndex, &ListBox, false);
ListBox.VSplitRight(20.0f, &ListBox, &Scroll);
static float s_ScrollValue = 0;
s_ScrollValue = UI()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue);
int PreviousIndex = m_DemolistSelectedIndex;
HandleListInputs(ListBox, s_ScrollValue, 3.0f, &m_ScrollOffset, s_aCols[0].m_Rect.h, m_DemolistSelectedIndex, m_vDemos.size());
if(PreviousIndex != m_DemolistSelectedIndex)
{
str_copy(g_Config.m_UiDemoSelected, m_vDemos[m_DemolistSelectedIndex].m_aName);
DemolistOnUpdate(false);
}
// set clipping
UI()->ClipEnable(&ListBox);
CUIRect OriginalView = ListBox;
int Num = (int)(ListBox.h / s_aCols[0].m_Rect.h) + 1;
int ScrollNum = maximum<int>(m_vDemos.size() - Num + 1, 0);
ListBox.y -= s_ScrollValue * ScrollNum * s_aCols[0].m_Rect.h;
int ItemIndex = -1; int ItemIndex = -1;
bool DoubleClicked = false;
for(auto &Item : m_vDemos) for(auto &Item : m_vDemos)
{ {
ItemIndex++; ItemIndex++;
CUIRect Row; const CListboxItem ListItem = s_ListBox.DoNextItem(&Item, ItemIndex == m_DemolistSelectedIndex);
ListBox.HSplitTop(ms_ListheaderHeight, &Row, &ListBox); if(!ListItem.m_Visible)
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)
{
if(Selected)
{
CUIRect Rect = Row;
Rect.Margin(0.5f, &Rect);
Rect.Draw(ColorRGBA(1, 1, 1, 0.5f), IGraphics::CORNER_ALL, 4.0f);
}
else if(UI()->MouseHovered(&Row))
{
CUIRect Rect = Row;
Rect.Margin(0.5f, &Rect);
Rect.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f);
}
if(UI()->DoButtonLogic(Item.m_aName, Selected, &Row))
{
DoubleClicked |= ItemIndex == m_DoubleClickIndex;
str_copy(g_Config.m_UiDemoSelected, Item.m_aName);
DemolistOnUpdate(false);
m_DoubleClickIndex = ItemIndex;
}
}
else
{
// don't render invisible items
continue; continue;
}
CUIRect Row = ListItem.m_Rect;
CUIRect FileIcon; CUIRect FileIcon;
Row.VSplitLeft(Row.h, &FileIcon, &Row); Row.VSplitLeft(Row.h, &FileIcon, &Row);
Row.VSplitLeft(5.0f, 0, &Row); Row.VSplitLeft(5.0f, 0, &Row);
FileIcon.Margin(1.0f, &FileIcon); FileIcon.Margin(1.0f, &FileIcon);
FileIcon.x += 2.0f; FileIcon.x += 2.0f;
const char *pIconType;
const char *pIconType;
if(str_comp(Item.m_aFilename, "..") == 0) if(str_comp(Item.m_aFilename, "..") == 0)
pIconType = "\xEF\xA0\x82"; pIconType = "\xEF\xA0\x82";
else if(Item.m_IsDir) else if(Item.m_IsDir)
@ -1338,13 +1045,13 @@ void CMenus::RenderDemoList(CUIRect MainView)
} }
} }
UI()->ClipDisable(); const int NewSelected = s_ListBox.DoEnd();
if(NewSelected != m_DemolistSelectedIndex)
bool Activated = false;
if(UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (DoubleClicked && Input()->MouseDoubleClick()))
{ {
UI()->SetActiveItem(nullptr); m_DemolistSelectedIndex = NewSelected;
Activated = true; if(m_DemolistSelectedIndex >= 0)
str_copy(g_Config.m_UiDemoSelected, m_vDemos[m_DemolistSelectedIndex].m_aName);
DemolistOnUpdate(false);
} }
static CButtonContainer s_RefreshButton; static CButtonContainer s_RefreshButton;
@ -1362,7 +1069,7 @@ void CMenus::RenderDemoList(CUIRect MainView)
} }
static CButtonContainer s_PlayButton; static CButtonContainer s_PlayButton;
if(DoButton_Menu(&s_PlayButton, m_DemolistSelectedIsDir ? Localize("Open") : Localize("Play", "Demo browser"), 0, &PlayRect) || Activated || (Input()->KeyPress(KEY_P) && m_pClient->m_GameConsole.IsClosed() && m_DemoPlayerState == DEMOPLAYER_NONE)) 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() && m_DemoPlayerState == DEMOPLAYER_NONE))
{ {
if(m_DemolistSelectedIndex >= 0) if(m_DemolistSelectedIndex >= 0)
{ {

View file

@ -21,6 +21,7 @@
#include <game/client/gameclient.h> #include <game/client/gameclient.h>
#include <game/client/render.h> #include <game/client/render.h>
#include <game/client/ui.h> #include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/client/ui_scrollregion.h> #include <game/client/ui_scrollregion.h>
#include <game/localization.h> #include <game/localization.h>
@ -242,16 +243,15 @@ void CMenus::RenderPlayers(CUIRect MainView)
ButtonBar.VSplitLeft(Width, &Button, &ButtonBar); ButtonBar.VSplitLeft(Width, &Button, &ButtonBar);
RenderTools()->RenderIcon(IMAGE_GUIICONS, SPRITE_GUIICON_MUTE, &Button); RenderTools()->RenderIcon(IMAGE_GUIICONS, SPRITE_GUIICON_MUTE, &Button);
ButtonBar.VSplitLeft(20.0f, 0, &ButtonBar); ButtonBar.VSplitLeft(20.0f, nullptr, &ButtonBar);
ButtonBar.VSplitLeft(Width, &Button, &ButtonBar); ButtonBar.VSplitLeft(Width, &Button, &ButtonBar);
RenderTools()->RenderIcon(IMAGE_GUIICONS, SPRITE_GUIICON_EMOTICON_MUTE, &Button); RenderTools()->RenderIcon(IMAGE_GUIICONS, SPRITE_GUIICON_EMOTICON_MUTE, &Button);
ButtonBar.VSplitLeft(20.0f, 0, &ButtonBar); ButtonBar.VSplitLeft(20.0f, nullptr, &ButtonBar);
ButtonBar.VSplitLeft(Width, &Button, &ButtonBar); ButtonBar.VSplitLeft(Width, &Button, &ButtonBar);
RenderTools()->RenderIcon(IMAGE_GUIICONS, SPRITE_GUIICON_FRIEND, &Button); RenderTools()->RenderIcon(IMAGE_GUIICONS, SPRITE_GUIICON_FRIEND, &Button);
int TotalPlayers = 0; int TotalPlayers = 0;
for(const auto &pInfoByName : m_pClient->m_Snap.m_apInfoByName) for(const auto &pInfoByName : m_pClient->m_Snap.m_apInfoByName)
{ {
if(!pInfoByName) if(!pInfoByName)
@ -265,14 +265,11 @@ void CMenus::RenderPlayers(CUIRect MainView)
TotalPlayers++; TotalPlayers++;
} }
static int s_VoteList = 0; static CListBox s_ListBox;
static float s_ScrollValue = 0; s_ListBox.DoStart(24.0f, TotalPlayers, 1, 3, -1, &Options);
CUIRect List = Options;
// List.HSplitTop(28.0f, 0, &List);
UiDoListboxStart(&s_VoteList, &List, 24.0f, "", "", TotalPlayers, 1, -1, s_ScrollValue);
// options // options
static int s_aPlayerIDs[MAX_CLIENTS][3] = {{0}}; static char s_aPlayerIDs[MAX_CLIENTS][3] = {{0}};
for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i) for(int i = 0, Count = 0; i < MAX_CLIENTS; ++i)
{ {
@ -280,25 +277,27 @@ void CMenus::RenderPlayers(CUIRect MainView)
continue; continue;
int Index = m_pClient->m_Snap.m_apInfoByName[i]->m_ClientID; int Index = m_pClient->m_Snap.m_apInfoByName[i]->m_ClientID;
if(Index == m_pClient->m_Snap.m_LocalClientID) if(Index == m_pClient->m_Snap.m_LocalClientID)
continue; continue;
CListboxItem Item = UiDoListboxNextItem(&m_pClient->m_aClients[Index]); CGameClient::CClientData &CurrentClient = m_pClient->m_aClients[Index];
const CListboxItem Item = s_ListBox.DoNextItem(&CurrentClient);
Count++; Count++;
if(!Item.m_Visible) if(!Item.m_Visible)
continue; continue;
CUIRect Row = Item.m_Rect;
if(Count % 2 == 1) if(Count % 2 == 1)
Item.m_Rect.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 10.0f); Row.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 5.0f);
Item.m_Rect.VSplitRight(300.0f, &Player, &Item.m_Rect); Row.VSplitRight(s_ListBox.ScrollbarWidthMax() - s_ListBox.ScrollbarWidth(), &Row, nullptr);
Row.VSplitRight(300.0f, &Player, &Row);
// player info // player info
Player.VSplitLeft(28.0f, &Button, &Player); Player.VSplitLeft(28.0f, &Button, &Player);
CTeeRenderInfo TeeInfo = m_pClient->m_aClients[Index].m_RenderInfo; CTeeRenderInfo TeeInfo = CurrentClient.m_RenderInfo;
TeeInfo.m_Size = Button.h; TeeInfo.m_Size = Button.h;
CAnimState *pIdleState = CAnimState::GetIdle(); CAnimState *pIdleState = CAnimState::GetIdle();
@ -308,59 +307,57 @@ void CMenus::RenderPlayers(CUIRect MainView)
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
Player.HSplitTop(1.5f, 0, &Player); Player.HSplitTop(1.5f, nullptr, &Player);
Player.VSplitMid(&Player, &Button); Player.VSplitMid(&Player, &Button);
Item.m_Rect.VSplitRight(200.0f, &Button2, &Item.m_Rect); Row.VSplitRight(210.0f, &Button2, &Row);
CTextCursor Cursor; CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, Player.x, Player.y + (Player.h - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); TextRender()->SetCursor(&Cursor, Player.x, Player.y + (Player.h - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Player.w; Cursor.m_LineWidth = Player.w;
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[Index].m_aName, -1); TextRender()->TextEx(&Cursor, CurrentClient.m_aName, -1);
TextRender()->SetCursor(&Cursor, Button.x, Button.y + (Button.h - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); TextRender()->SetCursor(&Cursor, Button.x, Button.y + (Button.h - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Button.w; Cursor.m_LineWidth = Button.w;
TextRender()->TextEx(&Cursor, m_pClient->m_aClients[Index].m_aClan, -1); TextRender()->TextEx(&Cursor, CurrentClient.m_aClan, -1);
// TextRender()->SetCursor(&Cursor, Button2.x,Button2.y, 14.0f, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END);
// Cursor.m_LineWidth = Button.w;
ColorRGBA Color(1.0f, 1.0f, 1.0f, 0.5f); ColorRGBA Color(1.0f, 1.0f, 1.0f, 0.5f);
m_pClient->m_CountryFlags.Render(m_pClient->m_aClients[Index].m_Country, &Color, m_pClient->m_CountryFlags.Render(CurrentClient.m_Country, &Color,
Button2.x, Button2.y + Button2.h / 2.0f - 0.75f * Button2.h / 2.0f, 1.5f * Button2.h, 0.75f * Button2.h); Button2.x, Button2.y + Button2.h / 2.0f - 0.75f * Button2.h / 2.0f, 1.5f * Button2.h, 0.75f * Button2.h);
// ignore chat button // ignore chat button
Item.m_Rect.HMargin(2.0f, &Item.m_Rect); Row.HMargin(2.0f, &Row);
Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect); Row.VSplitLeft(Width, &Button, &Row);
Button.VSplitLeft((Width - Button.h) / 4.0f, 0, &Button); Button.VSplitLeft((Width - Button.h) / 4.0f, nullptr, &Button);
Button.VSplitLeft(Button.h, &Button, 0); Button.VSplitLeft(Button.h, &Button, nullptr);
if(g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[Index].m_Friend) if(g_Config.m_ClShowChatFriends && !CurrentClient.m_Friend)
DoButton_Toggle(&s_aPlayerIDs[Index][0], 1, &Button, false); DoButton_Toggle(&s_aPlayerIDs[Index][0], 1, &Button, false);
else if(DoButton_Toggle(&s_aPlayerIDs[Index][0], m_pClient->m_aClients[Index].m_ChatIgnore, &Button, true)) else if(DoButton_Toggle(&s_aPlayerIDs[Index][0], CurrentClient.m_ChatIgnore, &Button, true))
m_pClient->m_aClients[Index].m_ChatIgnore ^= 1; CurrentClient.m_ChatIgnore ^= 1;
// ignore emoticon button // ignore emoticon button
Item.m_Rect.VSplitLeft(20.0f, &Button, &Item.m_Rect); Row.VSplitLeft(30.0f, nullptr, &Row);
Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect); Row.VSplitLeft(Width, &Button, &Row);
Button.VSplitLeft((Width - Button.h) / 4.0f, 0, &Button); Button.VSplitLeft((Width - Button.h) / 4.0f, nullptr, &Button);
Button.VSplitLeft(Button.h, &Button, 0); Button.VSplitLeft(Button.h, &Button, nullptr);
if(g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[Index].m_Friend) if(g_Config.m_ClShowChatFriends && !CurrentClient.m_Friend)
DoButton_Toggle(&s_aPlayerIDs[Index][1], 1, &Button, false); DoButton_Toggle(&s_aPlayerIDs[Index][1], 1, &Button, false);
else if(DoButton_Toggle(&s_aPlayerIDs[Index][1], m_pClient->m_aClients[Index].m_EmoticonIgnore, &Button, true)) else if(DoButton_Toggle(&s_aPlayerIDs[Index][1], CurrentClient.m_EmoticonIgnore, &Button, true))
m_pClient->m_aClients[Index].m_EmoticonIgnore ^= 1; CurrentClient.m_EmoticonIgnore ^= 1;
// friend button // friend button
Item.m_Rect.VSplitLeft(20.0f, &Button, &Item.m_Rect); Row.VSplitLeft(10.0f, nullptr, &Row);
Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect); Row.VSplitLeft(Width, &Button, &Row);
Button.VSplitLeft((Width - Button.h) / 4.0f, 0, &Button); Button.VSplitLeft((Width - Button.h) / 4.0f, nullptr, &Button);
Button.VSplitLeft(Button.h, &Button, 0); Button.VSplitLeft(Button.h, &Button, nullptr);
if(DoButton_Toggle(&s_aPlayerIDs[Index][2], m_pClient->m_aClients[Index].m_Friend, &Button, true)) if(DoButton_Toggle(&s_aPlayerIDs[Index][2], CurrentClient.m_Friend, &Button, true))
{ {
if(m_pClient->m_aClients[Index].m_Friend) if(CurrentClient.m_Friend)
m_pClient->Friends()->RemoveFriend(m_pClient->m_aClients[Index].m_aName, m_pClient->m_aClients[Index].m_aClan); m_pClient->Friends()->RemoveFriend(CurrentClient.m_aName, CurrentClient.m_aClan);
else else
m_pClient->Friends()->AddFriend(m_pClient->m_aClients[Index].m_aName, m_pClient->m_aClients[Index].m_aClan); m_pClient->Friends()->AddFriend(CurrentClient.m_aName, CurrentClient.m_aClan);
} }
} }
UiDoListboxEnd(&s_ScrollValue, 0); s_ListBox.DoEnd();
} }
void CMenus::RenderServerInfo(CUIRect MainView) void CMenus::RenderServerInfo(CUIRect MainView)
@ -492,8 +489,6 @@ void CMenus::RenderServerInfo(CUIRect MainView)
bool CMenus::RenderServerControlServer(CUIRect MainView) bool CMenus::RenderServerControlServer(CUIRect MainView)
{ {
static int s_VoteList = 0;
static float s_ScrollValue = 0;
CUIRect List = MainView; CUIRect List = MainView;
int Total = m_pClient->m_Voting.m_NumVoteOptions; int Total = m_pClient->m_Voting.m_NumVoteOptions;
int NumVoteOptions = 0; int NumVoteOptions = 0;
@ -508,7 +503,8 @@ bool CMenus::RenderServerControlServer(CUIRect MainView)
TotalShown++; TotalShown++;
} }
UiDoListboxStart(&s_VoteList, &List, 19.0f, "", "", TotalShown, 1, s_CurVoteOption, s_ScrollValue); static CListBox s_ListBox;
s_ListBox.DoStart(19.0f, TotalShown, 1, 3, s_CurVoteOption, &List);
int i = -1; int i = -1;
for(CVoteOptionClient *pOption = m_pClient->m_Voting.m_pFirst; pOption; pOption = pOption->m_pNext) for(CVoteOptionClient *pOption = m_pClient->m_Voting.m_pFirst; pOption; pOption = pOption->m_pNext)
@ -517,21 +513,23 @@ bool CMenus::RenderServerControlServer(CUIRect MainView)
if(m_aFilterString[0] != '\0' && !str_utf8_find_nocase(pOption->m_aDescription, m_aFilterString)) if(m_aFilterString[0] != '\0' && !str_utf8_find_nocase(pOption->m_aDescription, m_aFilterString))
continue; continue;
CListboxItem Item = UiDoListboxNextItem(pOption);
if(Item.m_Visible)
UI()->DoLabel(&Item.m_Rect, pOption->m_aDescription, 13.0f, TEXTALIGN_LEFT);
if(NumVoteOptions < Total) if(NumVoteOptions < Total)
aIndices[NumVoteOptions] = i; aIndices[NumVoteOptions] = i;
NumVoteOptions++; NumVoteOptions++;
const CListboxItem Item = s_ListBox.DoNextItem(pOption);
if(!Item.m_Visible)
continue;
SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Item.m_Rect, pOption->m_aDescription, 13.0f, TEXTALIGN_LEFT, Props);
} }
bool Call; s_CurVoteOption = s_ListBox.DoEnd();
s_CurVoteOption = UiDoListboxEnd(&s_ScrollValue, &Call);
if(s_CurVoteOption < Total) if(s_CurVoteOption < Total)
m_CallvoteSelectedOption = aIndices[s_CurVoteOption]; m_CallvoteSelectedOption = aIndices[s_CurVoteOption];
return Call; return s_ListBox.WasItemActivated();
} }
bool CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators) bool CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators)
@ -556,36 +554,36 @@ bool CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators)
aPlayerIDs[NumOptions++] = Index; aPlayerIDs[NumOptions++] = Index;
} }
static int s_VoteList = 0; static CListBox s_ListBox;
static float s_ScrollValue = 0; s_ListBox.DoStart(24.0f, NumOptions, 1, 3, Selected, &MainView);
CUIRect List = MainView;
UiDoListboxStart(&s_VoteList, &List, 24.0f, "", "", NumOptions, 1, Selected, s_ScrollValue);
for(int i = 0; i < NumOptions; i++) for(int i = 0; i < NumOptions; i++)
{ {
CListboxItem Item = UiDoListboxNextItem(&aPlayerIDs[i]); const CListboxItem Item = s_ListBox.DoNextItem(&aPlayerIDs[i]);
if(!Item.m_Visible)
continue;
if(Item.m_Visible) CUIRect TeeRect, Label;
{ Item.m_Rect.VSplitLeft(Item.m_Rect.h, &TeeRect, &Label);
CTeeRenderInfo TeeInfo = m_pClient->m_aClients[aPlayerIDs[i]].m_RenderInfo;
TeeInfo.m_Size = Item.m_Rect.h;
CAnimState *pIdleState = CAnimState::GetIdle(); CTeeRenderInfo TeeInfo = m_pClient->m_aClients[aPlayerIDs[i]].m_RenderInfo;
vec2 OffsetToMid; TeeInfo.m_Size = TeeRect.h;
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid);
vec2 TeeRenderPos(Item.m_Rect.x + Item.m_Rect.h / 2, Item.m_Rect.y + Item.m_Rect.h / 2 + OffsetToMid.y);
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos); CAnimState *pIdleState = CAnimState::GetIdle();
vec2 OffsetToMid;
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid);
vec2 TeeRenderPos(TeeRect.x + TeeInfo.m_Size / 2, TeeRect.y + TeeInfo.m_Size / 2 + OffsetToMid.y);
Item.m_Rect.x += TeeInfo.m_Size; RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
UI()->DoLabel(&Item.m_Rect, m_pClient->m_aClients[aPlayerIDs[i]].m_aName, 16.0f, TEXTALIGN_LEFT);
} SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Item.m_Rect, m_pClient->m_aClients[aPlayerIDs[i]].m_aName, 16.0f, TEXTALIGN_LEFT, Props);
} }
bool Call; Selected = s_ListBox.DoEnd();
Selected = UiDoListboxEnd(&s_ScrollValue, &Call);
m_CallvoteSelectedPlayer = Selected != -1 ? aPlayerIDs[Selected] : -1; m_CallvoteSelectedPlayer = Selected != -1 ? aPlayerIDs[Selected] : -1;
return Call; return s_ListBox.WasItemActivated();
} }
void CMenus::RenderServerControl(CUIRect MainView) void CMenus::RenderServerControl(CUIRect MainView)
@ -1007,70 +1005,37 @@ void CMenus::RenderGhost(CUIRect MainView)
View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 0); View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 0);
CUIRect Scroll; const int NumGhosts = m_vGhosts.size();
View.VSplitRight(20.0f, &View, &Scroll);
static float s_ScrollValue = 0;
s_ScrollValue = UI()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue);
int NumGhosts = m_vGhosts.size();
static int s_SelectedIndex = 0; static int s_SelectedIndex = 0;
HandleListInputs(View, s_ScrollValue, 1.0f, nullptr, s_aCols[0].m_Rect.h, s_SelectedIndex, NumGhosts); static CListBox s_ListBox;
s_ListBox.DoStart(17.0f, NumGhosts, 1, 3, s_SelectedIndex, &View, false);
// set clipping
UI()->ClipEnable(&View);
CUIRect OriginalView = View;
int Num = (int)(View.h / s_aCols[0].m_Rect.h) + 1;
int ScrollNum = maximum(NumGhosts - Num + 1, 0);
View.y -= s_ScrollValue * ScrollNum * s_aCols[0].m_Rect.h;
int NewSelected = -1;
bool DoubleClicked = false;
for(int i = 0; i < NumGhosts; i++) for(int i = 0; i < NumGhosts; i++)
{ {
const CGhostItem *pItem = &m_vGhosts[i]; const CGhostItem *pGhost = &m_vGhosts[i];
CUIRect Row; const CListboxItem Item = s_ListBox.DoNextItem(pGhost);
View.HSplitTop(17.0f, &Row, &View); if(!Item.m_Visible)
continue;
// make sure that only those in view can be selected
if(Row.y + Row.h > OriginalView.y && Row.y < OriginalView.y + OriginalView.h)
{
if(i == s_SelectedIndex)
{
CUIRect r = Row;
r.Margin(1.5f, &r);
r.Draw(ColorRGBA(1, 1, 1, 0.5f), IGraphics::CORNER_ALL, 4.0f);
}
if(UI()->DoButtonLogic(pItem, 0, &Row))
{
NewSelected = i;
DoubleClicked |= NewSelected == m_DoubleClickIndex;
m_DoubleClickIndex = NewSelected;
}
}
ColorRGBA rgb = ColorRGBA(1.0f, 1.0f, 1.0f); ColorRGBA rgb = ColorRGBA(1.0f, 1.0f, 1.0f);
if(pItem->m_Own) if(pGhost->m_Own)
rgb = color_cast<ColorRGBA>(ColorHSLA(0.33f, 1.0f, 0.75f)); rgb = color_cast<ColorRGBA>(ColorHSLA(0.33f, 1.0f, 0.75f));
TextRender()->TextColor(rgb.WithAlpha(pItem->HasFile() ? 1.0f : 0.5f)); TextRender()->TextColor(rgb.WithAlpha(pGhost->HasFile() ? 1.0f : 0.5f));
for(int c = 0; c < NumCols; c++) for(int c = 0; c < NumCols; c++)
{ {
CUIRect Button; CUIRect Button;
Button.x = s_aCols[c].m_Rect.x; Button.x = s_aCols[c].m_Rect.x;
Button.y = Row.y; Button.y = Item.m_Rect.y;
Button.h = Row.h; Button.h = Item.m_Rect.h;
Button.w = s_aCols[c].m_Rect.w; Button.w = s_aCols[c].m_Rect.w;
int Id = s_aCols[c].m_Id; int Id = s_aCols[c].m_Id;
if(Id == COL_ACTIVE) if(Id == COL_ACTIVE)
{ {
if(pItem->Active()) if(pGhost->Active())
{ {
Graphics()->WrapClamp(); Graphics()->WrapClamp();
Graphics()->TextureSet(GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[(SPRITE_OOP + 7) - SPRITE_OOP]); Graphics()->TextureSet(GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[(SPRITE_OOP + 7) - SPRITE_OOP]);
@ -1088,7 +1053,7 @@ void CMenus::RenderGhost(CUIRect MainView)
TextRender()->SetCursor(&Cursor, Button.x, Button.y + (Button.h - 12.0f) / 2.f, 12.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END); TextRender()->SetCursor(&Cursor, Button.x, Button.y + (Button.h - 12.0f) / 2.f, 12.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Button.w; Cursor.m_LineWidth = Button.w;
TextRender()->TextEx(&Cursor, pItem->m_aPlayer, -1); TextRender()->TextEx(&Cursor, pGhost->m_aPlayer, -1);
} }
else if(Id == COL_TIME) else if(Id == COL_TIME)
{ {
@ -1097,7 +1062,7 @@ void CMenus::RenderGhost(CUIRect MainView)
Cursor.m_LineWidth = Button.w; Cursor.m_LineWidth = Button.w;
char aBuf[64]; char aBuf[64];
str_time(pItem->m_Time / 10, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf)); str_time(pGhost->m_Time / 10, TIME_HOURS_CENTISECS, aBuf, sizeof(aBuf));
TextRender()->TextEx(&Cursor, aBuf, -1); TextRender()->TextEx(&Cursor, aBuf, -1);
} }
} }
@ -1105,10 +1070,7 @@ void CMenus::RenderGhost(CUIRect MainView)
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
} }
UI()->ClipDisable(); s_SelectedIndex = s_ListBox.DoEnd();
if(NewSelected != -1)
s_SelectedIndex = NewSelected;
Status.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_B, 5.0f); Status.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_B, 5.0f);
Status.Margin(5.0f, &Status); Status.Margin(5.0f, &Status);
@ -1136,7 +1098,7 @@ void CMenus::RenderGhost(CUIRect MainView)
static CButtonContainer s_GhostButton; static CButtonContainer s_GhostButton;
const char *pText = pGhost->Active() ? Localize("Deactivate") : Localize("Activate"); const char *pText = pGhost->Active() ? Localize("Deactivate") : Localize("Activate");
if(DoButton_Menu(&s_GhostButton, pText, 0, &Button) || (DoubleClicked && Input()->MouseDoubleClick())) if(DoButton_Menu(&s_GhostButton, pText, 0, &Button) || s_ListBox.WasItemActivated())
{ {
if(pGhost->Active()) if(pGhost->Active())
{ {

View file

@ -20,6 +20,7 @@
#include <game/client/render.h> #include <game/client/render.h>
#include <game/client/skin.h> #include <game/client/skin.h>
#include <game/client/ui.h> #include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/client/ui_scrollregion.h> #include <game/client/ui_scrollregion.h>
#include <game/localization.h> #include <game/localization.h>
@ -366,9 +367,9 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
// country flag selector // country flag selector
MainView.HSplitTop(20.0f, 0, &MainView); MainView.HSplitTop(20.0f, 0, &MainView);
static float s_ScrollValue = 0.0f;
int OldSelected = -1; int OldSelected = -1;
UiDoListboxStart(&s_ScrollValue, &MainView, 50.0f, Localize("Country / Region"), "", m_pClient->m_CountryFlags.Num(), 6, OldSelected, s_ScrollValue); static CListBox s_ListBox;
s_ListBox.DoStart(50.0f, m_pClient->m_CountryFlags.Num(), 10, 3, OldSelected, &MainView, true, &s_ListBoxUsed);
for(size_t i = 0; i < m_pClient->m_CountryFlags.Num(); ++i) for(size_t i = 0; i < m_pClient->m_CountryFlags.Num(); ++i)
{ {
@ -376,26 +377,29 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
if(pEntry->m_CountryCode == *pCountry) if(pEntry->m_CountryCode == *pCountry)
OldSelected = i; OldSelected = i;
CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected >= 0 && (size_t)OldSelected == i, s_ListBoxUsed); const CListboxItem Item = s_ListBox.DoNextItem(&pEntry->m_CountryCode, OldSelected >= 0 && (size_t)OldSelected == i, &s_ListBoxUsed);
if(Item.m_Visible) if(!Item.m_Visible)
continue;
CUIRect FlagRect;
Item.m_Rect.Margin(5.0f, &FlagRect);
FlagRect.HSplitBottom(12.0f, &FlagRect, &Label);
Label.HSplitTop(2.0f, nullptr, &Label);
float OldWidth = FlagRect.w;
FlagRect.w = FlagRect.h * 2;
FlagRect.x += (OldWidth - FlagRect.w) / 2.0f;
ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f);
m_pClient->m_CountryFlags.Render(pEntry->m_CountryCode, &Color, FlagRect.x, FlagRect.y, FlagRect.w, FlagRect.h);
if(pEntry->m_Texture.IsValid())
{ {
Item.m_Rect.Margin(5.0f, &Item.m_Rect); SLabelProperties ItemLabelProps;
Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); ItemLabelProps.m_AlignVertically = 0;
float OldWidth = Item.m_Rect.w; UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_CENTER);
Item.m_Rect.w = Item.m_Rect.h * 2;
Item.m_Rect.x += (OldWidth - Item.m_Rect.w) / 2.0f;
ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f);
m_pClient->m_CountryFlags.Render(pEntry->m_CountryCode, &Color, Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h);
if(pEntry->m_Texture.IsValid())
UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_CENTER);
} }
} }
bool Clicked = false; const int NewSelected = s_ListBox.DoEnd();
const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0, &Clicked);
if(Clicked)
s_ListBoxUsed = true;
if(OldSelected != NewSelected) if(OldSelected != NewSelected)
{ {
*pCountry = m_pClient->m_CountryFlags.GetByIndex(NewSelected)->m_CountryCode; *pCountry = m_pClient->m_CountryFlags.GetByIndex(NewSelected)->m_CountryCode;
@ -711,7 +715,8 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
static std::vector<CUISkin> s_vSkinListHelper; static std::vector<CUISkin> s_vSkinListHelper;
static std::vector<CUISkin> s_vFavoriteSkinListHelper; static std::vector<CUISkin> s_vFavoriteSkinListHelper;
static int s_SkinCount = 0; static int s_SkinCount = 0;
static float s_ScrollValue = 0.0f; static CListBox s_ListBox;
// be nice to the CPU // be nice to the CPU
static auto s_SkinLastRebuildTime = time_get_nanoseconds(); static auto s_SkinLastRebuildTime = time_get_nanoseconds();
const auto CurTime = time_get_nanoseconds(); const auto CurTime = time_get_nanoseconds();
@ -780,7 +785,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
}; };
int OldSelected = -1; int OldSelected = -1;
UiDoListboxStart(&s_InitSkinlist, &SkinList, 50.0f, Localize("Skins"), "", s_vSkinList.size(), 4, OldSelected, s_ScrollValue); s_ListBox.DoStart(50.0f, s_vSkinList.size(), 4, 1, OldSelected, &SkinList);
for(size_t i = 0; i < s_vSkinList.size(); ++i) for(size_t i = 0; i < s_vSkinList.size(); ++i)
{ {
const CSkin *pSkinToBeDraw = s_vSkinList[i].m_pSkin; const CSkin *pSkinToBeDraw = s_vSkinList[i].m_pSkin;
@ -788,74 +793,75 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
if(str_comp(pSkinToBeDraw->GetName(), pSkinName) == 0) if(str_comp(pSkinToBeDraw->GetName(), pSkinName) == 0)
OldSelected = i; OldSelected = i;
CListboxItem Item = UiDoListboxNextItem(pSkinToBeDraw, OldSelected >= 0 && (size_t)OldSelected == i); const CListboxItem Item = s_ListBox.DoNextItem(pSkinToBeDraw, OldSelected >= 0 && (size_t)OldSelected == i);
if(Item.m_Visible) if(!Item.m_Visible)
continue;
const CUIRect OriginalRect = Item.m_Rect;
CTeeRenderInfo Info = OwnSkinInfo;
Info.m_CustomColoredSkin = *pUseCustomColor;
Info.m_OriginalRenderSkin = pSkinToBeDraw->m_OriginalSkin;
Info.m_ColorableRenderSkin = pSkinToBeDraw->m_ColorableSkin;
Info.m_SkinMetrics = pSkinToBeDraw->m_Metrics;
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &Info, OffsetToMid);
TeeRenderPos = vec2(OriginalRect.x + 30, OriginalRect.y + OriginalRect.h / 2 + OffsetToMid.y);
RenderTools()->RenderTee(pIdleState, &Info, Emote, vec2(1.0f, 0.0f), TeeRenderPos);
OriginalRect.VSplitLeft(60.0f, 0, &Label);
{ {
auto OriginalRect = Item.m_Rect; SLabelProperties Props;
Props.m_MaxWidth = Label.w;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Label, pSkinToBeDraw->GetName(), 12.0f, TEXTALIGN_LEFT, Props);
}
if(g_Config.m_Debug)
{
ColorRGBA BloodColor = *pUseCustomColor ? color_cast<ColorRGBA>(ColorHSLA(*pColorBody)) : pSkinToBeDraw->m_BloodColor;
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(BloodColor.r, BloodColor.g, BloodColor.b, 1.0f);
IGraphics::CQuadItem QuadItem(Label.x, Label.y, 12.0f, 12.0f);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
}
CTeeRenderInfo Info = OwnSkinInfo; // render skin favorite icon
Info.m_CustomColoredSkin = *pUseCustomColor; {
const auto SkinItFav = m_SkinFavorites.find(pSkinToBeDraw->GetName());
Info.m_OriginalRenderSkin = pSkinToBeDraw->m_OriginalSkin; const auto IsFav = SkinItFav != m_SkinFavorites.end();
Info.m_ColorableRenderSkin = pSkinToBeDraw->m_ColorableSkin; CUIRect FavIcon;
Info.m_SkinMetrics = pSkinToBeDraw->m_Metrics; OriginalRect.HSplitTop(20.0f, &FavIcon, nullptr);
FavIcon.VSplitRight(20.0f, nullptr, &FavIcon);
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &Info, OffsetToMid); if(IsFav)
TeeRenderPos = vec2(Item.m_Rect.x + 30, Item.m_Rect.y + Item.m_Rect.h / 2 + OffsetToMid.y);
RenderTools()->RenderTee(pIdleState, &Info, Emote, vec2(1.0f, 0.0f), TeeRenderPos);
Item.m_Rect.VSplitLeft(60.0f, 0, &Item.m_Rect);
{ {
SLabelProperties Props; RenderFavIcon(FavIcon, IsFav);
Props.m_MaxWidth = Item.m_Rect.w;
UI()->DoLabel(&Item.m_Rect, pSkinToBeDraw->GetName(), 12.0f, TEXTALIGN_LEFT, Props);
} }
if(g_Config.m_Debug) else
{ {
ColorRGBA BloodColor = *pUseCustomColor ? color_cast<ColorRGBA>(ColorHSLA(*pColorBody)) : pSkinToBeDraw->m_BloodColor; if(UI()->MouseInside(&FavIcon))
Graphics()->TextureClear();
Graphics()->QuadsBegin();
Graphics()->SetColor(BloodColor.r, BloodColor.g, BloodColor.b, 1.0f);
IGraphics::CQuadItem QuadItem(Item.m_Rect.x, Item.m_Rect.y, 12.0f, 12.0f);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
}
// render skin favorite icon
{
const auto SkinItFav = m_SkinFavorites.find(pSkinToBeDraw->GetName());
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); RenderFavIcon(FavIcon, IsFav);
} }
}
if(UI()->DoButtonLogic(&pSkinToBeDraw->m_Metrics.m_Body, 0, &FavIcon))
{
if(IsFav)
{
m_SkinFavorites.erase(SkinItFav);
}
else else
{ {
if(UI()->MouseInside(&FavIcon)) m_SkinFavorites.emplace(pSkinToBeDraw->GetName());
{
RenderFavIcon(FavIcon, IsFav);
}
}
if(UI()->DoButtonLogic(&pSkinToBeDraw->m_Metrics.m_Body, 0, &FavIcon))
{
if(IsFav)
{
m_SkinFavorites.erase(SkinItFav);
}
else
{
m_SkinFavorites.emplace(pSkinToBeDraw->GetName());
}
s_InitSkinlist = true;
} }
s_InitSkinlist = true;
} }
} }
} }
const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); const int NewSelected = s_ListBox.DoEnd();
if(OldSelected != NewSelected) if(OldSelected != NewSelected)
{ {
mem_copy(pSkinName, s_vSkinList[NewSelected].m_pSkin->GetName(), sizeof(g_Config.m_ClPlayerSkin)); mem_copy(pSkinName, s_vSkinList[NewSelected].m_pSkin->GetName(), sizeof(g_Config.m_ClPlayerSkin));
@ -1435,22 +1441,25 @@ int CMenus::RenderDropDown(int &CurDropDownState, CUIRect *pRect, int CurSelecti
{ {
if(CurDropDownState != 0) if(CurDropDownState != 0)
{ {
const float RowHeight = 24.0f;
const float RowSpacing = 3.0f;
CUIRect ListRect; CUIRect ListRect;
pRect->HSplitTop(24.0f * PickNum, &ListRect, pRect); pRect->HSplitTop((RowHeight + RowSpacing) * PickNum, &ListRect, pRect);
char aBuf[1024]; static CListBox s_ListBox;
UiDoListboxStart(&pButtonContainer, &ListRect, 24.0f, "", aBuf, PickNum, 1, CurSelection, ScrollVal); s_ListBox.DoAutoSpacing(RowSpacing);
s_ListBox.DoStart(RowHeight, PickNum, 1, 3, CurSelection, &ListRect);
for(int i = 0; i < PickNum; ++i) for(int i = 0; i < PickNum; ++i)
{ {
CListboxItem Item = UiDoListboxNextItem(pIDs[i], CurSelection == i); const CListboxItem Item = s_ListBox.DoNextItem(pIDs[i], CurSelection == i);
if(Item.m_Visible) if(!Item.m_Visible)
{ continue;
str_copy(aBuf, pStr[i]);
UI()->DoLabel(&Item.m_Rect, aBuf, 16.0f, TEXTALIGN_CENTER); SLabelProperties Props;
} Props.m_AlignVertically = 0;
UI()->DoLabel(&Item.m_Rect, pStr[i], 16.0f, TEXTALIGN_CENTER, Props);
} }
bool ClickedItem = false; int NewIndex = s_ListBox.DoEnd();
int NewIndex = UiDoListboxEnd(&ScrollVal, NULL, &ClickedItem); if(s_ListBox.WasItemSelected() || s_ListBox.WasItemActivated())
if(ClickedItem)
{ {
CurDropDownState = 0; CurDropDownState = 0;
return NewIndex; return NewIndex;
@ -1518,7 +1527,7 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
MainView.VSplitLeft(340.0f, &MainView, 0); MainView.VSplitLeft(340.0f, &MainView, 0);
// display mode list // display mode list
static float s_ScrollValue = 0; static CListBox s_ListBox;
static const float sc_RowHeightResList = 22.0f; static const float sc_RowHeightResList = 22.0f;
static const float sc_FontSizeResListHeader = 12.0f; static const float sc_FontSizeResListHeader = 12.0f;
static const float sc_FontSizeResList = 10.0f; static const float sc_FontSizeResList = 10.0f;
@ -1529,7 +1538,7 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
} }
UI()->DoLabel(&ModeLabel, aBuf, sc_FontSizeResListHeader, TEXTALIGN_CENTER); UI()->DoLabel(&ModeLabel, aBuf, sc_FontSizeResListHeader, TEXTALIGN_CENTER);
UiDoListboxStart(&s_NumNodes, &ModeList, sc_RowHeightResList, Localize("Display Modes"), aBuf, s_NumNodes - 1, 1, OldSelected, s_ScrollValue); s_ListBox.DoStart(sc_RowHeightResList, s_NumNodes, 1, 3, OldSelected, &ModeList);
for(int i = 0; i < s_NumNodes; ++i) for(int i = 0; i < s_NumNodes; ++i)
{ {
@ -1542,16 +1551,18 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
OldSelected = i; OldSelected = i;
} }
CListboxItem Item = UiDoListboxNextItem(&s_aModes[i], OldSelected == i); const CListboxItem Item = s_ListBox.DoNextItem(&s_aModes[i], OldSelected == i);
if(Item.m_Visible) if(!Item.m_Visible)
{ continue;
int G = std::gcd(s_aModes[i].m_CanvasWidth, s_aModes[i].m_CanvasHeight);
str_format(aBuf, sizeof(aBuf), " %dx%d @%dhz %d bit (%d:%d)", s_aModes[i].m_CanvasWidth, s_aModes[i].m_CanvasHeight, s_aModes[i].m_RefreshRate, Depth, s_aModes[i].m_CanvasWidth / G, s_aModes[i].m_CanvasHeight / G); int G = std::gcd(s_aModes[i].m_CanvasWidth, s_aModes[i].m_CanvasHeight);
UI()->DoLabel(&Item.m_Rect, aBuf, sc_FontSizeResList, TEXTALIGN_LEFT); str_format(aBuf, sizeof(aBuf), " %dx%d @%dhz %d bit (%d:%d)", s_aModes[i].m_CanvasWidth, s_aModes[i].m_CanvasHeight, s_aModes[i].m_RefreshRate, Depth, s_aModes[i].m_CanvasWidth / G, s_aModes[i].m_CanvasHeight / G);
} SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Item.m_Rect, aBuf, sc_FontSizeResList, TEXTALIGN_LEFT, Props);
} }
const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); const int NewSelected = s_ListBox.DoEnd();
if(OldSelected != NewSelected) if(OldSelected != NewSelected)
{ {
const int Depth = s_aModes[NewSelected].m_Red + s_aModes[NewSelected].m_Green + s_aModes[NewSelected].m_Blue > 16 ? 24 : 16; const int Depth = s_aModes[NewSelected].m_Red + s_aModes[NewSelected].m_Green + s_aModes[NewSelected].m_Blue > 16 ? 24 : 16;
@ -2093,12 +2104,11 @@ void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, std::vector<C
io_close(File); io_close(File);
} }
void CMenus::RenderLanguageSelection(CUIRect MainView) bool CMenus::RenderLanguageSelection(CUIRect MainView)
{ {
static int s_LanguageList = 0; static int s_SelectedLanguage = -1;
static int s_SelectedLanguage = 0;
static std::vector<CLanguage> s_vLanguages; static std::vector<CLanguage> s_vLanguages;
static float s_ScrollValue = 0; static CListBox s_ListBox;
if(s_vLanguages.empty()) if(s_vLanguages.empty())
{ {
@ -2113,27 +2123,29 @@ void CMenus::RenderLanguageSelection(CUIRect MainView)
} }
} }
int OldSelected = s_SelectedLanguage; const int OldSelected = s_SelectedLanguage;
UiDoListboxStart(&s_LanguageList, &MainView, 24.0f, Localize("Language"), "", s_vLanguages.size(), 1, s_SelectedLanguage, s_ScrollValue); s_ListBox.DoStart(24.0f, s_vLanguages.size(), 1, 3, s_SelectedLanguage, &MainView, true);
for(auto &Language : s_vLanguages) for(auto &Language : s_vLanguages)
{ {
CListboxItem Item = UiDoListboxNextItem(&Language.m_Name); const CListboxItem Item = s_ListBox.DoNextItem(&Language.m_Name, s_SelectedLanguage != -1 && !str_comp(s_vLanguages[s_SelectedLanguage].m_Name.c_str(), Language.m_Name.c_str()));
if(Item.m_Visible) if(!Item.m_Visible)
{ continue;
CUIRect Rect;
Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &Rect, &Item.m_Rect); CUIRect FlagRect, Label;
Rect.VMargin(6.0f, &Rect); Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &FlagRect, &Label);
Rect.HMargin(3.0f, &Rect); FlagRect.VMargin(6.0f, &FlagRect);
ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f); FlagRect.HMargin(3.0f, &FlagRect);
m_pClient->m_CountryFlags.Render(Language.m_CountryCode, &Color, Rect.x, Rect.y, Rect.w, Rect.h); ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f);
Item.m_Rect.HSplitTop(2.0f, 0, &Item.m_Rect); m_pClient->m_CountryFlags.Render(Language.m_CountryCode, &Color, FlagRect.x, FlagRect.y, FlagRect.w, FlagRect.h);
UI()->DoLabel(&Item.m_Rect, Language.m_Name.c_str(), 16.0f, TEXTALIGN_LEFT);
} SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Label, Language.m_Name.c_str(), 16.0f, TEXTALIGN_LEFT, Props);
} }
s_SelectedLanguage = UiDoListboxEnd(&s_ScrollValue, 0); s_SelectedLanguage = s_ListBox.DoEnd();
if(OldSelected != s_SelectedLanguage) if(OldSelected != s_SelectedLanguage)
{ {
@ -2141,6 +2153,8 @@ void CMenus::RenderLanguageSelection(CUIRect MainView)
g_Localization.Load(s_vLanguages[s_SelectedLanguage].m_FileName.c_str(), Storage(), Console()); g_Localization.Load(s_vLanguages[s_SelectedLanguage].m_FileName.c_str(), Storage(), Console());
GameClient()->OnLanguageChange(); GameClient()->OnLanguageChange();
} }
return s_ListBox.WasItemActivated();
} }
void CMenus::RenderSettings(CUIRect MainView) void CMenus::RenderSettings(CUIRect MainView)

View file

@ -3,7 +3,9 @@
#include <engine/shared/config.h> #include <engine/shared/config.h>
#include <engine/storage.h> #include <engine/storage.h>
#include <engine/textrender.h> #include <engine/textrender.h>
#include <game/client/gameclient.h> #include <game/client/gameclient.h>
#include <game/client/ui_listbox.h>
#include <game/localization.h> #include <game/localization.h>
#include "menus.h" #include "menus.h"
@ -457,7 +459,6 @@ void CMenus::RenderSettingsCustom(CUIRect MainView)
// skin selector // skin selector
MainView.HSplitTop(MainView.h - 10.0f - ms_ButtonHeight, &CustomList, &MainView); MainView.HSplitTop(MainView.h - 10.0f - ms_ButtonHeight, &CustomList, &MainView);
static float s_ScrollValue = 0.0f;
if(gs_aInitCustomList[s_CurCustomTab]) if(gs_aInitCustomList[s_CurCustomTab])
{ {
int ListSize = 0; int ListSize = 0;
@ -533,7 +534,8 @@ void CMenus::RenderSettingsCustom(CUIRect MainView)
SearchListSize = gs_vpSearchExtrasList.size(); SearchListSize = gs_vpSearchExtrasList.size();
} }
UiDoListboxStart(&gs_aInitCustomList[s_CurCustomTab], &CustomList, TextureHeight + 15.0f + 10.0f + Margin, "", "", SearchListSize, CustomList.w / (Margin + TextureWidth), OldSelected, s_ScrollValue, true); static CListBox s_ListBox;
s_ListBox.DoStart(TextureHeight + 15.0f + 10.0f + Margin, SearchListSize, CustomList.w / (Margin + TextureWidth), 1, OldSelected, &CustomList, false);
for(size_t i = 0; i < SearchListSize; ++i) for(size_t i = 0; i < SearchListSize; ++i)
{ {
const SCustomItem *pItem = GetCustomItem(s_CurCustomTab, i); const SCustomItem *pItem = GetCustomItem(s_CurCustomTab, i);
@ -571,30 +573,30 @@ void CMenus::RenderSettingsCustom(CUIRect MainView)
OldSelected = i; OldSelected = i;
} }
CListboxItem Item = UiDoListboxNextItem(pItem, OldSelected >= 0 && (size_t)OldSelected == i); const CListboxItem Item = s_ListBox.DoNextItem(pItem, OldSelected >= 0 && (size_t)OldSelected == i);
CUIRect ItemRect = Item.m_Rect; CUIRect ItemRect = Item.m_Rect;
ItemRect.Margin(Margin / 2, &ItemRect); ItemRect.Margin(Margin / 2, &ItemRect);
if(Item.m_Visible) if(!Item.m_Visible)
continue;
CUIRect TextureRect;
ItemRect.HSplitTop(15, &ItemRect, &TextureRect);
TextureRect.HSplitTop(10, NULL, &TextureRect);
UI()->DoLabel(&ItemRect, pItem->m_aName, ItemRect.h - 2, TEXTALIGN_CENTER);
if(pItem->m_RenderTexture.IsValid())
{ {
CUIRect TextureRect; Graphics()->WrapClamp();
ItemRect.HSplitTop(15, &ItemRect, &TextureRect); Graphics()->TextureSet(pItem->m_RenderTexture);
TextureRect.HSplitTop(10, NULL, &TextureRect); Graphics()->QuadsBegin();
UI()->DoLabel(&ItemRect, pItem->m_aName, ItemRect.h - 2, TEXTALIGN_CENTER); Graphics()->SetColor(1, 1, 1, 1);
if(pItem->m_RenderTexture.IsValid()) IGraphics::CQuadItem QuadItem(TextureRect.x + (TextureRect.w - TextureWidth) / 2, TextureRect.y + (TextureRect.h - TextureHeight) / 2, TextureWidth, TextureHeight);
{ Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->WrapClamp(); Graphics()->QuadsEnd();
Graphics()->TextureSet(pItem->m_RenderTexture); Graphics()->WrapNormal();
Graphics()->QuadsBegin();
Graphics()->SetColor(1, 1, 1, 1);
IGraphics::CQuadItem QuadItem(TextureRect.x + (TextureRect.w - TextureWidth) / 2, TextureRect.y + (TextureRect.h - TextureHeight) / 2, TextureWidth, TextureHeight);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
Graphics()->WrapNormal();
}
} }
} }
const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); const int NewSelected = s_ListBox.DoEnd();
if(OldSelected != NewSelected) if(OldSelected != NewSelected)
{ {
if(GetCustomItem(s_CurCustomTab, NewSelected)->m_aName[0] != '\0') if(GetCustomItem(s_CurCustomTab, NewSelected)->m_aName[0] != '\0')

View file

@ -49,7 +49,6 @@ void CMenus::RenderStartMenu(CUIRect MainView)
{ {
dbg_msg("menus", "couldn't open link '%s'", pLink); dbg_msg("menus", "couldn't open link '%s'", pLink);
} }
m_DoubleClickIndex = -1;
} }
ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space
@ -62,7 +61,6 @@ void CMenus::RenderStartMenu(CUIRect MainView)
{ {
dbg_msg("menus", "couldn't open link '%s'", pLink); dbg_msg("menus", "couldn't open link '%s'", pLink);
} }
m_DoubleClickIndex = -1;
} }
ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space
@ -88,7 +86,6 @@ void CMenus::RenderStartMenu(CUIRect MainView)
PopupWarning(Localize("Warning"), Localize("Can't find a Tutorial server"), Localize("Ok"), 10s); PopupWarning(Localize("Warning"), Localize("Can't find a Tutorial server"), Localize("Ok"), 10s);
s_JoinTutorialTime = 0.0f; s_JoinTutorialTime = 0.0f;
} }
m_DoubleClickIndex = -1;
} }
ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space
@ -101,7 +98,6 @@ void CMenus::RenderStartMenu(CUIRect MainView)
{ {
dbg_msg("menus", "couldn't open link '%s'", pLink); dbg_msg("menus", "couldn't open link '%s'", pLink);
} }
m_DoubleClickIndex = -1;
} }
ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space ExtMenu.HSplitBottom(5.0f, &ExtMenu, 0); // little space

View file

@ -0,0 +1,254 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <base/system.h>
#include <base/vmath.h>
#include <engine/config.h>
#include <engine/shared/config.h>
#include <game/localization.h>
#include "ui_listbox.h"
CListBox::CListBox()
{
m_ScrollOffset = vec2(0.0f, 0.0f);
m_ListBoxUpdateScroll = false;
m_aFilterString[0] = '\0';
m_FilterOffset = 0.0f;
m_HasHeader = false;
m_AutoSpacing = 0.0f;
m_ScrollbarIsShown = false;
}
void CListBox::DoBegin(const CUIRect *pRect)
{
// setup the variables
m_ListBoxView = *pRect;
}
void CListBox::DoHeader(const CUIRect *pRect, const char *pTitle, float HeaderHeight, float Spacing)
{
CUIRect View = *pRect;
CUIRect Header;
// background
View.HSplitTop(HeaderHeight + Spacing, &Header, 0);
Header.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), m_BackgroundCorners & IGraphics::CORNER_T, 5.0f);
// draw header
View.HSplitTop(HeaderHeight, &Header, &View);
SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Header, pTitle, Header.h * CUI::ms_FontmodHeight * 0.8f, TEXTALIGN_CENTER, Props);
View.HSplitTop(Spacing, &Header, &View);
// setup the variables
m_ListBoxView = View;
m_HasHeader = true;
}
void CListBox::DoSpacing(float Spacing)
{
CUIRect View = m_ListBoxView;
View.HSplitTop(Spacing, 0, &View);
m_ListBoxView = View;
}
bool CListBox::DoFilter(float FilterHeight, float Spacing)
{
CUIRect Filter;
CUIRect View = m_ListBoxView;
// background
View.HSplitTop(FilterHeight + Spacing, &Filter, 0);
Filter.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_NONE, 0.0f);
// draw filter
View.HSplitTop(FilterHeight, &Filter, &View);
Filter.Margin(Spacing, &Filter);
const float FontSize = Filter.h * CUI::ms_FontmodHeight * 0.8f;
CUIRect Label, EditBox;
Filter.VSplitLeft(Filter.w / 5.0f, &Label, &EditBox);
Label.y += Spacing;
UI()->DoLabel(&Label, Localize("Search:"), FontSize, TEXTALIGN_CENTER);
bool Changed = UI()->DoClearableEditBox(m_aFilterString, m_aFilterString + 1, &EditBox, m_aFilterString, sizeof(m_aFilterString), FontSize, &m_FilterOffset);
View.HSplitTop(Spacing, &Filter, &View);
m_ListBoxView = View;
return Changed;
}
void CListBox::DoFooter(const char *pBottomText, float FooterHeight)
{
m_pBottomText = pBottomText;
m_FooterHeight = FooterHeight;
}
void CListBox::DoStart(float RowHeight, int NumItems, int ItemsPerRow, int RowsPerScroll, int SelectedIndex, const CUIRect *pRect, bool Background, bool *pActive, int BackgroundCorners)
{
CUIRect View;
if(pRect)
View = *pRect;
else
View = m_ListBoxView;
// background
m_BackgroundCorners = BackgroundCorners;
if(Background)
View.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), m_BackgroundCorners & (m_HasHeader ? IGraphics::CORNER_B : IGraphics::CORNER_ALL), 5.0f);
// draw footers
if(m_pBottomText)
{
CUIRect Footer;
View.HSplitBottom(m_FooterHeight, &View, &Footer);
Footer.VSplitLeft(10.0f, 0, &Footer);
SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Footer, m_pBottomText, Footer.h * CUI::ms_FontmodHeight * 0.8f, TEXTALIGN_CENTER, Props);
}
// setup the variables
m_ListBoxView = View;
m_RowView = {};
m_ListBoxSelectedIndex = SelectedIndex;
m_ListBoxNewSelected = SelectedIndex;
m_ListBoxNewSelOffset = 0;
m_ListBoxItemIndex = 0;
m_ListBoxRowHeight = RowHeight;
m_ListBoxNumItems = NumItems;
m_ListBoxItemsPerRow = ItemsPerRow;
m_ListBoxDoneEvents = false;
m_ListBoxItemActivated = false;
m_ListBoxItemSelected = false;
// handle input
if(!pActive || *pActive)
{
if(UI()->ConsumeHotkey(CUI::HOTKEY_DOWN))
m_ListBoxNewSelOffset += 1;
else if(UI()->ConsumeHotkey(CUI::HOTKEY_UP))
m_ListBoxNewSelOffset -= 1;
else if(UI()->ConsumeHotkey(CUI::HOTKEY_PAGE_UP))
m_ListBoxNewSelOffset = -ItemsPerRow * RowsPerScroll * 4;
else if(UI()->ConsumeHotkey(CUI::HOTKEY_PAGE_DOWN))
m_ListBoxNewSelOffset = ItemsPerRow * RowsPerScroll * 4;
else if(UI()->ConsumeHotkey(CUI::HOTKEY_HOME))
m_ListBoxNewSelOffset = 1 - m_ListBoxNumItems;
else if(UI()->ConsumeHotkey(CUI::HOTKEY_END))
m_ListBoxNewSelOffset = m_ListBoxNumItems - 1;
}
// setup the scrollbar
m_ScrollOffset = vec2(0.0f, 0.0f);
CScrollRegionParams ScrollParams;
ScrollParams.m_ScrollbarWidth = ScrollbarWidthMax();
ScrollParams.m_ScrollUnit = (m_ListBoxRowHeight + m_AutoSpacing) * RowsPerScroll;
m_ScrollRegion.Begin(&m_ListBoxView, &m_ScrollOffset, &ScrollParams);
m_ListBoxView.y += m_ScrollOffset.y;
}
CListboxItem CListBox::DoNextRow()
{
CListboxItem Item = {};
if(m_ListBoxItemIndex % m_ListBoxItemsPerRow == 0)
m_ListBoxView.HSplitTop(m_ListBoxRowHeight, &m_RowView, &m_ListBoxView);
m_ScrollRegion.AddRect(m_RowView);
if(m_ListBoxUpdateScroll && m_ListBoxSelectedIndex == m_ListBoxItemIndex)
{
m_ScrollRegion.ScrollHere(CScrollRegion::SCROLLHERE_KEEP_IN_VIEW);
m_ListBoxUpdateScroll = false;
}
m_RowView.VSplitLeft(m_RowView.w / (m_ListBoxItemsPerRow - m_ListBoxItemIndex % m_ListBoxItemsPerRow), &Item.m_Rect, &m_RowView);
Item.m_Selected = m_ListBoxSelectedIndex == m_ListBoxItemIndex;
Item.m_Visible = !m_ScrollRegion.IsRectClipped(Item.m_Rect);
m_ListBoxItemIndex++;
return Item;
}
CListboxItem CListBox::DoNextItem(const void *pId, bool Selected, bool *pActive)
{
if(m_AutoSpacing > 0.0f && m_ListBoxItemIndex > 0)
DoSpacing(m_AutoSpacing);
const int ThisItemIndex = m_ListBoxItemIndex;
if(Selected)
{
if(m_ListBoxSelectedIndex == m_ListBoxNewSelected)
m_ListBoxNewSelected = ThisItemIndex;
m_ListBoxSelectedIndex = ThisItemIndex;
}
CListboxItem Item = DoNextRow();
bool ItemClicked = false;
if(Item.m_Visible && UI()->DoButtonLogic(pId, 0, &Item.m_Rect))
{
ItemClicked = true;
m_ListBoxNewSelected = ThisItemIndex;
m_ListBoxItemSelected = true;
if(pActive)
*pActive = true;
}
else
ItemClicked = false;
const bool ProcessInput = !pActive || *pActive;
// process input, regard selected index
if(m_ListBoxSelectedIndex == ThisItemIndex)
{
if(ProcessInput && !m_ListBoxDoneEvents)
{
m_ListBoxDoneEvents = true;
if(UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (ItemClicked && Input()->MouseDoubleClick()))
{
m_ListBoxItemActivated = true;
UI()->SetActiveItem(nullptr);
}
}
Item.m_Rect.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, ProcessInput ? 0.5f : 0.33f), IGraphics::CORNER_ALL, 5.0f);
}
if(UI()->HotItem() == pId && !m_ScrollRegion.IsAnimating())
{
Item.m_Rect.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.33f), IGraphics::CORNER_ALL, 5.0f);
}
return Item;
}
CListboxItem CListBox::DoSubheader()
{
CListboxItem Item = DoNextRow();
Item.m_Rect.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.2f), IGraphics::CORNER_NONE, 0.0f);
return Item;
}
int CListBox::DoEnd()
{
m_ScrollRegion.End();
m_ScrollbarIsShown = m_ScrollRegion.IsScrollbarShown();
if(m_ListBoxNewSelOffset != 0 && m_ListBoxSelectedIndex != -1 && m_ListBoxSelectedIndex == m_ListBoxNewSelected)
{
m_ListBoxNewSelected = clamp(m_ListBoxNewSelected + m_ListBoxNewSelOffset, 0, m_ListBoxNumItems - 1);
ScrollToSelected();
}
return m_ListBoxNewSelected;
}
bool CListBox::FilterMatches(const char *pNeedle) const
{
return !m_aFilterString[0] || str_find_nocase(pNeedle, m_aFilterString);
}

View file

@ -0,0 +1,68 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_UI_LISTBOX_H
#define GAME_CLIENT_UI_LISTBOX_H
#include "ui_scrollregion.h"
struct CListboxItem
{
bool m_Visible;
bool m_Selected;
CUIRect m_Rect;
};
// Instances of CListBox must be static, as member addresses are used as UI item IDs
class CListBox : private CUIElementBase
{
private:
CUIRect m_ListBoxView;
CUIRect m_RowView;
float m_ListBoxRowHeight;
int m_ListBoxItemIndex;
int m_ListBoxSelectedIndex;
int m_ListBoxNewSelected;
int m_ListBoxNewSelOffset;
bool m_ListBoxUpdateScroll;
bool m_ListBoxDoneEvents;
int m_ListBoxNumItems;
int m_ListBoxItemsPerRow;
bool m_ListBoxItemSelected;
bool m_ListBoxItemActivated;
bool m_ScrollbarIsShown;
const char *m_pBottomText;
float m_FooterHeight;
float m_AutoSpacing;
CScrollRegion m_ScrollRegion;
vec2 m_ScrollOffset;
char m_aFilterString[128];
float m_FilterOffset;
int m_BackgroundCorners;
bool m_HasHeader;
protected:
CListboxItem DoNextRow();
public:
CListBox();
void DoBegin(const CUIRect *pRect);
void DoHeader(const CUIRect *pRect, const char *pTitle, float HeaderHeight = 20.0f, float Spacing = 2.0f);
void DoAutoSpacing(float Spacing = 20.0f) { m_AutoSpacing = Spacing; }
void DoSpacing(float Spacing = 20.0f);
bool DoFilter(float FilterHeight = 20.0f, float Spacing = 2.0f);
void DoFooter(const char *pBottomText, float FooterHeight = 20.0f); // call before DoStart to create a footer
void DoStart(float RowHeight, int NumItems, int ItemsPerRow, int RowsPerScroll, int SelectedIndex, const CUIRect *pRect = nullptr, bool Background = true, bool *pActive = nullptr, int BackgroundCorners = IGraphics::CORNER_ALL);
void ScrollToSelected() { m_ListBoxUpdateScroll = true; }
CListboxItem DoNextItem(const void *pID, bool Selected = false, bool *pActive = nullptr);
CListboxItem DoSubheader();
int DoEnd();
bool FilterMatches(const char *pNeedle) const;
bool WasItemSelected() const { return m_ListBoxItemSelected; }
bool WasItemActivated() const { return m_ListBoxItemActivated; }
bool ScrollbarIsShown() const { return m_ScrollbarIsShown; }
float ScrollbarWidth() const { return ScrollbarIsShown() ? ScrollbarWidthMax() : 0.0f; }
float ScrollbarWidthMax() const { return 20.0f; }
};
#endif

View file

@ -24,6 +24,7 @@
#include <game/client/gameclient.h> #include <game/client/gameclient.h>
#include <game/client/render.h> #include <game/client/render.h>
#include <game/client/ui.h> #include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/client/ui_scrollregion.h> #include <game/client/ui_scrollregion.h>
#include <game/generated/client_data.h> #include <game/generated/client_data.h>
#include <game/localization.h> #include <game/localization.h>
@ -4244,66 +4245,11 @@ static int EditorListdirCallback(const char *pName, int IsDir, int StorageType,
Item.m_IsDir = IsDir != 0; Item.m_IsDir = IsDir != 0;
Item.m_IsLink = false; Item.m_IsLink = false;
Item.m_StorageType = StorageType; Item.m_StorageType = StorageType;
pEditor->m_vFileList.push_back(Item); pEditor->m_vCompleteFileList.push_back(Item);
return 0; return 0;
} }
void CEditor::AddFileDialogEntry(int Index, CUIRect *pView)
{
m_FilesCur++;
if(m_FilesCur - 1 < m_FilesStartAt || m_FilesCur >= m_FilesStopAt)
return;
CUIRect Button, FileIcon;
pView->HSplitTop(15.0f, &Button, pView);
pView->HSplitTop(2.0f, nullptr, pView);
Button.VSplitLeft(Button.h, &FileIcon, &Button);
Button.VSplitLeft(5.0f, nullptr, &Button);
const char *pIconType;
if(!m_vFileList[Index].m_IsDir)
{
switch(m_FileDialogFileType)
{
case FILETYPE_MAP:
pIconType = "\xEF\x89\xB9";
break;
case FILETYPE_IMG:
pIconType = "\xEF\x80\xBE";
break;
case FILETYPE_SOUND:
pIconType = "\xEF\x80\x81";
break;
default:
pIconType = "";
}
}
else
{
if(str_comp(m_vFileList[Index].m_aFilename, "..") == 0)
pIconType = "\xEF\xA0\x82";
else
pIconType = "\xEF\x81\xBB";
}
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_LEFT);
TextRender()->SetCurFont(nullptr);
if(DoButton_File(&m_vFileList[Index], m_vFileList[Index].m_aName, m_FilesSelectedIndex == Index, &Button, 0, nullptr))
{
if(!m_vFileList[Index].m_IsDir)
str_copy(m_aFileDialogFileName, m_vFileList[Index].m_aFilename, sizeof(m_aFileDialogFileName));
else
m_aFileDialogFileName[0] = 0;
m_PreviewImageIsLoaded = false;
m_FileDialogActivate |= Index == m_FilesSelectedIndex && Input()->MouseDoubleClick();
m_FilesSelectedIndex = Index;
}
}
void CEditor::RenderFileDialog() void CEditor::RenderFileDialog()
{ {
// GUI coordsys // GUI coordsys
@ -4318,7 +4264,7 @@ void CEditor::RenderFileDialog()
View.Draw(ColorRGBA(0, 0, 0, 0.75f), IGraphics::CORNER_ALL, 5.0f); View.Draw(ColorRGBA(0, 0, 0, 0.75f), IGraphics::CORNER_ALL, 5.0f);
View.Margin(10.0f, &View); View.Margin(10.0f, &View);
CUIRect Title, FileBox, FileBoxLabel, ButtonBar, Scroll, PathBox; CUIRect Title, FileBox, FileBoxLabel, ButtonBar, PathBox;
View.HSplitTop(18.0f, &Title, &View); View.HSplitTop(18.0f, &Title, &View);
View.HSplitTop(5.0f, nullptr, &View); // some spacing View.HSplitTop(5.0f, nullptr, &View); // some spacing
View.HSplitBottom(14.0f, &View, &ButtonBar); View.HSplitBottom(14.0f, &View, &ButtonBar);
@ -4330,7 +4276,6 @@ void CEditor::RenderFileDialog()
View.HSplitBottom(10.0f, &View, nullptr); // some spacing View.HSplitBottom(10.0f, &View, nullptr); // some spacing
if(m_FileDialogFileType == CEditor::FILETYPE_IMG) if(m_FileDialogFileType == CEditor::FILETYPE_IMG)
View.VSplitMid(&View, &Preview); View.VSplitMid(&View, &Preview);
View.VSplitRight(20.0f, &View, &Scroll);
// title // title
Title.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f); Title.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f);
@ -4340,15 +4285,17 @@ void CEditor::RenderFileDialog()
// pathbox // pathbox
char aPath[128], aBuf[128]; char aPath[128], aBuf[128];
if(m_FilesSelectedIndex != -1) if(m_FilesSelectedIndex != -1)
Storage()->GetCompletePath(m_vFileList[m_FilesSelectedIndex].m_StorageType, m_pFileDialogPath, aPath, sizeof(aPath)); Storage()->GetCompletePath(m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType, m_pFileDialogPath, aPath, sizeof(aPath));
else else
aPath[0] = 0; aPath[0] = 0;
str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath); str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath);
UI()->DoLabel(&PathBox, aBuf, 10.0f, TEXTALIGN_LEFT); UI()->DoLabel(&PathBox, aBuf, 10.0f, TEXTALIGN_LEFT);
// filebox
static CListBox s_ListBox;
if(m_FileDialogStorageType == IStorage::TYPE_SAVE) if(m_FileDialogStorageType == IStorage::TYPE_SAVE)
{ {
// filebox
UI()->DoLabel(&FileBoxLabel, "Filename:", 10.0f, TEXTALIGN_LEFT); UI()->DoLabel(&FileBoxLabel, "Filename:", 10.0f, TEXTALIGN_LEFT);
static float s_FileBoxID = 0; static float s_FileBoxID = 0;
if(DoEditBox(&s_FileBoxID, &FileBox, m_aFileDialogFileName, sizeof(m_aFileDialogFileName), 10.0f, &s_FileBoxID)) if(DoEditBox(&s_FileBoxID, &FileBox, m_aFileDialogFileName, sizeof(m_aFileDialogFileName), 10.0f, &s_FileBoxID))
@ -4358,6 +4305,19 @@ void CEditor::RenderFileDialog()
if(m_aFileDialogFileName[i] == '/' || m_aFileDialogFileName[i] == '\\') if(m_aFileDialogFileName[i] == '/' || m_aFileDialogFileName[i] == '\\')
str_copy(&m_aFileDialogFileName[i], &m_aFileDialogFileName[i + 1], (int)(sizeof(m_aFileDialogFileName)) - i); str_copy(&m_aFileDialogFileName[i], &m_aFileDialogFileName[i + 1], (int)(sizeof(m_aFileDialogFileName)) - i);
m_FilesSelectedIndex = -1; m_FilesSelectedIndex = -1;
m_aFilesSelectedName[0] = '\0';
// find first valid entry, if it exists
for(size_t i = 0; i < m_vpFilteredFileList.size(); i++)
{
if(str_comp_nocase(m_vpFilteredFileList[i]->m_aName, m_aFileDialogFileName) == 0)
{
m_FilesSelectedIndex = i;
str_copy(m_aFilesSelectedName, m_vpFilteredFileList[i]->m_aName);
break;
}
}
if(m_FilesSelectedIndex >= 0)
s_ListBox.ScrollToSelected();
} }
if(m_FileDialogOpening) if(m_FileDialogOpening)
@ -4365,119 +4325,73 @@ void CEditor::RenderFileDialog()
} }
else else
{ {
//searchbox // render search bar
FileBox.VSplitRight(250, &FileBox, nullptr); FileBox.VSplitRight(250, &FileBox, nullptr);
CUIRect ClearBox; CUIRect ClearBox;
FileBox.VSplitRight(15, &FileBox, &ClearBox); FileBox.VSplitRight(15, &FileBox, &ClearBox);
UI()->DoLabel(&FileBoxLabel, "Search:", 10.0f, TEXTALIGN_LEFT); UI()->DoLabel(&FileBoxLabel, "Search:", 10.0f, TEXTALIGN_LEFT);
str_copy(m_aFileDialogPrevSearchText, m_aFileDialogSearchText, sizeof(m_aFileDialogPrevSearchText));
static float s_SearchBoxID = 0; static float s_SearchBoxID = 0;
DoEditBox(&s_SearchBoxID, &FileBox, m_aFileDialogSearchText, sizeof(m_aFileDialogSearchText), 10.0f, &s_SearchBoxID, false, IGraphics::CORNER_L); bool SearchUpdated = DoEditBox(&s_SearchBoxID, &FileBox, m_aFileDialogFilterString, sizeof(m_aFileDialogFilterString), 10.0f, &s_SearchBoxID, false, IGraphics::CORNER_L);
if(m_FileDialogOpening) if(m_FileDialogOpening)
UI()->SetActiveItem(&s_SearchBoxID); UI()->SetActiveItem(&s_SearchBoxID);
// clearSearchbox button // clear search button
{ {
static int s_ClearButton = 0; static int s_ClearButton = 0;
ClearBox.Draw(ColorRGBA(1, 1, 1, 0.33f * UI()->ButtonColorMul(&s_ClearButton)), IGraphics::CORNER_R, 3.0f); ClearBox.Draw(ColorRGBA(1, 1, 1, 0.33f * UI()->ButtonColorMul(&s_ClearButton)), IGraphics::CORNER_R, 3.0f);
UI()->DoLabel(&ClearBox, "×", 10.0f, TEXTALIGN_CENTER); UI()->DoLabel(&ClearBox, "×", 10.0f, TEXTALIGN_CENTER);
if(UI()->DoButtonLogic(&s_ClearButton, 0, &ClearBox)) if(UI()->DoButtonLogic(&s_ClearButton, 0, &ClearBox))
{ {
m_aFileDialogSearchText[0] = 0; SearchUpdated = true;
m_aFileDialogFilterString[0] = 0;
UI()->SetActiveItem(&s_SearchBoxID); UI()->SetActiveItem(&s_SearchBoxID);
} }
} }
if(str_comp(m_aFileDialogPrevSearchText, m_aFileDialogSearchText)) if(SearchUpdated)
m_FileDialogScrollValue = 0.0f; {
RefreshFilteredFileList();
if(m_vpFilteredFileList.empty())
{
m_FilesSelectedIndex = -1;
}
else if(m_FilesSelectedIndex == -1 || (m_aFileDialogFilterString[0] && !str_find_nocase(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName, m_aFileDialogFilterString)))
{
// we need to refresh selection
m_FilesSelectedIndex = -1;
for(size_t i = 0; i < m_vpFilteredFileList.size(); i++)
{
if(str_find_nocase(m_vpFilteredFileList[i]->m_aName, m_aFileDialogFilterString))
{
m_FilesSelectedIndex = i;
break;
}
}
if(m_FilesSelectedIndex == -1)
{
// select first item
m_FilesSelectedIndex = 0;
}
}
if(m_FilesSelectedIndex >= 0)
str_copy(m_aFilesSelectedName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName);
else
m_aFilesSelectedName[0] = '\0';
s_ListBox.ScrollToSelected();
}
} }
m_FileDialogOpening = false; m_FileDialogOpening = false;
int Num = (int)(View.h / 17.0f) + 1;
m_FileDialogScrollValue = UI()->DoScrollbarV(&m_FileDialogScrollValue, &Scroll, m_FileDialogScrollValue);
int ScrollNum = 0;
for(size_t i = 0; i < m_vFileList.size(); i++)
{
m_vFileList[i].m_IsVisible = false;
if(!m_aFileDialogSearchText[0] || str_utf8_find_nocase(m_vFileList[i].m_aName, m_aFileDialogSearchText))
{
AddFileDialogEntry(i, &View);
m_vFileList[i].m_IsVisible = true;
ScrollNum++;
}
}
ScrollNum -= Num - 1;
if(ScrollNum > 0)
{
if(Input()->KeyPress(KEY_MOUSE_WHEEL_UP))
m_FileDialogScrollValue -= 3.0f / ScrollNum;
if(Input()->KeyPress(KEY_MOUSE_WHEEL_DOWN))
m_FileDialogScrollValue += 3.0f / ScrollNum;
}
else
ScrollNum = 0;
if(m_FilesSelectedIndex > -1) if(m_FilesSelectedIndex > -1)
{ {
if(!m_vFileList[m_FilesSelectedIndex].m_IsVisible)
{
m_FilesSelectedIndex = 0;
}
for(int i = 0; i < Input()->NumEvents(); i++)
{
int NewIndex = -1;
if(Input()->GetEvent(i).m_Flags & IInput::FLAG_PRESS)
{
if(Input()->GetEvent(i).m_Key == KEY_DOWN)
{
for(NewIndex = m_FilesSelectedIndex + 1; NewIndex < (int)m_vFileList.size(); NewIndex++)
{
if(m_vFileList[NewIndex].m_IsVisible)
break;
}
}
if(Input()->GetEvent(i).m_Key == KEY_UP)
{
for(NewIndex = m_FilesSelectedIndex - 1; NewIndex >= 0; NewIndex--)
{
if(m_vFileList[NewIndex].m_IsVisible)
break;
}
}
}
if(NewIndex > -1 && NewIndex < (int)m_vFileList.size())
{
//scroll
float IndexY = View.y - m_FileDialogScrollValue * ScrollNum * 17.0f + NewIndex * 17.0f;
int ScrollPos = View.y > IndexY ? -1 : View.y + View.h < IndexY + 17.0f ? 1 : 0;
if(ScrollPos)
{
if(ScrollPos < 0)
m_FileDialogScrollValue = ((float)(NewIndex) + 0.5f) / ScrollNum;
else
m_FileDialogScrollValue = ((float)(NewIndex - Num) + 2.5f) / ScrollNum;
}
if(!m_vFileList[NewIndex].m_IsDir)
str_copy(m_aFileDialogFileName, m_vFileList[NewIndex].m_aFilename, sizeof(m_aFileDialogFileName));
else
m_aFileDialogFileName[0] = 0;
m_FilesSelectedIndex = NewIndex;
m_PreviewImageIsLoaded = false;
}
}
if(m_FileDialogFileType == CEditor::FILETYPE_IMG && !m_PreviewImageIsLoaded && m_FilesSelectedIndex > -1) if(m_FileDialogFileType == CEditor::FILETYPE_IMG && !m_PreviewImageIsLoaded && m_FilesSelectedIndex > -1)
{ {
if(str_endswith(m_vFileList[m_FilesSelectedIndex].m_aFilename, ".png")) if(str_endswith(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, ".png"))
{ {
char aBuffer[1024]; char aBuffer[1024];
str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_pFileDialogPath, m_vFileList[m_FilesSelectedIndex].m_aFilename); str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_pFileDialogPath, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename);
if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, IStorage::TYPE_ALL)) if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, IStorage::TYPE_ALL))
{ {
@ -4513,33 +4427,64 @@ void CEditor::RenderFileDialog()
} }
} }
for(int i = 0; i < Input()->NumEvents(); i++) s_ListBox.DoStart(15.0f, m_vpFilteredFileList.size(), 1, 5, m_FilesSelectedIndex, &View, false);
for(size_t i = 0; i < m_vpFilteredFileList.size(); i++)
{ {
if(Input()->GetEvent(i).m_Flags & IInput::FLAG_PRESS && !UiPopupOpen() && !m_PopupEventActivated) const CListboxItem Item = s_ListBox.DoNextItem(m_vpFilteredFileList[i], m_FilesSelectedIndex >= 0 && (size_t)m_FilesSelectedIndex == i);
if(!Item.m_Visible)
continue;
CUIRect Button, FileIcon;
Item.m_Rect.VSplitLeft(Item.m_Rect.h, &FileIcon, &Button);
Button.VSplitLeft(5.0f, nullptr, &Button);
const char *pIconType;
if(!m_vpFilteredFileList[i]->m_IsDir)
{ {
if(Input()->GetEvent(i).m_Key == KEY_RETURN || Input()->GetEvent(i).m_Key == KEY_KP_ENTER) switch(m_FileDialogFileType)
m_FileDialogActivate = true; {
case FILETYPE_MAP:
pIconType = "\xEF\x89\xB9";
break;
case FILETYPE_IMG:
pIconType = "\xEF\x80\xBE";
break;
case FILETYPE_SOUND:
pIconType = "\xEF\x80\x81";
break;
default:
pIconType = "";
}
} }
else
{
if(str_comp(m_vpFilteredFileList[i]->m_aFilename, "..") == 0)
pIconType = "\xEF\xA0\x82";
else
pIconType = "\xEF\x81\xBB";
}
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_LEFT);
TextRender()->SetCurFont(nullptr);
SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Button, m_vpFilteredFileList[i]->m_aName, 10.0f, TEXTALIGN_LEFT, Props);
} }
if(m_FileDialogScrollValue < 0) const int NewSelection = s_ListBox.DoEnd();
m_FileDialogScrollValue = 0; if(NewSelection != m_FilesSelectedIndex)
if(m_FileDialogScrollValue > 1) {
m_FileDialogScrollValue = 1; m_FilesSelectedIndex = NewSelection;
str_copy(m_aFilesSelectedName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName);
m_FilesStartAt = (int)(ScrollNum * m_FileDialogScrollValue); if(!m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir)
if(m_FilesStartAt < 0) str_copy(m_aFileDialogFileName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename);
m_FilesStartAt = 0; else
m_aFileDialogFileName[0] = '\0';
m_FilesStopAt = m_FilesStartAt + Num; m_PreviewImageIsLoaded = false;
}
m_FilesCur = 0;
// set clipping
UI()->ClipEnable(&View);
// disable clipping again
UI()->ClipDisable();
// the buttons // the buttons
static int s_OkButton = 0; static int s_OkButton = 0;
@ -4549,35 +4494,34 @@ void CEditor::RenderFileDialog()
CUIRect Button; CUIRect Button;
ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button); ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button);
bool IsDir = m_FilesSelectedIndex >= 0 && m_vFileList[m_FilesSelectedIndex].m_IsDir; bool IsDir = m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir;
if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, nullptr) || m_FileDialogActivate) if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, nullptr) || s_ListBox.WasItemActivated() || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
{ {
m_FileDialogActivate = false;
if(IsDir) // folder if(IsDir) // folder
{ {
if(str_comp(m_vFileList[m_FilesSelectedIndex].m_aFilename, "..") == 0) // parent folder if(str_comp(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, "..") == 0) // parent folder
{ {
if(fs_parent_dir(m_pFileDialogPath)) if(fs_parent_dir(m_pFileDialogPath))
m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link
} }
else // sub folder else // sub folder
{ {
if(m_vFileList[m_FilesSelectedIndex].m_IsLink) if(m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsLink)
{ {
m_pFileDialogPath = m_aFileDialogCurrentLink; // follow the link m_pFileDialogPath = m_aFileDialogCurrentLink; // follow the link
str_copy(m_aFileDialogCurrentLink, m_vFileList[m_FilesSelectedIndex].m_aFilename, sizeof(m_aFileDialogCurrentLink)); str_copy(m_aFileDialogCurrentLink, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, sizeof(m_aFileDialogCurrentLink));
} }
else else
{ {
char aTemp[IO_MAX_PATH_LENGTH]; char aTemp[IO_MAX_PATH_LENGTH];
str_copy(aTemp, m_pFileDialogPath, sizeof(aTemp)); str_copy(aTemp, m_pFileDialogPath, sizeof(aTemp));
str_format(m_pFileDialogPath, IO_MAX_PATH_LENGTH, "%s/%s", aTemp, m_vFileList[m_FilesSelectedIndex].m_aFilename); str_format(m_pFileDialogPath, IO_MAX_PATH_LENGTH, "%s/%s", aTemp, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename);
} }
} }
FilelistPopulate(!str_comp(m_pFileDialogPath, "maps") || !str_comp(m_pFileDialogPath, "mapres") ? m_FileDialogStorageType : FilelistPopulate(!str_comp(m_pFileDialogPath, "maps") || !str_comp(m_pFileDialogPath, "mapres") ? m_FileDialogStorageType :
m_vFileList[m_FilesSelectedIndex].m_StorageType); m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType);
if(m_FilesSelectedIndex >= 0 && !m_vFileList[m_FilesSelectedIndex].m_IsDir) if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir)
str_copy(m_aFileDialogFileName, m_vFileList[m_FilesSelectedIndex].m_aFilename, sizeof(m_aFileDialogFileName)); str_copy(m_aFileDialogFileName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, sizeof(m_aFileDialogFileName));
else else
m_aFileDialogFileName[0] = 0; m_aFileDialogFileName[0] = 0;
} }
@ -4594,10 +4538,10 @@ void CEditor::RenderFileDialog()
m_PopupEventActivated = true; m_PopupEventActivated = true;
} }
else if(m_pfnFileDialogFunc) else if(m_pfnFileDialogFunc)
m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vFileList[m_FilesSelectedIndex].m_StorageType : m_FileDialogStorageType, m_pFileDialogUser); m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType, m_pFileDialogUser);
} }
else if(m_pfnFileDialogFunc) else if(m_pfnFileDialogFunc)
m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vFileList[m_FilesSelectedIndex].m_StorageType : m_FileDialogStorageType, m_pFileDialogUser); m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType, m_pFileDialogUser);
} }
} }
@ -4636,9 +4580,42 @@ void CEditor::RenderFileDialog()
} }
} }
void CEditor::RefreshFilteredFileList()
{
m_vpFilteredFileList.clear();
for(const CFilelistItem &Item : m_vCompleteFileList)
{
if(!m_aFileDialogFilterString[0] || str_find_nocase(Item.m_aName, m_aFileDialogFilterString))
{
m_vpFilteredFileList.push_back(&Item);
}
}
if(!m_vpFilteredFileList.empty())
{
if(m_aFilesSelectedName[0])
{
for(size_t i = 0; i < m_vpFilteredFileList.size(); i++)
{
if(m_aFilesSelectedName[0] && str_comp(m_vpFilteredFileList[i]->m_aName, m_aFilesSelectedName) == 0)
{
m_FilesSelectedIndex = i;
break;
}
}
}
m_FilesSelectedIndex = clamp<int>(m_FilesSelectedIndex, 0, m_vpFilteredFileList.size() - 1);
str_copy(m_aFilesSelectedName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName);
}
else
{
m_FilesSelectedIndex = -1;
m_aFilesSelectedName[0] = '\0';
}
}
void CEditor::FilelistPopulate(int StorageType) void CEditor::FilelistPopulate(int StorageType)
{ {
m_vFileList.clear(); m_vCompleteFileList.clear();
if(m_FileDialogStorageType != IStorage::TYPE_SAVE && !str_comp(m_pFileDialogPath, "maps")) if(m_FileDialogStorageType != IStorage::TYPE_SAVE && !str_comp(m_pFileDialogPath, "maps"))
{ {
CFilelistItem Item; CFilelistItem Item;
@ -4647,18 +4624,17 @@ void CEditor::FilelistPopulate(int StorageType)
Item.m_IsDir = true; Item.m_IsDir = true;
Item.m_IsLink = true; Item.m_IsLink = true;
Item.m_StorageType = IStorage::TYPE_SAVE; Item.m_StorageType = IStorage::TYPE_SAVE;
m_vFileList.push_back(Item); m_vCompleteFileList.push_back(Item);
} }
Storage()->ListDirectory(StorageType, m_pFileDialogPath, EditorListdirCallback, this); Storage()->ListDirectory(StorageType, m_pFileDialogPath, EditorListdirCallback, this);
std::sort(m_vFileList.begin(), m_vFileList.end()); std::sort(m_vCompleteFileList.begin(), m_vCompleteFileList.end());
m_FilesSelectedIndex = m_vFileList.empty() ? -1 : 0; RefreshFilteredFileList();
m_PreviewImageIsLoaded = false; m_FilesSelectedIndex = m_vpFilteredFileList.empty() ? -1 : 0;
m_FileDialogActivate = false; if(m_FilesSelectedIndex >= 0)
str_copy(m_aFilesSelectedName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName);
if(m_FilesSelectedIndex >= 0 && !m_vFileList[m_FilesSelectedIndex].m_IsDir)
str_copy(m_aFileDialogFileName, m_vFileList[m_FilesSelectedIndex].m_aFilename, sizeof(m_aFileDialogFileName));
else else
m_aFileDialogFileName[0] = 0; m_aFilesSelectedName[0] = '\0';
m_PreviewImageIsLoaded = false;
} }
void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle, const char *pButtonText, void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle, const char *pButtonText,
@ -4672,12 +4648,11 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle
m_pfnFileDialogFunc = pfnFunc; m_pfnFileDialogFunc = pfnFunc;
m_pFileDialogUser = pUser; m_pFileDialogUser = pUser;
m_aFileDialogFileName[0] = 0; m_aFileDialogFileName[0] = 0;
m_aFileDialogSearchText[0] = 0; m_aFileDialogFilterString[0] = 0;
m_aFileDialogCurrentFolder[0] = 0; m_aFileDialogCurrentFolder[0] = 0;
m_aFileDialogCurrentLink[0] = 0; m_aFileDialogCurrentLink[0] = 0;
m_pFileDialogPath = m_aFileDialogCurrentFolder; m_pFileDialogPath = m_aFileDialogCurrentFolder;
m_FileDialogFileType = FileType; m_FileDialogFileType = FileType;
m_FileDialogScrollValue = 0.0f;
m_PreviewImageIsLoaded = false; m_PreviewImageIsLoaded = false;
m_FileDialogOpening = true; m_FileDialogOpening = true;

View file

@ -770,8 +770,8 @@ public:
m_BrushColorEnabled = true; m_BrushColorEnabled = true;
m_aFileName[0] = 0; m_aFileName[0] = '\0';
m_aFileSaveName[0] = 0; m_aFileSaveName[0] = '\0';
m_ValidSaveFilename = false; m_ValidSaveFilename = false;
m_PopupEventActivated = false; m_PopupEventActivated = false;
@ -782,17 +782,14 @@ public:
m_pFileDialogTitle = nullptr; m_pFileDialogTitle = nullptr;
m_pFileDialogButtonText = nullptr; m_pFileDialogButtonText = nullptr;
m_pFileDialogUser = nullptr; m_pFileDialogUser = nullptr;
m_aFileDialogFileName[0] = 0; m_aFileDialogFileName[0] = '\0';
m_aFileDialogCurrentFolder[0] = 0; m_aFileDialogCurrentFolder[0] = '\0';
m_aFileDialogCurrentLink[0] = 0; m_aFileDialogCurrentLink[0] = '\0';
m_aFilesSelectedName[0] = '\0';
m_aFileDialogFilterString[0] = '\0';
m_pFileDialogPath = m_aFileDialogCurrentFolder; m_pFileDialogPath = m_aFileDialogCurrentFolder;
m_FileDialogActivate = false;
m_FileDialogOpening = false; m_FileDialogOpening = false;
m_FileDialogScrollValue = 0.0f;
m_FilesSelectedIndex = -1; m_FilesSelectedIndex = -1;
m_FilesStartAt = 0;
m_FilesCur = 0;
m_FilesStopAt = 999;
m_SelectEntitiesImage = "DDNet"; m_SelectEntitiesImage = "DDNet";
@ -867,6 +864,7 @@ public:
CLayerGroup *m_apSavedBrushes[10]; CLayerGroup *m_apSavedBrushes[10];
void RefreshFilteredFileList();
void FilelistPopulate(int StorageType); void FilelistPopulate(int StorageType);
void InvokeFileDialog(int StorageType, int FileType, const char *pTitle, const char *pButtonText, void InvokeFileDialog(int StorageType, int FileType, const char *pTitle, const char *pButtonText,
const char *pBasepath, const char *pDefaultName, const char *pBasepath, const char *pDefaultName,
@ -950,12 +948,10 @@ public:
char m_aFileDialogFileName[IO_MAX_PATH_LENGTH]; char m_aFileDialogFileName[IO_MAX_PATH_LENGTH];
char m_aFileDialogCurrentFolder[IO_MAX_PATH_LENGTH]; char m_aFileDialogCurrentFolder[IO_MAX_PATH_LENGTH];
char m_aFileDialogCurrentLink[IO_MAX_PATH_LENGTH]; char m_aFileDialogCurrentLink[IO_MAX_PATH_LENGTH];
char m_aFileDialogSearchText[64]; char m_aFilesSelectedName[IO_MAX_PATH_LENGTH];
char m_aFileDialogPrevSearchText[64]; char m_aFileDialogFilterString[64];
char *m_pFileDialogPath; char *m_pFileDialogPath;
bool m_FileDialogActivate;
int m_FileDialogFileType; int m_FileDialogFileType;
float m_FileDialogScrollValue;
int m_FilesSelectedIndex; int m_FilesSelectedIndex;
char m_aFileDialogNewFolderName[64]; char m_aFileDialogNewFolderName[64];
char m_aFileDialogErrString[64]; char m_aFileDialogErrString[64];
@ -971,14 +967,11 @@ public:
bool m_IsDir; bool m_IsDir;
bool m_IsLink; bool m_IsLink;
int m_StorageType; int m_StorageType;
bool m_IsVisible;
bool operator<(const CFilelistItem &Other) const { 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; } bool operator<(const CFilelistItem &Other) const { 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; }
}; };
std::vector<CFilelistItem> m_vFileList; std::vector<CFilelistItem> m_vCompleteFileList;
int m_FilesStartAt; std::vector<const CFilelistItem *> m_vpFilteredFileList;
int m_FilesCur;
int m_FilesStopAt;
std::vector<std::string> m_vSelectEntitiesFiles; std::vector<std::string> m_vSelectEntitiesFiles;
std::string m_SelectEntitiesImage; std::string m_SelectEntitiesImage;
@ -1229,7 +1222,6 @@ public:
void RenderMenubar(CUIRect Menubar); void RenderMenubar(CUIRect Menubar);
void RenderFileDialog(); void RenderFileDialog();
void AddFileDialogEntry(int Index, CUIRect *pView);
void SelectGameLayer(); void SelectGameLayer();
void SortImages(); void SortImages();
bool SelectLayerByTile(); bool SelectLayerByTile();