1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "ui_listbox.h"
4
5#include <base/system.h>
6#include <base/vmath.h>
7
8#include <engine/config.h>
9#include <engine/shared/config.h>
10
11#include <game/localization.h>
12
13CListBox::CListBox()
14{
15 Reset();
16}
17
18void CListBox::Reset()
19{
20 m_ListBoxView = m_RowView = CUIRect{.x: 0.0f, .y: 0.0f, .w: 0.0f, .h: 0.0f};
21 m_ListBoxUpdateScroll = false;
22 m_ScrollbarShown = false;
23 m_AutoSpacing = 0.0f;
24 m_ScrollRegion.Reset();
25 m_ScrollbarWidth = 20.0f;
26 m_ScrollbarMargin = 5.0f;
27 m_HasHeader = false;
28 m_Active = true;
29}
30
31void CListBox::DoHeader(const CUIRect *pRect, const char *pTitle, float HeaderHeight, float Spacing)
32{
33 CUIRect View = *pRect;
34 CUIRect Header;
35
36 // background
37 View.HSplitTop(Cut: HeaderHeight + Spacing, pTop: &Header, pBottom: nullptr);
38 Header.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: m_BackgroundCorners & IGraphics::CORNER_T, Rounding: 5.0f);
39
40 // draw header
41 View.HSplitTop(Cut: HeaderHeight, pTop: &Header, pBottom: &View);
42 Ui()->DoLabel(pRect: &Header, pText: pTitle, Size: Header.h * CUi::ms_FontmodHeight * 0.8f, Align: TEXTALIGN_MC);
43
44 View.HSplitTop(Cut: Spacing, pTop: &Header, pBottom: &View);
45
46 // setup the variables
47 m_ListBoxView = View;
48 m_HasHeader = true;
49}
50
51void CListBox::DoSpacing(float Spacing)
52{
53 CUIRect View = m_ListBoxView;
54 View.HSplitTop(Cut: Spacing, pTop: nullptr, pBottom: &View);
55 m_ListBoxView = View;
56}
57
58void CListBox::DoStart(float RowHeight, int NumItems, int ItemsPerRow, int RowsPerScroll, int SelectedIndex, const CUIRect *pRect, bool Background, int BackgroundCorners, bool ForceShowScrollbar)
59{
60 CUIRect View;
61 if(pRect)
62 View = *pRect;
63 else
64 View = m_ListBoxView;
65
66 // background
67 m_BackgroundCorners = BackgroundCorners;
68 if(Background)
69 View.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: m_BackgroundCorners & (m_HasHeader ? IGraphics::CORNER_B : IGraphics::CORNER_ALL), Rounding: 5.0f);
70
71 // setup the variables
72 m_ListBoxView = View;
73 m_RowView = {};
74 m_ListBoxSelectedIndex = SelectedIndex;
75 m_ListBoxNewSelected = SelectedIndex;
76 m_ListBoxNewSelOffset = 0;
77 m_ListBoxItemIndex = 0;
78 m_ListBoxRowHeight = RowHeight;
79 m_ListBoxNumItems = NumItems;
80 m_ListBoxItemsPerRow = ItemsPerRow;
81 m_ListBoxItemActivated = false;
82 m_ListBoxItemSelected = false;
83
84 // handle input
85 if(m_Active && !Input()->ModifierIsPressed() && !Input()->ShiftIsPressed() && !Input()->AltIsPressed())
86 {
87 if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_DOWN))
88 m_ListBoxNewSelOffset += m_ListBoxItemsPerRow;
89 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_UP))
90 m_ListBoxNewSelOffset -= m_ListBoxItemsPerRow;
91 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_RIGHT) && m_ListBoxItemsPerRow > 1)
92 m_ListBoxNewSelOffset += 1;
93 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_LEFT) && m_ListBoxItemsPerRow > 1)
94 m_ListBoxNewSelOffset -= 1;
95 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_PAGE_UP))
96 m_ListBoxNewSelOffset = -ItemsPerRow * RowsPerScroll * 4;
97 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_PAGE_DOWN))
98 m_ListBoxNewSelOffset = ItemsPerRow * RowsPerScroll * 4;
99 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_HOME))
100 m_ListBoxNewSelOffset = 1 - m_ListBoxNumItems;
101 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_END))
102 m_ListBoxNewSelOffset = m_ListBoxNumItems - 1;
103 }
104
105 // setup the scrollbar
106 vec2 ScrollOffset = vec2(0.0f, 0.0f);
107 CScrollRegionParams ScrollParams;
108 ScrollParams.m_ScrollbarWidth = ScrollbarWidthMax();
109 ScrollParams.m_ScrollbarMargin = ScrollbarMargin();
110 ScrollParams.m_ScrollUnit = (m_ListBoxRowHeight + m_AutoSpacing) * RowsPerScroll;
111 ScrollParams.m_Flags = ForceShowScrollbar ? CScrollRegionParams::FLAG_CONTENT_STATIC_WIDTH : 0;
112 m_ScrollRegion.Begin(pClipRect: &m_ListBoxView, pOutOffset: &ScrollOffset, pParams: &ScrollParams);
113 m_ListBoxView.y += ScrollOffset.y;
114}
115
116CListboxItem CListBox::DoNextRow()
117{
118 CListboxItem Item = {};
119
120 if(m_ListBoxItemIndex % m_ListBoxItemsPerRow == 0)
121 m_ListBoxView.HSplitTop(Cut: m_ListBoxRowHeight, pTop: &m_RowView, pBottom: &m_ListBoxView);
122 m_ScrollRegion.AddRect(Rect: m_RowView);
123 if(m_ListBoxUpdateScroll && m_ListBoxSelectedIndex == m_ListBoxItemIndex)
124 {
125 m_ScrollRegion.ScrollHere(Option: CScrollRegion::SCROLLHERE_KEEP_IN_VIEW);
126 m_ListBoxUpdateScroll = false;
127 }
128
129 m_RowView.VSplitLeft(Cut: m_RowView.w / (m_ListBoxItemsPerRow - m_ListBoxItemIndex % m_ListBoxItemsPerRow), pLeft: &Item.m_Rect, pRight: &m_RowView);
130
131 Item.m_Selected = m_ListBoxSelectedIndex == m_ListBoxItemIndex;
132 Item.m_Visible = !m_ScrollRegion.RectClipped(Rect: Item.m_Rect);
133
134 m_ListBoxItemIndex++;
135 return Item;
136}
137
138CListboxItem CListBox::DoNextItem(const void *pId, bool Selected, float CornerRadius)
139{
140 if(m_AutoSpacing > 0.0f && m_ListBoxItemIndex > 0)
141 DoSpacing(Spacing: m_AutoSpacing);
142
143 const int ThisItemIndex = m_ListBoxItemIndex;
144 if(Selected)
145 {
146 if(m_ListBoxSelectedIndex == m_ListBoxNewSelected)
147 m_ListBoxNewSelected = ThisItemIndex;
148 m_ListBoxSelectedIndex = ThisItemIndex;
149 }
150
151 CListboxItem Item = DoNextRow();
152 const int ItemClicked = Item.m_Visible ? Ui()->DoButtonLogic(pId, Checked: 0, pRect: &Item.m_Rect, Flags: BUTTONFLAG_LEFT) : 0;
153 if(ItemClicked)
154 {
155 m_ListBoxNewSelected = ThisItemIndex;
156 m_ListBoxItemSelected = true;
157 m_Active = true;
158 }
159
160 // process input, regard selected index
161 if(m_ListBoxNewSelected == ThisItemIndex)
162 {
163 if(m_Active)
164 {
165 if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_ENTER) || (ItemClicked == 1 && Ui()->DoDoubleClickLogic(pId)))
166 {
167 m_ListBoxItemActivated = true;
168 Ui()->SetActiveItem(nullptr);
169 }
170 }
171
172 Item.m_Rect.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, m_Active ? 0.5f : 0.33f), Corners: IGraphics::CORNER_ALL, Rounding: CornerRadius);
173 }
174 if(Ui()->HotItem() == pId && !m_ScrollRegion.Animating())
175 {
176 Item.m_Rect.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.33f), Corners: IGraphics::CORNER_ALL, Rounding: CornerRadius);
177 }
178
179 return Item;
180}
181
182CListboxItem CListBox::DoSubheader()
183{
184 CListboxItem Item = DoNextRow();
185 Item.m_Rect.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.2f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f);
186 return Item;
187}
188
189int CListBox::DoEnd()
190{
191 m_ScrollRegion.End();
192 m_Active |= m_ScrollRegion.Active();
193
194 m_ScrollbarShown = m_ScrollRegion.ScrollbarShown();
195 if(m_ListBoxNewSelOffset != 0 && m_ListBoxNumItems > 0 && m_ListBoxSelectedIndex == m_ListBoxNewSelected)
196 {
197 if(m_ListBoxNewSelected == -1)
198 m_ListBoxNewSelected = 0;
199 else
200 m_ListBoxNewSelected = std::clamp(val: m_ListBoxNewSelected + m_ListBoxNewSelOffset, lo: 0, hi: m_ListBoxNumItems - 1);
201 ScrollToSelected();
202 }
203 return m_ListBoxNewSelected;
204}
205