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
ui.cpp
ui.h
ui_listbox.cpp
ui_listbox.h
ui_rect.cpp
ui_rect.h
ui_scrollregion.cpp

View file

@ -30,6 +30,7 @@
#include <game/client/components/menu_background.h>
#include <game/client/components/sounds.h>
#include <game/client/gameclient.h>
#include <game/client/ui_listbox.h>
#include <game/generated/client_data.h>
#include <game/localization.h>
@ -79,7 +80,6 @@ CMenus::CMenus()
m_aCallvoteReason[0] = 0;
m_FriendlistSelectedIndex = -1;
m_DoubleClickIndex = -1;
m_DemoPlayerState = DEMOPLAYER_NONE;
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))
{
m_ShowStart = true;
m_DoubleClickIndex = -1;
}
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]))
{
NewPage = PAGE_NEWS;
m_DoubleClickIndex = -1;
}
}
else if(m_ActivePage == PAGE_DEMOS)
@ -732,7 +730,6 @@ int CMenus::RenderMenubar(CUIRect r)
{
DemolistPopulate();
NewPage = PAGE_DEMOS;
m_DoubleClickIndex = -1;
}
}
else
@ -744,7 +741,6 @@ int CMenus::RenderMenubar(CUIRect r)
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET)
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
NewPage = PAGE_INTERNET;
m_DoubleClickIndex = -1;
}
Box.VSplitLeft(100.0f, &Button, &Box);
@ -754,7 +750,6 @@ int CMenus::RenderMenubar(CUIRect r)
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN)
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
NewPage = PAGE_LAN;
m_DoubleClickIndex = -1;
}
Box.VSplitLeft(100.0f, &Button, &Box);
@ -764,7 +759,6 @@ int CMenus::RenderMenubar(CUIRect r)
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES)
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
NewPage = PAGE_FAVORITES;
m_DoubleClickIndex = -1;
}
Box.VSplitLeft(90.0f, &Button, &Box);
@ -777,7 +771,6 @@ int CMenus::RenderMenubar(CUIRect r)
ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET);
}
NewPage = PAGE_DDNET;
m_DoubleClickIndex = -1;
}
Box.VSplitLeft(90.0f, &Button, &Box);
@ -790,7 +783,6 @@ int CMenus::RenderMenubar(CUIRect r)
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
}
NewPage = PAGE_KOG;
m_DoubleClickIndex = -1;
}
}
}
@ -1323,7 +1315,6 @@ int CMenus::Render()
{
UpdateMusicState();
s_Frame++;
m_DoubleClickIndex = -1;
RefreshBrowserTab(g_Config.m_UiPage);
if(g_Config.m_UiPage == PAGE_INTERNET)
@ -1397,7 +1388,6 @@ int CMenus::Render()
{
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
SetMenuPage(PAGE_INTERNET);
m_DoubleClickIndex = -1;
}
// render current page
@ -1837,36 +1827,38 @@ int CMenus::Render()
}
else if(m_Popup == POPUP_LANGUAGE)
{
Box = Screen;
Box.Margin(150.0f, &Box);
Box.HSplitTop(20.f, &Part, &Box);
Box.HSplitBottom(20.f, &Box, &Part);
Box.HSplitBottom(24.f, &Box, &Part);
Box.HSplitBottom(20.f, &Box, 0);
CUIRect Button;
Screen.Margin(150.0f, &Box);
Box.HSplitTop(20.0f, nullptr, &Box);
Box.HSplitBottom(20.0f, &Box, nullptr);
Box.HSplitBottom(24.0f, &Box, &Button);
Box.HSplitBottom(20.0f, &Box, nullptr);
Box.VMargin(20.0f, &Box);
RenderLanguageSelection(Box);
Part.VMargin(120.0f, &Part);
const bool Activated = RenderLanguageSelection(Box);
Button.VMargin(120.0f, &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;
}
else if(m_Popup == POPUP_COUNTRY)
{
Box = Screen;
Box.Margin(150.0f, &Box);
Box.HSplitTop(20.f, &Part, &Box);
Box.HSplitBottom(20.f, &Box, &Part);
Box.HSplitBottom(24.f, &Box, &Part);
Box.HSplitBottom(20.f, &Box, 0);
CUIRect ButtonBar;
Screen.Margin(150.0f, &Box);
Box.HSplitTop(20.0f, nullptr, &Box);
Box.HSplitBottom(20.0f, &Box, nullptr);
Box.HSplitBottom(24.0f, &Box, &ButtonBar);
Box.HSplitBottom(20.0f, &Box, nullptr);
Box.VMargin(20.0f, &Box);
ButtonBar.VMargin(100.0f, &ButtonBar);
static int s_CurSelection = -2;
if(s_CurSelection == -2)
s_CurSelection = g_Config.m_BrFilterCountryIndex;
static float s_ScrollValue = 0.0f;
static CListBox s_ListBox;
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)
{
@ -1874,29 +1866,31 @@ int CMenus::Render()
if(pEntry->m_CountryCode == s_CurSelection)
OldSelected = i;
CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected >= 0 && (size_t)OldSelected == i);
if(Item.m_Visible)
{
CUIRect Label;
Item.m_Rect.Margin(5.0f, &Item.m_Rect);
Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
float OldWidth = Item.m_Rect.w;
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);
UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_CENTER);
}
const CListboxItem Item = s_ListBox.DoNextItem(pEntry, OldSelected >= 0 && (size_t)OldSelected == i);
if(!Item.m_Visible)
continue;
CUIRect FlagRect, Label;
Item.m_Rect.Margin(5.0f, &FlagRect);
FlagRect.HSplitBottom(12.0f, &FlagRect, &Label);
Label.HSplitTop(2.0f, nullptr, &Label);
const float OldWidth = FlagRect.w;
FlagRect.w = FlagRect.h * 2.0f;
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);
SLabelProperties ItemLabelProps;
ItemLabelProps.m_AlignVertically = 0;
UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_CENTER, ItemLabelProps);
}
bool Activated = false;
const int NewSelected = UiDoListboxEnd(&s_ScrollValue, &Activated);
const int NewSelected = s_ListBox.DoEnd();
if(OldSelected != NewSelected)
s_CurSelection = m_pClient->m_CountryFlags.GetByIndex(NewSelected)->m_CountryCode;
CUIRect CancelButton, OkButton;
Part.VMargin(100.0f, &Part);
Part.VSplitMid(&CancelButton, &OkButton, 40.0f);
ButtonBar.VSplitMid(&CancelButton, &OkButton, 40.0f);
static CButtonContainer s_CancelButton;
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;
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;
Client()->ServerBrowserUpdate();
@ -2195,35 +2189,35 @@ void CMenus::PopupConfirmDemoReplaceVideo()
}
#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;
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;
break;
}
}
const int OldSelected = SelectedTheme;
static int s_ListBox = 0;
static float s_ScrollValue = 0.0f;
UiDoListboxStart(&s_ListBox, &MainView, 26.0f, Localize("Theme"), "", vThemesRef.size(), 1, -1, s_ScrollValue);
static CListBox s_ListBox;
s_ListBox.DoHeader(&MainView, Localize("Theme"), 20.0f);
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);
CTheme &Theme = vThemesRef[i];
const CTheme &Theme = vThemes[i];
const CListboxItem Item = s_ListBox.DoNextItem(&Theme.m_Name, i == SelectedTheme);
if(!Item.m_Visible)
continue;
CUIRect Icon;
Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &Icon, &Item.m_Rect);
CUIRect Icon, Label;
Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &Icon, &Label);
// draw icon if it exists
if(Theme.m_IconTexture.IsValid())
@ -2254,16 +2248,18 @@ void CMenus::RenderThemeSelection(CUIRect MainView, bool Header)
else // generic
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;
int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0, &ItemActive);
SelectedTheme = s_ListBox.DoEnd();
if(ItemActive && NewSelected != SelectedTheme)
if(OldSelected != SelectedTheme)
{
str_copy(g_Config.m_ClMenuMap, vThemesRef[NewSelected].m_Name.c_str());
m_pBackground->LoadMenuBackground(vThemesRef[NewSelected].m_HasDay, vThemesRef[NewSelected].m_HasNight);
const CTheme &Theme = vThemes[SelectedTheme];
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);
}
}
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);
}
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.
* 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
int m_SelectedIndex;
int m_DoubleClickIndex;
int m_ScrollOffset;
void RenderServerbrowserServerList(CUIRect View);
void Connect(const char *pAddress);
void PopupConfirmSwitchServer();
@ -565,8 +547,8 @@ protected:
void OnConfigSave(IConfigManager *pConfigManager);
// found in menus_settings.cpp
void RenderLanguageSelection(CUIRect MainView);
void RenderThemeSelection(CUIRect MainView, bool Header = true);
bool RenderLanguageSelection(CUIRect MainView);
void RenderThemeSelection(CUIRect MainView);
void RenderSettingsGeneral(CUIRect MainView);
void RenderSettingsPlayer(CUIRect MainView);
void RenderSettingsDummyPlayer(CUIRect MainView);
@ -746,7 +728,6 @@ private:
static int GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser);
void SetMenuPage(int NewPage);
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
void RenderInGameNetwork(CUIRect MainView);

