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