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 <base/system.h>
4#include <base/vmath.h>
5
6#include <engine/config.h>
7#include <engine/shared/config.h>
8
9#include <game/localization.h>
10
11#include "ui_listbox.h"
12
13CListBox::CListBox()
14{
15 m_ScrollOffset = vec2(0.0f, 0.0f);
16 m_ListBoxUpdateScroll = false;
17 m_ScrollbarWidth = 20.0f;
18 m_ScrollbarMargin = 5.0f;
19 m_HasHeader = false;
20 m_AutoSpacing = 0.0f;
21 m_ScrollbarShown = false;
22 m_Active = true;
23}
24
25void CListBox::DoBegin(const CUIRect *pRect)
26{
27 // setup the variables
28 m_ListBoxView = *pRect;
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: 0);
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: 0, pBottom: &View);
55 m_ListBoxView = View;
56}
57
58void CListBox::DoFooter(const char *pBottomText, float FooterHeight)
59{
60 m_pBottomText = pBottomText;
61 m_FooterHeight = FooterHeight;
62}
63
64void CListBox::DoStart(float RowHeight, int NumItems, int ItemsPerRow, int RowsPerScroll, int SelectedIndex, const CUIRect *pRect, bool Background, int BackgroundCorners, bool ForceShowScrollbar)
65{
66 CUIRect View;
67 if(pRect)
68 View = *pRect;
69 else
70 View = m_ListBoxView;
71
72 // background
73 m_BackgroundCorners = BackgroundCorners;
74 if(Background)
75 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);
76
77 // draw footers
78 if(m_pBottomText)
79 {
80 CUIRect Footer;
81 View.HSplitBottom(Cut: m_FooterHeight, pTop: &View, pBottom: &Footer);
82 Footer.VSplitLeft(Cut: 10.0f, pLeft: 0, pRight: &Footer);
83 Ui()->DoLabel(pRect: &Footer, pText: m_pBottomText, Size: Footer.h * CUi::ms_FontmodHeight * 0.8f, Align: TEXTALIGN_MC);
84 }
85
86 // setup the variables
87 m_ListBoxView = View;
88 m_RowView = {};
89 m_ListBoxSelectedIndex = SelectedIndex;
90 m_ListBoxNewSelected = SelectedIndex;
91 m_ListBoxNewSelOffset = 0;
92 m_ListBoxItemIndex = 0;
93 m_ListBoxRowHeight = RowHeight;
94 m_ListBoxNumItems = NumItems;
95 m_ListBoxItemsPerRow = ItemsPerRow;
96 m_ListBoxItemActivated = false;
97 m_ListBoxItemSelected = false;
98
99 // handle input
100 if(m_Active && !Input()->ModifierIsPressed() && !Input()->ShiftIsPressed() && !Input()->AltIsPressed())
101 {
102 if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_DOWN))
103 m_ListBoxNewSelOffset += 1;
104 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_UP))
105 m_ListBoxNewSelOffset -= 1;
106 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_PAGE_UP))
107 m_ListBoxNewSelOffset = -ItemsPerRow * RowsPerScroll * 4;
108 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_PAGE_DOWN))
109 m_ListBoxNewSelOffset = ItemsPerRow * RowsPerScroll * 4;
110 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_HOME))
111 m_ListBoxNewSelOffset = 1 - m_ListBoxNumItems;
112 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_END))
113 m_ListBoxNewSelOffset = m_ListBoxNumItems - 1;
114 }
115
116 // setup the scrollbar
117 m_ScrollOffset = vec2(0.0f, 0.0f);
118 CScrollRegionParams ScrollParams;
119 ScrollParams.m_ScrollbarWidth = ScrollbarWidthMax();
120 ScrollParams.m_ScrollbarMargin = ScrollbarMargin();
121 ScrollParams.m_ScrollUnit = (m_ListBoxRowHeight + m_AutoSpacing) * RowsPerScroll;
122 ScrollParams.m_Flags = ForceShowScrollbar ? CScrollRegionParams::FLAG_CONTENT_STATIC_WIDTH : 0;
123 m_ScrollRegion.Begin(pClipRect: &m_ListBoxView, pOutOffset: &m_ScrollOffset, pParams: &ScrollParams);
124 m_ListBoxView.y += m_ScrollOffset.y;
125}
126
127CListboxItem CListBox::DoNextRow()
128{
129 CListboxItem Item = {};
130
131 if(m_ListBoxItemIndex % m_ListBoxItemsPerRow == 0)
132 m_ListBoxView.HSplitTop(Cut: m_ListBoxRowHeight, pTop: &m_RowView, pBottom: &m_ListBoxView);
133 m_ScrollRegion.AddRect(Rect: m_RowView);
134 if(m_ListBoxUpdateScroll && m_ListBoxSelectedIndex == m_ListBoxItemIndex)
135 {
136 m_ScrollRegion.ScrollHere(Option: CScrollRegion::SCROLLHERE_KEEP_IN_VIEW);
137 m_ListBoxUpdateScroll = false;
138 }
139
140 m_RowView.VSplitLeft(Cut: m_RowView.w / (m_ListBoxItemsPerRow - m_ListBoxItemIndex % m_ListBoxItemsPerRow), pLeft: &Item.m_Rect, pRight: &m_RowView);
141
142 Item.m_Selected = m_ListBoxSelectedIndex == m_ListBoxItemIndex;
143 Item.m_Visible = !m_ScrollRegion.RectClipped(Rect: Item.m_Rect);
144
145 m_ListBoxItemIndex++;
146 return Item;
147}
148
149CListboxItem CListBox::DoNextItem(const void *pId, bool Selected, float CornerRadius)
150{
151 if(m_AutoSpacing > 0.0f && m_ListBoxItemIndex > 0)
152 DoSpacing(Spacing: m_AutoSpacing);
153
154 const int ThisItemIndex = m_ListBoxItemIndex;
155 if(Selected)
156 {
157 if(m_ListBoxSelectedIndex == m_ListBoxNewSelected)
158 m_ListBoxNewSelected = ThisItemIndex;
159 m_ListBoxSelectedIndex = ThisItemIndex;
160 }
161
162 CListboxItem Item = DoNextRow();
163 bool ItemClicked = false;
164
165 if(Item.m_Visible && Ui()->DoButtonLogic(pId, Checked: 0, pRect: &Item.m_Rect))
166 {
167 ItemClicked = true;
168 m_ListBoxNewSelected = ThisItemIndex;
169 m_ListBoxItemSelected = true;
170 m_Active = true;
171 }
172 else
173 ItemClicked = false;
174
175 // process input, regard selected index
176 if(m_ListBoxNewSelected == ThisItemIndex)
177 {
178 if(m_Active)
179 {
180 if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_ENTER) || (ItemClicked && Ui()->DoDoubleClickLogic(pId)))
181 {
182 m_ListBoxItemActivated = true;
183 Ui()->SetActiveItem(nullptr);
184 }
185 }
186
187 Item.m_Rect.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, m_Active ? 0.5f : 0.33f), Corners: IGraphics::CORNER_ALL, Rounding: CornerRadius);
188 }
189 if(Ui()->HotItem() == pId && !m_ScrollRegion.Animating())
190 {
191 Item.m_Rect.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.33f), Corners: IGraphics::CORNER_ALL, Rounding: CornerRadius);
192 }
193
194 return Item;
195}
196
197CListboxItem CListBox::DoSubheader()
198{
199 CListboxItem Item = DoNextRow();
200 Item.m_Rect.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.2f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f);
201 return Item;
202}
203
204int CListBox::DoEnd()
205{
206 m_ScrollRegion.End();
207 m_Active |= m_ScrollRegion.Active();
208
209 m_ScrollbarShown = m_ScrollRegion.ScrollbarShown();
210 if(m_ListBoxNewSelOffset != 0 && m_ListBoxNumItems > 0 && m_ListBoxSelectedIndex == m_ListBoxNewSelected)
211 {
212 m_ListBoxNewSelected = clamp(val: (m_ListBoxNewSelected == -1 ? 0 : m_ListBoxNewSelected) + m_ListBoxNewSelOffset, lo: 0, hi: m_ListBoxNumItems - 1);
213 ScrollToSelected();
214 }
215 return m_ListBoxNewSelected;
216}
217