View file

@ -12,12 +12,12 @@
#include <game/client/components/console.h>
#include <game/client/components/countryflags.h>
#include <game/client/gameclient.h>
#include <game/client/render.h>
#include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/localization.h>
#include <game/client/gameclient.h>
#include "menus.h"
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);
CUIRect Scroll;
View.VSplitRight(20.0f, &View, &Scroll);
int NumServers = ServerBrowser()->NumSortedServers();
// 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);
}
static float s_ScrollValue = 0;
s_ScrollValue = UI()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue);
if(UI()->ConsumeHotkey(CUI::HOTKEY_TAB))
{
const int Direction = Input()->ShiftIsPressed() ? -1 : 1;
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))
{
const CServerInfo *pItem = ServerBrowser()->SortedGet(m_SelectedIndex);
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
}
static CListBox s_ListBox;
s_ListBox.DoStart(ms_ListheaderHeight, NumServers, 1, 3, m_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(NumServers - Num + 1, 0);
View.y -= s_ScrollValue * ScrollNum * s_aCols[0].m_Rect.h;
int NewSelected = -1;
bool DoubleClicked = false;
int NumPlayers = 0;
static int s_PrevSelectedIndex = -1;
if(s_PrevSelectedIndex != m_SelectedIndex)
{
s_ListBox.ScrollToSelected();
s_PrevSelectedIndex = m_SelectedIndex;
}
m_SelectedIndex = -1;
// reset friend counter
@ -229,23 +214,17 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
for(int i = 0; i < NumServers; i++)
{
int ItemIndex = i;
const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
const CServerInfo *pItem = ServerBrowser()->SortedGet(i);
NumPlayers += pItem->m_NumFilteredPlayers;
CUIRect Row;
const int UIRectCount = 2 + (COL_VERSION + 1) * 3;
//initialize
if(pItem->m_pUIElement == NULL)
if(pItem->m_pUIElement == nullptr)
{
const int UIRectCount = 2 + (COL_VERSION + 1) * 3;
pItem->m_pUIElement = UI()->GetNewUIElement(UIRectCount);
}
int Selected = str_comp(pItem->m_aAddress, g_Config.m_UiServerAddress) == 0; //selected_index==ItemIndex;
View.HSplitTop(ms_ListheaderHeight, &Row, &View);
if(Selected)
const CListboxItem ListItem = s_ListBox.DoNextItem(pItem, str_comp(pItem->m_aAddress, g_Config.m_UiServerAddress) == 0);
if(ListItem.m_Selected)
m_SelectedIndex = i;
// update friend counter
@ -273,31 +252,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
}
}
// 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 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
if(!ListItem.m_Visible)
{
// reset active item, if not visible
if(UI()->CheckActiveItem(pItem))
@ -312,8 +267,8 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
CUIRect Button;
char aTemp[64];
Button.x = s_aCols[c].m_Rect.x;
Button.y = Row.y;
Button.h = Row.h;
Button.y = ListItem.m_Rect.y;
Button.h = ListItem.m_Rect.h;
Button.w = s_aCols[c].m_Rect.w;
int ID = s_aCols[c].m_ID;
@ -469,16 +424,22 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
}
}
UI()->ClipDisable();
if(NewSelected != -1)
const int NewSelected = s_ListBox.DoEnd();
if(NewSelected != m_SelectedIndex)
{
// select the new server
const CServerInfo *pItem = ServerBrowser()->SortedGet(NewSelected);
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
if(DoubleClicked && Input()->MouseDoubleClick())
Connect(g_Config.m_UiServerAddress);
m_SelectedIndex = NewSelected;
if(m_SelectedIndex >= 0)
{
// select the new server
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.
// 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)
{
static int s_VoteList = 0;
static float s_ScrollValue = 0;
UiDoListboxStart(&s_VoteList, &ServerScoreBoard, 26.0f, Localize("Scoreboard"), "", pSelectedServer->m_NumReceivedClients, 1, -1, s_ScrollValue);
static CListBox s_ListBox;
s_ListBox.DoAutoSpacing(1.0f);
s_ListBox.DoStart(25.0f, pSelectedServer->m_NumReceivedClients, 1, 3, -1, &ServerScoreBoard);
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)
continue;
CUIRect Name, Clan, Score, Flag;
Item.m_Rect.HSplitTop(25.0f, &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();
}
Name = Item.m_Rect;
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(0.5f, 1.0f, 0.5f, 0.15f + (i % 2 + 1) * 0.05f);
Name.Draw(Color, IGraphics::CORNER_ALL, 4.0f);
@ -1159,17 +1112,17 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
// score
char aTemp[16];
if(!pSelectedServer->m_aClients[i].m_Player)
if(!CurrentClient.m_Player)
str_copy(aTemp, "SPEC");
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;
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
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;
while(ScoreFontSize >= 4.0f && TextRender()->TextWidth(0, ScoreFontSize, aTemp, -1, -1.0f) > Score.w)
@ -1182,7 +1135,7 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
// name
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;
const char *pName = pSelectedServer->m_aClients[i].m_aName;
const char *pName = CurrentClient.m_aName;
bool Printed = false;
if(g_Config.m_BrFilterString[0])
Printed = PrintHighlighted(pName, [this, &Cursor, pName](const char *pFilteredStr, const int FilterLen) {
@ -1198,7 +1151,7 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
// clan
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;
const char *pClan = pSelectedServer->m_aClients[i].m_aClan;
const char *pClan = CurrentClient.m_aClan;
Printed = false;
if(g_Config.m_BrFilterString[0])
Printed = PrintHighlighted(pClan, [this, &Cursor, pClan](const char *pFilteredStr, const int FilterLen) {
@ -1213,10 +1166,20 @@ void CMenus::RenderServerbrowserServerDetail(CUIRect View)
// flag
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);
ServerFriends.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 4.0f);
UI()->DoLabel(&FilterHeader, Localize("Friends"), FontSize + 4.0f, TEXTALIGN_CENTER);
CUIRect Button, List;
CUIRect List;
ServerFriends.Margin(3.0f, &ServerFriends);
ServerFriends.VMargin(3.0f, &ServerFriends);
ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends);
// friends list(remove friend)
static float s_ScrollValue = 0;
static CListBox s_ListBox;
if(m_FriendlistSelectedIndex >= (int)m_vFriends.size())
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());
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)
{
Item.m_Rect.Margin(1.5f, &Item.m_Rect);
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);
CUIRect NameClanLabels, NameLabel, ClanLabel, OnState;
Item.m_Rect.VSplitRight(30.0f, &NameClanLabels, &OnState);
NameClanLabels.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.1f), IGraphics::CORNER_L, 4.0f);
Item.m_Rect.VMargin(2.5f, &Item.m_Rect);
Item.m_Rect.HSplitTop(12.0f, &Item.m_Rect, &Button);
UI()->DoLabel(&Item.m_Rect, Friend.m_pFriendInfo->m_aName, FontSize, TEXTALIGN_LEFT);
UI()->DoLabel(&Button, Friend.m_pFriendInfo->m_aClan, FontSize, TEXTALIGN_LEFT);
NameClanLabels.VMargin(2.5f, &NameClanLabels);
NameClanLabels.HSplitTop(12.0f, &NameLabel, &ClanLabel);
UI()->DoLabel(&NameLabel, Friend.m_pFriendInfo->m_aName, 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.HMargin((OnState.h - FontSize) / 3, &OnState);
OnState.VMargin(5.0f, &OnState);
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%i", Friend.m_NumFound);
UI()->DoLabel(&OnState, aBuf, FontSize + 2, TEXTALIGN_RIGHT);
}
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.VMargin(5.0f, &OnState);
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%i", Friend.m_NumFound);
UI()->DoLabel(&OnState, aBuf, FontSize + 2, TEXTALIGN_RIGHT);
}
bool Activated = false;
m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, &Activated);
m_FriendlistSelectedIndex = s_ListBox.DoEnd();
// 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;
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_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
m_ScrollOffset = ItemIndex;
m_SelectedIndex = ItemIndex;
Found = true;
}
@ -1345,6 +1306,7 @@ void CMenus::RenderServerbrowserFriends(CUIRect View)
}
}
CUIRect Button;
ServerFriends.HSplitTop(2.5f, 0, &ServerFriends);
ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
if(m_FriendlistSelectedIndex != -1)

