diff --git a/CMakeLists.txt b/CMakeLists.txt index f691fa09e..a3e50c68a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 21ed94b0b..06e307d5b 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -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 &vThemesRef = m_pBackground->GetThemes(); + const std::vector &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; -} diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index e3a5d6a65..1e514f81f 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -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); diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 99f4066b2..5e8a2a298 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -12,12 +12,12 @@ #include #include +#include #include #include +#include #include -#include - #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) diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index c3338a41c..98f5835c6 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -14,11 +14,10 @@ #include #include #include -#include - #include - +#include #include +#include #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(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) { diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 798e12714..efd91cea6 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -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(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()) { diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 300ed9b8f..908cde215 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -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 s_vSkinListHelper; static std::vector 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(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(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 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) diff --git a/src/game/client/components/menus_settings_assets.cpp b/src/game/client/components/menus_settings_assets.cpp index fc9f1beb9..33925a560 100644 --- a/src/game/client/components/menus_settings_assets.cpp +++ b/src/game/client/components/menus_settings_assets.cpp @@ -3,7 +3,9 @@ #include #include #include + #include +#include #include #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') diff --git a/src/game/client/components/menus_start.cpp b/src/game/client/components/menus_start.cpp index 25b603e9c..aa861cabb 100644 --- a/src/game/client/components/menus_start.cpp +++ b/src/game/client/components/menus_start.cpp @@ -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 diff --git a/src/game/client/ui_listbox.cpp b/src/game/client/ui_listbox.cpp new file mode 100644 index 000000000..bf0b191b4 --- /dev/null +++ b/src/game/client/ui_listbox.cpp @@ -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 +#include + +#include +#include + +#include + +#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); +} diff --git a/src/game/client/ui_listbox.h b/src/game/client/ui_listbox.h new file mode 100644 index 000000000..83364730c --- /dev/null +++ b/src/game/client/ui_listbox.h @@ -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 diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index b622ab583..93edc8ed9 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -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(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; diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 30c9730a2..aac7db65a 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -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 m_vFileList; - int m_FilesStartAt; - int m_FilesCur; - int m_FilesStopAt; + std::vector m_vCompleteFileList; + std::vector m_vpFilteredFileList; std::vector 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();