View file

@ -14,11 +14,10 @@
#include <game/client/components/console.h>
#include <game/client/gameclient.h>
#include <game/client/render.h>
#include <game/localization.h>
#include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/generated/client_data.h>
#include <game/localization.h>
#include "maplayers.h"
#include "menus.h"
@ -636,246 +635,6 @@ void CMenus::RenderDemoPlayer(CUIRect MainView)
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)
{
CMenus *pSelf = (CMenus *)pUser;
@ -1206,78 +965,26 @@ void CMenus::RenderDemoList(CUIRect MainView)
}
}
// scrollbar
CUIRect Scroll;
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;
static CListBox s_ListBox;
s_ListBox.DoStart(ms_ListheaderHeight, m_vDemos.size(), 1, 3, m_DemolistSelectedIndex, &ListBox, false);
int ItemIndex = -1;
bool DoubleClicked = false;
for(auto &Item : m_vDemos)
{
ItemIndex++;
CUIRect Row;
ListBox.HSplitTop(ms_ListheaderHeight, &Row, &ListBox);
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
const CListboxItem ListItem = s_ListBox.DoNextItem(&Item, ItemIndex == m_DemolistSelectedIndex);
if(!ListItem.m_Visible)
continue;
}
CUIRect Row = ListItem.m_Rect;
CUIRect FileIcon;
Row.VSplitLeft(Row.h, &FileIcon, &Row);
Row.VSplitLeft(5.0f, 0, &Row);
FileIcon.Margin(1.0f, &FileIcon);
FileIcon.x += 2.0f;
const char *pIconType;
const char *pIconType;
if(str_comp(Item.m_aFilename, "..") == 0)
pIconType = "\xEF\xA0\x82";
else if(Item.m_IsDir)
@ -1338,13 +1045,13 @@ void CMenus::RenderDemoList(CUIRect MainView)
}
}
UI()->ClipDisable();
bool Activated = false;
if(UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (DoubleClicked && Input()->MouseDoubleClick()))
const int NewSelected = s_ListBox.DoEnd();
if(NewSelected != m_DemolistSelectedIndex)
{
UI()->SetActiveItem(nullptr);
Activated = true;
m_DemolistSelectedIndex = NewSelected;
if(m_DemolistSelectedIndex >= 0)
str_copy(g_Config.m_UiDemoSelected, m_vDemos[m_DemolistSelectedIndex].m_aName);
DemolistOnUpdate(false);
}
static CButtonContainer s_RefreshButton;
@ -1362,7 +1069,7 @@ void CMenus::RenderDemoList(CUIRect MainView)
}
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)
{

View file

@ -21,6 +21,7 @@
#include <game/client/gameclient.h>
#include <game/client/render.h>
#include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/client/ui_scrollregion.h>
#include <game/localization.h>
@ -242,16 +243,15 @@ void CMenus::RenderPlayers(CUIRect MainView)
ButtonBar.VSplitLeft(Width, &Button, &ButtonBar);
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);
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);
RenderTools()->RenderIcon(IMAGE_GUIICONS, SPRITE_GUIICON_FRIEND, &Button);
int TotalPlayers = 0;
for(const auto &pInfoByName : m_pClient->m_Snap.m_apInfoByName)
{
if(!pInfoByName)
@ -265,14 +265,11 @@ void CMenus::RenderPlayers(CUIRect MainView)
TotalPlayers++;
}
static int s_VoteList = 0;
static float s_ScrollValue = 0;
CUIRect List = Options;
// List.HSplitTop(28.0f, 0, &List);
UiDoListboxStart(&s_VoteList, &List, 24.0f, "", "", TotalPlayers, 1, -1, s_ScrollValue);
static CListBox s_ListBox;
s_ListBox.DoStart(24.0f, TotalPlayers, 1, 3, -1, &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)
{
@ -280,25 +277,27 @@ void CMenus::RenderPlayers(CUIRect MainView)
continue;
int Index = m_pClient->m_Snap.m_apInfoByName[i]->m_ClientID;
if(Index == m_pClient->m_Snap.m_LocalClientID)
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++;
if(!Item.m_Visible)
continue;
CUIRect Row = Item.m_Rect;
if(Count % 2 == 1)
Item.m_Rect.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 10.0f);
Item.m_Rect.VSplitRight(300.0f, &Player, &Item.m_Rect);
Row.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 5.0f);
Row.VSplitRight(s_ListBox.ScrollbarWidthMax() - s_ListBox.ScrollbarWidth(), &Row, nullptr);
Row.VSplitRight(300.0f, &Player, &Row);
// player info
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;
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);
Player.HSplitTop(1.5f, 0, &Player);
Player.HSplitTop(1.5f, nullptr, &Player);
Player.VSplitMid(&Player, &Button);
Item.m_Rect.VSplitRight(200.0f, &Button2, &Item.m_Rect);
Row.VSplitRight(210.0f, &Button2, &Row);
CTextCursor Cursor;
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;
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);
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);
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);
// ignore chat button
Item.m_Rect.HMargin(2.0f, &Item.m_Rect);
Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect);
Button.VSplitLeft((Width - Button.h) / 4.0f, 0, &Button);
Button.VSplitLeft(Button.h, &Button, 0);
if(g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[Index].m_Friend)
Row.HMargin(2.0f, &Row);
Row.VSplitLeft(Width, &Button, &Row);
Button.VSplitLeft((Width - Button.h) / 4.0f, nullptr, &Button);
Button.VSplitLeft(Button.h, &Button, nullptr);
if(g_Config.m_ClShowChatFriends && !CurrentClient.m_Friend)
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))
m_pClient->m_aClients[Index].m_ChatIgnore ^= 1;
else if(DoButton_Toggle(&s_aPlayerIDs[Index][0], CurrentClient.m_ChatIgnore, &Button, true))
CurrentClient.m_ChatIgnore ^= 1;
// ignore emoticon button
Item.m_Rect.VSplitLeft(20.0f, &Button, &Item.m_Rect);
Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect);
Button.VSplitLeft((Width - Button.h) / 4.0f, 0, &Button);
Button.VSplitLeft(Button.h, &Button, 0);
if(g_Config.m_ClShowChatFriends && !m_pClient->m_aClients[Index].m_Friend)
Row.VSplitLeft(30.0f, nullptr, &Row);
Row.VSplitLeft(Width, &Button, &Row);
Button.VSplitLeft((Width - Button.h) / 4.0f, nullptr, &Button);
Button.VSplitLeft(Button.h, &Button, nullptr);
if(g_Config.m_ClShowChatFriends && !CurrentClient.m_Friend)
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))
m_pClient->m_aClients[Index].m_EmoticonIgnore ^= 1;
else if(DoButton_Toggle(&s_aPlayerIDs[Index][1], CurrentClient.m_EmoticonIgnore, &Button, true))
CurrentClient.m_EmoticonIgnore ^= 1;
// friend button
Item.m_Rect.VSplitLeft(20.0f, &Button, &Item.m_Rect);
Item.m_Rect.VSplitLeft(Width, &Button, &Item.m_Rect);
Button.VSplitLeft((Width - Button.h) / 4.0f, 0, &Button);
Button.VSplitLeft(Button.h, &Button, 0);
if(DoButton_Toggle(&s_aPlayerIDs[Index][2], m_pClient->m_aClients[Index].m_Friend, &Button, true))
Row.VSplitLeft(10.0f, nullptr, &Row);
Row.VSplitLeft(Width, &Button, &Row);
Button.VSplitLeft((Width - Button.h) / 4.0f, nullptr, &Button);
Button.VSplitLeft(Button.h, &Button, nullptr);
if(DoButton_Toggle(&s_aPlayerIDs[Index][2], CurrentClient.m_Friend, &Button, true))
{
if(m_pClient->m_aClients[Index].m_Friend)
m_pClient->Friends()->RemoveFriend(m_pClient->m_aClients[Index].m_aName, m_pClient->m_aClients[Index].m_aClan);
if(CurrentClient.m_Friend)
m_pClient->Friends()->RemoveFriend(CurrentClient.m_aName, CurrentClient.m_aClan);
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)
@ -492,8 +489,6 @@ void CMenus::RenderServerInfo(CUIRect MainView)
bool CMenus::RenderServerControlServer(CUIRect MainView)
{
static int s_VoteList = 0;
static float s_ScrollValue = 0;
CUIRect List = MainView;
int Total = m_pClient->m_Voting.m_NumVoteOptions;
int NumVoteOptions = 0;
@ -508,7 +503,8 @@ bool CMenus::RenderServerControlServer(CUIRect MainView)
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;
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))
continue;
CListboxItem Item = UiDoListboxNextItem(pOption);
if(Item.m_Visible)
UI()->DoLabel(&Item.m_Rect, pOption->m_aDescription, 13.0f, TEXTALIGN_LEFT);
if(NumVoteOptions < Total)
aIndices[NumVoteOptions] = i;
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 = UiDoListboxEnd(&s_ScrollValue, &Call);
s_CurVoteOption = s_ListBox.DoEnd();
if(s_CurVoteOption < Total)
m_CallvoteSelectedOption = aIndices[s_CurVoteOption];
return Call;
return s_ListBox.WasItemActivated();
}
bool CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators)
@ -556,36 +554,36 @@ bool CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators)
aPlayerIDs[NumOptions++] = Index;
}
static int s_VoteList = 0;
static float s_ScrollValue = 0;
CUIRect List = MainView;
UiDoListboxStart(&s_VoteList, &List, 24.0f, "", "", NumOptions, 1, Selected, s_ScrollValue);
static CListBox s_ListBox;
s_ListBox.DoStart(24.0f, NumOptions, 1, 3, Selected, &MainView);
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)
{
CTeeRenderInfo TeeInfo = m_pClient->m_aClients[aPlayerIDs[i]].m_RenderInfo;
TeeInfo.m_Size = Item.m_Rect.h;
CUIRect TeeRect, Label;
Item.m_Rect.VSplitLeft(Item.m_Rect.h, &TeeRect, &Label);
CAnimState *pIdleState = CAnimState::GetIdle();
vec2 OffsetToMid;
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);
CTeeRenderInfo TeeInfo = m_pClient->m_aClients[aPlayerIDs[i]].m_RenderInfo;
TeeInfo.m_Size = TeeRect.h;
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;
UI()->DoLabel(&Item.m_Rect, m_pClient->m_aClients[aPlayerIDs[i]].m_aName, 16.0f, TEXTALIGN_LEFT);
}
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
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 = UiDoListboxEnd(&s_ScrollValue, &Call);
Selected = s_ListBox.DoEnd();
m_CallvoteSelectedPlayer = Selected != -1 ? aPlayerIDs[Selected] : -1;
return Call;
return s_ListBox.WasItemActivated();
}
void CMenus::RenderServerControl(CUIRect MainView)
@ -1007,70 +1005,37 @@ void CMenus::RenderGhost(CUIRect MainView)
View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 0);
CUIRect Scroll;
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();
const int NumGhosts = m_vGhosts.size();
static int s_SelectedIndex = 0;
HandleListInputs(View, s_ScrollValue, 1.0f, nullptr, s_aCols[0].m_Rect.h, s_SelectedIndex, NumGhosts);
// 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;
static CListBox s_ListBox;
s_ListBox.DoStart(17.0f, NumGhosts, 1, 3, s_SelectedIndex, &View, false);
for(int i = 0; i < NumGhosts; i++)
{
const CGhostItem *pItem = &m_vGhosts[i];
CUIRect Row;
View.HSplitTop(17.0f, &Row, &View);
// 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;
}
}
const CGhostItem *pGhost = &m_vGhosts[i];
const CListboxItem Item = s_ListBox.DoNextItem(pGhost);
if(!Item.m_Visible)
continue;
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));
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++)
{
CUIRect Button;
Button.x = s_aCols[c].m_Rect.x;
Button.y = Row.y;
Button.h = Row.h;
Button.y = Item.m_Rect.y;
Button.h = Item.m_Rect.h;
Button.w = s_aCols[c].m_Rect.w;
int Id = s_aCols[c].m_Id;
if(Id == COL_ACTIVE)
{
if(pItem->Active())
if(pGhost->Active())
{
Graphics()->WrapClamp();
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);
Cursor.m_LineWidth = Button.w;
TextRender()->TextEx(&Cursor, pItem->m_aPlayer, -1);
TextRender()->TextEx(&Cursor, pGhost->m_aPlayer, -1);
}
else if(Id == COL_TIME)
{
@ -1097,7 +1062,7 @@ void CMenus::RenderGhost(CUIRect MainView)
Cursor.m_LineWidth = Button.w;
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);
}
}
@ -1105,10 +1070,7 @@ void CMenus::RenderGhost(CUIRect MainView)
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
}
UI()->ClipDisable();
if(NewSelected != -1)
s_SelectedIndex = NewSelected;
s_SelectedIndex = s_ListBox.DoEnd();
Status.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_B, 5.0f);
Status.Margin(5.0f, &Status);
@ -1136,7 +1098,7 @@ void CMenus::RenderGhost(CUIRect MainView)
static CButtonContainer s_GhostButton;
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())
{

View file

@ -20,6 +20,7 @@
#include <game/client/render.h>
#include <game/client/skin.h>
#include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/client/ui_scrollregion.h>
#include <game/localization.h>
@ -366,9 +367,9 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
// country flag selector
MainView.HSplitTop(20.0f, 0, &MainView);
static float s_ScrollValue = 0.0f;
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)
{
@ -376,26 +377,29 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView)
if(pEntry->m_CountryCode == *pCountry)
OldSelected = i;
CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected >= 0 && (size_t)OldSelected == i, s_ListBoxUsed);
if(Item.m_Visible)
const CListboxItem Item = s_ListBox.DoNextItem(&pEntry->m_CountryCode, OldSelected >= 0 && (size_t)OldSelected == i, &s_ListBoxUsed);
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);
Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label);
float OldWidth = Item.m_Rect.w;
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);
SLabelProperties ItemLabelProps;
ItemLabelProps.m_AlignVertically = 0;
UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_CENTER);
}
}
bool Clicked = false;
const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0, &Clicked);
if(Clicked)
s_ListBoxUsed = true;
const int NewSelected = s_ListBox.DoEnd();
if(OldSelected != NewSelected)
{
*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_vFavoriteSkinListHelper;
static int s_SkinCount = 0;
static float s_ScrollValue = 0.0f;
static CListBox s_ListBox;
// be nice to the CPU
static auto s_SkinLastRebuildTime = time_get_nanoseconds();
const auto CurTime = time_get_nanoseconds();
@ -780,7 +785,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
};
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)
{
const CSkin *pSkinToBeDraw = s_vSkinList[i].m_pSkin;
@ -788,74 +793,75 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
if(str_comp(pSkinToBeDraw->GetName(), pSkinName) == 0)
OldSelected = i;
CListboxItem Item = UiDoListboxNextItem(pSkinToBeDraw, OldSelected >= 0 && (size_t)OldSelected == i);
if(Item.m_Visible)
const CListboxItem Item = s_ListBox.DoNextItem(pSkinToBeDraw, OldSelected >= 0 && (size_t)OldSelected == i);
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;
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(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);
// 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)
{
SLabelProperties Props;
Props.m_MaxWidth = Item.m_Rect.w;
UI()->DoLabel(&Item.m_Rect, pSkinToBeDraw->GetName(), 12.0f, TEXTALIGN_LEFT, Props);
RenderFavIcon(FavIcon, IsFav);
}
if(g_Config.m_Debug)
else
{
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(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)
if(UI()->MouseInside(&FavIcon))
{
RenderFavIcon(FavIcon, IsFav);
}
}
if(UI()->DoButtonLogic(&pSkinToBeDraw->m_Metrics.m_Body, 0, &FavIcon))
{
if(IsFav)
{
m_SkinFavorites.erase(SkinItFav);
}
else
{
if(UI()->MouseInside(&FavIcon))
{
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;
m_SkinFavorites.emplace(pSkinToBeDraw->GetName());
}
s_InitSkinlist = true;
}
}
}
const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0);
const int NewSelected = s_ListBox.DoEnd();
if(OldSelected != NewSelected)
{
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)
{
const float RowHeight = 24.0f;
const float RowSpacing = 3.0f;
CUIRect ListRect;
pRect->HSplitTop(24.0f * PickNum, &ListRect, pRect);
char aBuf[1024];
UiDoListboxStart(&pButtonContainer, &ListRect, 24.0f, "", aBuf, PickNum, 1, CurSelection, ScrollVal);
pRect->HSplitTop((RowHeight + RowSpacing) * PickNum, &ListRect, pRect);
static CListBox s_ListBox;
s_ListBox.DoAutoSpacing(RowSpacing);
s_ListBox.DoStart(RowHeight, PickNum, 1, 3, CurSelection, &ListRect);
for(int i = 0; i < PickNum; ++i)
{
CListboxItem Item = UiDoListboxNextItem(pIDs[i], CurSelection == i);
if(Item.m_Visible)
{
str_copy(aBuf, pStr[i]);
UI()->DoLabel(&Item.m_Rect, aBuf, 16.0f, TEXTALIGN_CENTER);
}
const CListboxItem Item = s_ListBox.DoNextItem(pIDs[i], CurSelection == i);
if(!Item.m_Visible)
continue;
SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&Item.m_Rect, pStr[i], 16.0f, TEXTALIGN_CENTER, Props);
}
bool ClickedItem = false;
int NewIndex = UiDoListboxEnd(&ScrollVal, NULL, &ClickedItem);
if(ClickedItem)
int NewIndex = s_ListBox.DoEnd();
if(s_ListBox.WasItemSelected() || s_ListBox.WasItemActivated())
{
CurDropDownState = 0;
return NewIndex;
@ -1518,7 +1527,7 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
MainView.VSplitLeft(340.0f, &MainView, 0);
// display mode list
static float s_ScrollValue = 0;
static CListBox s_ListBox;
static const float sc_RowHeightResList = 22.0f;
static const float sc_FontSizeResListHeader = 12.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);
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)
{
@ -1542,16 +1551,18 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
OldSelected = i;
}
CListboxItem Item = UiDoListboxNextItem(&s_aModes[i], OldSelected == i);
if(Item.m_Visible)
{
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);
UI()->DoLabel(&Item.m_Rect, aBuf, sc_FontSizeResList, TEXTALIGN_LEFT);
}
const CListboxItem Item = s_ListBox.DoNextItem(&s_aModes[i], OldSelected == i);
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);
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)
{
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);
}
void CMenus::RenderLanguageSelection(CUIRect MainView)
bool CMenus::RenderLanguageSelection(CUIRect MainView)
{
static int s_LanguageList = 0;
static int s_SelectedLanguage = 0;
static int s_SelectedLanguage = -1;
static std::vector<CLanguage> s_vLanguages;
static float s_ScrollValue = 0;
static CListBox s_ListBox;
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)
{
CListboxItem Item = UiDoListboxNextItem(&Language.m_Name);
if(Item.m_Visible)
{
CUIRect Rect;
Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &Rect, &Item.m_Rect);
Rect.VMargin(6.0f, &Rect);
Rect.HMargin(3.0f, &Rect);
ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f);
m_pClient->m_CountryFlags.Render(Language.m_CountryCode, &Color, Rect.x, Rect.y, Rect.w, Rect.h);
Item.m_Rect.HSplitTop(2.0f, 0, &Item.m_Rect);
UI()->DoLabel(&Item.m_Rect, Language.m_Name.c_str(), 16.0f, TEXTALIGN_LEFT);
}
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)
continue;
CUIRect FlagRect, Label;
Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &FlagRect, &Label);
FlagRect.VMargin(6.0f, &FlagRect);
FlagRect.HMargin(3.0f, &FlagRect);
ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f);
m_pClient->m_CountryFlags.Render(Language.m_CountryCode, &Color, FlagRect.x, FlagRect.y, FlagRect.w, FlagRect.h);
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)
{
@ -2141,6 +2153,8 @@ void CMenus::RenderLanguageSelection(CUIRect MainView)
g_Localization.Load(s_vLanguages[s_SelectedLanguage].m_FileName.c_str(), Storage(), Console());
GameClient()->OnLanguageChange();
}
return s_ListBox.WasItemActivated();
}
void CMenus::RenderSettings(CUIRect MainView)

View file

@ -3,7 +3,9 @@
#include <engine/shared/config.h>
#include <engine/storage.h>
#include <engine/textrender.h>
#include <game/client/gameclient.h>
#include <game/client/ui_listbox.h>
#include <game/localization.h>
#include "menus.h"
@ -457,7 +459,6 @@ void CMenus::RenderSettingsCustom(CUIRect MainView)
// skin selector
MainView.HSplitTop(MainView.h - 10.0f - ms_ButtonHeight, &CustomList, &MainView);
static float s_ScrollValue = 0.0f;
if(gs_aInitCustomList[s_CurCustomTab])
{
int ListSize = 0;
@ -533,7 +534,8 @@ void CMenus::RenderSettingsCustom(CUIRect MainView)
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)
{
const SCustomItem *pItem = GetCustomItem(s_CurCustomTab, i);
@ -571,30 +573,30 @@ void CMenus::RenderSettingsCustom(CUIRect MainView)
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;
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;
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())
{
Graphics()->WrapClamp();
Graphics()->TextureSet(pItem->m_RenderTexture);
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();
}
Graphics()->WrapClamp();
Graphics()->TextureSet(pItem->m_RenderTexture);
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(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);
}
m_DoubleClickIndex = -1;
}
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);
}
m_DoubleClickIndex = -1;
}
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);
s_JoinTutorialTime = 0.0f;
}
m_DoubleClickIndex = -1;
}
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);
}
m_DoubleClickIndex = -1;
}
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/render.h>
#include <game/client/ui.h>
#include <game/client/ui_listbox.h>
#include <game/client/ui_scrollregion.h>
#include <game/generated/client_data.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_IsLink = false;
Item.m_StorageType = StorageType;
pEditor->m_vFileList.push_back(Item);
pEditor->m_vCompleteFileList.push_back(Item);
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()
{
// GUI coordsys
@ -4318,7 +4264,7 @@ void CEditor::RenderFileDialog()
View.Draw(ColorRGBA(0, 0, 0, 0.75f), IGraphics::CORNER_ALL, 5.0f);
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(5.0f, nullptr, &View); // some spacing
View.HSplitBottom(14.0f, &View, &ButtonBar);
@ -4330,7 +4276,6 @@ void CEditor::RenderFileDialog()
View.HSplitBottom(10.0f, &View, nullptr); // some spacing
if(m_FileDialogFileType == CEditor::FILETYPE_IMG)
View.VSplitMid(&View, &Preview);
View.VSplitRight(20.0f, &View, &Scroll);
// title
Title.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f);
@ -4340,15 +4285,17 @@ void CEditor::RenderFileDialog()
// pathbox
char aPath[128], aBuf[128];
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
aPath[0] = 0;
str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath);
UI()->DoLabel(&PathBox, aBuf, 10.0f, TEXTALIGN_LEFT);
// filebox
static CListBox s_ListBox;
if(m_FileDialogStorageType == IStorage::TYPE_SAVE)
{
// filebox
UI()->DoLabel(&FileBoxLabel, "Filename:", 10.0f, TEXTALIGN_LEFT);
static float s_FileBoxID = 0;
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] == '\\')
str_copy(&m_aFileDialogFileName[i], &m_aFileDialogFileName[i + 1], (int)(sizeof(m_aFileDialogFileName)) - i);
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)
@ -4365,119 +4325,73 @@ void CEditor::RenderFileDialog()
}
else
{
//searchbox
// render search bar
FileBox.VSplitRight(250, &FileBox, nullptr);
CUIRect ClearBox;
FileBox.VSplitRight(15, &FileBox, &ClearBox);
UI()->DoLabel(&FileBoxLabel, "Search:", 10.0f, TEXTALIGN_LEFT);
str_copy(m_aFileDialogPrevSearchText, m_aFileDialogSearchText, sizeof(m_aFileDialogPrevSearchText));
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)
UI()->SetActiveItem(&s_SearchBoxID);
// clearSearchbox button
// clear search button
{
static int s_ClearButton = 0;
ClearBox.Draw(ColorRGBA(1, 1, 1, 0.33f * UI()->ButtonColorMul(&s_ClearButton)), IGraphics::CORNER_R, 3.0f);
UI()->DoLabel(&ClearBox, "×", 10.0f, TEXTALIGN_CENTER);
if(UI()->DoButtonLogic(&s_ClearButton, 0, &ClearBox))
{
m_aFileDialogSearchText[0] = 0;
SearchUpdated = true;
m_aFileDialogFilterString[0] = 0;
UI()->SetActiveItem(&s_SearchBoxID);
}
}
if(str_comp(m_aFileDialogPrevSearchText, m_aFileDialogSearchText))
m_FileDialogScrollValue = 0.0f;
if(SearchUpdated)
{
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;
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_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(str_endswith(m_vFileList[m_FilesSelectedIndex].m_aFilename, ".png"))
if(str_endswith(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, ".png"))
{
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))
{
@ -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)
m_FileDialogActivate = true;
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_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)
m_FileDialogScrollValue = 0;
if(m_FileDialogScrollValue > 1)
m_FileDialogScrollValue = 1;
m_FilesStartAt = (int)(ScrollNum * m_FileDialogScrollValue);
if(m_FilesStartAt < 0)
m_FilesStartAt = 0;
m_FilesStopAt = m_FilesStartAt + Num;
m_FilesCur = 0;
// set clipping
UI()->ClipEnable(&View);
// disable clipping again
UI()->ClipDisable();
const int NewSelection = s_ListBox.DoEnd();
if(NewSelection != m_FilesSelectedIndex)
{
m_FilesSelectedIndex = NewSelection;
str_copy(m_aFilesSelectedName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName);
if(!m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir)
str_copy(m_aFileDialogFileName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename);
else
m_aFileDialogFileName[0] = '\0';
m_PreviewImageIsLoaded = false;
}
// the buttons
static int s_OkButton = 0;
@ -4549,35 +4494,34 @@ void CEditor::RenderFileDialog()
CUIRect Button;
ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button);
bool IsDir = m_FilesSelectedIndex >= 0 && m_vFileList[m_FilesSelectedIndex].m_IsDir;
if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, nullptr) || m_FileDialogActivate)
bool IsDir = m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir;
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(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))
m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link
}
else // sub folder
{
if(m_vFileList[m_FilesSelectedIndex].m_IsLink)
if(m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsLink)
{
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
{
char aTemp[IO_MAX_PATH_LENGTH];
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 :
m_vFileList[m_FilesSelectedIndex].m_StorageType);
if(m_FilesSelectedIndex >= 0 && !m_vFileList[m_FilesSelectedIndex].m_IsDir)
str_copy(m_aFileDialogFileName, m_vFileList[m_FilesSelectedIndex].m_aFilename, sizeof(m_aFileDialogFileName));
m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType);
if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir)
str_copy(m_aFileDialogFileName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, sizeof(m_aFileDialogFileName));
else
m_aFileDialogFileName[0] = 0;
}
@ -4594,10 +4538,10 @@ void CEditor::RenderFileDialog()
m_PopupEventActivated = true;
}
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)
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)
{
m_vFileList.clear();
m_vCompleteFileList.clear();
if(m_FileDialogStorageType != IStorage::TYPE_SAVE && !str_comp(m_pFileDialogPath, "maps"))
{
CFilelistItem Item;
@ -4647,18 +4624,17 @@ void CEditor::FilelistPopulate(int StorageType)
Item.m_IsDir = true;
Item.m_IsLink = true;
Item.m_StorageType = IStorage::TYPE_SAVE;
m_vFileList.push_back(Item);
m_vCompleteFileList.push_back(Item);
}
Storage()->ListDirectory(StorageType, m_pFileDialogPath, EditorListdirCallback, this);
std::sort(m_vFileList.begin(), m_vFileList.end());
m_FilesSelectedIndex = m_vFileList.empty() ? -1 : 0;
m_PreviewImageIsLoaded = false;
m_FileDialogActivate = false;
if(m_FilesSelectedIndex >= 0 && !m_vFileList[m_FilesSelectedIndex].m_IsDir)
str_copy(m_aFileDialogFileName, m_vFileList[m_FilesSelectedIndex].m_aFilename, sizeof(m_aFileDialogFileName));
std::sort(m_vCompleteFileList.begin(), m_vCompleteFileList.end());
RefreshFilteredFileList();
m_FilesSelectedIndex = m_vpFilteredFileList.empty() ? -1 : 0;
if(m_FilesSelectedIndex >= 0)
str_copy(m_aFilesSelectedName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName);
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,
@ -4672,12 +4648,11 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle
m_pfnFileDialogFunc = pfnFunc;
m_pFileDialogUser = pUser;
m_aFileDialogFileName[0] = 0;
m_aFileDialogSearchText[0] = 0;
m_aFileDialogFilterString[0] = 0;
m_aFileDialogCurrentFolder[0] = 0;
m_aFileDialogCurrentLink[0] = 0;
m_pFileDialogPath = m_aFileDialogCurrentFolder;
m_FileDialogFileType = FileType;
m_FileDialogScrollValue = 0.0f;
m_PreviewImageIsLoaded = false;
m_FileDialogOpening = true;

View file

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